Vec în Rust

Vectorul dinamic din Rust: o listă de elemente de același tip, ținute unul după altul în memorie, care crește singură.

Sintaxă
pub struct Vec<T> { /* câmpuri private */ }

let v: Vec<T> = Vec::new();
let v = vec![a, b, c];
let v = vec![valoare; n];
let v: Vec<T> = iterator.collect();

Ce face Vec

Vec<T> este colecția pe care o vei folosi cel mai des în Rust. Este un tablou care își gestionează singur memoria (pe heap): adaugi elemente cu push, iar vectorul crește cât are nevoie. Elementele stau unul lângă altul, deci accesul după poziție (v[i]) este foarte rapid. Pozițiile încep de la 0.

Vec::new() creează un vector gol și nu alocă nimic până la primul element. Macro-ul vec![1, 2, 3] creează un vector cu valori, iar vec![0; 5] unul cu cinci zerouri. Poți obține un Vec și dintr-un iterator, cu collect(), sau dintr-un tablou, cu Vec::from([..]). Ca orice variabilă Rust, vectorul trebuie declarat cu let mut dacă vrei să-l modifici.

Vectorul este proprietarul elementelor: când vectorul iese din domeniu, elementele sunt eliberate. Dacă îl dai unei funcții prin valoare, el se mută și nu îl mai poți folosi. Funcțiile care doar citesc primesc de obicei &[T] (un slice), iar cele care modifică primesc &mut Vec<T>. Multe metode folosite pe Vec (sort, contains, iter, windows) vin de fapt de la slice-uri și merg și pe tablouri.

Parametri

Parametru Descriere
T Tipul elementelor: i32, f64, String, &str, o structură proprie etc. Toate elementele au același tip.
vec![a, b, c] Macro care creează vectorul direct cu elementele date.
vec![valoare; n] Creează n copii ale valorii (tipul trebuie să implementeze Clone).

Ce returnează

Vec::new() returnează un vector gol, fără memorie alocată. Metodele vectorului au pagini separate.

Exemple

Exemplul 1: Moduri de a crea un vector

Un vector gol are nevoie de tipul scris explicit (Vec<i32>), pentru că nu există elemente din care compilatorul să-l deducă. {:?} afișează tot vectorul.

fn main() {
    let goale: Vec<i32> = Vec::new();
    let note = vec![9, 7, 10];
    let zerouri = vec![0; 5];
    let orase = Vec::from(["Iași", "Cluj", "Timișoara"]);
    let patrate: Vec<u32> = (1..=5).map(|x| x * x).collect();

    println!("goale:   {:?} (len = {})", goale, goale.len());
    println!("note:    {:?}", note);
    println!("zerouri: {:?}", zerouri);
    println!("orase:   {:?}", orase);
    println!("patrate: {:?}", patrate);
}

Rezultat

goale:   [] (len = 0)
note:    [9, 7, 10]
zerouri: [0, 0, 0, 0, 0]
orase:   ["Iași", "Cluj", "Timișoara"]
patrate: [1, 4, 9, 16, 25]

Exemplul 2: Adăugare, parcurgere și media notelor

Tipul Vec<i32> este dedus din primul push. for nota in &note parcurge vectorul fără să-l consume, așa că îl putem folosi și după buclă.

fn main() {
    let mut note = Vec::new();
    note.push(8);
    note.push(10);
    note.push(7);

    let mut suma = 0;
    for nota in &note {
        suma += nota;
    }
    let media = suma as f64 / note.len() as f64;
    println!("Ana are {} note: {:?}", note.len(), note);
    println!("Media: {:.2}", media);
    println!("Prima notă: {}", note[0]);
}

Rezultat

Ana are 3 note: [8, 10, 7]
Media: 8.33
Prima notă: 8

Exemplul 3: Vectorul dat unei funcții: împrumut sau mutare

total doar citește, deci primește &[f64]. adauga_tva modifică, deci primește &mut Vec<f64>. La final, let mutat = cos; mută vectorul: de acum doar mutat poate fi folosit.

fn total(preturi: &[f64]) -> f64 {
    preturi.iter().sum()
}

fn adauga_tva(preturi: &mut Vec<f64>) {
    for p in preturi.iter_mut() {
        *p *= 1.21;
    }
}

fn main() {
    let mut cos = vec![12.5, 30.0, 7.25];
    println!("Total fără TVA: {:.2} lei", total(&cos));

    adauga_tva(&mut cos);
    let afisare: Vec<String> = cos.iter().map(|p| format!("{p:.2}")).collect();
    println!("Prețuri cu TVA: {}", afisare.join(", "));

    let mutat = cos; // cos a fost mutat și nu mai poate fi folosit
    println!("Total cu TVA: {:.2} lei", total(&mutat));
}

Rezultat

Total fără TVA: 49.75 lei
Prețuri cu TVA: 15.12, 36.30, 8.77
Total cu TVA: 60.20 lei

Greșeli frecvente

  • Citești o poziție care nu există, de exemplu v[3] pe un vector cu 3 elemente. Programul se oprește cu panic: index out of bounds: the len is 3 but the index is 3. Când poziția poate lipsi, folosește v.get(i), care returnează Option.
  • Scrii let v = Vec::new(); și nu adaugi nimic: compilatorul spune type annotations needed for Vec<_>. Scrie tipul: let v: Vec<i32> = Vec::new();.
  • Folosești vectorul după ce l-ai dat prin valoare unei funcții sau altei variabile: eroarea borrow of moved value. Dă-l prin referință (&v) sau, dacă chiar ai nevoie de două copii, cu v.clone().
  • Parametri de tip &Vec<T> pentru funcții care doar citesc. Merge, dar &[T] este mai general: acceptă și tablouri, și bucăți de vector (&v[1..3]).

Comenzi înrudite

  • Vec::push: push adaugă un element la sfârșitul vectorului; pop îl scoate pe ultimul și îl returnează ca Option.
  • Vec::with_capacity: Creează un vector cu loc rezervat dinainte; capacity spune cât loc are, iar reserve mărește locul fără să adauge elemente.
  • Vec::get: Citește elementul de pe o poziție fără risc de panic: returnează Some(&element) sau None dacă poziția nu există.
  • Vec::iter: Cele trei moduri de a parcurge un vector: iter dă referințe, iter_mut dă referințe mutabile, into_iter dă elementele.
  • Vec::len: len returnează numărul de elemente din vector; is_empty spune dacă vectorul nu are niciun element.
  • VecDeque: Coada cu două capete: adaugă și scoate rapid elemente atât la început, cât și la sfârșit.
  • HashMap: Dicționarul din Rust: asociază fiecărei chei o valoare și o găsește foarte repede după cheie. Ordinea nu este garantată.