Concetti alla base dell'organizzazione del codice
Prima di iniziare, è importante illustrare i concetti alla base dell'organizzazione del codice nei programmi Rust:
- Un pacchetto:
- Contiene funzionalità all'interno di uno o più crate.
- Include informazioni su come creare tali crate. Le informazioni sono disponibili nel file
Cargo.toml
.
- Un crate:
- È un'unità di compilazione, ovvero la quantità minima di codice su cui il compilatore Rust può operare.
- Dopo la compilazione, produce un eseguibile o una libreria.
- Contiene un modulo di primo livello implicito senza nome.
- Un modulo:
- È un'unità di organizzazione del codice (probabilmente annidata) all'interno di un crate.
- Può avere definizioni ricorsive che si estendono su altri moduli.
Pacchetto
Ogni volta che si esegue il comando $ cargo new <project-name>
, Cargo crea automaticamente un pacchetto:
$ cargo new my-project
Created binary (application) `my-project` package
Questo codice è relativo a un pacchetto che contiene solo src/main.rs
, che significa che contiene solo un crate binario denominato my-project
:
my-project
├── src
│ └── main.rs
└── Cargo.toml
È possibile includere più crate binari in un pacchetto inserendo i file nella directory src/bin
. Ogni file sarà un crate binario distinto.
Se un pacchetto contiene src/main.rs
e src/lib.rs
, significa che ha due crate: una libreria e un file binario. Entrambi hanno lo stesso nome del pacchetto.
Casse
Il modello di compilazione di Rust è incentrato su artefatti denominati crate che possono essere compilati in un file binario o in una libreria.
Ogni progetto creato con il comando cargo new
è esso stesso un crate. Tutto il codice Rust di terze parti che è possibile usare come dipendenze nel progetto è anche, ciascuno, un singolo crate.
Crate di libreria
È già stato illustrato come creare un programma binario. La creazione di una libreria è altrettanto semplice. Per creare una libreria, passare il parametro della riga di comando --lib
al comando cargo new
:
$ cargo new --lib my-library
Created library `my-library` package
Come si può notare, invece di un file src/main.rs
, ora si ottiene un file =src/lib.rs.
my-library
├── src
│ └── lib.rs
└── Cargo.toml
Quando si indica a Cargo di compilare questo crate, si ottiene un file di libreria denominato libmy_library.rlib
che può essere pubblicato e collegato ad altri progetti.
Moduli
Rust offre un potente sistema di moduli che può essere usato per suddividere in modo gerarchico il codice in unità logiche che facilitano anche la leggibilità e il riutilizzo.
Un modulo è una raccolta di elementi:
- Costanti
- Alias di tipo
- Funzioni
- Struct
- Enumerazioni
- Tratti
- Blocchi
impl
- Altri moduli
I moduli controllano anche la privacy degli elementi. La privacy consente di identificare un elemento come pubblico o privato. Pubblico indica che l'elemento può essere usato da codice esterno. Privato indica che l'elemento è un dettaglio di implementazione interno e non è disponibile per l'uso in codice esterno.
Ecco un esempio di modulo:
mod math {
type Complex = (f64, f64);
pub fn sin(f: f64) -> f64 { /* ... */ }
pub fn cos(f: f64) -> f64 { /* ... */ }
pub fn tan(f: f64) -> f64 { /* ... */ }
}
println!("{}", math::cos(45.0));
Se un file di origine contiene dichiarazioni mod
, il contenuto dei file dei moduli dovrebbe essere inserito nelle posizioni in cui si trovano le dichiarazioni mod
nel file di origine, prima di eseguire il compilatore su di esso. In altre parole, i moduli non vengono compilati singolarmente, ma vengono compilati solo i crate.
Si sarà probabilmente notata la parola chiave pub
all'inizio delle definizioni di funzione nel modulo math
.
Il compilatore Rust verifica se gli elementi possono essere usati o meno tra i moduli. Per impostazione predefinita, tutti gli elementi in Rust sono privati e sono accessibili solo al modulo corrente e ai relativi discendenti. Quando invece un elemento viene dichiarato come pub
, può essere considerato come accessibile al mondo esterno. Ad esempio:
// Declare a private struct
struct Foo;
// Declare a public struct with a private field
pub struct Bar {
field: i32,
}
// Declare a public enum with two public variants
pub enum State {
PubliclyAccessibleVariant,
PubliclyAccessibleVariant2,
}
Le regole sulla privacy di Rust sono sorprendentemente efficaci per la creazione di gerarchie di moduli che espongono API pubbliche nascondendo i dettagli di implementazione interni.