Модули, документация, тестване
Модули
Начин да си организираме кода в отделни namespaces.
Обикновенно йерархията от модули съвпада с йерархията на файловете на проекта ни.
Йерархия
Проекта ни съдържа един главен файл, който е и главния модул:
- src/main.rs - ако проекта е изпълнима програма (binary)
- src/lib.rs - ако проекта е библиотека (library)
Типа на проекта се задава, когато изпълним cargo new или cargo init:
cargo new project-name --libcargo new project-name --bincargo new project-name- по подразбиране се създава binary
При създаване на нов проект получаваме следната йерахия от файлове:
$ cargo new communicator --lib
$ tree communicator
communicator
├── Cargo.toml
└── src
└── lib.rs
Можем да дефинираме подмодули в същия файл.
// src/lib.rs
mod network {
fn connect() { /* ... */ }
}
mod client {
fn init() { /* ... */ }
}
fn main() {}
// src/lib.rs
mod network {
fn connect() { /* ... */ }
}
mod client {
fn init() { /* ... */ }
}
Можем да дефинираме подмодули и в отделни файлове
- декларираме подмодулите с
mod MOD_NAME; - компилатора търси файл
./MOD_NAME.rsили./MOD_NAME/mod.rsспрямо текущия файл
communicator
├── Cargo.toml
└── src
├── client.rs
├── lib.rs
└── network.rs
// src/lib.rs
mod network;
mod client;
// src/network.rs
fn connect() { /* ... */ }
// src/client.rs
fn init() { /* ... */ }
Можем да имаме няколко нива на подмодули.
// src/lib.rs
mod network {
fn connect() { /* ... */ }
mod tcp {
/* ... */
}
}
mod network {
fn connect() { /* ... */ }
mod tcp {
/* ... */
}
}
fn main() {}
Ако искаме да са в отделни файлове трябва да използваме директории
- модула
::network::tcpе във файлsrc/network/tcp.rs(технически можем да го сложим и вsrc/network/tcp/mod.rs, но няма смисъл) - модула
::networkможе да е вsrc/network.rsилиsrc/network/mod.rs- няма правилно и грешно, и двете се срещат, изберете си конвенция и я следвайте:src/network/mod.rs- цялото съдържание на модула е видимо на едно място в йерархията (ако използвате някакъв вид file tree view на редактора ви), но ще имате множество файлове mod.rs в проектаsrc/network.rs- обратното на горното
communicator
├── Cargo.toml
└── src
├── client.rs
├── lib.rs
└── network
├── tcp.rs
└── mod.rs
communicator
├── Cargo.toml
└── src
├── client.rs
├── lib.rs
├── network.rs
└── network
└── tcp.rs
Use
Всяко декларирано нещо (item) - структира, функция, модул, т.н. - си има канонично име.
То започва с ключовата дума crate, ако е дефинирано в нашия проект
crate::some_module::some_item
Или с името на библиотеката (crate-а), ако е дефинирано извън проекта. Стандартната библиотека (std) се счита за външна библиотека:
::std::vec::Vec::external_crate::some_module::some_item
Можем да използавме директно всичко декларирано в текущия модул.
Освен това външните библиотеки са автоматично импортирани и могат да се използват директно, без началните ::
std::vec::Vecexternal_crate::some_module::some_item
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 означава родителския модул (едно ниво нагоре по йерархията).
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 се импортират неща от друг модул.
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.
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.
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. Ако всички полета са публични, можем да използваме синтаксиса за конструиране на обект литерал от структурата.
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. Тогава ще трябва да добавим публични методи или функции за работа със структурата.
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 е:
foo()за read-only getter - връща&Tfoo_mut()за read-write getter - връща&mut Tset_foo()за setter - приемаT
Забележете, че няма проблем да имаме поле и метод с едно и също име.
структури, енуми и други
Полетата на структурите са private по подразбиране и трябва да се означат с pub, за да се направят публични.
Полетата на структурите-кортежи (tupple structs) също са private по подразбиране
Вариантите на enum винаги са публични, но самия enum може да се дефинира като public или private.
Функции и асоциирани типове в trait също са публични, но самия trait може да се дефинира като public или private.
// съдържанието на токена не може да се достъпи от външния свят
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 иначе.
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