crate

Rust 程序由 crate(板条箱)组成。每个 crate 都是既完整又内聚的单元,包括单个库或可执行程序的所有源代码,以及任何相关的测试、示例、工具、配置和其他杂项。

版本

在 Cargo.toml [package] 部分中修改 edition 表示自己用的是什么版本的 Rust

配置文件

你可以在 Cargo.toml 文件中放置几个配置设定区段,这些设定会影响 cargo 生成的 rustc 命令行.

可以查看附件 Cargo.toml 的配置设定区段对照表

添加编译 debug

[profile.release]
debug = true  # 在release构建中启用debug符号

模块

crate 是关于项目间代码共享的,而模块是关于项目内代码组织的。

例子 1

mod spores {
    use cells::;

    /// 由成年蕨类植物产生的细胞。作为蕨类植物生命周期的一部分,细胞会随风
    /// 传播。一个孢子会长成原叶体(一个完整的独立有机体,最大直径达5毫米),
    /// 原叶体产生的受精卵会长成新的蕨类植物(植物的性别很复杂)
    pub struct Spore {
        ...
    }

    /// 模拟减数分裂产生孢子
    pub fn produce_spore(factory: &mut Sporangium) -> Spore {
        ...
    }

    /// 提取特定孢子中的基因
    pub(crate) fn genes(spore: &Spore) -> Vec<Gene> {
        ...
    }

    /// 混合基因以准备减数分裂(细胞分裂间期的一部分)
    fn recombine(parent: &mut Cell) {
        ...
    }

    ...
}
  • pub 关键字会使某个语法项声明为公共项,这样它就可以从模块外部访问了。
  • pub(crate)关键字可以在这个 crate 中的任何地方使用,但不会作为外部接口的一部分公开。它不能被其他 crate 使用,也不会出现在这个 crate 的文档中。
  • 未标记为 pub 的内容都是私有的,只能在定义它的模块及其任意子模块中使用
  • pub(super),让语法项只对其父模块可见。

嵌套模块

mod plant_structures {
    pub mod roots {
        ...
    }
    pub mod stems {
        ...
    }
    pub mod leaves {
        ...
    }
}

你希望嵌套模块中的语法项对其他 crate 可见,请务必将它和它所在的模块标记为公开的。

通过这种方式,我们可以写出一个完整的程序,把大量代码和完整的模块层次结构以我们想要的任何方式关联起来,并放在同一个源文件中。

例子 2 用一个文件写一个项目

car.rs

pub mod car {
    pub mod engine {
        pub struct Engine {
            name: String,
        }

        impl Engine {
            pub fn new(name: String) -> Self {
                Engine { name }
            }

            pub fn on(&self) {
                println!("engine {} is run", self.name)
            }
        }
    }
    mod gearbox {}
    mod wheel {}

    mod frame {}

    mod start {}
}

main.rs


mod car;


fn main() {
    let engine = car::car::engine::Engine::new("EA8888".to_string());
    engine.on()
}

//engine EA8888 is run

单独文件中的模块

例子 3

文件结构

src
├── car
│   └── engine.rs
├── car.rs
└── main.rs

car/engine.rs

pub struct Engine {
    name: String,
    power: u8,
}

impl Engine {
    pub fn new(name: String, power: u8) -> Engine {
        Engine {
            name,
            power,
        }
    }

    pub fn start(&self) {
        println!("Engine {} started power is {}", self.name, self.power);
    }

    pub fn stop(&self) {
        println!("Engine {} stopped {} ", self.name, self.power);
    }
}

car.rs

pub mod engine;

main.rs

mod car;
fn main() {
   let benz =  car::engine::Engine::new("V8".to_string(), 5);
    benz.start();
    benz.stop();
}

路径与导入

刚刚的列子 我们使用了 car::engine::Engine::new("V8".to_string(), 5);很麻烦也很长这时我们就可以采用 use 来简化我们的开发

mod car;
use car::engine::Engine;
fn main() {
   let benz =  Engine::new("V8".to_string(), 5);
    benz.start();
    benz.stop();
}

可以一次性导入多个模块

use std::collections::;  // 同时导入两个模块

use std::fs::; // 同时导入`std::fs`和`std::fs::File`

use std::io::prelude::*;  // 导入所有语法项

设置不同的名称

use std::io::Result as IOResult;

// 这个返回类型只是`std::io::Result<()>`的另一种写法:
fn save_spore(spore: &Spore) -> IOResult<()>
...

假如你想看到父的模块你可以使用 super 来进行引入

// proteins/synthesis.rs
use super::AminoAcid;  // 从父模块显式导入

pub fn synthesize(seq: &[AminoAcid])  // 正确
    ...
  • super 父模块看到
  • self 自己模块看到
  • crate 当前模块
  • :: 被称为绝对路径

标准库预导入

Rust 的行为就好像每个模块(包括根模块)都用以下导入语句开头一样.

use std::prelude::v1::*;

公开导入

这意味着 Leaf 和 Root 是 plant_structures 模块的公共语法项

// 在plant_structures/mod.rs中
...
pub use self::leaves::Leaf;
pub use self::roots::Root;

公开结构体字段

pub struct Fern {
    pub roots: RootSet,
    pub stems: StemSet
}

静态变量与常量

除了函数、类型和嵌套模块,模块还可以定义常量和静态变量。

关键字 const 用于引入常量,其语法和 let 一样,只是它可以标记为 pub,并且必须写明类型。此外,常量的命名规约是 UPPERCASE_NAMES:

pub const ROOM_TEMPERATURE: f64 = 20.0;  // 摄氏度

static 关键字引入了一个静态语法项,跟常量几乎是一回事:

pub static ROOM_TEMPERATURE: f64 = 68.0;  // 华氏度

将程序变成库

第一步是将现有的项目分为两部分:一个库 crate,其中包含所有共享代码;一个可执行文件,其中只包含你现在的命令行程序才需要的代码。

Cargo.toml

[package]
name = "rust_tutorials"
version = "0.1.0"
authors = ["You <you@example.com>"]
edition = "2021"
  1. 将文件 src/main.rs 重命名为 src/lib.rs。
  2. 将 pub 关键字添加到 src/lib.rs 中的语法项上,这些语法项将成为这个库的公共特性。
  3. 将 main 函数移动到某个临时文件中。(我们暂时不同管它。)
  4. cargo build

默认设定下,cargo build 会查看源目录中的文件并根据文件名确定要构建的内容。当它发现存在文件 src/lib.rs 时,就知道要构建一个库。

src/lib.rs 中的代码构成了库的根模块。其他使用这个库的 crate 只能访问这个根模块的公共语法项。

src/bin 目录

让原来的命令行程序 rust_tutorials 再次运行起来也很简单,因为 Cargo 对和库位于同一个 crate 中的小型程序有一些内置支持。

将自己的程序和库放在同一个 crate 中。请将下面这段代码放入名为 src/bin/benz.rs 的文件中:

use rust_tutorials::Engine;  //rust_tutorials 就是上方 Cargo.toml 中的 name
fn main() {
    let benz = Engine::new("V8".to_string(), 8);
    benz.start();
    benz.stop();
}

既然 rust_tutorials 现在是一个库,那么我们也就多了一种选择:把这个程序放在它自己的独立项目中,再保存到一个完全独立的目录中,然后在它自己的 Cargo.toml 中将 rust_tutorials 列为依赖项:

benz = { path = "./benz" }

属性

Rust 程序中的任何语法项都可以用属性进行装饰。属性是 Rust 的通用语法,用于向编译器提供各种指令和建议。假设你收到了如下警告:

libgit2.rs: warning: type `git_revspec` should have a camel case name
    such as `GitRevspec`, #[warn(non_camel_case_types)] on by default

但是你选择这个名字是有特别原因的,只希望 Rust 对此“闭嘴”。那么通过在此类型上添加 #[allow] 属性就可以禁用这条警告:

#[allow(non_camel_case_types)]
pub struct git_revspec {
    ...
}

可以查看官方文档中更多的 cfg https://rustwiki.org/zh-CN/reference/conditional-compilation.html?highlight=cfg_attr

测试与文档

Rust 中内置了一个简单的单元测试框架。测试是标有 #[test] 属性的普通函数:

#[test]
fn math_works() {
    let x: i32 = 1;
    assert!(x.is_positive());
    assert_eq!(x + 1, 2);
}

cargo test 会运行项目中的所有测试

$ cargo test
   Compiling math_test v0.1.0 (file:///.../math_test)
     Running target/release/math_test-e31ed91ae51ebf22

running 1 test
test math_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

文档

cargo doc --no-deps --open

--open Cargo 随后在浏览器中打开此文档。

--no-deps 只为本身生成文档,而不会为它依赖的所有 crate 生成文档。

文档会保存在 taget/doc 目录下.

添加了 doc 标记 就可以在文档中看到该内容.

#[doc = "Hello Doc"]
//!Hello Doc
fun abc(){

}

指定依赖

image = "0.6.1"
image = { git = "https://github.com/Piston/image.git", rev = "528f19c" }
image = { path = "vendor/image" }

最终对应结果可以看附件中的 Cargo.toml 版本对照表.

cargo.lock 可以控制软件版本一致性.

附件

Cargo.toml 的配置设定区段

命令行 使用 Cargo.toml 区段
cargo build [profile.dev]
cargo build --release [profile.release]
cargo test [profile.test]

Cargo.toml 版本对照表

Cargo.toml 行 含义
image = "=0.10.0" 仅使用确切的版本 0.10.0
image = ">=1.0.5 使用 1.0.5 或更高版本(甚至 2.9,如果其可用的话)
image = ">1.0.5 <1.1.9" 使用高于 1.0.5 但低于 1.1.9 的版本
image = "<=2.7.10" 使用 2.7.10 或更早的任何版本