Toată documentația
Toate ghidurile

Învață programare

Rust de la zero: sigur și rapid

Instalezi Rust, rulezi cu Cargo și înveți variabilele, proprietatea (ownership), structurile, colecțiile și gestionarea erorilor.

Rust este un limbaj rapid, la fel de aproape de calculator ca C++, dar care te oprește din compilare când ai o greșeală de memorie. Compilatorul e strict și vorbăreț: te certă, dar îți și explică ce ai de reparat. De aceea mulți încep să-l iubească după primele zile de luptă cu el.

Presupunem că ai citit „Ce este programarea” și ai pregătit calculatorul cu „Mediu de lucru pe Windows”. Exemplele de pe această pagină se rulează fișier cu fișier, deci le poți copia direct.

Instalare cu rustup

Rust se instalează cu rustup, unealta oficială. Intră pe rustup.rs, descarcă instalatorul pentru Windows și urmează pașii impliciți. Pe Windows, instalatorul îți poate cere și uneltele de build ale Microsoft (Visual Studio Build Tools): acceptă, sunt necesare pentru linkare.

  1. Instalează rustup de pe rustup.rs.
  2. Deschide un terminal NOU (cel vechi nu vede încă noile comenzi).
  3. Scrie rustc --version și cargo --version. Dacă vezi versiuni, ești gata.
  4. Ca să actualizezi mai târziu, rulează rustup update.

Cargo: unealta de proiect. Cargo creează proiecte, compilează și rulează. Pentru un proiect nou:

bash
cargo new salut
cd salut
cargo run

cargo new face folderul salut cu fișierul Cargo.toml (descrierea proiectului) și src/main.rs (codul). cargo run compilează și pornește programul. Pentru un fișier mic poți folosi și direct rustc main.rs, fără Cargo.

Primul program

main.rsrust
fn main() {
    println!("Salut, DominosLand!");
}

Rezultat

Salut, DominosLand!

fn declară o funcție, iar main este locul de unde începe programul. println! se termină cu semnul exclamării pentru că este un macro, nu o funcție obișnuită. Acoladele {} din text sunt locuri unde se inserează valori.

Variabile și mutabilitate

În Rust, o variabilă creată cu let nu se poate schimba. Dacă vrei să o schimbi, spui explicit: let mut. Asta te ajută să vezi la prima privire ce se poate modifica. Poți și să „umbrești” un nume, adică să creezi o variabilă nouă cu același nume.

variabile.rsrust
fn main() {
    let nivel = 5;
    let mut xp = 100;
    xp += 50;
    println!("Nivel {nivel}, XP {xp}");

    let x = 5;
    let x = x * 2;
    println!("x este acum {x}");
}

Rezultat

Nivel 5, XP 150
x este acum 10

Dacă încerci nivel = 6 pe o variabilă fără mut, compilatorul refuză: cannot assign twice to immutable variable. Nu e o greșeală de limbaj, e o protecție.

Tipuri de bază. Rust deduce de obicei tipul singur, dar îl poți scrie. Numerele întregi au mărimi: i32 (cu semn), u32 și u8 (fără semn). Numerele cu zecimale sunt f64. Mai ai bool, char (un caracter între apostrofuri), tupluri (grupuri de valori) și array-uri (listă cu lungime fixă).

tipuri.rsrust
fn main() {
    let nivel: i32 = 7;
    let viata: f64 = 87.5;
    let activ: bool = true;
    let clasa: char = 'M';
    println!("{nivel} {viata} {activ} {clasa}");

    let pozitie: (i32, i32) = (10, -4);
    println!("x={}, y={}", pozitie.0, pozitie.1);

    let scoruri = [10, 20, 30];
    println!("{} scoruri, primul este {}", scoruri.len(), scoruri[0]);
}

Rezultat

7 87.5 true M
x=10, y=-4
3 scoruri, primul este 10

Text: String și &str. Rust are două feluri principale de text. &str este o bucată de text împrumutată, de pildă un literal scris între ghilimele în cod. String este text deținut de program, care poate crește. Pentru început, ține minte: ai nevoie de String când vrei să modifici sau să construiești text.

text.rsrust
fn main() {
    let mut mesaj = String::from("Salut");
    mesaj.push_str(", lume");

    let nume: &str = "Alex";
    println!("{mesaj}! Numele are {} litere.", nume.len());
    println!("{}", nume.to_uppercase());
}

Rezultat

Salut, lume! Numele are 4 litere.
ALEX

Condiții și bucle

