|
Come abbiamo accennato nel capitolo precedente, Rust, come altri linguaggi, prevede i range. Questi sono particolari sequenze delimitate di elementi e normalmente sono create sulla base di interi o caratteri. E' possibile anche creare range su tipi personalizzati purchè questi implementino obbligatoriamente il trait PartialOrd per permettere il confronto di elementi e preferibilmente, ma non obbligatoriamente, il trait sized per questioni di ottimizzazione e performance. In questo paragrafo ci occuperemo comunque solo dei tipi standard. La definizione formale interna ci dice già tutto dei range: pub struct Range<Idx> pub start: Idx, pub end: Idx, } in pratica siamo di fronte ad una struct in cui è presente: - un parametro generico che ci indica il tipo di inizio e fine dei delimitatori ed è quel <Idx> - un valore di partenza - start - un valore finale - end Il formato (almeno quello più consueto ma come vedremo a fine paragrafo ve ne sono anche un po' diversi), come abbiamo visto nel precedente paragrafo può presentarsi in due modalità che ripetiamo pari pari qui: start..end che equivale, supponendo di ispezionare tale range con la variabile x a: start <= x < end quindi escluso il valore più alto e si parla di range esclusivo. Se vogliamo che anche questo sia incluso (quindi qui abbiamo il range inclusivo) la sintassi diventa: start ..= end quindi 0..5 comprende tutti gli elementi da 0 a 4 0..=5 comprende tutti gli elementi da 0 a 5. I campi start ed end possono essere modificati se il range è dichiarato mutabile: let mut r1 = 0..5; r1.start = 2; I range, come abbiamo visto, sono estremamente utili nelle operazioni di iterazione, potete rivedere gli esempi che abbiamo dato nei passati paragrafi. Abbiamo tuttavia evidenziato nel paragrafo dedicato all'iterazione while non è possibile usare i range direttamente con esso. In pratica questo codice: let mut x = 0; while x in (0..=5) { non compila, in fondo while è legato ad una valutazione booleana stretta. Possiamo aggirare il problema come segue, nel caso vi fosse necessario usare per forza un range con un metodo molto scolastico:
Abbiamo fatto uso dei campi start e end propri della definizione di un range per delimitare l'ambito di applicazione del while. Un'alternativa forse più "rusticeana" è la seguente: let mut x = 0; while (0..=5).contains(&x) { println!("x nel range {}", x); x += 1; } Come detto i range possono lavorare anche su altre tipologie di dati, come i caratteri: for x in 'a'..'f' { print!("{}", x) questo codice espone a video i caratteri da 'a' a 'e' Un caso diverso è se si ha a che fare con i float. ovvero è lecito dichiarare un range come segue let r1 = 1.1..=1.9; ma i float non implementano il trait step necessario per le iterazioni. Quindi lo create ma, sostanzialmente non ve ne fate nulla. Se volete stampare una iterazione sui float dovrete gestire manualmente la cosa, come nel seguente esempio, che in realtà con c'entra con i range ma che presento a titolo didattico per fornire una soluzione al problema:
fn main() {
let start = 0.0; // valore iniziale
let end = 1.0; // valore finale
let step = 0.1; // passo di incremento
let mut current = start;
while current < end {
println!("{:.1}", current);
current += step;
}
}
I range hanno solo due campi come detto, start e end ma dispongono anche di alcune proprietà utili per la loro manipolazione:
Oltre al range standard, oggetto finora di questo capitolo, che abbiamo visto nelle due forme esclusivo ed inclusivo, ne esistono altri formati che ci permettono ulteriori espressività. Possiamo quindi presentare la seguente tabella che riassume quello che abbiamo a disposizione in Rust:
Bisogna fare attenzione che la manipolabilità non è uguale per tutti. Ad esempio i range del secondo e del terzo o dell'ultimo punto non implementano il trait Iterator, per cui non sono attraversabili con un semplice for. Una soluzione è ricorrere a take come nell'esempio seguente:
I range non sono indicizzati, se è necessario ricavare un elemento ad un certa posizione dobbiamo convertirlo in un tipo indicizzato oppure usare un iteratore, vedremo nel capitolo dedicato a questo argomento l'uso di nth(). Per quanto riguarda la conversione verso un tipo indicizzato un metodo semplice è passare da range a vettore tramite il metodo collect e abbiamo visto un esempio proprio nel capitolo dedicato ai vettori. |