Le strutture in Rust, o struct, sono un tipo di dati personalizzati che ci permettono di raggruppare insieme e nominare più dati che servono per un determinato programma o entità. Si tratta del modo in cui Rust ci permette di lavorare con gli oggetti ed è molto flessibile.
Possiamo creare strutture vuote, strutture simili alle tuple per creare tipi di dato e strutture con ogni tipo di dato. Le strutture vuote non hanno proprietà ma possiamo lo stesso implementare metodi e funzioni; ci permettono di creare e implementare estensioni alle strutture principali. Per creare un struttura vuota basta definirla senza aggiungere altri campi.
struct NameStruct;
let my_struct = NameStruct;
Le tuple struct sono simili alle tuple e hanno il vantaggio di essere nominati e avere i loro metodi. Le strutture possono essere facilmente strutturare e destrutturare una tupla.
struct Point(f64,f64);
let point_instance = Point(10.2,35.6);
let (x,y) = (point_instance.0, point_instance.1);
let point_tuple = (20.5, 13.8);
let point_instance = Point(point_tuple.0, point_tuple.1);
//Oppure
let (x,y) = (20.5, 13.8);
let point_instance2 = Point(x,y);
Infine abbiamo le strutture con gli attributi. Al loro interno possiamo inserire dati complessi che vengono allocati nell’heap. Quando creiamo un’istanza dobbiamo compilare tutti i campi.
struct User {
username: String,
email: String,
password: String,
}
fn main() {
let user = User {
username: "abc123".to_string(),
email: "name@email.com".to_string(),
password: "Ab_1".to_string(),
};
println!("{}",user.username);
println!("{}",user.email);
println!("{}",user.password);
}
Un’istanza può ereditare i dati non dichiarati da un’altra già esistente.
let user2 = User{
password: String::from("CD_2"), //or "CD_2".to_string()
..user //user.username, user.email
};
Derivazioni automatiche
Le struct personalizzate non hanno di default tutti i metodi necessari per la stampa, la copia e il confronto. Dobbiamo dire al linguaggio quali tratti vogliamo implementare con #[derive()]
#[derive(Debug)]
struct User {
username: String,
email: String,
password: String,
}
fn main() {
let user = User {
username: "abc123".to_string(),
email: "name@email.com".to_string(),
password: "Ab_1".to_string(),
};
println!("{:?}",user);
}
Tratto | Etichetta |
---|---|
Debug | Implementa la stampa per il debug |
Clone | Crea una copia del valore |
Copy | Abilita copie per tipi semplici |
PartialEq | Permette il confronto di uguaglianza tra istanze di tutti i tipi di dato, anche quelli che possono avere valore NaN |
Eq | Impone l’uguglianza totale, valido soltanto per tipi che hanno sempre un valore fisso e sicuro |
PartialOrd | Aggiunge gli altri operatori di confrontro <,>,<=,>= |
Ord | Viene usato per il confronto di ordinamento totale in modo simile a Eq. |
Hash | Permette di generare hash, sequenze di byte, per tipi complessi. |
Implementazione e metodi nelle strutture di Rust
Un metodo è una funzione definita all’interno di un tipo di dato complesso, come le struct, il cui primo parametro è il l’istanza del tipo di dato che lo contiene. In altri linguaggi di programmazione, i metodi vengono inseriti all’interno delle classi.
In Rust vale sempre il concetto di prorietà, perciò non passiamo come argomento l’istanza ma un suo riferimento. Proprio come in altri linguaggi, possiamo creare oggetti ma anche semplici programmi per effettuare calcoli.
Dopo avere definito una struct, possiamo definire la sua implementazione al cui interno aggiungiamo i metodi. Nel seguente codice:
#[derive(Debug)]
struct Point {
x: f64,
y: f64,
}
impl Point {
fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
fn move_to(&mut self,x: f64, y: f64) -> &mut Point {
self.x = x;
self.y = y;
self
}
}
Il ritorno Self agisce da costruttore mentre &mut Point ( o &mut Self) agisce come un aggiornamento all’istanza a cui si fa riferimento. Con il metodo new possiamo facilmente creare un’istanza della struct. Possiamo implementare questo metodo cliccando con il tasto destro sul nome della struct e l’editor avrà la voce per farlo in automatico. Anche i metodi get e set possono essere implementati in questo modo.
Possiamo inoltre creare più implementazioni della stessa struttura con metodi diversi. Possiamo farlo sia usando lo stesso nome che dando nomi diversi per capire lo scopo di ogni implementazione. In questo modo potremmo aggiungere estensioni e plugin ad una struttura che rappresenta un programma.
Implementazione getter e setter
Possiamo facilmente creare i metodi per accedere o modificare le coordinate del punto. Si tratta dei metodi chiamati getter e setter nei linguaggi di programmazione. Possiamo chiamare questi metodi come vogliamo ma spesso abbiamo già del codice riutilizzabile che possiamo richiamare dal nostro IDE.
Su VS Code, clicchiamo con il tasto destro del mouse su uno dei campi della struttura Point oppure usare la tastiera per selezionarlo e cliccare Ctrl + . . Clicchiamo su Generate getter/setter. Scegliamo l’opzione desiderata.
