Легко понять модули Rust в нескольких файлах с помощью этого руководства

Легко понять модули Rust в нескольких файлах с помощью этого руководства

18 ноября 2022 г.

TL;DR

  • 💡 Легкое и простое объяснение модулей Rust в разных файлах.
  • 🤿 Мы подробно рассмотрим реальный пример, чтобы изучить модульную систему.
  • 📈 Множество диаграмм, которые помогут вам разобраться.

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

Поехали.

Модули Rust в файлах

Rust требует, чтобы разработчики вручную создавали деревья модулей. Это можно сделать, объявив модули с помощью ключевого слова mod.

Дерево модулей начинается с корня крейта, обычно это src/lib.rs для крейта с библиотекой или src/main.rs для бинарного крейта. Компилятор Rust сначала ищет модули для компиляции в корне ящика.

Допустим, вы хотите импортировать модуль "a" в двоичный крейт, вы можете объявить модуль следующим образом:

main.rs

mod a;

fn main() { /* do amazing things */ }

Компилятор будет искать модуль в каталоге src в следующих местах:

В src/a.rs

.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── a.rs
    └── main.rs

Или в src/a/mod.rs

.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── a
       └── mod.rs
    └── main.rs

Объявив mod a в main.rs, вы построили такое дерево модулей:

Initial Rust module tree

Подмодули Rust в файлах

В модуле вы можете создавать подмодули для дальнейшей организации кода. Допустим, вы хотите объявить модули "b" и "c" в модуле "a":

/src/a/mod.rs

mod b;
mod c;

Компилятор будет искать подмодули в каталоге src/a:

.
├── Cargo.lock
├── Cargo.toml
└── src
    ├── a
       ├── b.rs
       ├── c.rs
       └── mod.rs
    └── main.rs

Теперь вы построили такое дерево:

Rust module tree with submodules

Видимость с "pub"

По умолчанию все элементы в модуле являются частными. Они видны только элементам в том же модуле.

источник/a/mod.rs

mod b;
mod c;

fn do_a() {} // only the other functions in module a can use it
             // it's not visible to main.rs

Чтобы его родительские модули имели доступ к функции do_a, нам нужно добавить ключевое слово pub.

источник/a/mod.rs

pub fn do_a() {} // now it's visible to main.rs

Мы можем получить доступ к do_a, используя квалификатор пути < em>::.

источник/main.rs

mod a;

fn main() {
    a::do_a();
}

Мы можем использовать тот же шаблон для подмодулей.

источник/a/b.rs

pub fn do_b() {} // visible to module "a" and all the submodules of module "a"

Добавив pub в do_b, функция теперь доступна для модуля "a".

источник/a/mod.rs

mod b;
mod c;

pub fn do_a {
    b::do_b();
}

do_b также доступен для подмодулей модуля "c". Вы можете получить к нему доступ как по абсолютному, так и по относительному пути.

src/a/c.rs

pub fn do_c {
    crate::a::b::do_b(); // absolute path
    super::b::do_b(); // relative path
}

Реэкспорт элементов

Элемент подмодуля недоступен для неродительского модуля. Например, мы можем попытаться получить доступ к do_b в main.rs

.

источник/main.rs

mod:a;

fn main() {
    a::b::do_b();
    // ^^^^ function `do_b` is private
}

Вы увидите сообщение об ошибке, в котором говорится, что do_b является частным. Это потому, что do_b пока доступен только в модуле "a". Чтобы сделать его видимым для корня ящика, нам нужно повторно экспортировать его, добавив pub в объявление модуля «b» из модуля «a».

источник/a/mod.rs

pub mod b;
// --snip--

Декларация об использовании

использование объявление может помочь вам сократить путь при доступе к элементу в другом модуле. Например, мы можем провести рефакторинг модуля "a":

источник/a/mod.rs

mod b;
mod c;

use b::do_b;
use c::do_c;

pub fn do_a {
    do_b();
    do_c();
}

Он создает привязку локального имени к своему пути для do_b и do_c. use очень полезно для длинных путей.

Пример из реальной жизни

Чтобы продемонстрировать модульную систему Rust, я создал простой интерфейс командной строки под названием affme, что означает "подтвердить меня".

affme demo

affme — это генератор самоутверждения. Интерфейс командной строки принимает имя в качестве параметра и отображает рандомизированное подтверждение.

<цитата>

Демонстрация доступна на GitHub. Не стесняйтесь взглянуть на репозиторий и попробовать✨

Дизайн кода прост:

affme code design

В блоке "Формат"

