Home Rhyylen
Contatto
 
 
 
Rust Language
Capitolo 18
Selezione con if

Con questo paragrafo interrompiamo l'analisi dei tipi e, anche in preparazione all'importante agìrgomento che sono le funzioni, affrontiamo una delle istruzioni più note nell'intero mondo della programmazione. Si tratta della modalità più semplice per effettuare una selezione durante il flusso di esecuzione di un programma. Praticamente tutti i linguaggi prevedono questo tipo di costrutto, molto comodo, utile e di chiaro significato.

Stiamo parlando di una istruzione di selezione caratterizzata dalle keyword if ed else e che si può presentare nelle seguenti 3 forme:

1)

if condizione {
  istruzioni
}


2)

if condizione  {
  istruzioni
}

else {
  istruzioni
}


3)

if condizione {
  istruzioni
}

else if condizione {
  istruzioni
}

....
else {
  istruzioni
}


laddove la "condizione" può in realtà assumere le due forme booleane true o false. Diversamente da altri linguaggi la coppia di parentesi graffe è necessaria anche laddove il blocco istruzioni fosse composto da una sola di esse, scelta che condivido. Vediamo una serie di esempi che mostrano la progressione dei 3 punti evidenziati:

  Esempio 18.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
use std::io;
use std::io::prelude::*;
fn main(){
    print!( "Inserisci un numero: ");
    io::stdout().flush().ok().expect("");
    let mut input01 = String::new();
    io::stdin().read_line(&mut input01);
    let x01: i32 = input01.trim().parse()
    .ok()
    .expect("Voglio un numero!");
    if x01 > 10
    {
        print!("Inserito numero > 10");
    }
}

Abbiamo ampliato la nostra interattività inserendo istruzioni di input per le quali c'è l'apposito paragrafo ma che per il momento non ci interessa approfondire. Il codice dalla riga 11 alla 14 realizza il punto uno della nostra definizione formale. La riga 11 presenta una condizione booleana che, se verificata, esegue il codice alla 13. Molto intuitivo.
Facciamo un passo avanti:

  Esempio 18.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
190
use std::io;
use std::io::prelude::*;
fn main(){
    print!( "Inserisci un numero: ");
    io::stdout().flush().ok().expect("");
    let mut input01 = String::new();
    io::stdin().read_line(&mut input01);
    let x01: i32 = input01.trim().parse()
    .ok()
    .expect("Please type a number!");
    if x01 > 10
    {
        print!("Inserito numero > 10");
    }
    else
    {
        print!("Inserito un numero <= 10");
    }
}

Direi che quanto avviene dalla riga 11 alla 18 è abbastanza chiaro. Entra in scena la keyword else, che viene coinvolta laddove la condizione alla riga 11 non si realizzasse.
E veniamo all'ultimo formato:

  Esempio 18.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
use std::io;
use std::io::prelude::*;
fn main(){
    print!( "Inserisci un numero: ");
    io::stdout().flush().ok().expect("");
    let mut input01 = String::new();
    io::stdin().read_line(&mut input01);
    let x01: i32 = input01.trim().parse().ok()
    .expect("Please type a number!");
    if x01 <= 10
    {
        print!("Nido o asilo o le elementari");
    }
    else if (x01 > 10 && x01 < 14)
    {
        print!("Vai alla scuola secondaria");
    }
    else if (x01 >= 14 && x01 < 18)
    {
        print!("Sei alle superiori?");
    }
    else
    {
        print!("O sei all'UNI o hai finito la scuola");
    }
}

