Модули, документация, тестване

Модули

Начин да си организираме кода в отделни namespaces.
Обикновенно йерархията от модули съвпада с йерархията на файловете на проекта ни.

Йерархия

Проекта ни съдържа един главен файл, който е и главния модул:

Типа на проекта се задава, когато изпълним cargo new или cargo init:

При създаване на нов проект получаваме следната йерахия от файлове:

1 2 3 4 5 6 7
$ cargo new communicator --lib

$ tree communicator
communicator
├── Cargo.toml
└── src
    └── lib.rs

Можем да дефинираме подмодули в същия файл.

1 2 3 4 5 6 7 8 9
// src/lib.rs

mod network {
    fn connect() { /* ... */ }
}

mod client {
    fn init() { /* ... */ }
}
fn main() {}
// src/lib.rs

mod network {
    fn connect() { /* ... */ }
}

mod client {
    fn init() { /* ... */ }
}

Можем да дефинираме подмодули и в отделни файлове

1 2 3 4 5 6
communicator
├── Cargo.toml
└── src
    ├── client.rs
    ├── lib.rs
    └── network.rs
1 2 3
// src/lib.rs
mod network;
mod client;

1 2
// src/network.rs
fn connect() { /* ... */ }

1 2
// src/client.rs
fn init() { /* ... */ }

Можем да имаме няколко нива на подмодули.

1 2 3 4 5 6 7 8 9
// src/lib.rs

mod network {
    fn connect() { /* ... */ }

    mod tcp {
        /* ... */
    }
}
mod network {
    fn connect() { /* ... */ }

    mod tcp {
        /* ... */
    }
}
fn main() {}

Ако искаме да са в отделни файлове трябва да използваме директории

1 2 3 4 5 6 7 8
communicator
├── Cargo.toml
└── src
    ├── client.rs
    ├── lib.rs
    └── network
        ├── tcp.rs
        └── mod.rs

1 2 3 4 5 6 7 8
communicator
├── Cargo.toml
└── src
    ├── client.rs
    ├── lib.rs
    ├── network.rs
    └── network
        └── tcp.rs

Use

Всяко декларирано нещо (item) - структира, функция, модул, т.н. - си има канонично име.
То започва с ключовата дума crate, ако е дефинирано в нашия проект

Или с името на библиотеката (crate-а), ако е дефинирано извън проекта. Стандартната библиотека (std) се счита за външна библиотека:

Можем да използавме директно всичко декларирано в текущия модул.
Освен това външните библиотеки са автоматично импортирани и могат да се използват директно, без началните ::

1 2 3 4 5 6 7 8
mod client {
    fn load_config() { /* ... */ }

    fn init() {
        // това е `crate::client::load_config`
        load_config();
    }
}
mod client {
    fn load_config() { /* ... */ }

    fn init() {
        // това е `crate::client::load_config`
        load_config();
    }
}
fn main() {}

Неща декларирани в друг модул могат да се използват през пълното канонично име.
Също така може да се използва self::... или super::... за релативен път. self означава текущия модул, super означава родителския модул (едно ниво нагоре по йерархията).

1 2 3 4 5 6 7 8 9 10 11
mod network {
    pub fn connect() { /* ... */ }
}

mod client {
    fn init() {
        crate::network::connect();
        // или
        super::network::connect();
    }
}
fn main() {}
mod network {
    pub fn connect() { /* ... */ }
}

mod client {
    fn init() {
        crate::network::connect();
        // или
        super::network::connect();
    }
}

С ключовата дума use се импортират неща от друг модул.

1 2 3 4 5 6 7 8 9 10 11 12
mod network {
    pub fn connect() { /* ... */ }
}

mod client {
    use crate::network::connect;
    // или use super::network::connect;

    fn init() {
        connect();
    }
}
fn main() {}
mod network {
    pub fn connect() { /* ... */ }
}

mod client {
    use crate::network::connect;
    // или use super::network::connect;

    fn init() {
        connect();
    }
}

Могат да се импортират по няколко неща наведнъж - use crate::client::{something, some_other_thing};
Импортираните неща могат да се преименуват - use crate::client::something as some_other_thing;
Може да се импортира всичко от даден модул с glob import - use crate::client::*; - не се препоръчва, освен в специални случаи (тестове, prelude модули), защото прави кода по-труден за разбиране.

Достъп

В Rust има две нива на достъп - public и private.

Private неща могат да се достъпват само от модула, в който са дефинирани, или от подмодули на него.
Public неща могат да се достъпват и от модули по-нагоре в йерархията.

С други думи енкапсулацията в Rust става на ниво модули. В даден модул имаме директен достъп до всичко останало дефинирано в модула. Това е различно от повечето обектно-ориентирани езици, където енкапсулацията се случва на ниво класове.

Нивото на достъп по подразбиране е private.

1 2 3 4 5 6 7 8
mod db {
    struct User {
        username: String,
        email: String,
    }
}

