Concetti alla base dell'organizzazione del codice

Completato

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.