Anche qui è tutto abbastanza chiaro, alla riga 22 abbiamo un else senza condizione che funge da rastrello per tutti i casi non considerati nei rami precedenti. E' da notare che le parentesi presenti alle righe 14 e 18 sono opzionali, anzi il compilatore vi dimostrerà di non apprezzarle segnalando un warning. Potete inserire tante ramificazione else + if quante ne volete, tuttavia è buona norma non esagerare con il loro numero, se dovete considerare molte condizioni alternative sarà bene usare altri costrutti, tipo il pattern matching. Ovviamente, bisogna scegliere con attenzione i valori o le casistiche che costituiscono i criteri di selezione, lo dico anche se è banale perchè si vedono errori che fanno perdere tempo inutile anche per cose semplici come la manipolazione di questi costrutti.
Altra considerazione importante è che verrà eseguito il ramo corrispondente alla prima condizione soddisfatta, tutte quelle successive, anche se fossero soddisfatte, verranno saltate. Due rami non potranno essere eseguiti.
Come detto Rust un linguaggio expression oriented, ogni espressione esprime un valore che potrà essere reale o anche il semplice unit (). Questo vale anche per il nostro selettore if + else, quindi possiamo abbinarlo ad una variabile, con le consuete cautele di congruenza valori / tipo. Vediamo come:

  Esempio 18.4
1
2
3
4
5
6
7
8
9
10
11
12
13
use std::io;
use std::io::prelude::*;
fn main(){
    print!( "Inserisci un numero: ");
    io::stdout().flush().ok().expect("");
    let mut input01 = String::new();
    io::stdin().read_line(&mut input01);
    let x01: i32 = input01.trim().parse()
    .ok()
    .expect("Please type a number!");
    let y = if x01 % 2 == 0 {"pari"} else {"dispari"};
    print!("{}", y);
}

Il cuore di questo codice è ovviamente alla riga 11 dove il valore che esce dalla selezione viene attribuito alla variabile y.

La natura espressiva si può anche rivelare in questo esempio:

println!("{}", if x == 0 { "Ciao" }
else {"Boh"});

con attribuzione direttamente alla funzione di stampa.
Si evidenzia in casi come questi la necessità di avere compatibilità tra le espressioni che derivano dal nostro if. Ad esempio il seguente codice non compila:

let x = if condizione {
    42 // intero
} else {
    "ciao" // stringa
};


in quanto un ramo esprime un intero e l'altro una sequenza letterale, chiaramente incompatibili.
Un piccola carenza, peraltro non grave, del costrutto if in Rust rispetto ad altri linguaggi è la mancanza dell'operatore ternario. Un esempio rapido per chiarire può essere il seguente:

In C (e in altri linguaggi similmente) possiamo scrivere:
int x = (a > b) ? a : b;

In Rust l'equivalente obbliga l'uso di else:
let x = if a > b { a } else { b };

Molto interessante è la sequenza if - let. Questa permette di usare insieme un controllo di selezione con un pattern matching e quindi eseguire una qualche azione sulla base del confronto effettuato. Risulta utile quando ci serve per esempio lavorare solo su una parte ad esempio di una ennupla e di un enumerativo e non abbiamo bisogno di effettuare un match su tutti gli elementi (vedere paragrafo del pattern matching). Un semplice esempio potrà chiarire il concetto:

  Esempio 18.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fn main(){
    let lettere = ('a', 'b');
    if let ('a', x) = lettere
    {
        println!("{}", x);
    }
    else
    {
        println!("non esiste nessuna lettera 'a'");
    }
    if let (y, 'b') = lettere
    {
        println!("{}", y);
    }
    else
    {
        println!("non esiste nessuna lettera 'b'");
    }
}

In questo caso usiamo questo elegante costrutto per lavorare solo su un elemento della ennupla (altrettanto si può fare con un enumerativo) senza necessità di effettuare alcuna operazione di ricerca. In pratica quando possiamo lavorare solo con una parte degli elementi di una struttura complessa questa sequenza ci può venire utile. Vediamo appunto un esempio che coinvolge un enumerativo:

  Esempio 18.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum Event {
    KeyPress(char),
    Click { x: i32, y: i32 },
    None
}

fn main() {
    let event = Event::Click{x:3, y:3};

    if let Event::KeyPress(c) = event {
    println!("Premuto tasto '{}'", c);
    }
    else {
        println!("Avvenuto altro tipo di evento");
    }
}

If - let verrà molto utile con il tipo Option che impareremo a conoscere tra qualche paragrafo.