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

Добавление путей в область видимости ключевым словом use

Having to write out the paths to call functions can feel inconvenient and repetitive. In Listing 7-7, whether we chose the absolute or relative path to the add_to_waitlist function, every time we wanted to call add_to_waitlist we had to specify front_of_house and hosting too. Fortunately, there’s a way to simplify this process: We can create a shortcut to a path with the use keyword once and then use the shorter name everywhere else in the scope.

In Listing 7-11, we bring the crate::front_of_house::hosting module into the scope of the eat_at_restaurant function so that we only have to specify hosting::add_to_waitlist to call the add_to_waitlist function in eat_at_restaurant.

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

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
Listing 7-11: Bringing a module into scope with use

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

Обратите внимание, что use создаёт псевдоним только для той конкретной области, в которой это объявление use и находится. В Листинге 7-12 функция eat_at_restaurant перемещается в новый дочерний модуль с именем customer, область видимости которого отличается от области видимости инструкции use, поэтому тело функции не будет компилироваться.

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

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}
Listing 7-12: A use statement only applies in the scope it’s in.

Ошибка компилятора показывает, что данный псевдоним не может использоваться в модуле customer:

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of unresolved module or unlinked crate `hosting`
   |
   = help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
   |
10 +     use crate::hosting;
   |

warning: unused import: `crate::front_of_house::hosting`
 --> src/lib.rs:7:5
  |
7 | use crate::front_of_house::hosting;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted

Обратите внимание, что есть также предупреждение о том, что use не спользуется в своей области видимости! Чтобы решить эту проблему, можно переместить use в модуль customer, или же можно сослаться на псевдоним в родительском модуле с помощью super::hosting в дочернем модуле customer.

Создание идиоматических путей с use

В Листинге 7-11 вы могли задаться вопросом, почему мы указали use crate::front_of_house::hosting, а затем вызвали hosting::add_to_waitlist внутри eat_at_restaurant вместо указания в use полного пути прямо до функции add_to_waitlist для получения того же результата, что в Листинге 7-13.

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

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
}
Listing 7-13: Bringing the add_to_waitlist function into scope with use, which is unidiomatic

Хотя Листинги 7-11 и 7-13 выполняют одну и ту же задачу, Листинг 7-11 является идиоматическим способом подключения функции в область видимости с помощью use. Подключение родительского модуля функции в область видимости при помощи use означает, что мы должны указывать родительский модуль при вызове функции. Указание родительского модуля при вызове функции даёт понять, что функция не определена локально, но в то же время сводя к минимуму повторение полного пути. В коде Листинга 7-13 не ясно, где именно определена add_to_waitlist.

С другой стороны, при подключении структур, перечислений и других элементов использованием use, идиоматически правильным будет указывать полный путь. Листинг 7-14 показывает идиоматический способ подключения структуры стандартной библиотеки HashMap в область видимости бинарного крейта.

Filename: src/main.rs
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}
Listing 7-14: Bringing HashMap into scope in an idiomatic way

There’s no strong reason behind this idiom: It’s just the convention that has emerged, and folks have gotten used to reading and writing Rust code this way.

Исключением из этой идиомы является случай, когда мы подключаем два элемента с одинаковыми именами в область видимости используя инструкцию use — Rust просто не позволяет этого сделать. Листинг 7-15 показывает, как подключить в область действия два типа с одинаковыми именами Result, но из разных родительских модулей, и как на них ссылаться.

Filename: src/lib.rs
use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --код сокращён--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --код сокращён--
    Ok(())
}
Listing 7-15: Bringing two types with the same name into the same scope requires using their parent modules.

Как видите, использование имени родительских модулей позволяет различать два типа Result. Если бы вместо этого мы указали use std::fmt::Result и use std::io::Result, мы бы имели два типа Result в одной области видимости, и Rust не смог бы понять какой из двух Result мы имели в виду, когда нашёл бы их употребление в коде.

Предоставление новых имён с помощью ключевого слова as

There’s another solution to the problem of bringing two types of the same name into the same scope with use: After the path, we can specify as and a new local name, or alias, for the type. Listing 7-16 shows another way to write the code in Listing 7-15 by renaming one of the two Result types using as.

Filename: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --код сокращён--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --код сокращён--
    Ok(())
}
Listing 7-16: Renaming a type when it’s brought into scope with the as keyword

Во второй инструкции use мы выбрали новое имя IoResult для типа std::io::Result, которое теперь не будет конфликтовать с типом Result из std::fmt, который также подключён в область видимости. Оба Листинга 7-15 и 7-16 считаются идиоматичными, так что выбор за вами!

Реэкспорт имён с pub use

When we bring a name into scope with the use keyword, the name is private to the scope into which we imported it. To enable code outside that scope to refer to that name as if it had been defined in that scope, we can combine pub and use. This technique is called re-exporting because we’re bringing an item into scope but also making that item available for others to bring into their scope.

Листинг 7-17 показывает код из Листинга 7-11, где use в корневом модуле заменено на pub use.

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

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
Listing 7-17: Making a name available for any code to use from a new scope with pub use

До этого изменения внешний код должен был вызывать функцию add_to_waitlist, используя путь restaurant::front_of_house::hosting::add_to_waitlist(). Теперь, поскольку это объявление pub use реэкспортировало модуль hosting из корневого модуля, внешний код теперь может использовать вместо него путь restaurant::hosting::add_to_waitlist().

