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

Пути для ссылки на элемент в дереве модулей

Чтобы показать Rust, где искать элемент в дереве модулей, мы используем путь: так же, как мы используем путь при навигации по файловой системе. Чтобы вызвать функцию, нам нужно знать её путь.

Пути бывают двух видов:

  • Абсолютный путь — это полный путь, начинающийся от корня крейта; для кода из внешнего крейта абсолютный путь начинается с имени крейта, а для кода из текущего крейта он начинается со слова crate.
  • Относительный путь — это путь, начинающийся с текущего модуля и использующий ключевые слова self и super или идентификатор в текущем модуле.

Как абсолютные, так и относительные пути состоят из одного или нескольких идентификаторов, разделяемых двойными двоеточиями (::).

Returning to Listing 7-1, say we want to call the add_to_waitlist function. This is the same as asking: What’s the path of the add_to_waitlist function? Listing 7-3 contains Listing 7-1 with some of the modules and functions removed.

Мы покажем два способа вызова функции add_to_waitlist из новой функции eat_at_restaurant, определённой в корне крейта. Эти пути правильные, но остаётся ещё одна проблема, которая не позволит этому примеру скомпилироваться как есть. Мы скоро объясним, какая именно.

Функция eat_at_restaurant является частью общедоступного API нашего библиотечного крейта, поэтому мы помечаем её ключевым словом pub. В разделе “Раскрытие путей с помощью ключевого слова pub мы рассмотрим pub более подробно.

Filename: src/lib.rs
mod front_of_house {
    mod hosting {
        fn add_to_waitlist() {}
    }
}

pub fn eat_at_restaurant() {
    // Абсолютный путь
    crate::front_of_house::hosting::add_to_waitlist();

    // Относительный путь
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-3: Calling the add_to_waitlist function using absolute and relative paths

The first time we call the add_to_waitlist function in eat_at_restaurant, we use an absolute path. The add_to_waitlist function is defined in the same crate as eat_at_restaurant, which means we can use the crate keyword to start an absolute path. We then include each of the successive modules until we make our way to add_to_waitlist. You can imagine a filesystem with the same structure: We’d specify the path /front_of_house/hosting/add_to_waitlist to run the add_to_waitlist program; using the crate name to start from the crate root is like using / to start from the filesystem root in your shell.

Во втором вызове add_to_waitlist из eat_at_restaurant мы используем относительный путь. Путь начинается с имени модуля front_of_house, определённого на том же уровне дерева модулей, что и eat_at_restaurant. Аналогом пути в файловой системе было бы front_of_house/hosting/add_to_waitlist. Начало пути с имени модуля означает, что путь является относительным.

Выбор, использовать относительный или абсолютный путь, является решением, которое вы примете на основании вашего проекта. Решение зависит от того, с какой вероятностью вы переместите объявление элемента отдельно от или вместе с кодом, использующим этот элемент. Например, в случае перемещения модуля front_of_house и его функции eat_at_restaurant в другой модуль с именем customer_experience, будет необходимо обновить абсолютный путь до add_to_waitlist, но относительный путь всё равно будет действителен. Однако, если мы переместим отдельно функцию eat_at_restaurant в модуль с именем dining, то абсолютный путь вызова add_to_waitlist останется прежним, а относительный путь нужно будет обновить. Мы предпочитаем указывать абсолютные пути, потому что это позволяет проще перемещать определения кода и вызовы элементов независимо друг от друга.

Давайте попробуем скомпилировать код из Листинга 7-3 и выяснить, почему он всё ещё не компилируется. Ошибка, которую мы получаем, показана в Листинге 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: Compiler errors from building the code in Listing 7-3

Сообщения об ошибках говорят о том, что модуль hosting является приватным. Другими словами, у нас есть правильные пути к модулю hosting и функции add_to_waitlist, но Rust не позволяет нам использовать их, потому что у него нет доступа к приватным определениям. В Rust все элементы (функции, методы, структуры, перечисления, модули и константы) по умолчанию являются закрытыми для родительских модулей. Если вы хотите сделать элемент (например, функцию или структуру) приватным, вы помещаете его в модуль.

Items in a parent module can’t use the private items inside child modules, but items in child modules can use the items in their ancestor modules. This is because child modules wrap and hide their implementation details, but the child modules can see the context in which they’re defined. To continue with our metaphor, think of the privacy rules as being like the back office of a restaurant: What goes on in there is private to restaurant customers, but office managers can see and do everything in the restaurant they operate.

Rust chose to have the module system function this way so that hiding inner implementation details is the default. That way, you know which parts of the inner code you can change without breaking the outer code. However, Rust does give you the option to expose inner parts of child modules’ code to outer ancestor modules by using the pub keyword to make an item public.

Раскрытие путей с помощью ключевого слова pub

Давайте вернёмся к ошибке в Листинге 7-4, которая говорит, что модуль hosting является приватным. Мы хотим, чтобы функция eat_at_restaurant из родительского модуля имела доступ к функции add_to_waitlist в дочернем модуле, поэтому мы помечаем модуль hosting ключевым словом pub, как показано в Листинге 7-5.

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        fn add_to_waitlist() {}
    }
}

