Rust - from Mozilla

Dati e variabili            
 

Abbiamo visto nell'introduzione alcuni semplici concetti. Approfondiamo ora il discorso iniziando dalla definizione delle variabili. Come detto queste vengono introdotte dalla keyword let, variabili locali immutabili una volta inizializzate, oppure let mut nel caso in cui la variabile sia modificabile in più momenti diversi. Il seguente esempio ci mostra qualche cosa di interessante:

  Esempio 3.1
1
2
3
4
5
6
fn main()
{
  let mut x = 100;
  x = 100 * 2;
  io::println(int::str(x));
}

Riga 1: reincontriamo la funzione main, già vista nel primo paragrafo.
Riga 3: abbiamo una definizione di variabile. Come è evidente non è definito un tipo per essa; entra in gioco l'inferenza di tipo che attribuisce quello più corretto alla variabile x.
Riga 5: come si vede ancora una volta i :: ci permettono di utilizzare una funzione legata al tipo intero (int). Questa funzione è str che converte l'intero in stringa. Da sequenza dei doppi "due punti" è l'equivalente del . (punto) in altri linguaggi.

Rust permette anche di specificare il tipo della variabile in modo esplicito attraverso la seguente sintassi:

let nomevariabile : tipo = valore
oppure
let mut nomevariabile: tipo = valore

usando quindi i "due punti". Specificare il tipo è inoltre obbligatorio quando si abbia a che fare con delle costanti. Queste, che rappresentano dei valori immutabili vengono specificate tramite la keyword const e devono essere inizializzate in fase di codifica.

const x: int = 10

La differenza tra variabili locali introdotte da let (non let mut) e costanti è data dal fatto che, come appena detto, le costanti devono essere inizializzate subito, in fase di scrittura del codice mentre le varibili locali possono ricevere l'inizializzazione anche a run time (una volta sola); tuttavia quest'ultima inizializzazione va obbligatoriamente fatta prima di usare la variabile stessa altrimenti il compilatore si lamenta.

Per lavorare è utile avere qualche strumento con cui farlo. Vediamo pertanto i tipi base.
NB: quanto segue è un abbozzo.

Cominciamo dagli interi, il tipo concenttualmente più semplice. Rust prevede l'esistenza interi con segno e senza segno siano a 8, 16, 32 e 64 bit. I formati accettati sono decimale, ottale ed esadecimale, come consueto. Essi sono definiti per inferenza oppure esplicitati attraverso opportuni suffissi, ovvero i (che sta per intero) + n (che la lunghezza in bit), oppure u (unsigned) + n (lunghezza in bit). Ad esempio:

let x1 = 1; // intero ottenuto per inferenza
let x2 = 2i // intero, come evidenziato dal suffisso i
let x3 = 3i32 // il suffisso indica un intero 32 bit
let x4 = u16 // intero unsigned 16 bit

Ovviamente niente di strano anche per quanto riguarda le operazioni di base:

  Esempio 3.2
1
2
3
4
5
6
7
8
9
10
fn main()
{
  let x1 = 200;
  let x2 = 100;
  io::println(int::str(x1 + x2));
  io::println(int::str(x1 - x2));
  io::println(int::str(x1 * x2));
  io::println(int::str(x1 / x2));
  io::println(int::str(x1 % x2));
}

L'esempio non ha bisogno di troppe spiegazioni, unica segnalazione è che la riga 8 ci presenta la divisione e la 9 il resto di una divisione (in questo caso è 0). Un'altra operazione comune è l'elevamento a potenza che merita un esempio a parte, trattandosi di una funzione di libreria:

  Esempio 3.3
1
2
3
4
5
6
fn main()
{
  let mut f1 = 0.0;
  f1 = float::pow_with_uint(4, 3);
  io::println(float::to_str(f1, 5));
}

La funzione è pow_with_uint, che accetta in input due interi senza segno e restituisce un float. Lavorando solo con gli interi esiste l'alternativa pow che accetta come argomenti int come primo parametro e uint come secondo, quindi niente numeri negativi anche se, in realtà, il compilatore in loro presenza non segnala errore ma presenta risultati probabilmente incomprensibili di primo acchito... Usando pow si poteva scrivere una riga, apparentemente un po' contorta, in realtà semplice concettualmente del tipo:

io::println((int::to_str(int::pow(4,3))));

La funzione to_str del modulo float accetta in input un float e il numero di cifre da usare, nell'esempio 5, e restituisce la stringa rappresentativa corrispondente. Daremo uno sguardo a questi moduli che sono ricchi di funzioni utili.

A proposito dei float anche essi sono naturalmente presenti in Rust. La forma è quella consueta in molti altri linguaggi di programmazione insieme a qualche piccola particolarità che vediamo subito negli esempi di seguito:

1) 123.123
2) 3.0
3) 3f
4) 4f32
5) 5f64

Il primo caso è un float "normale". Il secondo anche, al di là del fatto che dopo la virgola ci sia il valore 0. il 3) sarebbe un intero dal momento che non presenta il classico punto separatore per i decimali, il 4 ci avvisa che il numero è un float 32 mentre il 5) che siamo davanti ad un float 64.

  Esempio 3.4
1
2
3
4
5
6
7
fn main()
{
  let mut f1 = 2.3;
  let x = 3.3;
  f1 = f1 * x;
  io::println(float::to_str(f1,5));
}

Da notare che il prodotto alla riga 5 avrebbe dato errore se, alla 4, avessimo definito x come 3 invece di 3.3; questo evidentemente perchè x a quel punto sarebbe risultato di tipo intero e Rust su queste commistioni è parecchio suscettibile. Se x fosse stato per forza definito come intero, perchè magari così veniva bene in altre parti del programma, avremmo dovuto modificare la riga 5 utilizzando l'operatore as, che incontreremo anche altrove:

f1 = f1 * (x as float);

I boolean sono, come in altri linguaggi, piuttosto banali: bool è la keyword mentre true e false sono i valori accettabili, gli unici, quindi non è permesso usare 1 e 0, come ad esempio si può fare in C.

  Esempio 3.5
1
2
3
4
5
6
7
8
9
fn main()
{
  let mut f1 = true;
  let f2 = true;
  io::println(bool::to_str(f1));
  io::println(bool::to_str(f2));
  let f1 = bool::not(f2);
  io::println(bool::to_str(f1));
}

La riga 7 ci presenta la funzione not che serve, come è chiaro dall'esempio, ad invertire i true e false.

Il tipo char indica chiaramente il singolo carattere. Il formato è quello UTF32.

  Esempio 3.6
1
2
3
4
5
fn main()
{
let x = 'a';
io::println(str::from_char(x));
}

Una caratteristica di tutti i tipi è quella di essere corredati da una gran quantità di funzioni, come è possibile intuire da questi semplici esempi, che consentono di gestirli in tutti gli aspetti. A questo scopo consiglio di fare una visita sul sito ufficiale per l'elenco completo.