Funcții

Funcțiile sunt omniprezente în codul Rust. Ai văzut deja una dintre cele mai importante funcții din limbaj: funcția main, care este punctul de intrare în multe programe. Ai văzut deja și cuvântul cheie fn, care îți permite să declari funcții noi.

Codul Rust folosește snake case ca stil convențional pentru numele funcțiilor și variabilelor, în care toate literele sunt minuscule și cuvintele sunt separate de caracterele de underscore. Iată un program care conține o definiție exemplară a unei funcții:

Numele fișierului: src/main.rs

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

Definim o funcție în Rust prin introducerea fn urmată de un nume de funcție și un set de paranteze. Parantezele acolade indică compilatorului unde începe și se termină corpul funcției.

Putem apela orice funcție pe care am definit-o introducând numele său urmat de un set de paranteze. Deoarece another_function este definită în program, ea poate fi apelată din interiorul funcției main. Observă că am definit another_function după funcția main în codul sursă; am fi putut să o definim și înainte. Rust nu îi pasă unde definim funcțiile noastre, doar că sunt definite într-un loc vizibil pentru codul apelant.

Să începem un nou proiect binar denumit functions pentru a continua explorarea funcțiilore. Pune exemplul another_function în src/main.rs și rulează-l. Ar trebui să vezi următoarea ieșire:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
     Running `target/debug/functions`
Hello, world!
Another function.

Liniile se execută în ordinea în care apar în funcția main. Mai întâi mesajul „Salut, lume!” este tipărit, apoi another_function este apelată și mesajul său este tipărit.

Parametri

Funcțiile pot fi definite cu parametri, aceștia fiind variabile speciale care fac parte din semnătura unei funcții. Când o funcție are parametri ea poate fi apelată cu valori concrete pentru acești parametri. Tehnic, aceste valori concrete se numesc argumente, dar în conversațiile de zi cu zi, oamenii tind să folosească termenii parametru și argument în mod interschimbabil atât pentru fiecare variabilă în definiția unei funcții, cât și pentru valorile concrete transmise când o funcție este apelată.

În această versiune a lui another_function, noi adăugăm un parametru:

Numele fișierului: src/main.rs

fn main() {
    another_function(5);
}

fn another_function(x: i32) {
    println!("The value of x is: {x}");
}

Încearcă să rulezi acest program; ar trebui să primești următoarea ieșire:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 1.21s
     Running `target/debug/functions`
The value of x is: 5

Declarația another_function are un parametru numit x. Tipul lui x este specificat ca fiind i32. Când transmitem 5 la another_function, macro println! plasează 5 acolo unde perechea de acolade cu x era în șirul de caractere format.

În semnăturile funcțiilor, trebuie să declari tipul fiecărui parametru. Acest lucru este o decizie deliberată în designul Rust: cererea de adnotații de tip în definițiile funcțiilor înseamnă că compilatorul aproape niciodată nu are nevoie de ele în alte părți din cod pentru a afla ce tip ai vrut să spui. De asemenea, compilatorul este capabil să ofere mesaje de eroare mult mai utile dacă știe ce tipuri așteaptă funcția.

Când definești mai mulți parametri, separă declarațiile de parametri cu virgule, în acest fel:

Numele fișierului: src/main.rs

fn main() {
    print_labeled_measurement(5, 'h');
}

fn print_labeled_measurement(value: i32, unit_label: char) {
    println!("The measurement is: {value}{unit_label}");
}

Acest exemplu creează o funcție numită print_labeled_measurement cu doi parametri. Primul parametru se numește value și este i32. Al doilea se numește unit_label și este de tip char. Funcția apoi afișează text care conține atât value cât și unit_label.

Să încercăm să rulăm acest cod: înlocuiește programul curent din fișierul src/main.rs al proiectului tău functions cu exemplul de mai sus și rulează-l folosind cargo run:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/functions`
The measurement is: 5h

Deoarece am apelat funcția cu 5 ca valoare pentru value și 'h' ca valoare pentru unit_label, ieșirea programului conține anume aceste valori.

Instrucțiuni și expresii

Corpurile funcțiilor sunt compuse dintr-o serie de instrucțiuni, care se pot încheia opțional cu o expresie. Până acum, funcțiile pe care le-am discutat nu au inclus o expresie finală, însă ai observat expresii utilizate ca parte a instrucțiunilor. Spre deosebire de multe alte limbaje de programare, Rust este un limbaj bazat pe expresii - o distincție importantă de înțeles. În continuare, să analizăm ce reprezintă instrucțiunile și expresiile și cum această diferențiere influențează structura corpurilor funcțiilor.

  • Instrucțiunile sunt directive care realizează o anumită acțiune și nu returnează o valoare.
  • Expresiile evaluează o valoare rezultantă. Să vedem câteva exemple.

De fapt, am folosit deja instrucțiuni și expresii. Crearea unei variabile și atribuirea unei valori cu un cuvânt cheie let este o instrucțiune. În listarea 3-1, let y = 6; este o instrucțiune.

Numele fișierului: src/main.rs

fn main() {
    let y = 6;
}

Listarea 3-1: O declarație a funcției main conținând o instrucțiune

Definițiile de funcții sunt, de asemenea, instrucțiuni; întregul exemplu precedent este o instrucțiune în sine.

Instrucțiunile nu returnează valori. Prin urmare, nu poți atribui o instrucțiune let unei alte variabile, așa cum încearcă să facă următorul cod; vei obține o eroare:

Numele fișierului: src/main.rs

fn main() {
    let x = (let y = 6);
}

Când rulezi acest program, eroarea pe care o obții arată așa:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error: expected expression, found `let` statement
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^

error: expected expression, found statement (`let`)
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: variable declaration using `let` is a statement

error[E0658]: `let` expressions in this position are unstable
 --> src/main.rs:2:14
  |
2 |     let x = (let y = 6);
  |              ^^^^^^^^^
  |
  = note: see issue #53667 <https://github.com/rust-lang/rust/issues/53667> for more information

warning: unnecessary parentheses around assigned value
 --> src/main.rs:2:13
  |
2 |     let x = (let y = 6);
  |             ^         ^
  |
  = note: `#[warn(unused_parens)]` on by default