* Требуется ввод данных пользователем, * объединяет ввод со случайным подтверждением и случайным смайликом, * применяет случайный цвет шрифта к объединенному утверждению, * и, наконец, выводит подтверждение. *

Чтобы продемонстрировать систему модулей в файлах, я разрабатываю дерево модулей следующим образом:

affme module tree design

Несколько вещей, о которых стоит упомянуть:

* Этот пакет состоит из двух крейтов, одного бинарного и одной библиотеки. Я использую библиотечный крейт для инкапсуляции реализации и бинарный крейт для выполнения CLI. * В корневой папке библиотеки src/lib.rs он обращается к функциям из модулей аффирмации и formatter. * Модуль аффирмации и оба подмодуля модуля formatter используют одну и ту же функцию модуля random для случайного выбора элемента. Поскольку модуль аффирмации и подмодули formatter находятся в разных ветвях дерева, нам нужно объявить модуль random в общем предке модуля. дерево.

В файловой системе это выглядит так:

.
├── Cargo.lock
├── Cargo.toml
├── src
   ├── affirmation.rs
   ├── formatter
      ├── color.rs
      ├── emoji.rs
      └── mod.rs
   ├── lib.rs
   ├── main.rs
   └── random.rs
└── target

Давайте углубимся в корень библиотеки, чтобы увидеть, как устроен код.

источник/lib.rs

mod affirmation;
mod formatter;
mod random;

use affirmation::Affirmation;
use formatter::format;

pub fn affirm(name: &str) -> String {
    let affirmation = Affirmation::new().random();
    format(affirmation, name)
}

Здесь вы можете увидеть объявления модуля вверху. Вы также можете найти объявления use для создания привязки локального имени для Affirmation и format.

Случайный модуль прост:

источник/random.rs

use rand::Rng;

pub fn pick<'a, T: ?Sized>(items: &[&'a T]) -> &'a T {
    let random_index: usize = rand::thread_rng().gen_range(0..items.len());
    items.get(random_index).unwrap()
}

У него есть общедоступная функция pick, которая возвращает случайный элемент из фрагмента массива. Я использую эту функцию для выбора случайных утверждений, смайликов и цветов. В качестве примера рассмотрим модуль аффирмации:

src/аффирмация.rs

use crate::random;

#[derive(Debug)]
pub struct Affirmation<'a> {
    affirmations: [&'a str; 6],
}

impl<'a> Affirmation<'a> {
    pub fn new() -> Self {
        let affirmations = [
            "You're beautiful",
            "You're awesome",
            "You're wonderful",
            "You've got this",
            "You can do all things",
            "Go get it",
        ];
        Affirmation { affirmations }
    }

    pub fn random(&self) -> &'a str {
        random::pick(&self.affirmations)
    }
}

Вы можете увидеть объявление use для модуля random. Модуль аффирмации может получить доступ к модулю random, поскольку модуль random был объявлен в корневом каталоге библиотеки. Я использую ключевое слово pub в структуре Affirmation и ее функциях, чтобы корень ящика мог видеть их.

Вы можете найти тот же шаблон кодирования в подмодулях emoji и color.

Чтобы собрать все воедино, давайте взглянем на модуль format.

источник/formatter/mod.rs

mod color;
mod emoji;

use color::Color;
use colored::*;
use emoji::Emoji;

pub fn format(affirmation: &str, name: &str) -> String {
    let emoji = Emoji::new();
    let color = Color::new();

    let phrase = format!("{}, {} {}", affirmation, name, emoji.random())
        .color(color.random())
        .bold()
        .to_string();

    format!(
        "{}n{}n{}n{}n{}",
        "*".repeat(phrase.len() + 2).magenta(),
        format!("*{}*", " ".repeat(phrase.len())).magenta(),
        format!("    ✏️  ...{}  ", phrase,),
        format!("*{}*", " ".repeat(phrase.len())).magenta(),
        "*".repeat(phrase.len() + 2).magenta()
    )
}

Он включает в себя подмодули color и emoji, поэтому мы можем объединить полное утверждение со случайными emoji и случайным цветом шрифта.

Заключительные мысли

Модули Rust в нескольких файлах немного отличаются от других языков, но как только вы поймете mod, use и pub, дизайн модуля становится проще и преднамереннее.

Памятка по модулю Rust

* Дерево модулей начинается с корня крейта. * Используйте mod, чтобы построить дерево с модулями и подмодулями. * Используйте pub, чтобы сделать элементы модуля видимыми для родительского модуля. * Вы можете реэкспортировать с помощью pub mod или pub use.

Ссылки


Эта статья изначально была опубликована на веб-сайте Daw-Chih< /а>. н


Оригинал