Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Ś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, super lub 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.

Nazwa pliku: src/lib.rs
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();
}
Listing 7-3: Wywoływanie funkcji add_to_waitlist za pomocą ścieżek absolutnych i względnych

Za 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
Listing 7-4: Błędy kompilatora podczas budowania kodu z Listingu 7-3

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.

Nazwa pliku: src/lib.rs
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();
}
Listing 7-5: Deklarowanie modułu hosting jako pub w celu użycia go z eat_at_restaurant

Niestety, 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
Listing 7-6: Błędy kompilatora podczas budowania kodu z Listingu 7-5

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.

Nazwa pliku: src/lib.rs
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();
}
Listing 7-7: Dodanie słowa kluczowego 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.

Nazwa pliku: src/lib.rs
fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}
Listing 7-8: Wywoływanie funkcji za pomocą ścieżki względnej zaczynającej się od super

Funkcja 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ą.

Nazwa pliku: src/lib.rs
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");
}
Listing 7-9: Struktura z niektórymi publicznymi i niektórymi prywatnymi polami

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.

Nazwa pliku: src/lib.rs
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;
}
Listing 7-10: Upublicznienie wyliczenia sprawia, że wszystkie jego warianty stają się publiczne.

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.