Re-exporting is useful when the internal structure of your code is different from how programmers calling your code would think about the domain. For example, in this restaurant metaphor, the people running the restaurant think about “front of house” and “back of house.” But customers visiting a restaurant probably won’t think about the parts of the restaurant in those terms. With pub use, we can write our code with one structure but expose a different structure. Doing so makes our library well organized for programmers working on the library and programmers calling the library. We’ll look at another example of pub use and how it affects your crate’s documentation in “Exporting a Convenient Public API” in Chapter 14.

Использование внешних пакетов

В Главе 2 мы запрограммировали игру в угадайку, где использовался внешний пакет с именем rand для генерации случайного числа. Чтобы использовать rand в нашем проекте, мы добавили эту строку в Cargo.toml.

Filename: Cargo.toml
rand = "0.8.5"

Добавление rand в качестве зависимости в Cargo.toml указывает Cargo загрузить пакет rand и все его зависимости с crates.io и сделать rand доступным для нашего проекта.

Затем, чтобы подключить определения rand в область видимости нашего пакета, мы добавили строку use, начинающуюся с названия пакета rand и списка элементов, которые мы хотим подключить в область видимости. Напомним, что в разделе “Генерация секретного числа”Главы 2 мы подключили трейт Rng в область видимости и вызвали функцию rand::thread_rng:

use std::io;

use rand::Rng;

fn main() {
    println!("Угадайте число!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("Загаданное число: {secret_number}");

    println!("Введите свою догадку.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Не удалось прочесть ввод.");

    println!("Вы предположили: {guess}");
}

Участники Сообщества Rust создали множество пакетов, доступных на crates.io, и добавление любого из них в ваш пакет включает в себя одни и те же шаги: нужно перечислить их в файле Cargo.toml вашего пакета и использовать use для подключения элементов внешних пакетов в область видимости.

Обратите внимание, что стандартная библиотека std также является крейтом, внешним по отношению к нашему пакету. Поскольку стандартная библиотека поставляется с языком Rust, нам не нужно изменять Cargo.toml для подключения std. Но нам нужно ссылаться на неё при помощи use, чтобы добавить элементы оттуда в область видимости нашего пакета. Например, с HashMap мы использовали бы эту строку:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
}

Это абсолютный путь, начинающийся с std, имени крейта стандартной библиотеки.

Using Nested Paths to Clean Up use Lists

Если мы используем несколько элементов, определённых в одном крейте или в том же модуле, то перечисление каждого элемента в отдельной строке может занимать много вертикального пространства в файле. Например, эти две инструкции use используются в нашей игре в угадайку (Листинг 2-4) для подключения элементов из std в область видимости:

Filename: src/main.rs
use rand::Rng;
// --код сокращён--
use std::cmp::Ordering;
use std::io;
// --код сокращён--

fn main() {
    println!("Угадайте число!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("Загаданное число: {secret_number}");

    println!("Введите свою догадку.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Не удалось прочесть ввод.");

    println!("Вы предположили: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Слишком маленькое!"),
        Ordering::Greater => println!("Слишком большое!"),
        Ordering::Equal => println!("Вы победили!"),
    }
}

Вместо этого, мы можем использовать перечисление путей, чтобы добавить эти элементы в область видимости одной строкой. Это делается, как показано в Листинге 7-18: указывается общая часть пути, за которой следуют два двоеточия, а затем фигурные скобки вокруг списка тех частей пути, которые отличаются.

Filename: src/main.rs
use rand::Rng;
// --код сокращён--
use std::{cmp::Ordering, io};
// --код сокращён--

fn main() {
    println!("Угадайте число!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("Загаданное число: {secret_number}");

    println!("Введите свою догадку.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Не удалось прочесть ввод.");

    let guess: u32 = guess.trim().parse().expect("Пожалуйста, введите число!");

    println!("Вы предположили: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Слишком маленькое!"),
        Ordering::Greater => println!("Слишком большое!"),
        Ordering::Equal => println!("Вы победили!"),
    }
}
Listing 7-18: Specifying a nested path to bring multiple items with the same prefix into scope

В больших программах, подключение множества элементов из одного пакета или модуля с использованием перечисления путей может значительно сократить количество необходимых отдельных инструкций use!

Использовать перечисление путей можно на любом уровне, что полезно при объединении двух инструкций use, которые имеют общую часть пути. Например, в Листинге 7-19 показаны две инструкции use: одна в область видимости подключает std::io, а другая — std::io::Write.

Filename: src/lib.rs
use std::io;
use std::io::Write;
Listing 7-19: Two use statements where one is a subpath of the other

Общей частью этих двух путей является std::io, и это полный первый путь. Чтобы объединить эти два пути в одной инструкции use, мы можем использовать ключевое слово self в перечислении путей, как показано в Листинге 7-20.

Filename: src/lib.rs
use std::io::{self, Write};
Listing 7-20: Combining the paths in Listing 7-19 into one use statement

Эта строка подключает std::io и std::io::Write в область видимости.

Importing Items with the Glob Operator

If we want to bring all public items defined in a path into scope, we can specify that path followed by the * glob operator:

#![allow(unused)]
fn main() {
use std::collections::*;
}

This use statement brings all public items defined in std::collections into the current scope. Be careful when using the glob operator! Glob can make it harder to tell what names are in scope and where a name used in your program was defined. Additionally, if the dependency changes its definitions, what you’ve imported changes as well, which may lead to compiler errors when you upgrade the dependency if the dependency adds a definition with the same name as a definition of yours in the same scope, for example.

The glob operator is often used when testing to bring everything under test into the tests module; we’ll talk about that in “How to Write Tests” in Chapter 11. The glob operator is also sometimes used as part of the prelude pattern: See the standard library documentation for more information on that pattern.