00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Slice in Rust

Le slice in Rust sono viste (riferimenti) a una sequenza contigua di elementi in una collezione. Non possiedono i dati: sono riferimenti a una porzione di dati esistenti. Le slice sono fondamentali in Rust perche permettono di lavorare con parti di stringhe, array e vettori in modo efficiente e sicuro, senza copiare i dati.

String slice (&str)

Una string slice &str è un riferimento a una porzione di una String (o di una stringa letterale). È il tipo più comune per passare stringhe a funzioni.

fn main() {
    let saluto = String::from("Ciao, mondo!");

    let ciao = &saluto[0..4];    // "Ciao"
    let mondo = &saluto[6..11];   // "mondo"

    println!("{ciao} {mondo}");
}

La sintassi [start..end] crea una slice dall’indice start (incluso) all’indice end (escluso). Si possono omettere gli estremi:

fn main() {
    let s = String::from("Rust!");

    let dal_inizio = &s[..4];  // "Rust" (da 0 a 4)
    let alla_fine = &s[1..];   // "ust!" (da 1 alla fine)
    let tutto = &s[..];        // "Rust!" (tutta la stringa)

    println!("{dal_inizio}, {alla_fine}, {tutto}");
}

Le stringhe letterali sono già delle &str:

fn main() {
    let s: &str = "Ciao Rust"; // Questa è già una slice
    println!("{s}");
}

Preferisci &str a &String nei parametri delle funzioni:

// Buona pratica: accetta &str
fn conta_vocali(testo: &str) -> usize {
    testo.chars().filter(|c| "aeiouAEIOU".contains(*c)).count()
}

fn main() {
    let stringa = String::from("Ciao Rust");
    println!("Vocali: {}", conta_vocali(&stringa));  // &String -> &str automatico
    println!("Vocali: {}", conta_vocali("hello"));   // &str direttamente
}

Array slice (&[T])

Una slice di array &[T] è un riferimento a una porzione di un array o vettore.

fn main() {
    let numeri = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    let primi_tre = &numeri[..3];    // [1, 2, 3]
    let centrali = &numeri[3..7];    // [4, 5, 6, 7]
    let ultimi = &numeri[7..];       // [8, 9, 10]

    println!("Primi tre: {:?}", primi_tre);
    println!("Centrali: {:?}", centrali);
    println!("Ultimi: {:?}", ultimi);
}

Anche i vettori si possono affettare:

fn somma(numeri: &[i32]) -> i32 {
    numeri.iter().sum()
}

fn main() {
    let vettore = vec![10, 20, 30, 40, 50];

    println!("Somma totale: {}", somma(&vettore));
    println!("Somma primi 3: {}", somma(&vettore[..3]));
    println!("Somma ultimi 2: {}", somma(&vettore[3..]));
}

Slice mutabili (&mut [T])

Le slice mutabili permettono di modificare gli elementi della sequenza originale.

fn azzera(slice: &mut [i32]) {
    for elem in slice.iter_mut() {
        *elem = 0;
    }
}

fn raddoppia(slice: &mut [i32]) {
    for elem in slice.iter_mut() {
        *elem *= 2;
    }
}

fn main() {
    let mut dati = [1, 2, 3, 4, 5, 6];

    raddoppia(&mut dati[..3]); // Raddoppia solo i primi 3
    println!("{:?}", dati);    // [2, 4, 6, 4, 5, 6]

    azzera(&mut dati[3..]);    // Azzera gli ultimi 3
    println!("{:?}", dati);    // [2, 4, 6, 0, 0, 0]
}

Metodi utili delle slice

Le slice offrono numerosi metodi per lavorare con i dati.

len e is_empty

fn main() {
    let dati = [1, 2, 3, 4, 5];
    let slice = &dati[..];

    println!("Lunghezza: {}", slice.len());         // 5
    println!("È vuota: {}", slice.is_empty());      // false

    let vuota: &[i32] = &[];
    println!("Vuota: {}", vuota.is_empty());        // true
}

first, last e get

fn main() {
    let numeri = [10, 20, 30, 40, 50];

    println!("Primo: {:?}", numeri.first());       // Some(10)
    println!("Ultimo: {:?}", numeri.last());       // Some(50)
    println!("Indice 2: {:?}", numeri.get(2));     // Some(30)
    println!("Indice 10: {:?}", numeri.get(10));   // None (sicuro, niente panic)

    // Accesso diretto con indice (può causare panic se fuori range)
    println!("Indice 2: {}", numeri[2]);           // 30
}

get() è preferibile all’accesso diretto con [] quando l’indice potrebbe essere fuori range.

iter

fn main() {
    let frutti = ["mela", "pera", "banana"];

    for (i, frutto) in frutti.iter().enumerate() {
        println!("{i}: {frutto}");
    }

    // Somma con iter
    let numeri = [1, 2, 3, 4, 5];
    let somma: i32 = numeri.iter().sum();
    println!("Somma: {somma}");
}

windows

windows(n) restituisce un iteratore su sotto-slice consecutive di dimensione n.

fn main() {
    let dati = [1, 2, 3, 4, 5];

    println!("Finestre di 3 elementi:");
    for finestra in dati.windows(3) {
        println!("  {:?}", finestra);
    }
    // [1, 2, 3]
    // [2, 3, 4]
    // [3, 4, 5]
}

chunks

chunks(n) divide la slice in blocchi di dimensione n. L’ultimo blocco può essere più corto.

fn main() {
    let dati = [1, 2, 3, 4, 5, 6, 7];

    println!("Blocchi di 3:");
    for blocco in dati.chunks(3) {
        println!("  {:?}", blocco);
    }
    // [1, 2, 3]
    // [4, 5, 6]
    // [7]
}

contains e starts_with / ends_with

fn main() {
    let numeri = [1, 2, 3, 4, 5];

    println!("Contiene 3: {}", numeri.contains(&3));   // true
    println!("Contiene 9: {}", numeri.contains(&9));   // false

    println!("Inizia con [1, 2]: {}", numeri.starts_with(&[1, 2])); // true
    println!("Finisce con [4, 5]: {}", numeri.ends_with(&[4, 5])); // true
}

sort e reverse (slice mutabili)

fn main() {
    let mut numeri = [5, 3, 8, 1, 9, 2, 7];

    numeri.sort();
    println!("Ordinato: {:?}", numeri);  // [1, 2, 3, 5, 7, 8, 9]

    numeri.reverse();
    println!("Invertito: {:?}", numeri); // [9, 8, 7, 5, 3, 2, 1]
}

Sintassi di slicing [start…end]

Riepilogo delle forme di slicing:

fn main() {
    let v = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    let _ = &v[2..5];   // [2, 3, 4]       - da indice 2 a 5 (escluso)
    let _ = &v[..4];    // [0, 1, 2, 3]    - dall'inizio a 4 (escluso)
    let _ = &v[6..];    // [6, 7, 8, 9]    - da 6 alla fine
    let _ = &v[..];     // [0, 1, ..., 9]  - tutta la slice
    let _ = &v[2..=5];  // [2, 3, 4, 5]    - da 2 a 5 (incluso)
}

Conclusione

Le slice sono uno strumento essenziale in Rust per lavorare con porzioni di dati in modo sicuro ed efficiente. Permettono di passare riferimenti a parti di collezioni senza copiare dati e senza rischiare accessi fuori dai limiti (con get()). Preferisci sempre &str a &String e &[T] a &Vec<T> nelle firme delle funzioni per rendere il codice più flessibile e idiomatico.