Controlul fluxului
Abilitatea de a rula unele coduri în funcție dacă o condiție este true
și de a rula un cod în mod repetat dacă o condiție este true
sunt blocuri de bază în majoritatea limbajelor de programare. Cele mai comune constructe care îți permit să controlezi fluxul de execuție al codului Rust sunt expresiile if
și buclele.
Expresiile if
O expresie if
îți permite să amifici codul în funcție de condiții. Tu furnizezi o condiție și apoi afirmi: „Dacă această condiție este îndeplinită, rulează acest bloc de cod. Dacă condiția nu este îndeplinită, nu rula acest bloc de cod”.
Creează un proiect nou numit branches în directoriul tău projects pentru a explora expresia if
. În fișierul src/main.rs, introdu următoarea linie:
Numele fișierului: src/main.rs
fn main() { let number = 3; if number < 5 { println!("condition was true"); } else { println!("condition was false"); } }
Toate expresiile if
încep cu cuvântul cheie if
, urmate de o condiție. În acest caz, condiția verifică dacă variabila number
are o valoare mai mică de 5. Plasăm blocul de cod care urmează să se execute dacă condiția este true
imediat după condiție între parantezele acolade. Blocurile de cod asociate cu condițiile din expresiile if
sunt uneori numite arms (ramuri), la fel ca ramurile din expresiile match
despre care am discutat în [secțiunea „Compararea supoziției cu numărul secret”][comparison-the supposition-the secret-number] a capitolului 2.
Opțional, putem include și o expresie else
, pe care am ales să o facem aici, pentru a oferi programului un bloc alternativ de cod care să se execute în cazul în care condiția evaluatează la false
. Dacă nu oferi o expresie else
și condiția este false
, programul va omite blocul if
și va trece la următoarea parte de cod.
Încearcă să rulezi acest cod; ar trebui să vezi următoarea ieșire:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was true
Să încercăm să schimbăm valoarea lui number
într-o valoare care face ca condiția să fie false
pentru a vedea ce se întâmplă:
fn main() {
let number = 7;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
}
Rulează din nou programul și uită-te la ieșire:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
condition was false
Este de asemenea important de menționat că în acest cod condiția trebuie să fie un bool
. Dacă condiția nu este un bool
, vom obține o eroare. De exemplu, încearcă să rulezi următorul cod:
Numele fișierului: src/main.rs
fn main() {
let number = 3;
if number {
println!("number was three");
}
}
De această dată, condiția if
evaluează valoarea 3
și Rust aruncă o eroare:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: mismatched types
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected `bool`, found integer
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error
Eroarea indică faptul că Rust se aștepta la un bool
, dar a primit un integer. Spre deosebire de limbajele precum Ruby și JavaScript, Rust nu va încerca să convertească automat tipurile non-Boolean într-un Boolean. Trebuie să fii explicit și să furnizezi întotdeauna if
cu un Boolean ca și condiție. Dacă vrem ca blocul de cod if
să ruleze doar atunci când un număr nu este egal cu 0
, de exemplu, putem schimba expresia if
astfel:
Numele fișierului: src/main.rs
fn main() { let number = 3; if number != 0 { println!("number was something other than zero"); } }
Rulând acest cod se va afișa numărul a fost altceva decât zero
.
Tratarea mai multor condiții cu else if
Poți utiliza mai multe condiții prin combinarea if
și else
într-o expresie else if
. De exemplu:
Numele fișierului: src/main.rs
fn main() { let number = 6; if number % 4 == 0 { println!("number is divisible by 4"); } else if number % 3 == 0 { println!("number is divisible by 3"); } else if number % 2 == 0 { println!("number is divisible by 2"); } else { println!("number is not divisible by 4, 3, or 2"); } }
Acest program are patru căi posibile pe care le poate urma. După ce îl rulezi, ar trebui să vezi următoarea ieșire:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/branches`
number is divisible by 3
Când acest program este executat, el verifică fiecare expresie if
pe rând și execută primul bloc pentru care condiția se evaluează la true
. Reține că, deși 6 este divizibil cu 2, nu vedem ieșirea numărul este divizibil cu 2
, nici nu vedem textul numărul nu este divizibil cu 4, 3 sau 2
din blocul else
. Asta pentru că Rust execută doar blocul pentru prima condiție true
, iar odată ce o găsește, nu mai verifică restul.
Folosirea prea multor expresii else if
poate încărca codul tău, așa că dacă ai mai mult de una, s-ar putea să vrei să refactorizezi codul. Capitolul 6 descrie o structură avansată de instrucțiuni condiționale în Rust numită match
pentru aceste cazuri.
Folosirea if
într-o instrucțiune let
Deoarece if
este o expresie, o putem folosi în partea dreaptă a unei instrucțiuni let
pentru a atribui rezultatul unei variabile, așa cum se vede în Listarea 3-2.
Numele fișierului: src/main.rs
fn main() { let condition = true; let number = if condition { 5 } else { 6 }; println!("The value of number is: {number}"); }
Listarea 3-2: Atribuirea rezultatului unei expresii if
unei variabile
Variabila number
va fi legată de o valoare în funcție de rezultatul expresiei if
. Rulează acest cod pentru a vedea ce se întâmplă:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
Finished dev [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/branches`
The value of number is: 5
Amintește-ți că blocurile de cod se evaluează până la ultima expresie din ele, iar numerele în sine sunt, de asemenea, expresii. În acest caz, valoarea întregii expresii if
depinde de ce bloc de cod se execută. Acest lucru înseamnă că valorile care au potențialul de a fi rezultate din fiecare ramură a if
trebuie să fie același tip; în Listarea 3-2, rezultatele atât ale ramurii if
, cât și ale celei else
erau numere întregi i32
. Dacă tipurile nu se potrivesc, așa cum este cazul în următorul exemplu, vom primi o eroare:
Numele fișierului: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { "six" };
println!("The value of number is: {number}");
}
La compilarea acestui cod primim o eroare deoarece ramurile if
și else
au tipuri de valori incompatibile, și Rust indică exact unde să găsim problema în textul programului:
$ cargo run
Compiling branches v0.1.0 (file:///projects/branches)
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:4:44
|
4 | let number = if condition { 5 } else { "six" };
| - ^^^^^ expected integer, found `&str`
| |
| expected because of this
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error
Expresia în blocul if
se evaluează ca un număr întreg, iar expresia din blocul else
se evaluează ca un string. Acest lucru nu funcționează deoarece variabilele trebuie să aibă un singur tip, iar Rust trebuie să știe în timpul compilării care este tipul variabilei number
, decisiv. Cunoașterea tipului number
îi permite compilatorului să verifice dacă tipul este valid oriunde folosim number
. Rust nu ar putea face asta dacă tipul number
ar fi fost determinat doar la runtime; compilatorul ar fi fost mai complex sau ar fi făcut mai puține garanții despre cod, dacă ar fi trebuit să țină cont de mai multe tipuri ipotetice pentru orice variabilă.
Repetarea cu bucle
Este adesea util să execuți un bloc de cod de mai multe ori. Pentru acest lucru, Rust oferă mai multe bucle, care vor rula prin codul din interiorul corpului buclei până la sfârșit și apoi vor începe imediat de la început. Pentru a experimenta cu buclele, să facem un nou proiect numit loops.
Rust are trei tipuri de bucle: loop
, while
și for
. Să încercăm fiecare.
Repetarea codului cu loop
Cuvântul cheie loop
indică Rust să execute un bloc de cod iar și iar pentru totdeauna sau până când îi spunem explicit să se oprească.
Ca exemplu, modifică fișierul src/main.rs din directoriul tău loops pentru a arăta în felul următor:
Numele fișierului: src/main.rs
fn main() {
loop {
println!("again!");
}
}
Când rulăm acest program, vom vedea din nou!
afișat iar și iar continuu până când oprim manual programul. Majoritatea terminalelor suportă comanda rapidă ctrl-c pentru a întrerupe un program care
este blocat într-o buclă continuă. Încearcă:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
Running `target/debug/loops`
again!
again!
again!
again!
^Cagain!
Simbolul ^C
reprezintă locul în care ai apăsat ctrl-c. Este posibil să vezi sau nu cuvântul din nou!
afișat după ^C
, în funcție de unde era codul în buclă când a primit semnalul de întrerupere.
Rust oferă de asemenea un mod prin care poți ieși dintr-o buclă folosind cod. Poți plasa cuvântul cheie break
în interiorul buclei pentru a indica programului când să se oprească execuția buclei. Amintește-ți că am făcut asta în jocul de ghicire din secțiunea “Oprirea după o ghicire corectă” din Capitolul 2 pentru a opri programul când utilizatorul a câștigat jocul prin ghicirea numărului corect.
Am folosit de asemenea continue
în jocul de ghicire, care într-o buclă indică programul să sară peste orice cod rămas în această iterație a buclei și să treacă la următoarea iterație.
Returnarea valorilor din bucle
Una dintre utilizările unei instrucțiuni loop
este de a reîncerca o operație despre care știi că ar putea eșua, cum ar fi verificarea dacă un thread și-a terminat treaba. Probabil că vei avea nevoie și de a trece rezultatul acelei operațiuni în afara buclei către restul codului tău. Pentru a face asta, poți adăuga valoarea pe care vrei să o returnezi după expresia break
pe care o folosești pentru a opri bucla; acea valoare va fi returnată din bucla astfel încât să o poți utiliza, așa cum se arată aici:
fn main() { let mut counter = 0; let result = loop { counter += 1; if counter == 10 { break counter * 2; } }; println!("The result is {result}"); }
Înainte de buclă, noi declarăm o variabilă numită counter
și o inițializăm la
0
. Apoi declaram o variabilă numită result
pentru a deține valoarea returnată din
buclă. La fiecare iterație a buclei, adăugăm 1
la variabila counter
, și apoi verificăm dacă counter
este egal cu 10
. Când este, folosim cuvântul cheie break
cu valoarea counter * 2
. După buclă, folosim un punct și virgulă pentru a încheia declarația care alocă valoarea pentru result
. În final, noi afișăm valoarea în result
, care în acest caz este 20
.
Etichete de buclă pentru deosebirea între bucle multiple
Dacă ai bucle în interioarele altei bucle, break
și continue
se aplică buclei interne din acel punct. Poți specifica opțional o etichetă de buclă pe o buclă pe care o poți folosi apoi cu break
sau continue
pentru a specifica că acele cuvinte cheie se aplică buclei etichetate în locul celei mai interne bucle. Etichetele de buclă trebuie să înceapă cu un singur apostrof. Iată un exemplu cu două bucle îmbricate:
fn main() { let mut count = 0; 'counting_up: loop { println!("count = {count}"); let mut remaining = 10; loop { println!("remaining = {remaining}"); if remaining == 9 { break; } if count == 2 { break 'counting_up; } remaining -= 1; } count += 1; } println!("End count = {count}"); }
Bucla externă are eticheta 'counting_up
, și va număra în sus de la 0 la 2. Bucla internă fără o etichetă numără în jos de la 10 la 9. Primul break
care nu specifică o etichetă va ieși doar din bucla internă. Declarația break 'counting_up;
va ieși din bucla externă. Acest cod afișează:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/loops`
count = 0
remaining = 10
remaining = 9
count = 1
remaining = 10
remaining = 9
count = 2
remaining = 10
End count = 2
Buclele condiționale cu while
Un program va avea frecvent nevoie să verifice o condiție în timpul unei bucle. Cât timp condiția rămâne true
, bucla se execută. Când condiția nu mai este true
, programul apelează break
, ceea ce oprește bucla. Comportamentul acesta poate fi realizat printr-o combinație de loop
, if
, else
și break
; poți să încerci chiar acum acest lucru într-un program, dacă vrei. Cu toate acestea, acest pattern este atât de frecvent încât Rust include o construcție specială pentru acesta, numită buclă while
. În Listarea 3-3, utilizăm while
pentru a rula bucla de trei ori, numărând în jos de fiecare dată, și după aceea, în afara buclei, afișăm un mesaj și ieșim din program.
Numele fișierului: src/main.rs
fn main() { let mut number = 3; while number != 0 { println!("{number}!"); number -= 1; } println!("LIFTOFF!!!"); }
Listarea 3-3: Utilizarea unei bucle while
pentru a rula cod atât timp cât o condiție este adevărată
Această construcție reduce semnificativ imbricările care ar fi fost necesare dacă s-ar folosi loop
, if
, else
și break
, oferind în același timp o mai mare claritate. Atâta timp cât o condiție este evaluată ca fiind true
, codul rulează; în caz contrar, acesta iese din buclă.
Parcurgerea unei colecții cu for
Ai opțiunea de a folosi structura while
pentru a parcurge elementele unei colecții, cum ar fi un array. De exemplu, bucla din listarea 3-4 afișează fiecare element din array-ul a
.
Numele fișierului: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; let mut index = 0; while index < 5 { println!("the value is: {}", a[index]); index += 1; } }
Listarea 3-4: Parcurgerea fiecărui element al unei colecții
folosind o buclă while
Aici, codul numără elementele din array. Începe de la indexul 0
, și apoi rulează până atinge indexul final din array (adică, când expresia index < 5
nu mai este true
). Rularea acestui cod va afișa fiecare element din array:
$ cargo run
Compiling loops v0.1.0 (file:///projects/loops)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target/debug/loops`
the value is: 10
the value is: 20
the value is: 30
the value is: 40
the value is: 50
Toate cele cinci valori ale array-ului apar în terminal, după cum și ne așteptam. Deși index
va atinge la un moment dat valoarea 5
, bucla se oprește înainte de a încerca să extragă o a șasea valoare din array.
Cu toate acestea, această abordare este predispusă la erori; am putea provoca panică programului dacă valoarea indexului sau condiția de test este incorectă. De exemplu, dacă ai modificat definiția array-ului a
pentru a avea patru elemente, dar ai uitat să actualizezi condiția la while index < 4
, codul s-ar opri cu panică. De asemenea rularea este lentă, deoarece compilatorul adaugă cod de execuție pentru a efectua verificarea condițională a indexului în granițele array-ului la fiecare parcurgere a buclei.
O alternativă mai concisă ar fi o buclă for
cu executarea unui cod pentru fiecare element din colecție. O buclă for
arată ca în codul din listarea 3-5.
Numele fișierului: src/main.rs
fn main() { let a = [10, 20, 30, 40, 50]; for element in a { println!("the value is: {element}"); } }
Listarea 3-5: Parcurgerea fiecărui element al unei colecții
folosind o buclă for
Dacă vom rula acest cod vom vedea aceeași ieșire ca în listarea 3-4. Mai important, noi am îmbunătățit acum securitatea codului și am eliminat riscul de bug-uri care ar putea rezulta din depășirea sfârșitului array-ului sau din ne-parcurgerea integrală și omisiunea unor elemente.
Folosind bucla for
, nu mai e nevoie să îți amintești să modifici orice alt cod dacă schimbi numărul de valori din array, cum ar fi cazul cu metoda folosită în listarea 3-4.
Siguranța și conciziunea buclelor for
le fac cel mai des folosit concept de bucle în Rust. Chiar și în situații în care se dorește de rulat un anumit cod de un număr de ori, cum ar fi exemplul cu numărătoarea inversă care a folosit o buclă while
în listarea 3-3, majoritatea programatorilor Rust ar folosi o buclă for
utilizând un Range
furnizat de biblioteca standard, care generează toate numerele în ordine începând de la un număr și terminând înainte de un alt număr.
Iată cum ar arăta numărătoarea inversă folosind o buclă for
și o altă metodă despre care încă nu am vorbit, rev
, pentru a inversa șirul:
Numele fișierului: src/main.rs
fn main() { for number in (1..4).rev() { println!("{number}!"); } println!("LIFTOFF!!!"); }
Acest cod pare mai elegant, nu-i așa?
Sumar
Ai reușit! Acesta a fost un capitol considerabil: ai învățat despre variabile, tipuri de date scalare și compuse, funcții, comentarii, expresii if
și bucle! Pentru a te antrena cu conceptele discutate în acest capitol, încearcă să scrii programe care să facă următoarele lucruri:
- Convertarea temperaturilor între Fahrenheit și Celsius.
- Generearea unui al n-lea număr Fibonacci.
- Imprimarea versurile „Un elefant se legăna”, profitând de repetiția din cântec.
Când ești gata să continui, vom discuta despre un concept din Rust care nu există în mod obișnuit în alte limbaje de programare: posesiunea.