if în Rust este o expresie: poate întoarce o valoare pe care o pui direct într-o variabilă. Condiția nu are nevoie de paranteze, dar trebuie să fie bool. Pentru repetare ai for (pe un interval sau o listă), while și loop (fără sfârșit, până la break).

control.rsrust
fn main() {
    let xp = 320;
    let rang = if xp >= 1000 {
        "Veteran"
    } else if xp >= 300 {
        "Avansat"
    } else {
        "Începător"
    };
    println!("{rang}");

    for i in 1..=3 {
        println!("Runda {i}");
    }

    let mut viata: i32 = 10;
    while viata > 0 {
        viata -= 4;
        println!("Viață: {viata}");
    }
}

Rezultat

Avansat
Runda 1
Runda 2
Runda 3
Viață: 6
Viață: 2
Viață: -2

Intervalul 1..=3 include și 3; 1..3 s-ar opri la 2.

Funcții. Parametrii au tipul scris explicit, iar tipul rezultatului vine după săgeată. Ultima expresie din funcție, fără punct și virgulă, este valoarea întoarsă.

functii.rsrust
fn patrat(x: i32) -> i32 {
    x * x
}

fn saluta(nume: &str) {
    println!("Salut, {nume}!");
}

fn main() {
    println!("{}", patrat(7));
    saluta("echipa");
}

Rezultat

49
Salut, echipa!

Dacă pui punct și virgulă după x * x, funcția nu mai întoarce nimic și compilatorul se plânge de tipuri. Detaliul acesta păcălește pe aproape toți la început.

Proprietatea (ownership), explicată blând

Aici Rust e diferit de alte limbaje. Regula e simplă: fiecare valoare are un singur proprietar. Când proprietarul iese din zonă, valoarea e ștearsă automat. Dacă dai valoarea altcuiva, ea se mută și vechea variabilă nu mai poate fi folosită. Asta previne multe bug-uri fără un garbage collector.

mutare.rsrust
fn main() {
    let a = String::from("DominosLand");
    let b = a.clone();
    println!("{a} {b}");

    let c = b;
    println!("{c}");
}

Rezultat

DominosLand DominosLand
DominosLand

clone() face o copie reală. Linia let c = b muta textul în c; după ea, b nu mai poate fi folosit. Dacă ai încerca asta, compilatorul ar spune borrow of moved value:

nu-compileaza.rsrust
fn main() {
    let a = String::from("salut");
    let b = a;
    println!("{a}");
}

Împrumutul (borrowing). De cele mai multe ori nu vrei să dai valoarea, ci doar să o lași pe cineva să se uite la ea sau s-o schimbe un pic. Atunci o împrumuți cu &. Cu &mut o împrumuți pentru modificare. Regula de aur: fie oricâte împrumuturi de citit, fie un singur împrumut de scriere, niciodată amândouă în același timp.

imprumut.rsrust
fn lungime(text: &String) -> usize {
    text.len()
}

fn adauga_semn(text: &mut String) {
    text.push('!');
}

fn main() {
    let mut nume = String::from("Alex");
    println!("{}", lungime(&nume));
    adauga_semn(&mut nume);
    println!("{nume}");
}

Rezultat

4
Alex!

Structuri, enum și match

struct grupează câmpuri. Metodele se scriu într-un bloc impl. Un enum este un tip cu un număr fix de variante, iar match verifică toate variantele: dacă uiți una, programul nu compilează.

jucator.rsrust
enum Rang {
    Incepator,
    Avansat,
    Veteran,
}

struct Jucator {
    nume: String,
    xp: u32,
}

impl Jucator {
    fn nou(nume: &str) -> Jucator {
        Jucator { nume: nume.to_string(), xp: 0 }
    }

    fn adauga_xp(&mut self, cantitate: u32) {
        self.xp += cantitate;
    }

    fn rang(&self) -> Rang {
        match self.xp {
            0..=99 => Rang::Incepator,
            100..=499 => Rang::Avansat,
            _ => Rang::Veteran,
        }
    }
}

fn eticheta(rang: &Rang) -> &str {
    match rang {
        Rang::Incepator => "începător",
        Rang::Avansat => "avansat",
        Rang::Veteran => "veteran",
    }
}

fn main() {
    let mut j = Jucator::nou("Alex");
    j.adauga_xp(120);
    println!("{} are {} XP, rang {}", j.nume, j.xp, eticheta(&j.rang()));
}

Rezultat

Alex are 120 XP, rang avansat

