module și vizibilitate (mod, pub, crate, super) în Rust

Module, macro-uri, atribute și teste Rust 1.0 (pub(crate): 1.18; mod.rs opțional: ediția 2018)

mod împarte codul în module; tot ce este într-un modul este privat implicit, iar pub îl face vizibil. crate și super încep căile.

Sintaxă
mod nume { ... }           // modul în același fișier
mod nume;                 // modul din nume.rs sau nume/mod.rs
pub fn f() { }            // vizibil în afara modulului
pub(crate) fn g() { }     // vizibil doar în crate-ul curent
crate::a::b::f()          // cale de la rădăcina crate-ului
super::f()                // din modulul părinte

Ce face module și vizibilitate (mod, pub, crate, super)

Un crate este unitatea de compilare: un program (cu main.rs) sau o bibliotecă (cu lib.rs). În interior, codul se organizează în module cu mod. Un modul poate fi scris în același fișier (mod plati { ... }) sau într-un fișier separat: mod plati; în main.rs încarcă src/plati.rs (sau src/plati/mod.rs).

Totul este privat implicit: funcțiile, structurile, câmpurile lor și sub-modulele se văd doar în modulul lor și în copiii lui. pub le face vizibile pentru exterior; pub(crate) le face vizibile în tot crate-ul, dar nu și pentru cine folosește biblioteca. La o structură pub, fiecare câmp are nevoie de propriul pub; la un enum pub, toate variantele sunt publice.

Căile se scriu cu ::. crate:: pornește de la rădăcina crate-ului (cale absolută), super:: urcă în modulul părinte, self:: înseamnă modulul curent. Ca să nu scrii căi lungi, aduci numele în domeniu cu use.

Exemple

Exemplul 1: Module, pub și căi

magazin::plati::total_cu_tva este publică, deci main o poate apela. cota_tva este privată, dar poate fi folosită de copilul ei, plati, prin super::. Produs are câmpul pret public și câmpul cod_intern privat, deci se construiește doar prin Produs::new.

mod magazin {
    fn cota_tva() -> f64 {
        0.21
    }

    pub struct Produs {
        pub nume: String,
        pub pret: f64,
        cod_intern: u32,
    }

    impl Produs {
        pub fn new(nume: &str, pret: f64) -> Self {
            Produs { nume: nume.to_string(), pret, cod_intern: 1000 + nume.len() as u32 }
        }

        pub fn eticheta(&self) -> String {
            format!("#{} {}", self.cod_intern, self.nume)
        }
    }

    pub mod plati {
        use super::Produs;

        pub fn total_cu_tva(produse: &[Produs]) -> f64 {
            let total: f64 = produse.iter().map(|p| p.pret).sum();
            total * (1.0 + super::cota_tva())
        }
    }
}

fn main() {
    let cos = vec![magazin::Produs::new("caiet", 8.0), magazin::Produs::new("pix", 2.0)];
    for p in &cos {
        println!("{}: {} lei", p.eticheta(), p.pret);
    }
    println!("Total cu TVA: {:.2} lei", crate::magazin::plati::total_cu_tva(&cos));
}

Rezultat

#1005 caiet: 8 lei
#1003 pix: 2 lei
Total cu TVA: 12.10 lei

Exemplul 2: Eroarea E0603: funcție privată

cota_tva nu are pub, deci nu poate fi apelată din main, care este în afara modulului magazin. Compilatorul arată unde este definită.

mod magazin {
    fn cota_tva() -> f64 {
        0.21
    }

    pub fn pret_final(pret: f64) -> f64 {
        pret * (1.0 + cota_tva())
    }
}

fn main() {
    println!("{}", magazin::pret_final(100.0));
    println!("{}", magazin::cota_tva());
}

Mesajul compilatorului (programul nu se compilează)

error[E0603]: function `cota_tva` is private
  --> main.rs:13:29
   |
13 |     println!("{}", magazin::cota_tva());
   |                             ^^^^^^^^ private function
   |
note: the function `cota_tva` is defined here
  --> main.rs:2:5
   |
 2 |     fn cota_tva() -> f64 {
   |     ^^^^^^^^^^^^^^^^^^^^

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0603`.

Exemplul 3: Module în fișiere separate

Structura unui proiect Cargo cu module în fișiere. mod plati; din main.rs încarcă src/plati.rs; mod card; din plati.rs încarcă src/plati/card.rs.

src/
├── main.rs        // mod plati;  ... plati::card::autorizeaza(120)
├── plati.rs       // pub mod card;  pub fn total(...) { ... }
└── plati/
    └── card.rs    // pub fn autorizeaza(suma: u32) -> bool { ... }

$ cargo run

Rezultat în terminal

   Compiling magazin v0.1.0 (/home/ana/proiecte/magazin)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/magazin`
Plată de 120 lei autorizată

Greșeli frecvente

  • Creezi fișierul src/plati.rs dar uiți mod plati; în main.rs. Fișierul este ignorat complet: Rust nu descoperă module după numele fișierelor.
  • Pui pub pe structură, dar nu și pe câmpuri: în afara modulului nu poți nici citi câmpurile, nici construi structura cu Produs { ... } (E0451). Adaugă pub câmpurilor sau oferă o funcție new publică.
  • Scrii mod în loc de use pentru a folosi un modul definit în altă parte. mod nume; declară (încarcă) modulul o singură dată, în părintele lui; în celelalte fișiere folosești use crate::nume;.

Comenzi înrudite

  • use: Aduce în domeniu un nume dintr-un modul sau crate (std::collections::BTreeMap), ca să-l poți folosi fără calea completă.
  • struct: Definește un tip nou care grupează mai multe valori cu nume (câmpuri), de exemplu un elev cu nume, clasă și medie.
  • teste (#[test]): O funcție marcată cu #[test] este un test automat: cargo test o rulează și raportează dacă a trecut sau a dat panică.
  • cargo new: Creează un proiect Rust nou: directorul, Cargo.toml, src/main.rs (sau src/lib.rs) și un depozit git.