|
I valori di tipo booleano, nella loro semplicità, sono tra i grandi protagonisti della programmazione. Si tratta tra l'altro, come evidenziato nel capitolo precedente, di uno dei tipi primitivi, built-in, del linguaggio. In Rust i valori ammessi sono due, come in molti altri linguaggi: * true * false vero e falso. Una variabile booleana si dichiara nel solito modo: let b1 = true: let b2: bool = false; let mut b3 = true; non sono ammessi altri valori ad esempio valori 0 e 1 che in altri linguaggi possono sostituirsi a true e false non sono consentiti. Questo è un bene. Vedremo comunque un esempio in fine paragrafo che chiarirà ulteriormente il rapporto tra interi e booleani. Come sempre, affinchè dette variabili siano modificabili è necessario che sia presente la solita keyword mut. Annotiamo che dal punto di vista tecnico una variabile booleana occupa lo spazio di un solo byte. In pratica basterebbe un bit (e in qualche condizione particolare il compilatore è in grado di ottimizzare usando un solo bit, come nei tipi complessi, potete informarvi relativamente alle tecniche di "niche optimization") ma in questo modo è possibile creare dei puntatori anche verso variabili booleane. Ora, una variabile dichiarata come negli esempi precedenti piuttosto raramente vi tornerà utile. Invece, nella maggioranza dei casi il valore booleano che tratterete sarà il risultato di una espressione ovvero una operazione di confronto. Vediamo quali sono gli operatori usati e chiariamo il concetto con un esempio:
Questi operatori danno origine ad espressioni il cui valore deve essere necessariamente vero o falso. Vediamo il seguente esempio:
che restituisce il seguente output:
Quando ci occuperemo delle istruzioni di selezione, che possono presentare naturalmente le più svariate casistiche, vedremo quanto importanti siano questi operatori. Sui valori booleani, true e false, è possibile compiere delle operazioni di carattere logico. Gli operatori principali sono i seguenti:
and true and true -> true true and false -> false false and true -> false false and false -> false or true or true -> true true or false -> true false or true -> true false or false -> false not not true -> false not false -> true xor true xor true -> false true xor false -> true false xor true -> true false xor false -> false == true == true -> true false == true -> false true == false -> false false == false -> true Il tipo bool essendo, come detto, primitivo in Rust implementa 5 trait fondamentali (oltre ad altri ancora): Clone, Copy, Sized, Send, Sync. Cosa questo significhi in dettaglio è materia molto più avanzata, per il momento diciamo che grazie al primo, siamo in grado di effetturare delle copie bit a bit. Il seguente codice crea due variabili booleane b1 e b2 che è copia di b1 ma da essa indipendente. fn main() { let mut b1 = true; let mut b2 = b1; b1 = false; print!("{}", b2); } b2 continua valere "true" mentre b1 è passato a false. Quindi b2 è del tutto staccato e indipendente da b1. Abbiamo detto che non è ammesso che valori numerici siano direttamente usati come booleani. E' tuttavia possibile effettuare una conversione da booleano ad intero mentre non è ammesso il contrario:
se escludiamo la riga 5, l'output è:
mentre reintroducendo la 5 si otterrebbe: error[E0054]: cannot cast `i32` as `bool` chiaro e forte. Quindi possiamo andare dai booleani verso gli interi ma non viceversa. Per quanto non utile nel nostro contesto segnalo anche la presenza di un crate che si chiama as_bool e fornisce un modo per rappresentare vari tipi in un contesto booleano. Esso si basa su una serie di regole:
Il suo uso, che necessita di qualche manipolazione un po' particolare, che fa necessariamente uso del file cargo.toml, esula un po' da questo momento del nostro percorso ma vediamo comunque un esempio. 1) lanciamo il comando cargo new asbool 2) il file cargo.toml può venire editato come segue: [package] name = "asbool" version = "0.1.0" edition = "2024" [dependencies] as_bool = "0.1.3" la parte in rosso è quella che ho inserito manualmente infine ecco il codice: use as_bool::AsBool;
fn main() {
println!("{}", 1.as_bool()); // true
println!("{}", 0.as_bool()); // false
println!("{}", true.as_bool());
println!("{}", Some(42).as_bool());
println!("{}", None::<i32>.as_bool());
}
che in base alle regole precedenti fornisce il seguente output:
I booleani internamente sono più complessi di quanto sembri e su di essi è possibile effettuare ulteriori manipolazioni (per fare un esempio i classici BitAnd e BitOr grazie ai trait omologhi che questo tipo implementa e che sui booleani agiscono come operatori logici) che però esulano dalla finalità di base di questo paragrafo. Vale però la pena fare una osservazione interessante:
Questo programma compila tranquillamente e restituisce un "not ok" in quanto le condizioni non sono verificate. Se però al posto dell'operatore && mettete un BitAnd ovvero & ne otterrete: thread 'main' (25288) panicked at r064.rs:5:26: index out of bounds: the len is 3 but the index is 10 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace questo perchè && verifica la prima condizione e trovandola non verificata non valuta la seconda mentre & le valuta entrambe e a quel punto risulta che v[i] ovvero v[10] non esiste perchè l'indice è fuori range. Questo tipo di valutazione "totale" vale anche per BitOr. |