use self::db::User;
error[E0603]: struct `User` is private --> src/bin/main_7142f7aa21b52896314d9ff4e7d0f754ca0da8d4.rs:10:15 | 10 | use self::db::User; | ^^^^ private struct | note: the struct `User` is defined here --> src/bin/main_7142f7aa21b52896314d9ff4e7d0f754ca0da8d4.rs:4:5 | 4 | struct User { | ^^^^^^^^^^^ For more information about this error, try `rustc --explain E0603`. error: could not compile `rust` (bin "main_7142f7aa21b52896314d9ff4e7d0f754ca0da8d4") due to 1 previous error
fn main() {}
mod db {
    struct User {
        username: String,
        email: String,
    }
}

use self::db::User;

За да може да се използва извън db модула, структурата трябва да се направи публично достъпна с ключовата дума pub.

1 2 3 4 5 6 7 8 9
mod db {
    pub struct User {
 // ^^^
        username: String,
        email: String,
    }
}

use self::db::User;
#![allow(unused_imports)]
fn main() {}
mod db {
    pub struct User {
 // ^^^
        username: String,
        email: String,
    }
}

use self::db::User;

Декларирана по този начин, структурата User е публично достъпна, но полетата са все още private. За да може да се достъпват полетата, трябва и те да се декларират като pub. Ако всички полета са публични, можем да използваме синтаксиса за конструиране на обект литерал от структурата.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
mod db {
    pub struct User {
        pub username: String,
        pub email: String,
     // ^^^
    }
}

use self::db::User;

fn main() {
    let user = User {
        username: "Иван Иванов".to_string(),
        email: "ivan@example.com".to_string(),
    };

    println!("{}", user.username);
    println!("{}", user.email);
}
Иван Иванов ivan@example.com
mod db {
    pub struct User {
        pub username: String,
        pub email: String,
     // ^^^
    }
}

use self::db::User;

fn main() {
    let user = User {
        username: "Иван Иванов".to_string(),
        email: "ivan@example.com".to_string(),
    };

    println!("{}", user.username);
    println!("{}", user.email);
}

Алтернативно, можем да оставим полетата private. Тогава ще трябва да добавим публични методи или функции за работа със структурата.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
mod db {
    #[derive(Debug)]
    pub struct User {
        username: String,
        email: String,
    }

    impl User {
        pub fn new(username: String, email: String) -> User {
     // ^^^
            User { username, email }
        }

        pub fn username(&self) -> &str {
     // ^^^
            &self.username
        }
    }

    pub fn make_user(username: String, email: String) -> User {
        // функцията има достъп до полетата на User, защото е декларирана
        // в същия модул
        User { username, email }
    }
}

use self::db::User;

fn main() {
    let user1 = User::new("Иван Иванов".to_string(), "ivan@example.com".to_string());
    let user2 = db::make_user("Стоян Стоянов".to_string(), "stoyan@example.com".to_string());

    println!("{:?}", user1);
    println!("{}", user2.username());
}
User { username: "Иван Иванов", email: "ivan@example.com" } Стоян Стоянов
mod db {
    #[derive(Debug)]
    pub struct User {
        username: String,
        email: String,
    }

    impl User {
        pub fn new(username: String, email: String) -> User {
     // ^^^
            User { username, email }
        }

        pub fn username(&self) -> &str {
     // ^^^
            &self.username
        }
    }

    pub fn make_user(username: String, email: String) -> User {
        // функцията има достъп до полетата на User, защото е декларирана
        // в същия модул
        User { username, email }
    }
}

use self::db::User;

fn main() {
    let user1 = User::new("Иван Иванов".to_string(), "ivan@example.com".to_string());
    let user2 = db::make_user("Стоян Стоянов".to_string(), "stoyan@example.com".to_string());

    println!("{:?}", user1);
    println!("{}", user2.username());
}

Конвенцията за името на getter функцията за поле foo е:

Забележете, че няма проблем да имаме поле и метод с едно и също име.

структури, енуми и други

Полетата на структурите са private по подразбиране и трябва да се означат с pub, за да се направят публични.
Полетата на структурите-кортежи (tupple structs) също са private по подразбиране
Вариантите на enum винаги са публични, но самия enum може да се дефинира като public или private.
Функции и асоциирани типове в trait също са публични, но самия trait може да се дефинира като public или private.

1 2 3 4 5 6 7 8 9 10
// съдържанието на токена не може да се достъпи от външния свят
pub struct SecretToken(u32);
// съдържанието на токена може да се достъпва или променя свободно
pub struct PublicToken(pub u32);

// енума може да се достъпва само от текущия модул
enum Foo { Variant1, Variant2(String) }
// енума може да се достъпва извън текущия модул.
// Всички варианти и стойностите на тези варианти са публично достъпни
pub enum Bar { Variant1, Variant2(String) }

pub use

use създава ново име, което е private за текущия модул.
pub use прави същото, но името е публично видимо извън модула. Това се нарича reexport и често се използва за предоставяне на достъп до предмети, които биха били private иначе.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
mod module {
    pub use submodule::A;
    use submodule::B;

    // private подмодул
    mod submodule {
        pub struct A {}
        pub struct B {}
    }
}

use module::A;               // ok
// use module::submodule::A; // грешка - `submodule` is private

// use module::B;            // грешка - `B` is private
// use module::submodule::B; // грешка - `submodule` private
#![allow(unused_imports)]
fn main() {}
mod module {
    pub use submodule::A;
    use submodule::B;

    // private подмодул
    mod submodule {
        pub struct A {}
        pub struct B {}
    }
}

use module::A;               // ok
// use module::submodule::A; // грешка - `submodule` is private

// use module::B;            // грешка - `B` is private
// use module::submodule::B; // грешка - `submodule` private

pub(crate)

Навсякъде, където можем да напишем pub, можем да напишем и pub(crate), pub(super) или pub(in some::path).
Тези варианти добавят допълнителни ограничения откъде може да се достъпва даденото нещо.

pub(crate) работи като pub в рамките на текущата библиотека (crate), но не позволява името да се достъпва извън това - от проекти, които използват текущия crate като външна библиотека.

pub(super) и pub(in some::path) ограничават видимостта до определен модул и на практика почти никога не се използват.

Тестове

TODO

Въпроси