Shopping cart

0

Cart

  • 0 item

Nessun prodotto nel carrello.

All categories
 Corso Gratuito di Programmazione Rust Lezione 018 – Un esempio di programma che utilizza le strutture

SE VUOI PRENDERE LA CERTIFICAZIONE PER QUESTO CORSO CLICCA QUI

Un Esempio di Programma Utilizzando Structs

Per capire quando potremmo voler utilizzare le struct, scriviamo un programma che calcoli l’area di un rettangolo. Inizieremo usando singole variabili e poi rifattorizzeremo il programma fino a quando non utilizzeremo le struct.

Creiamo un nuovo progetto binario con Cargo chiamato rectangles che prenderà larghezza e altezza di un rettangolo specificate in pixel e calcolerà l’area del rettangolo. Il Codice 5-8 mostra un breve programma con un modo per fare esattamente ciò nel file src/main.rs del nostro progetto.

rust

fn main() {
let width1 = 30;
let height1 = 50;
println!(
"The area of the rectangle is {} square pixels.",
area(width1, height1)
);
}

fn area(width: u32, height: u32) -> u32 {
width * height
}

Ora, eseguiamo questo programma usando cargo run:

scss

$ cargo run
Compiling rectangles v0.1.0 (file:///projects/rectangles)
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
Running `target/debug/rectangles`
The area of the rectangle is 1500 square pixels.

Questo codice riesce a calcolare l’area del rettangolo chiamando la funzione area con ciascuna dimensione, ma possiamo fare di più per rendere questo codice chiaro e leggibile.

Il problema con questo codice è evidente nella firma di area:

rust

fn area(width: u32, height: u32) -> u32 {

La funzione area dovrebbe calcolare l’area di un rettangolo, ma la funzione che abbiamo scritto ha due parametri, e non è chiaro da nessuna parte nel nostro programma che i parametri sono correlati. Sarebbe più leggibile e più gestibile raggruppare insieme larghezza e altezza. Abbiamo già discusso un modo in cui potremmo farlo nella sezione “Il Tipo Tupla” del Capitolo 3: utilizzando tuple.

Rifattorizzazione con Tuple

Il Codice 5-9 mostra un’altra versione del nostro programma che utilizza tuple.

rust

fn main() {
let rect1 = (30, 50);
println!(
"The area of the rectangle is {} square pixels.",
area(rect1)
);
}

fn area(dimensions: (u32, u32)) -> u32 {
dimensions.0 * dimensions.1
}

In un certo senso, questo programma è migliore. Le tuple ci permettono di aggiungere un po’ di struttura, e ora stiamo passando solo un argomento. Ma in un altro senso, questa versione è meno chiara: le tuple non nominano i loro elementi, quindi dobbiamo indicizzare le parti della tupla, rendendo il nostro calcolo meno ovvio.

Confondere larghezza e altezza non importerebbe per il calcolo dell’area, ma se volessimo disegnare il rettangolo sullo schermo, sarebbe importante! Dovremmo tenere presente che la larghezza è l’indice di tupla 0 e l’altezza è l’indice di tupla 1. Questo sarebbe ancora più difficile per qualcun altro capire e tenere presente se usasse il nostro codice. Poiché non abbiamo trasmesso il significato dei nostri dati nel nostro codice, ora è più facile introdurre errori.

Rifattorizzazione con Structs: Aggiunta di Più Significato

Usiamo le struct per aggiungere significato etichettando i dati. Possiamo trasformare la tupla che stiamo utilizzando in una struct con un nome per l’intero e nomi per le parti, come mostrato nel Codice 5-10.

rust

struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
}; println!(
"The area of the rectangle is {} square pixels.",
area(&rect1)
);
}

fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}

Qui abbiamo definito una struct e l’abbiamo chiamata Rectangle. All’interno delle parentesi graffe, abbiamo definito i campi come width e height, entrambi di tipo u32. Poi, in main, abbiamo creato un’istanza particolare di Rectangle che ha una larghezza di 30 e un’altezza di 50.

