Le strutture in Rust

Le strutture in Rust: definizione, implementazioni e metodi

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);

}
TrattoEtichetta
DebugImplementa la stampa per il debug
CloneCrea una copia del valore
CopyAbilita copie per tipi semplici
PartialEqPermette il confronto di uguaglianza tra istanze di tutti i tipi di dato, anche quelli che possono avere valore NaN
EqImpone l’uguglianza totale, valido soltanto per tipi che hanno sempre un valore fisso e sicuro
PartialOrdAggiunge gli altri operatori di confrontro <,>,<=,>=
OrdViene usato per il confronto di ordinamento totale in modo simile a Eq.
HashPermette 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.

Possiamo facilmente implementare i metodi get e set di Rust con VS Code
Possiamo facilmente implementare i metodi get e set di Rust con VS Code