// -- код сокращён --
pub fn eat_at_restaurant() {
    // Абсолютный путь
    crate::front_of_house::hosting::add_to_waitlist();

    // Относительный путь
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-5: Declaring the hosting module as pub to use it from eat_at_restaurant

К сожалению, код в Листинге 7-5 всё ещё приводит к ошибке, показанной в Листинге 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: Compiler errors from building the code in Listing 7-5

Что произошло? Добавление ключевого слова pub перед mod hosting сделало модуль общедоступным. После этого изменения, если мы можем получить доступ к модулю front_of_house, то мы можем получить доступ к модулю hosting. Но содержимое модуля hosting всё ещё является приватным: превращение модуля в общедоступный модуль не делает его содержимое общедоступным. Ключевое слово pub позволяет внешнему коду в модулях-предках обращаться только к модулю, без доступа ко внутреннему коду. Поскольку модули являются контейнерами, мы мало что можем сделать, просто сделав модуль общедоступным; нам нужно пойти дальше и сделать один или несколько элементов в модуле общедоступными.

Ошибки в Листинге 7-6 говорят, что функция add_to_waitlist является приватной. Правила приватности применяются к структурам, перечислениям, функциям и методам, также как и к модулям.

Давайте сделаем функцию add_to_waitlist также общедоступной, добавив ключевое слово pub перед её определением, как показано в Листинге 7-7.

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

// -- код сокращён --
pub fn eat_at_restaurant() {
    // Абсолютный путь
    crate::front_of_house::hosting::add_to_waitlist();

    // Относительный путь
    front_of_house::hosting::add_to_waitlist();
}
Listing 7-7: Adding the pub keyword to mod hosting and fn add_to_waitlist lets us call the function from eat_at_restaurant.

Код наконец-то компилируется! Чтобы понять, почему добавление ключевого слова pub позволяет нам использовать эти пути для add_to_waitlist в соответствии с правилами приватности, давайте рассмотрим абсолютный и относительный пути.

In the absolute path, we start with crate, the root of our crate’s module tree. The front_of_house module is defined in the crate root. While front_of_house isn’t public, because the eat_at_restaurant function is defined in the same module as front_of_house (that is, eat_at_restaurant and front_of_house are siblings), we can refer to front_of_house from eat_at_restaurant. Next is the hosting module marked with pub. We can access the parent module of hosting, so we can access hosting. Finally, the add_to_waitlist function is marked with pub, and we can access its parent module, so this function call works!

In the relative path, the logic is the same as the absolute path except for the first step: Rather than starting from the crate root, the path starts from front_of_house. The front_of_house module is defined within the same module as eat_at_restaurant, so the relative path starting from the module in which eat_at_restaurant is defined works. Then, because hosting and add_to_waitlist are marked with pub, the rest of the path works, and this function call is valid!

If you plan to share your library crate so that other projects can use your code, your public API is your contract with users of your crate that determines how they can interact with your code. There are many considerations around managing changes to your public API to make it easier for people to depend on your crate. These considerations are beyond the scope of this book; if you’re interested in this topic, see the Rust API Guidelines.

Лучшие практики для пакетов с бинарным и библиотечным крейтами

We mentioned that a package can contain both a src/main.rs binary crate root as well as a src/lib.rs library crate root, and both crates will have the package name by default. Typically, packages with this pattern of containing both a library and a binary crate will have just enough code in the binary crate to start an executable that calls code defined in the library crate. This lets other projects benefit from the most functionality that the package provides because the library crate’s code can be shared.

The module tree should be defined in src/lib.rs. Then, any public items can be used in the binary crate by starting paths with the name of the package. The binary crate becomes a user of the library crate just like a completely external crate would use the library crate: It can only use the public API. This helps you design a good API; not only are you the author, but you’re also a client!

В Главе 12 мы покажем эту практику организации кода на примере консольной программы, которая будет содержать как бинарный, так и библиотечный крейты.

Определение относительных путей с помощью super

We can construct relative paths that begin in the parent module, rather than the current module or the crate root, by using super at the start of the path. This is like starting a filesystem path with the .. syntax that means to go to the parent directory. Using super allows us to reference an item that we know is in the parent module, which can make rearranging the module tree easier when the module is closely related to the parent but the parent might be moved elsewhere in the module tree someday.

Рассмотрим код в Листинге 7-8, где моделируется ситуация, в которой повар исправляет неправильный заказ и лично приносит его клиенту. Функция fix_incorrect_order вызывает функцию deliver_order, определённую в родительском модуле, указывая путь к deliver_order, начинающийся с super:

Filename: 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: Calling a function using a relative path starting with super

The fix_incorrect_order function is in the back_of_house module, so we can use super to go to the parent module of back_of_house, which in this case is crate, the root. From there, we look for deliver_order and find it. Success! We think the back_of_house module and the deliver_order function are likely to stay in the same relationship to each other and get moved together should we decide to reorganize the crate’s module tree. Therefore, we used super so that we’ll have fewer places to update code in the future if this code gets moved to a different module.

Указание общедоступности структур и перечислений

Мы также можем использовать pub для обозначения структур и перечислений как общедоступных, но есть несколько дополнительных деталей использования pub со структурами и перечислениями. Если мы используем pub перед определением структуры, мы делаем структуру общедоступной, но поля структуры по-прежнему остаются приватными. Мы можем сделать каждое поле общедоступным или нет в каждом конкретном случае. В Листинге 7-9 мы определили общедоступную структуру back_of_house::Breakfast с общедоступным полем toast и с приватным полем seasonal_fruit. Это моделирует случай в ресторане, когда клиент может выбрать тип хлеба, который подаётся с едой, а шеф-повар решает какие фрукты сопровождают еду, исходя из того, что сезонно и что есть в наличии. Доступные фрукты быстро меняются, поэтому клиенты не могут выбирать фрукты или даже увидеть, какие фрукты они получат.

Filename: 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("персики"),
            }
        }
    }
}