help: remove these parentheses
  |
2 -     let x = (let y = 6);
2 +     let x = let y = 6;
  |

For more information about this error, try `rustc --explain E0658`.
warning: `functions` (bin "functions") generated 1 warning
error: could not compile `functions` due to 3 previous errors; 1 warning emitted

Instrucțiunea let y = 6 nu returnează o valoare, deci nu există nimic de atribuit lui x. Acest lucru este diferit față de ceea ce se întâmplă în alte limbaje, cum ar fi C și Ruby, unde atribuirea returnează valoarea atribuirii. În acele limbaje, poți scrie x = y = 6 și atât x, cât și y vor avea valoarea 6; însă nu în cazul lui Rust.

Expresiile evaluează o valoare și alcătuiesc majoritatea celorlalte coduri pe care le vei scrie în Rust. Consideră o operație matematică, cum ar fi 5 + 6, care este o expresie care evaluează la valoarea 11. Expresiile pot face parte din instrucțiuni: în Listarea 3-1, 6 în instrucțiunea let y = 6; este o expresie care evaluează valoarea 6. Apelarea unei funcții este o expresie. Apelarea unui macro este o expresie. Și un nou bloc de cod creat cu acolade este o expresie, spre exemplu:

Numele fișierului: src/main.rs

fn main() {
    let y = {
        let x = 3;
        x + 1
    };

    println!("The value of y is: {y}");
}

Expresia:

{
    let x = 3;
    x + 1
}

este un bloc care, în acest caz, evaluează la 4. În continuare această valoare este atribuită lui y ca parte a instrucțiunii let. Reține că linia x + 1 nu are un punct și virgulă la sfârșit, spre deosebire de cele mai multe linii pe care le-ai văzut până acum. Cauza e că expresiile nu includ semicoalone la sfârșit. Dacă adaugi un punct și virgulă la sfârșitul unei expresii, o transformi într-o instrucțiune, și ea nu va mai returna o valoare. Ține acest lucru în minte pe măsură ce vom explora în continuare valorile de retur a funcțiilor și expresiile.

Funcții cu valori de retur

Funcțiile pot întoarce valori codului care le apelează. Noi nu dăm un nume valorilor de retur, dar trebuie să le declarăm tipul după o săgeată (->). În Rust, valoarea de retur a funcției este sinonimă cu valoarea ultimei expresii din blocul corpului acelei funcții. Poți returna mai devreme dintr-o funcție folosind cuvântul cheie return și specificând o valoare, dar majoritatea funcțiilor returnează ultima expresie în mod implicit. Iată un exemplu de funcție care returnează o valoare:

Numele fișierului: src/main.rs

fn five() -> i32 {
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

Nu sunt apeluri de funcții, macrouri sau chiar declarații let în funcția five - doar numărul 5 în sine. Aceasta este o funcție perfect valabilă în Rust. Observă că tipul de retur al funcției este specificat și el, ca -> i32. Încearcă să rulezi acest cod; rezultatul ar trebui să arate așa:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/functions`
The value of x is: 5

Numărul 5 în five este valoarea de retur a funcției, motiv pentru care tipul de retur este i32. Să examinăm acest lucru mai detaliat. Sunt două aspecte importante: În primul rând, linia let x = five(); arată că folosim valoarea de retur a unei funcții pentru a inițializa o variabilă. Deoarece funcția five returnează un 5, această linie ar fi echivalentă cu următoarea:

#![allow(unused)]
fn main() {
let x = 5;
}

În al doilea rând, funcția five nu are parametri și definește tipul valorii de retur, dar corpul funcției este un singur 5, fără punct și virgulă, pentru că este anume expresia a cărei valoare vrem să o returnăm.

Să vedem un alt exemplu:

Numele fișierului: src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1
}

Rularea acestui cod va afișa Valoarea lui x este: 6. Dar dacă punem un punct și virgulă la finalul liniei care conține x + 1, schimbând-o dintr-o expresie într-o declarație, vom obține o eroare:

Numele fișierului: src/main.rs

fn main() {
    let x = plus_one(5);

    println!("The value of x is: {x}");
}

fn plus_one(x: i32) -> i32 {
    x + 1;
}

Compilarea acestui cod produce o eroare, astfel:

$ cargo run
   Compiling functions v0.1.0 (file:///projects/functions)
error[E0308]: mismatched types
 --> src/main.rs:7:24
  |
7 | fn plus_one(x: i32) -> i32 {
  |    --------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
8 |     x + 1;
  |          - help: remove this semicolon to return this value

For more information about this error, try `rustc --explain E0308`.
error: could not compile `functions` due to previous error

Mesajul principal de eroare, mismatched types, relevă problema de bază cu acest cod. Definiția funcției plus_one spune că va returna un i32, dar declarațiile nu evaluează la o valoare, ceea ce este exprimat prin (), tipul unit. Prin urmare, nu se returnează nimic, ceea ce contrazice definiția funcției și duce la o eroare. În această ieșire Rust chiar oferă un mesaj care ar putea ajuta la corectarea problemei: sugerează eliminarea punctului și virgulei, care într-adevăr ar remedia eroarea.