Kontrola zasięgu i prywatności za pomocą modułów
W tej sekcji omówimy moduły i inne części systemu modułów, a mianowicie ścieżki, które pozwalają nadawać nazwy elementom; słowo kluczowe use, które wprowadza ścieżkę do zasięgu; oraz słowo kluczowe pub, aby uczynić elementy publicznymi. Omówimy również słowo kluczowe as, pakiety zewnętrzne i operator glob.
Ściągawka z modułów
Zanim przejdziemy do szczegółów modułów i ścieżek, przedstawiamy tutaj szybkie odniesienie do tego, jak działają moduły, ścieżki, słowo kluczowe use i słowo kluczowe pub w kompilatorze, oraz jak większość programistów organizuje swój kod. Przejdziemy przez przykłady każdej z tych reguł w tym rozdziale, ale jest to świetne miejsce, aby odwołać się do nich jako przypomnienie o tym, jak działają moduły.
- Zacznij od korzenia kapsuły (crate root): Podczas kompilowania kapsuły, kompilator najpierw szuka kodu do skompilowania w pliku korzenia kapsuły (zazwyczaj src/lib.rs dla kapsuły bibliotecznej i src/main.rs dla kapsuły binarnej).
- Deklarowanie modułów: W pliku korzenia kapsuły możesz zadeklarować nowe moduły; powiedzmy, że deklarujesz moduł „garden” za pomocą
mod garden;. Kompilator będzie szukał kodu modułu w tych miejscach:- Wbudowane, w nawiasach klamrowych, które zastępują średnik po
mod garden - W pliku src/garden.rs
- W pliku src/garden/mod.rs
- Wbudowane, w nawiasach klamrowych, które zastępują średnik po
- Deklarowanie podmodułów: W dowolnym pliku innym niż korzeń kapsuły możesz deklarować podmoduły. Na przykład, możesz zadeklarować
mod vegetables;w src/garden.rs. Kompilator będzie szukał kodu podmodułu w katalogu nazwanym dla modułu rodzicielskiego w tych miejscach:- Wbudowane, bezpośrednio po
mod vegetables, w nawiasach klamrowych zamiast średnika - W pliku src/garden/vegetables.rs
- W pliku src/garden/vegetables/mod.rs
- Wbudowane, bezpośrednio po
- Ścieżki do kodu w modułach: Gdy moduł jest częścią twojej kapsuły, możesz odwoływać się do kodu w tym module z dowolnego innego miejsca w tej samej kapsule, o ile zasady prywatności na to pozwalają, używając ścieżki do kodu. Na przykład, typ
Asparagusw module warzyw ogrodowych znajdowałby się podcrate::garden::vegetables::Asparagus. - Prywatne vs publiczne: Kod w module jest domyślnie prywatny dla jego modułów nadrzędnych. Aby moduł był publiczny, zadeklaruj go za pomocą
pub modzamiastmod. Aby elementy w publicznym module były również publiczne, użyjpubprzed ich deklaracjami. - Słowo kluczowe
use: W zasięgu, słowo kluczoweusetworzy skróty do elementów, aby zmniejszyć powtarzanie długich ścieżek. W każdym zasięgu, który może odwoływać się docrate::garden::vegetables::Asparagus, możesz utworzyć skrót za pomocąuse crate::garden::vegetables::Asparagus;, a odtąd musisz tylko napisaćAsparagus, aby użyć tego typu w zasięgu.
Tutaj tworzymy binarną kapsułę o nazwie backyard, która ilustruje te zasady. Katalog kapsuły, również nazwany backyard, zawiera następujące pliki i katalogi:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs
Plik główny kapsuły w tym przypadku to src/main.rs, i zawiera:
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("Rosnę {plant:?}!");
}
Linia pub mod garden; informuje kompilator, aby uwzględnił kod znajdujący się w pliku src/garden.rs, który to plik zawiera:
pub mod vegetables;
Tutaj, pub mod vegetables; oznacza, że kod z src/garden/vegetables.rs również jest włączony. Ten kod to:
#[derive(Debug)]
pub struct Asparagus {}
Teraz przejdźmy do szczegółów tych zasad i pokażmy je w działaniu!
Grupowanie powiązanego kodu w modułach
Moduły pozwalają nam organizować kod w ramach kapsuły w celu zwiększenia czytelności i łatwego ponownego użycia. Moduły pozwalają nam również kontrolować prywatność elementów, ponieważ kod w module jest domyślnie prywatny. Prywatne elementy to wewnętrzne szczegóły implementacji niedostępne do użytku zewnętrznego. Możemy zdecydować się na uczynienie modułów i elementów w nich publicznymi, co udostępnia je, aby umożliwić zewnętrznemu kodowi ich używanie i zależność od nich.
Na przykład, napiszmy bibliotekę, która zapewnia funkcjonalność restauracji. Zdefiniujemy sygnatury funkcji, ale pozostawimy ich ciała puste, aby skoncentrować się na organizacji kodu, a nie na implementacji restauracji.
W branży restauracyjnej niektóre części restauracji są nazywane „front of house”, a inne „back of house”. Front of house to miejsce, gdzie są klienci; obejmuje to miejsca, gdzie gospodarze sadzają klientów, kelnerzy przyjmują zamówienia i płatności, a barmani przygotowują napoje. Back of house to miejsce, gdzie szefowie kuchni i kucharze pracują w kuchni, zmywacze naczyń sprzątają, a menedżerowie wykonują prace administracyjne.
Aby ustrukturyzować naszą kapsułę w ten sposób, możemy zorganizować jej funkcje w zagnieżdżone moduły. Utwórz nową bibliotekę o nazwie restaurant, uruchamiając cargo new restaurant --lib. Następnie wprowadź kod z Listingu 7-1 do pliku src/lib.rs, aby zdefiniować niektóre moduły i sygnatury funkcji; ten kod to sekcja „front of house”.
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
Definiujemy moduł za pomocą słowa kluczowego mod, a następnie nazwę modułu (w tym przypadku front_of_house). Ciało modułu znajduje się następnie w nawiasach klamrowych. Wewnątrz modułów możemy umieszczać inne moduły, jak w tym przypadku moduły hosting i serving. Moduły mogą również zawierać definicje innych elementów, takich jak struktury, typy wyliczeniowe, stałe, cechy, oraz, jak w Listingu 7-1, funkcje.
Używając modułów, możemy grupować powiązane definicje i nazywać ich relacje. Programiści korzystający z tego kodu mogą nawigować po kodzie na podstawie grup, zamiast czytać wszystkie definicje, co ułatwia im znalezienie odpowiednich definicji. Programiści dodający nową funkcjonalność do tego kodu wiedzieliby, gdzie umieścić kod, aby program pozostał uporządkowany.
Wcześniej wspomnieliśmy, że pliki src/main.rs i src/lib.rs nazywane są katalogami głównymi kapsuł (crate roots). Powodem ich nazwy jest to, że zawartość któregokolwiek z tych dwóch plików tworzy moduł o nazwie crate w katalogu głównym struktury modułów kapsuły, znanego jako drzewo modułów.
Listing 7-2 przedstawia drzewo modułów dla struktury z Listingu 7-1.
kapsuła (crate)
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
To drzewo pokazuje, jak niektóre moduły zagnieżdżają się w innych modułach; na przykład, hosting zagnieżdża się w front_of_house. Drzewo pokazuje również, że niektóre moduły są sąsiadami, co oznacza, że są zdefiniowane w tym samym module; hosting i serving są sąsiadami zdefiniowanymi w front_of_house. Jeśli moduł A jest zawarty w module B, mówimy, że moduł A jest dzieckiem modułu B, a moduł B jest rodzicem modułu A. Zauważ, że całe drzewo modułów jest zakorzenione pod niejawnym modułem o nazwie crate.
Drzewo modułów może przypominać drzewo katalogów systemu plików na Twoim komputerze; to bardzo trafne porównanie! Podobnie jak katalogi w systemie plików, używasz modułów do organizacji kodu. I podobnie jak pliki w katalogu, potrzebujemy sposobu na znalezienie naszych modułów.