rust
有自己的規則和約定用來組織模組,比如一個包最多可以有一個庫crate
,任意多個二進位制crate
、匯入資料夾內的模組的兩種約定方式... 知道這些約定,就可以快速瞭解rust
的模組系統。
先把一些術語說明一下:
包
是cargo的一個功能,當執行cargo new xxxx
的時候就是建立了一個包。crate
是二進位制或者庫專案。rust
約定在Cargo.toml
的同級目錄下包含src
目錄並且包含main.rs
檔案,就是與包同名的二進位制crate
,如果包目錄中包含src/lib.rs
,就是與包同名的庫crate
。包內可以有多crate
,多個crates
就是一個模組的樹形結構。如果一個包內同時包含src/main.rs
和src/lib.rs
,那麼他就有兩個crate
,如果想有多個二進位制crate
,rust
約定需要將檔案放在src/bin
目錄下,每個檔案就是一個單獨的crate
。crate根
用來描述如何構建crate
的檔案。比如src/main.rs
或者src/lib.rs
就是crate根
。crate根
檔案將由Cargo
傳遞給rustc
來實際構建庫或者二進位制專案。- 帶有
Cargo.toml
檔案的包用來描述如何構建crate
,一個包可以最多有一個庫crate
,任意多個二進位制crate
。
模組
模組以mod
開頭,下面建立了一個say
模組
mod say {
pub fn hello() {
println!("Hello, world!");
}
}
需要注意的是模組內,所有的項(函式、方法、結構體、列舉、模組和常量)預設都是私有的,可以用pub
將項變為公有,上面的程式碼裡pub fn hello()
就是把函式hello()
變為公有的。
子模組可以通過super
訪問父模組中所有的程式碼,包括私有程式碼。但是父模組中的程式碼不能訪問子模組中的私有程式碼。
mod say {
pub fn hello() {
println!("Hello, world!");
}
fn hello_2() {
println!("hello")
}
pub mod hi {
pub fn hi_1() {
super::hello_2();
}
pub fn hi_2() {
println!("hi there");
}
}
}
同一檔案內的模組
同一檔案內的模組,最外層的mod say
不用設定為pub
就可以訪問,但是mod say
下面的要設定成pub
才可以訪問。
fn main() {
// 相對路徑
say::hello();
// 絕對路徑呼叫
crate::say::hello();
say::hi::hi_1();
say::hi::hi_2();
}
mod say {
pub fn hello() {
println!("Hello, world!");
}
fn hello_2() {
println!("hello")
}
pub mod hi {
pub fn hi_1() {
super::hello_2();
}
pub fn hi_2() {
println!("hi there");
}
}
}
呼叫模組內的方法,可以使用絕對路徑以crate
開頭,也就是從crate根
開始查詢,say
模組定義在crate根 src/main.rs
中,所以就可以這麼呼叫crate::say::hello();
絕對路徑類似於Shell
中使用/
從檔案系統根開始查詢檔案。
相對路徑以模組名開始say
,他定義於main()
函式相同的模組中,類似Shell
在當前目錄開始查詢指定檔案say/hello
。
mod hi
是一個巢狀模組,使用時要寫比較長say::hi::hi_2();
,可以使用use
將名稱引入作用域。
use crate::say::hi;
fn main() {
// 相對路徑
say::hello();
// 絕對路徑呼叫
crate::say::hello();
// 不使用 use
say::hi::hi_1();
say::hi::hi_2();
// 使用 use 後就可以這麼呼叫
hi::hi_1();
}
使用pub use
重匯出名稱
不同的模組之前使用use
引入,預設也是私有的。如果希望呼叫的模組內use
引用的模組,就要用pub
公開,也叫重匯出
fn main() {
// 重匯出名稱
people::hi::hi_1();
people::hello();
// 但是不能
// people::say::hello();
}
mod say {
pub fn hello() {
println!("Hello, world!");
}
fn hello_2() {
println!("hello")
}
pub mod hi {
pub fn hi_1() {
super::hello_2();
}
pub fn hi_2() {
println!("hi there");
}
}
}
mod people {
// 重匯出名稱
pub use crate::say::hi;
use crate::say;
pub fn hello() {
say::hello();
}
}
如果想都匯出自己和嵌入的指定包可以用self
,例mod people_2
把模組people
和巢狀模組info
全部匯出來了。
use crate::say::hi;
fn main() {
// 相對路徑
say::hello();
// 絕對路徑呼叫
crate::say::hello();
// 不使用 use
say::hi::hi_1();
say::hi::hi_2();
// 使用 use 後就可以這麼呼叫
hi::hi_1();
// 重匯出名稱
people::hi::hi_1();
people::hello();
// 但是不能
// people::say::hello();
people_2::people::hello();
people_2::info::name();
}
mod say {
pub fn hello() {
println!("Hello, world!");
}
fn hello_2() {
println!("hello")
}
pub mod hi {
pub fn hi_1() {
super::hello_2();
}
pub fn hi_2() {
println!("hi there");
}
}
}
pub mod people {
// 重匯出名稱
pub use crate::say::hi;
use crate::say;
pub fn hello() {
say::hello();
}
pub mod info {
pub fn name() {
println!("zhangsang");
}
}
}
mod people_2 {
// 重匯出名稱
pub use crate::people::{self, info};
pub fn hello() {
info::name();
}
}
不同資料夾的引用
方式一
看一下目錄結構:
rust
的約定,在目錄下使用mod.rs
將模組匯出。
看一下user.rs的程式碼:
#[derive(Debug)]
pub struct User {
name: String,
age: i32
}
impl User {
pub fn new_user(name: String, age: i32) -> User {
User{
name,
age
}
}
pub fn name(&self) -> &str {
&self.name
}
}
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
然後在mod.rs
裡匯出:
pub mod user;
在main.rs
呼叫
mod user_info;
use user_info::user::User;
fn main() {
let u1 = User::new_user(String::from("tom"), 5);
println!("user name: {}", u1.name());
println!("1+2: {}", user_info::user::add(1, 2));
}
方式二
看一下目錄結構
和上面的不同之前是。這種方式是user_info
目錄裡沒有mod.rs
,但是在外面有一個user_info.rs
在user_info.rs
中使用pub mod user;
是告訴Rust
在另一個與模組同名的資料夾內(user_info資料夾)內載入模組user
。這也是rust
的一個約定,但比較推薦用上面的方式。
程式碼和上面是一樣的。
user.rs
#[derive(Debug)]
pub struct User {
name: String,
age: i32
}
impl User {
pub fn new_user(name: String, age: i32) -> User {
User{
name,
age
}
}
pub fn name(&self) -> &str {
&self.name
}
}
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
user_info.rs裡匯出
pub mod user;
在main.rs
呼叫
mod user_info;
use user_info::user::User;
fn main() {
let u1 = User::new_user(String::from("tom"), 5);
println!("user name: {}", u1.name());
println!("1+2: {}", user_info::user::add(1, 2));
}
使用外部包
使用外部包,一般就是從crates.io
下載,當然也可以自己指寫下載地點,或者使用我們本地的庫,或者自建的的倉庫。
一般方式
在Cargo.toml
的dependencies
下寫要匯入的依賴庫
[dependencies]
regex = "0.1.41"
執行cargo build
會從crates.io
下載依賴庫。
使用的時候,直接使用use
引入
use regex::Regex;
fn main() {
let re = Regex::new(r"^\d{4}-\d{2}-\d{2}$").unwrap();
println!("Did our date match? {}", re.is_match("2014-01-01"));
}
指定庫地址
除了crates.io
下載依賴庫,也可以自己指定地址,也可以指定branch
tag
commit
,比如下面這個
[dependencies]
# 可以和包不同名,也可以同名
my_rust_lib_1={package="my_lib_1",git="ssh://git@github.com/lpxxn/my_rust_lib_1.git",tag="v0.0.2"}
就是從github.com/lpxxn/my_rust_lib_1
上下載包。也可以使用https
my_rust_lib_1={package="my_lib_1",git="https://github.com/lpxxn/my_rust_lib_1.git",branch="master"}
執行cargo build
就會自動下載,使用的時候也是一樣的。
use my_rust_lib_1;
fn main() {
println!("Hello, world!");
println!("{}", my_rust_lib_1::add(1, 2));
let u = my_rust_lib_1::User::new_user(String::from("tom"), 2);
println!("user: {:#?}", u);
}
使用本地的庫
我們新建一個二進位制庫專案
cargo new pkg_demo_3
然後在pkg_demo_3
內建一個庫專案
cargo new --lib utils
然後就可以在 utils
裡寫我們的庫程式碼了
看一下現在的目錄結構
在utils
庫的user.rs
裡還是我們上面的程式碼
#[derive(Debug)]
pub struct User {
name: String,
age: i32
}
impl User {
pub fn new_user(name: String, age: i32) -> User {
User{
name,
age
}
}
pub fn name(&self) -> &str {
&self.name
}
}
pub fn add(x: i32, y: i32) -> i32 {
x + y
}
在lib.rs
裡對user
模組匯出
pub mod user;
pub use user::User;
然後在我們的二進位制庫的Cargo.toml
引入庫
[dependencies]
utils = { path = "utils", version = "0.1.0" }
path
就是庫專案的路徑
main.rs
使用use
引入就可以使用了
use utils::User;
fn main() {
let u = User::new_user(String::from("tom"), 5);
println!("user: {:#?}", u);
}
自建私有庫
除了crates.io
也可以自建registrie
。這個有時間再重新寫一篇帖子單獨說,可以先看一下官方文件。
官方文件:registrie
依賴官方文件
帖子 github 程式碼地址