Salta ai contenuti

Moduli in Rust

Finora ogni esempio è stato in un unico file main.rs. Per i programmi piccoli va benissimo, ma i progetti veri dividono il codice in pezzi logici. Lo strumento di Rust per farlo è il sistema dei moduli: mod, pub e use.

Un modulo è un blocco con un nome che raggruppa codice collegato:

mod math {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}

Output:

5
▶ Provalo tu

Due cose da notare: la funzione si chiama con math::add(...), usando il nome del modulo come percorso. Ed è segnata come pub: senza, non sarebbe raggiungibile da fuori del modulo.

Vale la pena sottolinearlo, perché sorprende molti: il contenuto di un modulo è privato di default. Se togli pub dall’esempio qui sopra, non compila più:

mod math {
fn add(a: i32, b: i32) -> i32 { // niente `pub`
a + b
}
}
fn main() {
let sum = math::add(2, 3); // ❌ errore: la funzione `add` è privata
println!("{sum}");
}

Devi segnare esplicitamente con pub tutto quello che vuoi far usare a chi sta fuori dal modulo. È una scelta voluta: permette a un modulo di nascondere le sue funzioni di supporto interne e di mostrare solo le parti pensate come API pubblica.

I moduli possono contenere altri moduli: è così che i progetti più grandi costruiscono una struttura ad albero:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
fn main() {
let area = shapes::circle::area(2.0);
println!("{area}");
}
▶ Provalo tu

Scrivere ogni volta shapes::circle::area(...) diventa noioso. use porta un percorso nello scope, così puoi riferirti a lui direttamente:

mod shapes {
pub mod circle {
pub fn area(radius: f64) -> f64 {
3.14159 * radius * radius
}
}
}
use shapes::circle;
fn main() {
let area = circle::area(2.0); // più corto di shapes::circle::area
println!("{area}");
}
▶ Provalo tu

In realtà hai già usato use: è la stessa parola chiave della pagina su HashMap (use std::collections::HashMap;).

Gli esempi qui sopra mettono tutto in un solo file, come richiede il Rust Playground. In un progetto vero sul tuo computer, di solito ogni modulo ha il suo file. Se avessi un progetto con un modulo math, di solito sarebbe fatto così:

src/
├── main.rs
└── math.rs
src/main.rs
mod math; // dice a Rust: cerca il codice di questo modulo in math.rs
fn main() {
let sum = math::add(2, 3);
println!("{sum}");
}
src/math.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}

mod math; (con il punto e virgola, senza corpo) dice a Rust di caricare il contenuto del modulo da math.rs nella stessa cartella. È lo schema che userai appena un progetto supera un singolo file.

Crea un modulo chiamato convert con due funzioni pubbliche: c_to_f (da Celsius a Fahrenheit) e f_to_c (al contrario). Da main, stampa 100°C sono 212°F e 212°F sono 100°C.

Suggerimento: le formule sono c * 9.0 / 5.0 + 32.0 e (f - 32.0) * 5.0 / 9.0. Non dimenticare pub, e chiama le funzioni con il nome del modulo, come convert::c_to_f(100.0).

Mostra la soluzione
mod convert {
pub fn c_to_f(c: f64) -> f64 {
c * 9.0 / 5.0 + 32.0
}
pub fn f_to_c(f: f64) -> f64 {
(f - 32.0) * 5.0 / 9.0
}
}
fn main() {
println!("100°C sono {}°F", convert::c_to_f(100.0));
println!("212°F sono {}°C", convert::f_to_c(212.0));
}

Output:

100°C sono 212°F
212°F sono 100°C
▶ Provalo tu

Entrambe le funzioni sono pub, quindi main (che sta fuori dal modulo) può chiamarle. Togli un pub e il compilatore ti dirà che quella funzione è privata.

Crea un modulo annidato school::grades con una funzione pubblica letter(score: u32) -> char che restituisce 'A' da 90 in su, 'B' da 80 in su e 'C' negli altri casi. Dentro grades scrivi anche una funzione di supporto privata is_at_least(score: u32, min: u32) -> bool che letter usa. Porta school::grades nello scope con use, poi stampa la lettera per 95, 85 e 70.

Suggerimento: sia il mod grades interno a school sia fn letter hanno bisogno di pub, ma is_at_least non deve averlo. Il codice dentro lo stesso modulo può sempre chiamare le funzioni private.

Mostra la soluzione
mod school {
pub mod grades {
pub fn letter(score: u32) -> char {
if is_at_least(score, 90) {
'A'
} else if is_at_least(score, 80) {
'B'
} else {
'C'
}
}
// privata: solo il codice dentro `grades` può chiamarla
fn is_at_least(score: u32, min: u32) -> bool {
score >= min
}
}
}
use school::grades;
fn main() {
for score in [95, 85, 70] {
println!("{score} -> {}", grades::letter(score));
}
}

Output:

95 -> A
85 -> B
70 -> C
▶ Provalo tu

letter può chiamare is_at_least perché stanno nello stesso modulo. Da main, grades::is_at_least(95, 90) fallirebbe con «function is_at_least is private» (la funzione è privata). È proprio questo il punto: il modulo mostra una piccola API pubblica e tiene per sé le sue funzioni di supporto.

  • mod nome { ... } raggruppa codice collegato in un modulo con un nome.
  • Tutto quello che sta dentro un modulo è privato di default: aggiungi pub per renderlo visibile fuori dal modulo.
  • use porta un percorso nello scope, così non devi scriverlo per intero ogni volta.
  • In un progetto su più file, mod nome; (senza corpo) carica il codice di quel modulo dal file corrispondente.
  • Il codice dentro un modulo può sempre usare gli elementi privati di quel modulo, quindi tieni private le funzioni di supporto e mostra solo quello che serve a chi chiama.

Verifica veloce

1. Di default, gli elementi dentro un mod sono pubblici o privati?

2. Come dici a Rust di caricare il codice di un modulo da math.rs invece di scriverlo direttamente lì?

Punteggio: 0 / 2