self este obiectul pe care lucrezi. &self înseamnă „doar citesc”, &mut self înseamnă „pot modifica”. Intervalul 0..=99 din match include ambele capete, iar _ prinde tot ce a rămas.

Vec și HashMap. Vec este lista care crește, iar HashMap ține perechi cheie-valoare. Un HashMap nu garantează ordinea la parcurgere, deci pentru un rezultat mereu identic sortăm cheile înainte de afișare.

colectii.rsrust
use std::collections::HashMap;

fn main() {
    let mut jocuri = vec!["Minecraft", "FiveM"];
    jocuri.push("CS2");
    println!("Avem {} jocuri", jocuri.len());
    for joc in &jocuri {
        println!("- {joc}");
    }

    let mut xp: HashMap<String, u32> = HashMap::new();
    xp.insert("Maria".to_string(), 300);
    xp.insert("Alex".to_string(), 120);
    *xp.entry("Alex".to_string()).or_insert(0) += 30;

    let mut nume: Vec<&String> = xp.keys().collect();
    nume.sort();
    for n in nume {
        println!("{n}: {}", xp[n]);
    }
}

Rezultat

Avem 3 jocuri
- Minecraft
- FiveM
- CS2
Alex: 150
Maria: 300

Erori cu Result

Rust nu are excepții. O funcție care poate eșua întoarce Result: Ok(valoare) la succes sau Err(eroare) la eșec. Cu match tratezi ambele cazuri, iar operatorul ? întoarce imediat eroarea mai departe dacă a apărut una.

erori.rsrust
fn parseaza_xp(text: &str) -> Result<u32, String> {
    match text.trim().parse::<u32>() {
        Ok(n) => Ok(n),
        Err(_) => Err(format!("'{text}' nu este un număr valid")),
    }
}

fn suma(a: &str, b: &str) -> Result<u32, std::num::ParseIntError> {
    let x: u32 = a.parse()?;
    let y: u32 = b.parse()?;
    Ok(x + y)
}

fn main() {
    for text in ["250", " 40 ", "abc", "-5"] {
        match parseaza_xp(text) {
            Ok(n) => println!("XP valid: {n}"),
            Err(e) => println!("Eroare: {e}"),
        }
    }

    println!("{:?}", suma("2", "3"));
    println!("{}", suma("2", "x").is_err());
}

Rezultat

XP valid: 250
XP valid: 40
Eroare: 'abc' nu este un număr valid
Eroare: '-5' nu este un număr valid
Ok(5)
true

Metoda unwrap() oprește programul cu panică dacă are o eroare în față. E bună în experimente, dar într-un program real tratează eroarea cu match sau cu ?.

Mini-proiect: clasament de jucători

Combinăm structuri, Vec, sortare și o funcție de nivel. Un jucător începe la nivelul 1; pentru nivelul 2 are nevoie de 100 XP, iar fiecare nivel următor cere cu 50 XP mai mult. Sortăm lista descrescător după XP și afișăm clasamentul.

clasament.rsrust
struct Jucator {
    nume: String,
    xp: u32,
}

fn nivel(xp: u32) -> u32 {
    let mut n = 1;
    let mut ramas = xp;
    let mut necesar = 100;
    while ramas >= necesar {
        ramas -= necesar;
        n += 1;
        necesar += 50;
    }
    n
}

fn main() {
    let mut lista = vec![
        Jucator { nume: "Maria".to_string(), xp: 1000 },
        Jucator { nume: "Alex".to_string(), xp: 250 },
        Jucator { nume: "Dan".to_string(), xp: 1500 },
    ];

    lista.sort_by(|a, b| b.xp.cmp(&a.xp));

    for (i, j) in lista.iter().enumerate() {
        println!("{}. {} - {} XP - nivel {}", i + 1, j.nume, j.xp, nivel(j.xp));
    }
}

Rezultat

1. Dan - 1500 XP - nivel 7
2. Maria - 1000 XP - nivel 6
3. Alex - 250 XP - nivel 3

Idei de continuare: citește jucătorii dintr-un fișier, adaugă o comandă care crește XP-ul unui nume dat sau transformă lista într-un HashMap. Același exercițiu există și în ghidul de C++, ca să vezi diferențele dintre limbaje.

Pașii următori

  • Citește cartea oficială Rust Book, capitolele despre ownership și traits.
  • Învață cargo test pentru teste și cargo clippy pentru sfaturi de stil.
  • Caută pachete (crates) pentru ce ai nevoie, apoi adaugă-le cu cargo add.
  • Când compilatorul te blochează, citește mesajul până la capăt: de multe ori îți spune chiar soluția.