La nostra funzione area è ora definita con un parametro, che abbiamo chiamato rectangle, il cui tipo è un riferimento immutabile di un’istanza di struct Rectangle. Come menzionato nel Capitolo 4, vogliamo prendere in prestito la struct piuttosto che prenderne la proprietà. In questo modo, main mantiene la sua proprietà e può continuare a utilizzare rect1, che è il motivo per cui utilizziamo & nella firma della funzione e dove chiamiamo la funzione.

La funzione area accede ai campi width e height dell’istanza di Rectangle (notare che l’accesso ai campi di un’istanza di struct presa in prestito non sposta i valori dei campi, motivo per cui spesso si vedono prestiti di struct). La nostra firma di funzione per area ora dice esattamente ciò che intendiamo: calcolare l’area di Rectangle, utilizzando i suoi campi width e height. Ciò trasmette che la larghezza e l’altezza sono correlate tra loro e dà nomi descrittivi ai valori anziché utilizzare i valori degli indici di tupla 0 e 1. Questo è un vantaggio per la chiarezza.

Aggiunta di Funzionalità Utili con Trait Derivati

Sarebbe utile poter stampare un’istanza di Rectangle mentre stiamo debuggando il nostro programma e vedere i valori di tutti i suoi campi. Il Codice 5-11 prova a utilizzare la macro println! come abbiamo fatto nei capitoli precedenti. Tuttavia, ciò non funzionerà.

rust

[Questo codice non compila!]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};

println!("rect1 is {}", rect1);
}

Quando compiliamo questo codice, otteniamo un errore con questo messaggio principale:

c

error[E0277]: `Rectangle` non implementa `std::fmt::Display`

La macro println! può fare molti tipi di formattazione e, per impostazione predefinita, le parentesi graffe dicono a println! di utilizzare la formattazione nota come Display: output destinato al consumo diretto dell’utente finale. I tipi primitivi che abbiamo visto finora implementano Display per impostazione predefinita perché c’è solo un modo in cui vorresti mostrare un 1 o qualsiasi altro tipo primitivo a un utente. Ma con le strutture, il modo in cui println! dovrebbe formattare l’output è meno chiaro perché ci sono più possibilità di visualizzazione: vuoi le virgole o no? Vuoi stampare le parentesi graffe? Tutti i campi devono essere mostrati? A causa di questa ambiguità, Rust non cerca di indovinare cosa vogliamo e le strutture non hanno un’implementazione fornita di Display da utilizzare con println! e il segnaposto {}.

Se continuiamo a leggere gli errori, troveremo questa nota utile:

perl

= help: il trait `std::fmt::Display` non è implementato per `Rectangle`
= nota: nei formati delle stringhe potresti essere in grado di utilizzare `{:?}` (o {:#?} per la stampa dettagliata) invece

Proviamoci! La chiamata della macro println! ora sarà simile a println!("rect1 is {:?}", rect1);. Mettere il specificatore 😕 all’interno delle parentesi graffe dice a println! che vogliamo usare un formato di output chiamato Debug. Il trait Debug ci consente di stampare la nostra struct in un modo utile per gli sviluppatori in modo che possiamo vedere il suo valore mentre stiamo debuggando il nostro codice.

Compiliamo il codice con questa modifica. Accidenti! Otteniamo ancora un errore:

go

error[E0277]: `Rectangle` non implementa `Debug`

Ma ancora una volta, il compilatore ci dà una nota utile:

mathematica

= help: il trait `Debug` non è implementato per `Rectangle`
= nota: aggiungi `#[derive(Debug)]` a `Rectangle` o manualmente `impl Debug for Rectangle`

Rust include la funzionalità per stampare informazioni di debug, ma dobbiamo optare esplicitamente per rendere disponibile tale funzionalità per la nostra struttura. Per fare ciò, aggiungiamo l’attributo esterno #[derive(Debug)] proprio prima della definizione della struttura, come mostrato nel Codice 5-12.

rust

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};

println!("rect1 is {:?}", rect1);
}

Ora, quando eseguiamo il programma, non otterremo errori e vedremo il seguente output:

less