pub fn eat_at_restaurant() {
    // Заказываем летом завтрак с ржаным хлебом.
    let mut meal = back_of_house::Breakfast::summer("ржаной");
    // Передумали насчёт хлеба, который мы хотим.
    meal.toast = String::from("пшеничный");
    println!("Я хочу {} тост, пожалуйста.", meal.toast);

    // Следующая строчка, если мы раскомментируем её, не скомпилируется:
    // нам не разрешено знать или изменять сезонный фрукт, который нам подадут.
    // meal.seasonal_fruit = String::from("черника");
}
Listing 7-9: A struct with some public fields and some private fields

Поскольку поле toast в структуре back_of_house::Breakfast является открытым, то в функции eat_at_restaurant можно писать и читать поле toast, используя обращение через точку. Обратите внимание, что мы не можем использовать поле seasonal_fruit в eat_at_restaurant, потому что seasonal_fruit является приватным. Попробуйте раскомментировать последнюю строчку, пытающуюся использовать значение поля seasonal_fruit, чтобы увидеть, какую ошибку вы получите!

Also, note that because back_of_house::Breakfast has a private field, the struct needs to provide a public associated function that constructs an instance of Breakfast (we’ve named it summer here). If Breakfast didn’t have such a function, we couldn’t create an instance of Breakfast in eat_at_restaurant, because we couldn’t set the value of the private seasonal_fruit field in eat_at_restaurant.

В отличие от структуры, если мы сделаем общедоступным перечисление, то все его варианты будут общедоступными. Нужно только указать pub перед ключевым словом enum, как показано в Листинге 7-10.

Filename: 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: Designating an enum as public makes all its variants public.

Поскольку мы сделали перечисление Appetizer общедоступным, мы можем использовать варианты Soup и Salad в функции eat_at_restaurant.

Перечисления не очень полезны, если их варианты не являются общедоступными: аннотирование всех вариантов перечисления как pub было бы раздражающе нудным. По этой причине варианты перечислений по умолчанию являются общедоступными. Структуры часто полезны, если их поля не являются общедоступными, поэтому поля структуры следуют общему правилу, согласно которому, всё по умолчанию является приватным, если не указано pub.

Есть ещё одна ситуация с pub, которую мы не освещали, и это последняя особенность системы модулей: ключевое слово use. Мы сначала опишем use само по себе, а затем покажем, как сочетать pub и use вместе.