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

Тема
Описание
Доп.

crate cargo-modules - Плагин Cargo для визуализации/анализа внутренней структуры crate.

Показывает структуру папки

sudo snap install tree

$ tree .

Современный стиль (без mod.rs)

С 2018 edition можно писать так:

$ tree src

src
├── lib.rs
├── main.rs
├── network
│   ├── client.rs
│   └── server.rs
└── network.rs

File lib.rs:

pub mod network;

File network.rs:


pub mod client;
pub mod server;

Рекомендация не использовать в подмодулях mod.rs

А вместо этого сделать реимпорт в нормально именованный файл и его уже импортировать

defining-modules-to-control-scope-and-privacy

├── Cargo.lock
├── Cargo.toml
├── src
│   ├── client
│   │   ├── client.rs
│   │   └── example_client.rs
│   ├── client.rs
│   └── main.rs

// File main.rs:
pub mod client;
 
fn main() {
    client::client::use_me();
    client::example_client::use_me_example();
}

// File client.rs:
pub mod client;
pub mod example_client;

// File client/example_client.rs:
pub fn use_me_example(){
    println!("use_me_example");
}

// File client/client.rs:
pub fn use_me(){
    println!("use_me");
}

Или так через явный путь

├── Cargo.lock
├── Cargo.toml
├── src
│   ├── client
│   │   ├── client.rs
│   │   └── example_client.rs
│   └── main.rs

#[path = "client/client.rs"]
pub mod client;
#[path = "client/example_client.rs"]
pub mod example_client;

fn main() {
    client::use_me();
    example_client::use_me_example();
}

Используйте pub use для удобного API

Чтобы не заставлять пользователей писать длинные пути

// lib.rs
pub mod network;
pub use network::{client, server};

Теперь пользователи вашей библиотеки смогут просто использовать готовый импорт use lib::client и use lib::server

Хорошая практика группировки таких импортов под общим названием prelude

pub mod prelude {
    pub use super::{
        cli::RepContent, client, errors, service, settings::Set,
    };
}

Тогда пользователи библиотеки смогут сразу импортировать все компоненты:

use prelude::*;

импорты хорошо разделять по следующим секциям (дабы не было кучей)

  1. std импорты
  2. импорты внешних крейтов
  3. внутренни crate:: импорты
  4. super:: импорты
  5. self:: импорты

Пример:


use std::num;

use diesel::prelude::*;
use tokio::fs;

use crate::{
    api::{UserLogin, UserQuery, UserReturn, UserReturnLogin, UserUpdate},
    db::{Conn, Result},
}

use super::super:: {
    orm::{error::ServiceError, schema, BaseDiesel, UserLoginType, UserType},
    BaseDb, ConnectType, QueryStore, Store, TokenType,
}

use self::submod::Something;

fn main(){
 ...
}

Внутри одной секции, и внутри каждой строки элементы должны идти в алфавитном порядке.

Модификаторы видимости pub

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

По умолчанию всё в Rust приватно.

Если ты пишешь просто fn foo() {}, она видна только внутри текущего модуля. Чтобы "пустить наружу", надо использовать pub.

Но pub можно "ограничить":

  • pub → доступен всем (глобально, за пределами crate)
  • pub(crate) → доступен в рамках текущего crate, но не наружу
  • pub(super) → доступен в родительском модуле
  • pub(self) → почти то же самое, что и приватный (редко нужен явно)
  • pub(in some::path) → доступен только внутри указанного пути (гранулярный контроль)

pub(crate)

  • функция/структура доступна во всём crate

  • но не видна пользователям снаружи, если библиотека опубликована на crates.io

Это очень удобно, когда у тебя есть внутренние утилиты или вспомогательные API, которые нужны многим модулям внутри библиотеки, но ты не хочешь засорять публичный интерфейс.

Пример проекта

src/
 ├── lib.rs
 ├── parser.rs
 └── utils.rs

File lib.rs:

pub mod parser;
mod utils; // не делаем pub, чтобы utils не торчал наружу

File parser.rs:

use crate::utils;

pub fn parse(input: &str) {
    // можем использовать crate-функцию, хотя utils не pub
    let normalized = utils::normalize(input);
    println!("Parsing: {normalized}");
}

File utils.rs:

// Эта функция доступна везде внутри crate
pub(crate) fn normalize(input: &str) -> String {
    input.trim().to_lowercase()
}

// Эта функция приватна даже внутри crate
fn secret_helper() {
    println!("secret!");
}

Использование

Если мы соберём бинарник внутри того же проекта

File main.rs:

use my_library::parser;

fn main() {
    parser::parse("   Hello Rust!   ");
    // my_library::utils::normalize("..."); // ❌ Ошибка: не видно снаружи
}

Что мы получили

  • normalize виден во всех модулях crate (например, parser, network, storage могут его звать).
  • Но не виден пользователю библиотеки. То есть API остаётся чистым.

Это классический случай: внутренний API для своих, но не часть контракта с пользователем.

pub(super) → видно только в родительском модуле (и его содержимом).

локальный внутренний доступ, только на один уровень вверх

Ситуация

Представь, у нас есть модуль parser, а внутри него — модуль lexer. lexer нужен только как внутренний помощник для parser, и мы не хотим, чтобы его API было доступно во всём crate.

Структура проекта

src/
 ├── lib.rs
 └── parser/
      ├── mod.rs   (или parser.rs)
      └── lexer.rs

File lib.rs:

pub mod parser;

File parser/mod.rs:

mod lexer;

pub fn parse(input: &str) {
    // можем использовать pub(super) функцию из lexer
    let tokens = lexer::tokenize(input);
    println!("Tokens: {:?}", tokens);
}

File parser/lexer.rs:

// Эта функция доступна только для родителя (parser), но не для всего crate
pub(super) fn tokenize(input: &str) -> Vec<&str> {
    input.split_whitespace().collect()
}

Использование

File main.rs

use my_library::parser;

fn main() {
    parser::parse("hello rust");
    // my_library::parser::lexer::tokenize("test"); 
    // ❌ Ошибка: tokenize = pub(super), а не pub
}

Что получилось

  • tokenize доступен только из parser, потому что у него pub(super).
  • Если бы было pub(crate), tokenize можно было бы вызвать из любого модуля внутри библиотеки.
  • Если бы было pub, то и пользователи библиотеки снаружи могли бы напрямую вызывать lexer::tokenize.

Таким образом pub(super) — это способ спрятать внутреннюю реализацию за фасадом модуля.