$ cargo run
Compilazione rectangles v0.1.0 (file:///projects/rectangles)
Terminato dev [non ottimizzato + informazioni di debug] target(s) in 0.48s
Esecuzione `target/debug/rectangles`
rect1 is Rectangle { width: 30, height: 50 }

Bello! Non è l’output più bello, ma mostra i valori di tutti i campi per questa istanza, cosa che sarebbe sicuramente utile durante il debug. Quando abbiamo strutture più grandi, è utile avere un output leggermente più facile da leggere; in quei casi, possiamo usare {:#?} invece di {:?} nella stringa di println!. In questo esempio, utilizzare lo stile {:#?} produrrà quanto segue:

less

$ cargo run
Compilazione rectangles v0.1.0 (file:///projects/rectangles)
Terminato dev [non ottimizzato + informazioni di debug] target(s) in 0.48s
Esecuzione `target/debug/rectangles`
rect1 is Rectangle {
width: 30,
height: 50,
}

Un altro modo per stampare un valore utilizzando il formato Debug è utilizzare la macro dbg!, che prende la proprietà di un’espressione (a differenza di println!, che prende un riferimento), stampa il file e il numero di riga in cui si trova quella chiamata della macro dbg! nel tuo codice insieme al valore risultante di quell’espressione e restituisce la proprietà del valore.

Nota: Chiamare la macro dbg! stampa sul flusso console di errore standard (stderr), a differenza di println!, che stampa sul flusso console di output standard (stdout). Parleremo di più su stderr e stdout nella sezione “Scrittura dei Messaggi di Errore sul Flusso Console di Errore Standard anziché sull’Output Standard” nel Capitolo 12.

Ecco un esempio in cui siamo interessati al valore che viene assegnato al campo width, nonché al valore dell’intera struct in rect1:

rust

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let scale = 2;
let rect1 = Rectangle {
width: dbg!(30 * scale),
height: 50,
};

dbg!(&rect1);
}

Possiamo mettere dbg! intorno all’espressione 30 * scale e, poiché dbg! restituisce la proprietà del valore dell’espressione, il campo width otterrà lo stesso valore come se non avessimo la chiamata dbg! lì. Non vogliamo che dbg! prenda la proprietà di rect1, quindi usiamo un riferimento a rect1 nella chiamata successiva. Ecco come appare l’output di questo esempio:

less

$ cargo run
Compilazione rectangles v0.1.0 (file:///projects/rectangles)
Terminato dev [non ottimizzato + informazioni di debug] target(s) in 0.61s
Esecuzione `target/debug/rectangles`
[src/main.rs:10] 30 * scale = 60
[src/main.rs:14] &rect1 = Rectangle {
width: 60,
height: 50,
}

Possiamo vedere che il primo bit di output proviene dalla riga 10 di src/main.rs dove stiamo debuggando l’espressione 30 * scale e il suo valore risultante è 60 (la formattazione Debug implementata per gli interi consiste nel stampare solo il loro valore). La chiamata dbg! alla riga 14 di src/main.rs produce il valore di &rect1, che è la struct Rectangle. Questo output utilizza la formattazione Debug piuttosto carina del tipo Rectangle. La macro dbg! può essere davvero utile quando stai cercando di capire cosa sta facendo il tuo codice!

Oltre al trait Debug, Rust ha fornito una serie di trait per noi da utilizzare con l’attributo derive che possono aggiungere comportamenti utili ai nostri tipi personalizzati. Quei trait e i loro comportamenti sono elencati nell’Appendice C. Copriremo come implementare questi trait con comportamenti personalizzati e come creare i tuoi trait nel Capitolo 10. Ci sono anche molti attributi diversi oltre a derive; per ulteriori informazioni, consulta la sezione “Attributi” del Riferimento Rust.

La nostra funzione area è molto specifica: calcola solo l’area dei rettangoli. Sarebbe utile legare questo comportamento più strettamente alla nostra struct Rectangle perché non funzionerà con nessun altro tipo. Vediamo come possiamo continuare a rifattorizzare questo codice trasformando la funzione area in un metodo area definito sul nostro tipo Rectangle.

Leave a Reply

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *