Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rust之模块化 #50

Open
BruceChen7 opened this issue Mar 9, 2023 · 0 comments
Open

rust之模块化 #50

BruceChen7 opened this issue Mar 9, 2023 · 0 comments

Comments

@BruceChen7
Copy link
Owner

BruceChen7 commented Mar 9, 2023

参考资料

cargo 命令

  • cargo new crate_name 默认编译出 bin 文件
  • cargo new crate_name --lib
  • cargo build --release

自定义 cargo build

  • cargo 主要有两种 build 模式,
    • 一种是 dev,
    • 一种是 release
  • 在开发的过程中,使用的是 dev 模式,发布使用 release 模式。
  • 能中 cargo.toml 中指定 build 中选项。
    [profile.dev]
    opt-level = 0
    [profile.release]
    opt-level = 3
  • 这里指定了 cargo build 优化的级别

描述项目信息

[package]
name = "guessing_game"
version = "0.1.0"
edition = "2018"
description = "A fun game where you guess what number the computer has chosen."
license = "MIT OR Apache-2.0"

[dependencies]
  • crate 是依赖树的顶点
  • crate 是编译的基本单元

cargo feature

  • 对编译的 lib 进行条件编译,或者可选依赖。
  • 如果是条件编译,加上 flag, --features。cargo.toml 中的 features section
  • 使用cfg 表达式来体条件编译代码
  • 在定义一个 feature 的的时候,能打开别的 feature。
  • 有一个原则,A 和 B 都依赖 C crate,如果 A 和 B 各自依赖的 feature,如果如果不能共存(也就是开了一个 feature,另一个 feature 就用不了),那么 cargo 将直接编译错误。
    • cargo 在编译的时候,会将 A 和 B 各自依赖的 feature 进行编译
  • 下面定义了一个 derive 的 feature,其依赖 sync crate
  • 主要减少编译的时间
[package]
name = "foo"

[features]
derive = ["syn"]

[dependencies]
syn = { version = "1", optional = true }

默认 feature

[features]
default = ["ico", "webp"] // 默认的两个feature
bmp = []
png = []
ico = ["bmp", "png"]
webp = []
  • 当 pakcage 编译时,使用默认的 feature 将会被编译, [] 指定的是依赖,包括 feature 和 crates

条件依赖

[dependencies]
gif = { version = "0.11.1", optional = true }

// rust code
cfg(feature = "gif")
  • 在编译的时候,加上--feature gif,来使这个 gif 的 feature 能够使用上。当然,也能显示的添加 feature
  • 这么个写法意味着 gif 这个依赖是可选的,这行代码意味着,你能写下如下的代码:
[dependencies]
ravif = { version = "0.6.3", optional = true }
rgb = { version = "0.8.25", optional = true }

[features]
avif = ["ravif", "rgb"]
  • 这样,feature avif 打开后,就依赖 ravif 和 rgb.

project, package, crate, module 的区别

  • project,package,crate,module 这些概念感觉相似。
  • 一个package/project能包含多个 binary crates一个或者零个 library binary
  • 一个 crate 能包含多个 module
  • 能认为 package 就是一个 project,一个 crate 就是一个暴露给外界的逻辑单元,一个 module 就是一个小问题的解法
  • project 是通过 cargo new --bin project_name 来生成的。
  • cargo new --lib project_name 生成带 library crate 的 project)
  • crate 能认为是从一个类似的 root .rs 文件开始的 rust 模块层次结构,这个 root 的.rs 文件通常类似于 lib.rs 或 main.rs
  • 能使用类似于#![feature]的属性
  • img
  • 上图:
    • 包含了一个 library crate,还有两个 binary crates:一个是 main.rs(这个名字是随便起的,能是其他的名字),一个是 main2.rs。
    • project 里面有 lib.rs 说明这个 project 是一个 library crate,这个 library 的名字是 project 的名字。main.rs/main2.rs 都能直接使用这个 library crate。
    • 能认为 bin 文件夹里面是单独的 crate,它们默认导入了这个 library crate。

module tree

  • project 里面的 module tree 必须显示地指明
  • 编译器看到的 project 的 module tree 仅仅含有main.rs 或者 lib.rs,又叫做 crate root,就是 crate 的根节点。代码 crate::foo 的 crate 指代的就是这个 crate root。
  • 这也就是经常在 main.rs/lib.rs 里面看到许多 mod xxx 的原因
  • 它们的存在就是为了将 project 里面的 modules 加到这个 crate 里面。比如在 main.rs 里面看到 mod channel,就是将 module channel 加进 crate 的 module tree 来。
  • mod foo;是声明模块,而./foo.rs 或者./foo/mod.rs 定义模块,而 mod foo {... } 既声明又定义模块。
  • use foo;是将一个模块加进当前的 scope
  • module system 最重要的提炼为下面三点
    • foo.rs 或者 foo/mod.rs, foo {... } 定义模块 foo。
    • 子模块必须在父模块在**声明 (**mod child),不然它们就不会存在
    • 子模块的内容要么显示地内联定义在父模块里面,如 mod {... },
    • 要么定义在文件为./child.rs 或者./child/mod.rs 里面
    • 当前文件夹与父模块同名的文件夹。

项目结构

  • 这是推荐的项目组织
    ├── Cargo.lock
    ├── Cargo.toml
    ├── benches
    │   └── large-input.rs
    ├── examples
    │   └── simple.rs
    ├── src
    │   ├── bin
    │   │   └── another_executable.rs
    │   ├── lib.rs
    │   └── main.rs
    └── tests
        └── some-integration-tests.rs
    

代码组织

  • 函数
  • mod, Can be mapped to a
    • Inline module
    • File
    • Directory hierarchy

crates can be mappe to a

  • lib.rs file on the same executable crate
  • Dependency crate specified on Cargo.toml

workspace

  • Helps to manage multiple crates as a single project

例子

  • self 关键字用来访问同一个 mod 下的函数
  • super 用来访问parent mod 下的函数,super 能访问在一个 mod 下访问 root functions
  • 使用 super::super::foo 来访问祖父模块中的函数 foo1。
    // main.rs
    fn greet() -> String {
    
    }
    mod greetings {
        pub fn hello() {
            println!("hello world");
        }
    }
    
    // calling private function in the same mod
    // . Calling private functions of the same module
    fn main() {
      phrases::greet();
    }
    
    mod phrases {
      pub fn greet() {
        hello(); //or self::hello();
      }
    
      fn hello() {
        println!("Hello, world!");
      }
    }
    
    // 02. Calling private functions of the parent module
    fn main() {
      phrases::greetings::hello();
    }
    
    mod phrases {
      fn private_fn() {
        println!("Hello, world!");
      }
    
      pub mod greetings {
        pub fn hello() {
          super::private_fn();
        }
      }
    }
    // main.rs
    fn main() {
      greetings::hello();
    }
    
    fn hello() {
      println!("Hello, world!");
    }
    
    mod greetings {
      pub fn hello() {
        super::hello();
      }
    }

在不同文件下访问 mod

// ↳ main.rs
mod greetings; // import greetings module

fn main() {
  greetings::hello();
}

// ↳ greetings.rs
// ⭐️ no need to wrap the code with a mod declaration. File itself acts as a module.
pub fn hello() { // function has to be public to access from outside
  println!("Hello, world!");
}

// 如果在 mod 下嵌套的其他的 mod,那么该 mod 必须声明为 pub
// ↳ main.rs
mod phrases;

fn main() {
  phrases::greetings::hello();
}

// ↳ phrases.rs
// mod 嵌套
pub mod greetings { // ⭐️ module has to be public to access from outside
  pub fn hello() {
    println!("Hello, world!");
  }
}
  • 文件名作为名字空间的一部分

在不同目录中模块

// ↳ main.rs
mod greetings;

fn main() {
  // 注意名字的写法
  greetings::hello();
}

// ↳ greetings/mod.rs
pub fn hello() { // ⭐️ function has to be public to access from outside
  println!("Hello, world!");
}
  • mod.rs in the directory module root is the entry point to the directory module.
  • All other files in that directory root, act as sub-modules of the directory module.
  • 在上面的例子中目录作为名字空间的一部分
// ↳ main.rs
mod phrases;

fn main() {
   // 在 mod 中声明了新的 mod,那么这部分也成为名字空间的一部分
  phrases::greetings::hello();
}

// ↳ phrases/mod.rs
pub mod greetings { // ⭐️ module has to be public to access from outside
  pub fn hello() {
    println!("Hello, world!");
  }
}

在 mod.rs 中访问子 mod

/ ↳ main.rs
mod phrases;

fn main() {
  phrases::hello()
}

// ↳ phrases/mod.rs
mod greetings;

pub fn hello() {
  greetings::hello()
}

// ↳ phrases/greetings.rs
pub fn hello() {
  println!("Hello, world!");
}
  • 上面的例子,如果要直接访问 greetings 中的 hello,那么就应该在 phrase/mod.rs 中声明 greetings 为 pub
    // ↳ main.rs
    mod phrases;
    
    fn main() {
        phrases::greetings::hello();
    }
    
    // ↳ phrases/mod.rs
    pub mod greetings;  // ⭐️ `pub mod` instead `mod`
    
    // ↳ phrases/greetings.rs
    pub fn hello() {
      println!("Hello, world!");
    }
  • 子目录的 mod.rs 是子模块的访问入口。
  • 由它来控制其他子模块的访问。
  • 注意不能直接在 main.rs 直接使用mod phrases::greetings
  • 但是能引入该模块的的 phrases::greetings::hello 这个函数到 phrases 模块下,这样就能在 main.rs 直接使用 phrases::hello()
    // ↳ phrases/greetings.rs
    pub fn hello() {
      println!("Hello, world!");
    }
    
    // ↳ phrases/mod.rs
    pub mod greetings;
    
    pub use self::greetings::hello; //re-export greetings::hello to phrases
    
    // ↳ main.rs
    mod phrases;
    
    fn main() {
        phrases::hello(); //you can call hello() directly from phrases
    }

crates

  • 概念上有点类似 package,在 rust 中,一个 crate 会产生二进制文件,或者是 lib。
  • src/main.rs是bin文件的入口,src/lib.rs则是库的入口。

lib.rs 和 main.rs 在一块使用

  • 对于二进制文件而言,这种方式比较常见
    // # Think we run
    cargo new --bin greetings
    touch greetings/src/lib.rs
    
    // # It generates,
    .
    ├── Cargo.toml
    └── src
       ├── lib.rs
       └── main.rs
    
    // # Think we modify following files
    // 01. greetings/src/lib.rs
    pub fn hello() {
        println!("Hello, world!");
    }
    
    // 02. greetings/src/main.rs
    extern crate greetings;
    
    fn main() {
        greetings::hello();
    }

在 cargo.toml 声明的依赖 crates

  • 多个 cargo 嵌套

    // # Think we run
    cargo new --bin phrases
    cargo new phrases/greetings
    
    // # It generates,
    .
    ├── Cargo.toml
    ├── greetings
    │  ├── Cargo.toml
    │  └── src
    │     └── lib.rs
    └── src
       └── main.rs
    
    // # Think we modify following files
    
    // 01. phrases/Cargo.toml
    [package]
    name = "phrases"
    version = "0.1.0"
    authors = ["Dumindu Rajarathna"]
    
    [dependencies]
    // 注意这里的依赖
    greetings = { path = "greetings" }
    
    // 02. phrases/greetings/src/lib.rs
    pub fn hello() {
        println!("Hello, world!");
    }
    
    // 03. phrases/src/main.rs
    // 引入 crates
    extern crate greetings;
    
    fn main() {
        greetings::hello();
    }
  • 引入 git crates

    // -- Cargo.toml --
    [dependencies]
    
    // 01. Get the latest commit on the master branch
    rocket = { git = "https://github.com/SergioBenitez/Rocket" }
    
    // 02. Get the latest commit of a specific branch
    rocket = { git = "https://github.com/SergioBenitez/Rocket", branch = "v0.3" }
    
    // 03. Get a specific tag
    rocket = { git = "https://github.com/SergioBenitez/Rocket", tag = "v0.3.2" }
    
    // 04. Get a specific revision (on master or any branch, according to rev)
    rocket = { git = "https://github.com/SergioBenitez/Rocket", rev = "8183f636305cef4adaa9525506c33cbea72d1745" }

crate metadata

  • 通过写相关的信息,来将 project 中的补充完整
    [package]
    name = "atuin"
    version = "13.0.0"
    authors = ["Ellie Huxtable <ellie@elliehuxtable.com>"]
    edition = "2021"
    rust-version = "1.59"
    license = "MIT"
    description = "atuin - magical shell history"
    homepage = "https://atuin.sh"
    repository = "https://github.com/ellie/atuin"
    readme = "README.md"
  • readme 用来指定文档
  • include/exclude 用来指定不想发布到 crates.io 的脚本等信息

profile section

  • 包含了 3 个字段
  • opt-level
    • 有 0 到 3 个能取的值,取的值越大,代码优化的越厉害
    • 也能设置成 s,表示考虑 binary size,越小越好
  • codegen-units
    • 表示的是 compile-time performance
    • 将代码编译拆分为更小的 chunk 是一种方法,能帮助减少项目的总编译时间。
    • 能用来优化编译的时间
  • lto
    • link-time optimization
    • 能用来减少 binary size
[profile.release]
codegen-units = 3
lto = true

发布 crate 到 crates.io 中,文档的编写

  • 写的库一般需要写文档,才能让开发者更好的使用,使用///在代码中引入文档。
  • 这种方式引入了整个 crate 的文档。使用cargo doc --open,在本地打开文档,样子如下:
