Î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.
- Instalează rustup de pe rustup.rs.
- Deschide un terminal NOU (cel vechi nu vede încă noile comenzi).
- Scrie rustc --version și cargo --version. Dacă vezi versiuni, ești gata.
- Ca să actualizezi mai târziu, rulează rustup update.
Cargo: unealta de proiect. Cargo creează proiecte, compilează și rulează. Pentru un proiect nou:
cargo new salut
cd salut
cargo runcargo 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
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.
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 10Dacă î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ă).
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 10Text: 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.
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.
ALEXCondiț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).
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ță: -2Intervalul 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ă.
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.
fn main() {
let a = String::from("DominosLand");
let b = a.clone();
println!("{a} {b}");
let c = b;
println!("{c}");
}Rezultat
DominosLand DominosLand
DominosLandclone() 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:
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.
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ă.
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 avansatself 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.
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: 300Erori 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.
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)
trueMetoda 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.
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 3Idei 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.