Slice in Rust
Una slice ti permette di prendere in prestito una parte di un array o di un vettore (una «vista» su un pezzo di esso) senza copiare i dati e senza prenderne il possesso. Se hai già usato il prestito (borrowing) con un singolo valore, la slice è la stessa idea applicata a un intervallo di elementi.
Una slice di un array
Sezione intitolata “Una slice di un array”fn main() { let numbers = [10, 20, 30, 40, 50];
let middle = &numbers[1..4]; // gli elementi agli indici 1, 2, 3 println!("{:?}", middle);}Output:
[20, 30, 40]1..4 è lo stesso tipo di range che hai visto nei cicli for: parte dall’indice 1 e si ferma prima dell’indice 4, quindi ottieni gli indici 1, 2 e 3.
Scorciatoie per i range delle slice
Sezione intitolata “Scorciatoie per i range delle slice”Alcune scorciatoie che capitano spesso:
fn main() { let numbers = [10, 20, 30, 40, 50];
println!("{:?}", &numbers[..3]); // dall'inizio fino all'indice 3 -> [10, 20, 30] println!("{:?}", &numbers[2..]); // dall'indice 2 alla fine -> [30, 40, 50] println!("{:?}", &numbers[..]); // tutto quanto -> [10, 20, 30, 40, 50]}Slice di stringhe
Sezione intitolata “Slice di stringhe”In realtà stai già usando le slice: &str è una slice di una String (o di un’altra stringa)! Ecco perché i due tipi vanno così d’accordo:
fn main() { let sentence = String::from("Una rapida volpe marrone");
let first_word = &sentence[0..3]; println!("{first_word}"); // "Una"}Gli indici di una slice di stringa contano i byte, non le lettere. Qui va tutto bene perché «Una» non ha lettere accentate; una lettera come «è» occupa 2 byte, quindi con gli accenti gli indici vanno scelti con più attenzione.
Perché le slice invece di copiare?
Sezione intitolata “Perché le slice invece di copiare?”Potresti pensare di risolvere il problema copiando in un nuovo Vec o in una nuova String la parte che ti interessa. Le slice evitano del tutto la copia: puntano semplicemente a una parte dei dati che esistono già, come un riferimento (&) punta a un singolo valore. Quindi niente allocazioni in più, niente copie, e continuano a valere le regole di prestito che conosci dalla pagina sul Borrowing.
Una funzione che riceve una slice
Sezione intitolata “Una funzione che riceve una slice”Le slice sono particolarmente utili come parametri di funzione, perché accettano sia array sia vettori:
fn sum(numbers: &[i32]) -> i32 { let mut total = 0; for n in numbers { total += n; } total}
fn main() { let array = [1, 2, 3]; let vector = vec![4, 5, 6];
println!("{}", sum(&array)); println!("{}", sum(&vector));}&[i32] accetta una slice presa sia da un array sia da un Vec: una sola funzione, e funzionano entrambe le chiamate.
Modificare i dati con una slice mutabile
Sezione intitolata “Modificare i dati con una slice mutabile”Finora le nostre slice hanno solo letto i dati. Una slice mutabile, &mut [T], permette a una funzione di modificare gli elementi che ha ricevuto, ma solo quelli:
fn double_all(numbers: &mut [i32]) { for i in 0..numbers.len() { numbers[i] *= 2; }}
fn main() { let mut scores = vec![1, 2, 3, 4, 5];
double_all(&mut scores[1..3]); // solo gli indici 1 e 2 println!("{:?}", scores);
double_all(&mut scores); // tutto il vettore println!("{:?}", scores);}Output:
[1, 4, 6, 4, 5][2, 8, 12, 8, 10]La prima chiamata passa &mut scores[1..3], quindi double_all vede solo gli indici 1 e 2. Tutto quello che sta fuori da quel range è al sicuro. Dentro la funzione la slice riparte dall’indice 0: per double_all è soltanto una lista di due numeri.
È sicuro grazie a una regola che conoscerai per bene nella pagina sul Borrowing: finché la funzione tiene una slice &mut, nient’altro può usare quegli elementi, quindi due parti del codice non possono mai modificarli nello stesso momento. Anche il vettore deve essere dichiarato mut, altrimenti Rust non ti lascia prenderlo in prestito in modo mutabile.
Pratica
Sezione intitolata “Pratica”1. Dividi a metà
Sezione intitolata “1. Dividi a metà”Crea il vettore vec![3, 6, 9, 12, 15, 18]. Stampa la prima metà e la seconda metà come due slice separate. Suggerimento: calcola let mid = numbers.len() / 2; e usa ..mid e mid...
Mostra la soluzione
fn main() { let numbers = vec![3, 6, 9, 12, 15, 18]; let mid = numbers.len() / 2;
let first_half = &numbers[..mid]; let second_half = &numbers[mid..];
println!("Prima metà: {:?}", first_half); println!("Seconda metà: {:?}", second_half);}Output:
Prima metà: [3, 6, 9]Seconda metà: [12, 15, 18]Visto che mid è calcolato a partire da len(), il codice continua a funzionare anche se aggiungi o togli numeri. Con una lunghezza dispari, l’elemento in più finisce nella seconda metà.
2. Il più grande in qualsiasi lista
Sezione intitolata “2. Il più grande in qualsiasi lista”Scrivi una funzione largest(numbers: &[i32]) -> i32 che restituisce il numero più grande. Chiamala con un array, con un vettore e con una slice che contiene solo il primo elemento del vettore. Suggerimento: parti da numbers[0] e scorri il resto, come faresti con un array.
Mostra la soluzione
fn largest(numbers: &[i32]) -> i32 { let mut biggest = numbers[0]; for n in numbers { if *n > biggest { biggest = *n; } } biggest}
fn main() { let array = [7, 2, 9, 4]; let vector = vec![10, 35, 21];
println!("{}", largest(&array)); println!("{}", largest(&vector)); println!("{}", largest(&vector[..1]));}Output:
93510Scorrendo una slice ricevi un riferimento a ogni elemento (&i32), non il numero stesso. *n significa «il valore a cui punta n», quindi *n > biggest confronta due semplici numeri. Questa versione va in panic con una slice vuota, perché numbers[0] non esiste. Più avanti imparerai a restituire un Option per quel caso.
Riepilogo
Sezione intitolata “Riepilogo”- Una slice (
&[T]) prende in prestito una parte di un array o di un vettore senza copiarla. 1..4seleziona gli indici da 1 a 3; ometti uno dei due lati di..per partire dall’inizio o arrivare alla fine.&strè a sua volta una slice di testo: per questo stringhe e slice si comportano in modo così simile.- Preferisci
&[T]come tipo di parametro quando vuoi accettare sia array sia vettori. - Una slice mutabile (
&mut [T]) permette a una funzione di modificare solo gli elementi che ha ricevuto.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Che cosa ti permette di fare una slice con una parte di un array o di un vettore?
2. Quali indici include &numbers[1..4]?
3. Perché &[i32] è una buona scelta come tipo di parametro di una funzione?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- The Slice Type — The Rust Book (in inglese): arriva alle slice passo dopo passo, con l’esempio della «prima parola di una stringa».
- Arrays and Slices — Rust by Example (in inglese): esempi eseguibili di array presi in prestito come slice.
slice— documentazione della libreria standard (in inglese): i tanti metodi delle slice, comefirst,split_at,containsesort.- Slice types — The Rust Reference (in inglese): la definizione precisa, se vuoi sapere esattamente cosa significano
[T]e&[T].