// src/lib.rs
/// Adds one to the number given.
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
  • 文档中能使用markdown的格式,甚至能运行文档中的测试的例子:
 Doc-tests my_crate

running 1 test
test src/lib.rs - add_one (line 5) ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s

contained items文档编写

  • 能使用//!来编写item的文档,但是要注意的是这种方式,后面没有任何代码,这种方式不是用来注释后面的items,而是对上面的item进行注释。注意下面的注释方式带来的效果。
//! # My Crate
//!
//! `my_crate` is a collection of utilities to make performing certain
//! calculations more convenient.

/// Adds one to the number given.
// --snip--
///
/// # Examples
///
/// ```
/// let arg = 5;
/// let answer = my_crate::add_one(arg);
///
/// assert_eq!(6, answer);
/// ```
pub fn add_one(x: i32) -> i32 {
    x + 1
}
  • img

workspace

  • 由一个和多个crate组成的集合
  • member中写的都是目录,每个子目录下都有自己的Cargo.toml
  • 一般member都共享名字的前缀
    • 比如tokio, tokio-test, tokio-macros
    • crates name不必和member的值相同,但一般一样
// # Think we run
mkdir greetings
touch greetings/Cargo.toml
cargo new greetings/lib
cargo new --bin greetings/examples/hello

// # That generates,
.
├── Cargo.toml
├── examples
│  └── hello
│     ├── Cargo.toml
│     └── src
│        └── main.rs
└── lib
   ├── Cargo.toml
   └── src
      └── lib.rs

// # Think we modify following files

// 01. greetings/Cargo.toml
[workspace]
members = [
    "lib",
    "examples/hello"
]

// 02.1 greetings/lib/Cargo.toml
[package]
name = "greetings"
version = "0.1.0"
authors = ["Dumindu Madunuwan"]

[dependencies]

// 02.2 greetings/lib/src/lib.rs
pub fn hello() {
    println!("Hello, world!");
}

// 03.1 greetings/examples/hello/Cargo.toml
[package]
name = "hello"
version = "0.1.0"
authors = ["Dumindu Madunuwan"]

[dependencies]
// 在 crates 中学会依赖
greetings = { path = "../../lib" }

// 03.2 greetings/examples/hello/src/main.rs
extern crate greetings;

fn main() {
    greetings::hello();
}

use

  • 类似于Cpp中的typedef,将一个元素的全路径名称绑定到一个新的名称

    // -- Initial code without use keyword --
    mod phrases {
      pub mod greetings {
        pub fn hello() {
          println!("Hello, world!");
        }
      }
    }
    
    fn main() {
      phrases::greetings::hello(); // using full path
    }
    
    
    // -- Usage of use keyword --
    // 01. create alias for module
    use phrases::greetings;
    fn main() {
      greetings::hello();
    }
    
    // 02. create alias for module elements
    use phrases::greetings::hello;
    fn main() {
      hello();
    }
    
    // 03. customize names with as keyword
    use phrases::greetings::hello as greet;
    fn main() {
      greet();
    }
  • use keyword is used to import the elements of other crates including std

// -- 01. importing elements --
use std::fs::File;

fn main() {
    File::create("empty.txt").expect("Can not create the file!");
}


// -- 02. importing module and elements--
std::fs::{self, File} //use std::fs; use std::fs::File;

fn main() {
    fs::create_dir("some_dir").expect("Can not create the directry!");
    File::create("some_dir/empty.txt").expect("Can not create the file!");
}


// -- 03. importing multiple elements
use std::fs::File;
use std::io::{BufReader, BufRead}; //use std::io::BufReader; use std::io::BufRead;

fn main() {
    let file = File::open("src/hello.txt").expect("file not found");
    let buf_reader = BufReader::new(file);

    for line in buf_reader.lines() {
        println!("{}", line.unwrap());
    }
}

对于 std crates,不需要使用extern crate std;

pub use

  • 当创建一个模块,you can export things from another module into your module.
  • So after that they can be accessed directly from your module. This is called re-exporting
// ↳ main.rs
mod phrases;

fn main() {
    phrases::hello(); //not directly map
}

// ↳ phrases/mod.rs
pub mod greetings;

pub use self::greetings::hello; //re-export greetings::hello to phrases

// ↳ phrases/greetings.rs
pub fn hello() {
  println!("Hello, world!");
}

注释

// Line comments
/* Block comments */
  • //! to write crate and module-level documentation,
  • Always avoid block comments, Use line comments instead.

#type/rust #public

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant