Tuple in Rust
Una tupla raggruppa in un unico valore più valori, anche di tipi diversi. È un modo veloce per mettere insieme cose collegate, senza definire un tipo nuovo apposta.
Creare una tupla
Sezione intitolata “Creare una tupla”fn main() { let person: (&str, i32, bool) = ("Alice", 30, true); println!("{:?}", person);}Output:
("Alice", 30, true)Nota che la tupla mescola in un solo valore un &str, un i32 e un bool: un array non può farlo, perché tutti i suoi elementi devono avere lo stesso tipo.
Per stampare una tupla intera usa {:?} (formattazione di debug) invece di {}. Con le tuple, la semplice {} non funziona.
Leggere gli elementi per posizione
Sezione intitolata “Leggere gli elementi per posizione”Usa .0, .1, .2 e così via: le posizioni di una tupla partono da zero:
fn main() { let person = ("Alice", 30, true);
println!("Nome: {}", person.0); println!("Età: {}", person.1); println!("Attivo: {}", person.2);}Output:
Nome: AliceEtà: 30Attivo: trueLa posizione deve essere un numero scritto direttamente nel codice. Non puoi usare una variabile, come person.i, perché ogni posizione può contenere un tipo diverso e Rust deve conoscere il tipo in fase di compilazione.
Destrutturare una tupla
Sezione intitolata “Destrutturare una tupla”Puoi tirare fuori ogni valore in una sua variabile, in una riga sola:
fn main() { let person = ("Alice", 30, true); let (name, age, is_active) = person;
println!("{name} ha {age} anni, attivo: {is_active}");}Output:
Alice ha 30 anni, attivo: trueSpesso è più chiaro che ripetere .0, .1, .2, soprattutto quando una tupla ha più di due o tre campi.
Restituire più valori da una funzione
Sezione intitolata “Restituire più valori da una funzione”Le tuple sono il modo naturale per restituire più di un valore, visto che una funzione può dichiarare un solo tipo di ritorno:
fn min_max(numbers: &[i32]) -> (i32, i32) { let min = *numbers.iter().min().unwrap(); let max = *numbers.iter().max().unwrap(); (min, max)}
fn main() { let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]); println!("min: {smallest}, max: {largest}");}Output:
min: 1, max: 9Per ora non preoccuparti di &[i32], .iter() o .unwrap(): li trovi nelle pagine sulle Slice, sugli Iteratori e su Option. Qui la cosa da notare è il tipo di ritorno (i32, i32) e il modo in cui main lo destruttura subito in due variabili.
Modificare le tuple e scambiare valori
Sezione intitolata “Modificare le tuple e scambiare valori”Se una tupla è dichiarata con mut, puoi cambiarne i campi uno alla volta, usando le stesse posizioni .0, .1. I valori possono cambiare, ma la forma no: una tupla mantiene sempre il numero e i tipi dei campi con cui è nata.
La destrutturazione funziona anche al contrario: puoi assegnare dentro variabili che esistono già. Così ottieni uno scambio elegante in una riga, senza variabile temporanea. Se ti servono solo alcuni campi, scrivi _ per quelli che non ti interessano:
fn main() { let mut point = (3, 4); point.0 += 10; // modifica un campo sul posto println!("il punto è {:?}", point);
let mut a = 1; let mut b = 2; (a, b) = (b, a); // scambia i due valori println!("a = {a}, b = {b}");
let (x, _) = point; // tieni x, ignora il secondo campo println!("x = {x}");}Output:
il punto è (13, 4)a = 2, b = 1x = 13Lo scambio funziona perché Rust costruisce prima il lato destro, (b, a). Solo dopo lo spacchetta in a e b. Con _ e i pattern puoi fare molto di più: lo vedrai nella pagina sul Pattern matching.
La tupla vuota: ()
Sezione intitolata “La tupla vuota: ()”In realtà hai già incontrato una tupla senza accorgertene. () si chiama tipo unit, ed è quello che restituisce una funzione quando non restituisce niente di significativo: è il tipo di ritorno implicito di fn main().
Quando usare una tupla e quando una struct
Sezione intitolata “Quando usare una tupla e quando una struct”Le tuple sono ottime per raggruppamenti veloci e senza nome, soprattutto per restituire due o tre valori da una funzione. Quando un gruppo di valori rappresenta un concetto vero, con campi che hanno un nome (come una Person con nome ed età a cui farai riferimento per nome in tutto il codice), di solito una struct è la scelta migliore e più leggibile.
Pratica
Sezione intitolata “Pratica”1. Somma e prodotto
Sezione intitolata “1. Somma e prodotto”Scrivi una funzione sum_and_product(a: i32, b: i32) -> (i32, i32) che restituisce sia la somma sia il prodotto di due numeri. In main, chiamala con 5 e 6 e destruttura il risultato. Suggerimento: l’ultima riga della funzione è la tupla stessa, (a + b, a * b), senza punto e virgola.
Mostra la soluzione
fn sum_and_product(a: i32, b: i32) -> (i32, i32) { (a + b, a * b)}
fn main() { let (sum, product) = sum_and_product(5, 6); println!("somma = {sum}, prodotto = {product}");}Output:
somma = 11, prodotto = 302. Un rettangolo fatto di due angoli
Sezione intitolata “2. Un rettangolo fatto di due angoli”Un rettangolo si può salvare come una tupla di due punti, dove ogni punto è a sua volta una tupla: let rect = ((1, 2), (5, 5));. Calcola larghezza, altezza e area. Suggerimento: le tuple si possono annidare, e rect.1.0 significa «il primo campo del secondo punto».
Mostra la soluzione
fn main() { let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0; let height = rect.1.1 - rect.0.1;
println!("larghezza {width}, altezza {height}, area {}", width * height);}Output:
larghezza 4, altezza 3, area 12Puoi anche destrutturare entrambi i punti in un colpo: let ((x1, y1), (x2, y2)) = rect;. È più lungo da scrivere, ma x2 - x1 si legge meglio di rect.1.0 - rect.0.0. Quando i punti cominciano ad accumularsi così, è il segnale che una struct con campi con un nome sarebbe più chiara.
Riepilogo
Sezione intitolata “Riepilogo”- Una tupla raggruppa valori anche di tipi diversi:
(name, age, is_active). - Leggi i campi con
.0,.1,.2, oppure tirali fuori tutti insieme con la destrutturazione. - Le tuple sono il modo naturale per restituire più valori da una funzione.
- I campi di una tupla
mutpossono cambiare, ma la sua dimensione e i suoi tipi no.(a, b) = (b, a);scambia due valori. - Per raggruppamenti con un nome e riutilizzabili, usa invece una struct.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Che cosa può contenere una tupla che un array non può contenere?
2. Come leggi il secondo elemento di una tupla che si chiama person?
3. Dopo let mut a = 1; let mut b = 2; (a, b) = (b, a);, quanto valgono a e b?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- The Tuple Type — The Rust Book (in inglese): l’introduzione ufficiale a come creare, destrutturare e indicizzare le tuple.
- Tuples — Rust by Example (in inglese): esempi eseguibili con tuple annidate e tuple come argomenti di funzione.
- Il tipo
tuple— documentazione della libreria standard (in inglese): quali trait supportano le tuple, come il confronto, la copia e la stampa con{:?}. - Il tipo
unit— documentazione della libreria standard (in inglese): che cos’è()e dove compare. - Destructuring assignments — The Rust Reference (in inglese): le regole esatte dietro
(a, b) = (b, a);.