Ścieżki do odwoływania się do elementu w drzewie modułów
Aby wskazać Rustowi, gdzie znaleźć element w drzewie modułów, używamy ścieżki w taki sam sposób, jak używamy ścieżki podczas nawigowania po systemie plików. Aby wywołać funkcję, musimy znać jej ścieżkę.
Ścieżka może przyjmować dwie formy:
- Ścieżka absolutna to pełna ścieżka zaczynająca się od korzenia pakietu;
dla kodu z zewnętrznego pakietu ścieżka absolutna zaczyna się od nazwy
pakietu, a dla kodu z bieżącego pakietu zaczyna się od literału
crate. - Ścieżka względna zaczyna się od bieżącego modułu i używa
self,superlub identyfikatora w bieżącym module.
Zarówno ścieżki absolutne, jak i względne są poprzedzone jednym lub większą
liczbą identyfikatorów oddzielonych podwójnymi dwukropkami (::).
Wracając do Listingu 7-1, powiedzmy, że chcemy wywołać funkcję
add_to_waitlist. To samo, co zapytać: Jaka jest ścieżka do funkcji
add_to_waitlist? Listing 7-3 zawiera Listing 7-1 z usuniętymi niektórymi
modułami i funkcjami.
Pokażemy dwa sposoby wywołania funkcji add_to_waitlist z nowej funkcji,
eat_at_restaurant, zdefiniowanej w korzeniu pakietu. Te ścieżki są poprawne,
ale pozostaje jeszcze jeden problem, który uniemożliwi kompilację tego
przykładu w obecnej postaci. Wyjaśnimy dlaczego za chwilę.
Funkcja eat_at_restaurant jest częścią publicznego API naszego pakietu
bibliotecznego, dlatego oznaczamy ją słowem kluczowym pub. W sekcji
„Udostępnianie ścieżek za pomocą słowa kluczowego
pub” omówimy pub bardziej szczegółowo.
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Ścieżka absolutna
crate::front_of_house::hosting::add_to_waitlist();
// Ścieżka względna
front_of_house::hosting::add_to_waitlist();
}
add_to_waitlist za pomocą ścieżek absolutnych i względnychZa pierwszym razem, gdy wywołujemy funkcję add_to_waitlist w
eat_at_restaurant, używamy ścieżki absolutnej. Funkcja add_to_waitlist jest
zdefiniowana w tym samym pakiecie co eat_at_restaurant, co oznacza, że możemy
użyć słowa kluczowego crate do rozpoczęcia ścieżki absolutnej. Następnie
dołączamy każdy kolejny moduł, aż dotrzemy do add_to_waitlist. Można sobie
wyobrazić system plików o tej samej strukturze: określilibyśmy ścieżkę
/front_of_house/hosting/add_to_waitlist, aby uruchomić program
add_to_waitlist; użycie nazwy crate do rozpoczęcia od korzenia pakietu jest
podobne do użycia / do rozpoczęcia od korzenia systemu plików w Twojej
shellu.
Za drugim razem, gdy wywołujemy add_to_waitlist w eat_at_restaurant,
używamy ścieżki względnej. Ścieżka zaczyna się od front_of_house, nazwy
modułu zdefiniowanego na tym samym poziomie drzewa modułów co
eat_at_restaurant. Tutaj odpowiednikiem w systemie plików byłaby ścieżka
front_of_house/hosting/add_to_waitlist. Rozpoczęcie od nazwy modułu oznacza,
że ścieżka jest względna.
Wybór między ścieżką względną a absolutną to decyzja, którą podejmiesz w
zależności od projektu i od tego, czy bardziej prawdopodobne jest, że będziesz
przenosić kod definicji elementu oddzielnie od kodu, który go używa, czy też
razem z nim. Na przykład, gdybyśmy przenieśli moduł front_of_house i funkcję
eat_at_restaurant do modułu o nazwie customer_experience, musielibyśmy
zaktualizować ścieżkę absolutną do add_to_waitlist, ale ścieżka względna
nadal byłaby prawidłowa. Jednak gdybyśmy przenieśli funkcję
eat_at_restaurant oddzielnie do modułu o nazwie dining, ścieżka absolutna
do wywołania add_to_waitlist pozostałaby taka sama, ale ścieżka względna
musiałaby zostać zaktualizowana. Ogólnie preferujemy określanie ścieżek
absolutnych, ponieważ bardziej prawdopodobne jest, że będziemy chcieli
przenosić definicje kodu i wywołania elementów niezależnie od siebie.
Spróbujmy skompilować Listing 7-3 i dowiedzmy się, dlaczego jeszcze się nie skompiluje! Błędy, które otrzymujemy, są pokazane w Listingu 7-4.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
Komunikaty o błędach mówią, że moduł hosting jest prywatny. Innymi słowy,
mamy poprawne ścieżki do modułu hosting i funkcji add_to_waitlist, ale
Rust nie pozwala nam ich używać, ponieważ nie ma dostępu do prywatnych sekcji.
W Rust wszystkie elementy (funkcje, metody, struktury, wyliczenia, moduły
i stałe) są domyślnie prywatne dla modułów nadrzędnych. Jeśli chcesz
uczynić element, taki jak funkcja lub struktura, prywatnym, umieszczasz go
w module.
Elementy w module nadrzędnym nie mogą używać prywatnych elementów w modułach podrzędnych, ale elementy w modułach podrzędnych mogą używać elementów w modułach nadrzędnych. Dzieje się tak, ponieważ moduły podrzędne opakowują i ukrywają swoje szczegóły implementacji, ale moduły podrzędne mogą widzieć kontekst, w którym są zdefiniowane. Aby kontynuować naszą metaforę, pomyśl o zasadach prywatności jako o zapleczu restauracji: to, co się tam dzieje, jest prywatne dla klientów restauracji, ale menedżerowie biura mogą widzieć i robić wszystko w restauracji, którą prowadzą.
Rust zdecydował, aby system modułów działał w ten sposób, aby domyślnie ukrywać
wewnętrzne szczegóły implementacji. W ten sposób wiesz, które części kodu
wewnętrznego możesz zmienić bez uszkadzania kodu zewnętrznego. Jednak Rust
daje możliwość udostępniania wewnętrznych części kodu modułów podrzędnych
modułom nadrzędnym za pomocą słowa kluczowego pub, aby uczynić element
publicznym.
Udostępnianie ścieżek za pomocą słowa kluczowego pub
Wróćmy do błędu z Listingu 7-4, który informował nas, że moduł hosting jest
prywatny. Chcemy, aby funkcja eat_at_restaurant w module nadrzędnym miała
dostęp do funkcji add_to_waitlist w module potomnym, więc oznaczamy moduł
hosting słowem kluczowym pub, jak pokazano w Listingu 7-5.
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
hosting jako pub w celu użycia go z eat_at_restaurantNiestety, kod w Listingu 7-5 nadal skutkuje błędami kompilatora, jak pokazano w Listingu 7-6.
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:10:37
|
10 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:13:30
|
13 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
Co się stało? Dodanie słowa kluczowego pub przed mod hosting sprawia, że
moduł staje się publiczny. Dzięki tej zmianie, jeśli mamy dostęp do
front_of_house, możemy uzyskać dostęp do hosting. Ale zawartość hosting
jest nadal prywatna; uczynienie modułu publicznym nie sprawia, że jego
zawartość staje się publiczna. Słowo kluczowe pub na module pozwala tylko
kodowi w jego modułach nadrzędnych odwoływać się do niego, a nie uzyskiwać
dostępu do jego wewnętrznego kodu. Ponieważ moduły są kontenerami, nie możemy
wiele zdziałać, jedynie upubliczniając moduł; musimy pójść dalej i
zdecydować się na upublicznienie jednego lub więcej elementów w module.
Błędy w Listingu 7-6 mówią, że funkcja add_to_waitlist jest prywatna.
Zasady prywatności dotyczą również struktur, wyliczeń, funkcji i metod, a także
modułów.
Upublicznijmy również funkcję add_to_waitlist, dodając słowo kluczowe pub
przed jej definicją, jak w Listingu 7-7.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
pub do mod hosting i fn add_to_waitlist pozwala nam wywołać funkcję z eat_at_restaurant.Teraz kod się skompiluje! Aby zobaczyć, dlaczego dodanie słowa kluczowego pub
pozwala nam używać tych ścieżek w eat_at_restaurant w odniesieniu do zasad
prywatności, spójrzmy na ścieżki absolutne i względne.
W ścieżce absolutnej zaczynamy od crate, korzenia drzewa modułów naszego
pakietu. Moduł front_of_house jest zdefiniowany w korzeniu pakietu. Chociaż
front_of_house nie jest publiczny, ponieważ funkcja eat_at_restaurant jest
zdefiniowana w tym samym module co front_of_house (czyli eat_at_restaurant
i front_of_house są rodzeństwem), możemy odwoływać się do front_of_house
z eat_at_restaurant. Następny jest moduł hosting oznaczony pub. Mamy
dostęp do modułu nadrzędnego hosting, więc możemy uzyskać dostęp do
hosting. Na koniec, funkcja add_to_waitlist jest oznaczona pub, a my
mamy dostęp do jej modułu nadrzędnego, więc to wywołanie funkcji działa!
W ścieżce względnej logika jest taka sama jak w ścieżce absolutnej, z wyjątkiem
pierwszego kroku: zamiast zaczynać od korzenia pakietu, ścieżka zaczyna się od
front_of_house. Moduł front_of_house jest zdefiniowany w tym samym module
co eat_at_restaurant, więc ścieżka względna zaczynająca się od modułu, w
którym zdefiniowano eat_at_restaurant, działa. Następnie, ponieważ hosting
i add_to_waitlist są oznaczone jako pub, reszta ścieżki działa, a to
wywołanie funkcji jest prawidłowe!
Jeśli planujesz udostępnić swój pakiet biblioteczny, aby inne projekty mogły używać Twojego kodu, Twój publiczny interfejs API jest Twoją umową z użytkownikami Twojego pakietu, która określa, w jaki sposób mogą wchodzić w interakcje z Twoim kodem. Istnieje wiele kwestii związanych z zarządzaniem zmianami w Twoim publicznym interfejsie API, aby ułatwić innym poleganie na Twoim pakiecie. Te rozważania wykraczają poza zakres tej książki; jeśli interesuje Cię ten temat, zobacz Rust API Guidelines.
Najlepsze praktyki dla pakietów z binarnym i bibliotecznym
Wspomnieliśmy, że pakiet może zawierać zarówno korzeń pakietu binarnego src/main.rs, jak i korzeń pakietu bibliotecznego src/lib.rs, a oba pakiety będą domyślnie miały nazwę pakietu. Zazwyczaj pakiety z takim patternem, zawierające zarówno bibliotekę, jak i pakiet binarny, będą miały w pakiecie binarnym wystarczająco dużo kodu, aby uruchomić plik wykonywalny, który wywołuje kod zdefiniowany w pakiecie bibliotecznym. Pozwala to innym projektom korzystać z większości funkcjonalności, którą pakiet zapewnia, ponieważ kod pakietu bibliotecznego może być współdzielony.
Drzewo modułów powinno być zdefiniowane w src/lib.rs. Następnie wszelkie publiczne elementy mogą być używane w pakiecie binarnym, zaczynając ścieżki od nazwy pakietu. Pakiet binarny staje się użytkownikiem pakietu bibliotecznego, tak jak całkowicie zewnętrzny pakiet używałby pakietu bibliotecznego: może używać tylko publicznego API. Pomaga to zaprojektować dobre API; jesteś nie tylko autorem, ale także klientem!
W Rozdziale 12 zademonstrujemy tę praktykę organizacyjną za pomocą programu wiersza poleceń, który będzie zawierał zarówno pakiet binarny, jak i pakiet biblioteczny.
Rozpoczynanie ścieżek względnych za pomocą super
Możemy konstruować ścieżki względne, które zaczynają się w module nadrzędnym,
a nie w bieżącym module lub korzeniu pakietu, używając super na początku
ścieżki. Jest to podobne do rozpoczynania ścieżki systemu plików składnią ..,
która oznacza przejście do katalogu nadrzędnego. Użycie super pozwala nam
odwołać się do elementu, o którym wiemy, że znajduje się w module nadrzędnym,
co może ułatwić reorganizację drzewa modułów, gdy moduł jest ściśle powiązany z
nadrzędnym, ale rodzic może zostać kiedyś przeniesiony w inne miejsce w
drzewie modułów.
Rozważmy kod w Listingu 7-8, który modeluje sytuację, w której szef kuchni
poprawia nieprawidłowe zamówienie i osobiście dostarcza je klientowi. Funkcja
fix_incorrect_order zdefiniowana w module back_of_house wywołuje funkcję
deliver_order zdefiniowaną w module nadrzędnym, określając ścieżkę do
deliver_order, zaczynając od super.
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
superFunkcja fix_incorrect_order znajduje się w module back_of_house, więc
możemy użyć super, aby przejść do modułu nadrzędnego back_of_house, który
w tym przypadku jest crate, czyli korzeń. Stamtąd szukamy deliver_order
i znajdujemy ją. Sukces! Uważamy, że moduł back_of_house i funkcja
deliver_order prawdopodobnie pozostaną w tej samej relacji do siebie i
zostaną przeniesione razem, jeśli zdecydujemy się na reorganizację drzewa
modułów pakietu. Dlatego użyliśmy super, aby w przyszłości, w przypadku
przeniesienia tego kodu do innego modułu, było mniej miejsc do aktualizacji
kodu.
Upublicznianie struktur i wyliczeń
Możemy również użyć pub do oznaczenia struktur i wyliczeń jako publicznych,
ale istnieje kilka dodatkowych szczegółów dotyczących użycia pub ze
strukturami i wyliczeniami. Jeśli użyjemy pub przed definicją struktury,
uczynimy ją publiczną, ale pola struktury nadal będą prywatne. Możemy
uczynić każde pole publicznym lub nie, w zależności od przypadku. W Listingu
7-9 zdefiniowaliśmy publiczną strukturę back_of_house::Breakfast z
publicznym polem toast, ale prywatnym polem seasonal_fruit. To
modeluje sytuację w restauracji, gdzie klient może wybrać rodzaj pieczywa,
które jest podawane do posiłku, ale szef kuchni decyduje, jakie owoce
towarzyszą posiłkowi, w zależności od sezonu i dostępności w magazynie.
Dostępne owoce szybko się zmieniają, więc klienci nie mogą wybierać owoców
ani nawet widzieć, jakie owoce otrzymają.
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Zamów śniadanie latem z tostami żytnimi.
let mut meal = back_of_house::Breakfast::summer("Rye");
// Zmieniamy zdanie co do rodzaju pieczywa.
meal.toast = String::from("Wheat");
println!("Poproszę tost {} ", meal.toast);
// Następna linia się nie skompiluje, jeśli ją odkomentujemy; nie możemy
// zobaczyć ani modyfikować sezonowych owoców, które są podawane do posiłku.
// meal.seasonal_fruit = String::from("blueberries");
}
Ponieważ pole toast w strukturze back_of_house::Breakfast jest publiczne,
w eat_at_restaurant możemy zapisywać i odczytywać do pola toast używając
notacji kropkowej. Zauważ, że nie możemy użyć pola seasonal_fruit w
eat_at_restaurant, ponieważ seasonal_fruit jest prywatne. Spróbuj
odkomentować linię modyfikującą wartość pola seasonal_fruit, aby zobaczyć,
jaki błąd otrzymasz!
Należy również zauważyć, że ponieważ back_of_house::Breakfast ma prywatne
pole, struktura musi udostępniać publiczną funkcję skojarzoną, która
konstruuje instancję Breakfast (nazwaliśmy ją tutaj summer). Gdyby
Breakfast nie miała takiej funkcji, nie moglibyśmy utworzyć instancji
Breakfast w eat_at_restaurant, ponieważ nie moglibyśmy ustawić wartości
prywatnego pola seasonal_fruit w eat_at_restaurant.
W przeciwieństwie do tego, jeśli upublicznimy wyliczenie, wszystkie jego
warianty stają się publiczne. Potrzebujemy tylko pub przed słowem kluczowym
enum, jak pokazano w Listingu 7-10.
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
Ponieważ upubliczniliśmy wyliczenie Appetizer, możemy używać wariantów Soup
i Salad w eat_at_restaurant.
Wyliczenia nie są zbyt użyteczne, chyba że ich warianty są publiczne; byłoby
uciążliwe, gdybyśmy musieli annotować wszystkie warianty wyliczeń za pomocą
pub w każdym przypadku, dlatego domyślnie warianty wyliczeń są publiczne.
Struktury są często użyteczne bez publicznych pól, więc pola struktur
podlegają ogólnej zasadzie, że wszystko jest domyślnie prywatne, chyba że jest
anotowane za pomocą pub.
Istnieje jeszcze jedna sytuacja związana z pub, której nie omówiliśmy, i jest
to nasza ostatnia funkcja systemu modułów: słowo kluczowe use. Najpierw
omówimy use samo w sobie, a następnie pokażemy, jak łączyć pub i use.