In programmazione, quando definiamo e inizializziamo una variabile, il suo valore viene conservato nella memoria. La variabile è semplicemente il nome che accede a quella memoria. In Rust una variabile può essere definita con il termine let ma possiamo anche creare delle constanti che contengono valori fondamentali per tutto il programma.
Una caratteristica di Rust è che le variabili sono immutabili per impostazione predefinita: non possiamo cambiare il suo valore. Dato che si tratta di un linguaggio che viene usato per interagire con l’hardware è importante che i dati non vengano modificati accidentalmente. Tuttavia, possiamo permettere la modifica dei valori di una variabile se usiamo il termine mut.
//Non possiamo modificare il valore
let n1 = 5;
//Possiamo modificare il valore
let mut n2 = 3;
Quando creiamo una funzione, un blocco di istruzioni ripetute, possiamo passare i valori di una variabile per essere gestita al suo interno. Tuttavia, in Rust, se passiamo il valore di un dato complesso, la funzione ne diventa il proprietario. Perciò, questo codice funzionerà per agiamo con i numeri:
fn main() {
//Non possiamo modificare il valore
let n1 = 5;
let n2 = add(n1);
println!("s1:{},s2:{}",n1, n2);
}
fn add(n1:i32) -> i32 {
n1+2
}
Ma il codice sotto non funzionerà, perché l’oggetto String è un tipo di dati complesso perciò quando passeremo il suo valore alla funzione questa ne diventa il proprietario e la variabile di partenza è stata eliminata.
fn main() {
let s1 = String::from("Hello");
let s2 = add_string(s1); //s1 viene eliminato
println!("s1: {}",s1); //s1 non esiste più
println!("s2: {}",s2);
}
fn add_string(string:String) -> String {
let s = String::from("World");
string + &s
}
In questi casi una buona norma è quella di prendere soltanto il riferimento alla memoria della variabile senza prenderne il possesso. Si può fare usando il carattere & prima del tipo della variabile o prima di mut. Entrambi i metodi risulteranno corretti:
fn main() {
let s1 = String::from("Hello");
let s2 = add_string(&s1);
println!("s1: {}",s1);
println!("s2: {}",s2);
}
fn add_string(string:&String) -> String {
let s = String::from(" World");
//Usiamo .to_owned per copiare il valore e assegnargli un proprietario
//Questa variabile verrà eliminata dopo che la funzione finisce il suo
//lavoro
string.to_owned() + &s
}
fn main() {
let s1 = String::from("Hello");
let s2 = add_string(s1.clone()); //Il clone verrà eliminato dopo la funzione.
println!("s1: {}",s1);
println!("s2: {}",s2);
}
fn add_string(string:String) -> String {
let s = String::from("World");
string + &s
}
Un altro approccio ancora è quello di passare come parametro soltanto la stringa letterale come riferimento e la funzione creerà un oggetto di tipo String. In questo modo evitiamo di passare un dato complesso durante l’esecuzione del programma:
fn main() {
let s1 = String::from("Hello");
let s2 = add_string(&s1);
println!("s1: {}",s1);
println!("s2: {}",s2);
}
fn add_string(string:&str) -> String {
let s = String::from("World");
string.to_string() + &s
}
Parleremo meglio dei tipi di dato in Rust in un prossimo articolo.
Per quanto riguarda le constanti, Rust richiede che indichiamo esplicitamente il tipo di dato utilizzato, che il nome sia soltanto formato da caratteri in lettera maiuscola separate dal trattino basso, l’underscore, _, e che deve sempre avere un valore.
//Usiamo pub per permettere l'accesso in tutto il progetto
pub const PATH: &str = "./";