Rust 工具链

项目组织

  • 基本的项目结构布局,通用的文件和文件夹,一般可由 cargo 生成
入口相关的 code
📁 .cargo/项目本地的 cargo 配置,通常会包含 config.toml
📁 benches/通过运行 cargo bench 来为你的 crate 执行基准测试,一般需要设置默认为 nightly
📁 examples/通过示例展示如何使用你的 crate,他们会像外部用户一样看到你的 crate
  my_example.rs运行个别的示例像 cargo run --example my_example
📁 src/你项目的源代码
main.rs程序的默认入口文件,被作用于 cargo run
lib.rs库的默认入口文件,如 对于搜索 my_crate::f() 开始的地方
📁 src/bin/存放额外的二进制文件的地方,即使是在库项目中
extra.rs添加二进制文件,使用 cargo run --bin extra 去运行
📁 tests/通常用于存放集成测试,通过 cargo test 来调用,单元测试一般会保留在 src/ 文件中
.rustfmt.toml用作 cargo fmt 自定义的方式
.clippy.tomlclippy lints 的指定配置,通过使用 cargo clippy
build.rsPre-build script,一般在构建 C / FFI 会用到
Cargo.toml主项目的配置,一般用于定义依赖,组件等等
Cargo.lock可用于查看依赖关系细节,为程序添加 git,而不是库
rust-toolchain.toml为项目定义覆盖的工具链(channel、components、targets)

各种入口文件的小例子

// src/main.rs (程序的默认入口文件)

fn main() {
    println!("Hello, AwesomeProgram!");
}
// src/lib.rs (库的默认入口文件)

pub fn f() {}      // 因为是根目录中的公开目录,所以可从外部访问

mod m {
    pub fn g() {}  // 不是根目录中的公开路径(模块 m 不是公开的), 因此 g 不能在 crate 之外被访问
}  
// src/my_module.rs (单元测试可以在你项目中的任意文件)

fn f() -> u32 { 0 }

#[cfg(test)]
mod test {
    use super::f;           // 需要从上级模块中导入目录,可以访问没有公开的成员

    #[test]
    fn ff() {
        assert_eq!(f(), 0);
    }
}
// tests/sample.rs (集成测试的例子)

#[test]
fn my_sample() {
    assert_eq!(my_crate::f(), 123); // 集成测试和基准测试依赖第三方的 crate,因此,他们仅看到公开的目录
}
// benches/sample.rs (基准测试的例子)

#![feature(test)]   // 基准测试仍处于试验阶段

extern crate test;  // 在 2018 版本中需要添加

use test::{black_box, Bencher};

#[bench]
fn my_algo(b: &mut Bencher) {
    b.iter(|| black_box(my_crate::f())); // `black_box` 阻止 `f` 被优化掉
}
// build.rs (预构建脚本的例子)

fn main() {
    // 你需要依赖目标 OS 的环境变量,对于当前的 OS 可以表示 `#[cfg(...)]`
    let target_os = env::var("CARGO_CFG_TARGET_OS");
}
// src/lib.rs (过程宏的默认入口文件)

extern crate proc_macro;  // 显然需要像这样被导入

use proc_macro::TokenStream;

#[proc_macro_attribute]   // 可以作为 `#[my_attribute]` 被使用
pub fn my_attribute(_attr: TokenStream, item: TokenStream) -> TokenStream {
    item
}

// Cargo.toml

[package]
name = "my_crate"
version = "0.1.0"

[lib]
proc-macro = true

模块树和NameSpace导入的使用

模块树
模块和源文件的使用方式:
  • 模块树需要显式定义,它不是从文件系统树中隐式创建的
  • Module tree root是库、应用程序等等的入口文件,如 lib.rs
  • 实际模块的定义如下:
  • 一个在文件中定义的 mod m {},当使用 mod m; 时,将会先去读 m.rs 或者 m/mod.rs
  • .rs 的路径是基于嵌套的,如 mod a { mod b { mod c; }}}a/b/c.rsa/b/c/mod.rs
  • 通过一些 mod m; 来自 Module tree root 而没有路径的文件,是不会被编译器触及到的

  • Rust 有三种 NameSpaces
    Namespace 类型Namespace 函数Namespace 宏
    mod X {}fn X() {}macro_rules! X { ... }
    X (crate)const X: u8 = 1;
    trait X {}static X: u8 = 1;
    enum X {}
    union X {}
    struct X {}
    struct X;1
    struct X();2
    1. 类型函数中,定义类型 X 和常量 X
    2. 类型函数中,定义类型 X 和函数 X
    • 在任何给定的作用域中,例如在一个模块中,每个名称空间只能存在一个项目
      • enum X {}fn X() {} 可以共存
      • struct X;const X 可以共存
    • 使用 use my_mod::X; 时,所有被 X 调用的目录都会被导入

    *由于命名约定,如 按照约定 fnmod 是小写,并且大多数开发者不会把所有东西都命名为 X, 在通常情况下你不必担心这些类型,它们可以成为设计宏时的一个因素

    Cargo

    *必须要被了解的命令和工具

    命令描述
    cargo init在最近的版本创建一个新项目
    cargo build在调试模式中构建项目( --release 适用于所有可以优化的地方)
    cargo check检查项目是否可以编译成功(执行很快)
    cargo test为项目执行测试用例
    cargo doc --open在最近的版本创建一个新项目
    cargo run如果生成了一个二进制文件( main.rs )则执行你的项目
    cargo run --bin b运行二进制文件 b,将特性与其它依赖统一在一起,可能会造成混淆
    cargo run -p w运行子空间的 w,如你期望的可以更多的面对特性
    cargo ... --timings展示是什么 crate 导致你的构建花费了这么长时间,非常有用
    cargo tree显示依赖关系图
    cargo +{nightly, stable} ...在命令中使用给定的工具链,如 用于 'nightly only' 工具
    cargo +nightly ...一些 nightly-only 的命令,使用下面的命令替换 ...
    rustc -- -Zunpretty=expanded显示展开的宏
    rustup doc打开离线 Rust 文档,包括书籍,在没有网络环境下使用很好

    *这里的 cargo build 你也可以打印 cargo b,并且 --release 意味着可以用 -r 代替

    下面这些是可选的 rustup 组件,使用 rustup component add [tool] 安装它们

    工具描述
    cargo clippy增加 lints 捕获一些常见的 滥用API不符合语言习惯 的代码
    cargo fmt自动代码格式化 rustup component add rustfmt

    *有更多可添加的 cargo 插件 点我查看

    交叉编译

    🔘检查目标系统是支持的

    🔘通过 rustup target install X 安装不同目标系统的需求

    🔘安装原生的工具链

    从目标主机供应商获取(谷歌、苹果 ...),可能并非在所有的主机都可用(如 在 Windows 上没有 iOS 的工具连)

    一些工具链需要额外的构建步骤(如 安卓系统需要 make-standalone-toolchain.sh )

    🔘像下面这样更新 ~/.cargo/config.toml

    [target.aarch64-linux-android]
    linker = "[PATH_TO_TOOLCHAIN]/aarch64-linux-android/bin/aarch64-linux-android-clang"
    [target.aarch64-linux-android]
    linker = "C:/[PATH_TO_TOOLCHAIN]/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang.cmd"

    🔘设置环境变量(可选的,等待编译器如果有报错提示后再进行设置)

    set CC=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
    set CXX=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
    set AR=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android-ar.exe
    ...

    是否设置它们依赖于编译反馈的报错提示,不一定都是需要的

    一些平台的配置可能会非常敏感,取决于如何指定路径(如 \/ )和引用路径

    ✔️使用 cargo build --target=X 去编译

    工具指令

    *嵌入在源码中的特殊标记通常用于工具或者预处理

    在声明宏中,通过示例 macro_rules! 实现以下功能:
    在宏内解释
    $x:ty 宏的捕获,这里是一种类型
    $x:item 一个 item ,如 函数、结构、模块等等
    $x:block 一组语句或表达式 block {},如 { let x = 5; }
    $x:stmt 一个语句,如 let x = 1 + 1;String::new();vec![]
    $x:expr 一个表达式,如 x1 + 1String::new()vec![]
    $x:pat 一种分支模式,如 Some(t)(17, 'a')_
    $x:ty 一种类型,如 StringusizeVec<u8>
    $x:ident 一个标识符,如 在 let x = 0; 中,标识符是 x
    $x:path 一个路径,如 foostd::mem::replacetransmute::<_, int>
    $x:literal 一个字面值,如 3"foo"b"bar" 等等
    $x:lifetime 一个生命周期,如 'a'static 等等
    $x:meta 一个meta item,在这些 #[...]#![...] 里面的属性
    $x:vis 一个可见的修饰符,如 pubpub(crate)
    $x:tt 一个单独的 token tree点击查看更多
    $crate 一个卫生变量,定义宏的 crate

    文档
    在文档注释中的用法:
    在文档注释内解释
    ```...``` 包含一个文档测试,可以使用 cargo test去运行
    ```X,Y ...``` 同上,并且包括可选配置 X...Y
    rust 明确使用 Rust 去编写测试,由 Rust 工具实现的
    - 默认行为,编译测试,运行测试,如果发生恐慌则测试失败
    should_panic 编译测试,运行测试,执行期望应该是恐慌,如果不是,则失败
    no_run 编译测试,如果代码无法编译,则测试失败,不会运行测试
    compile_fail 编译测试,如果代码可以编译,则测试失败
    ignore 不会编译,不会运行,选择上面的选项
    edition2018 执行代码为Rust 2018版,默认为 2015版
    # 隐藏文档中的一行,如 # use x::hidden;
    [`S`] 创建指向结构体、枚举、特征、函数……的链接
    [`S`](crate::S) 也可以使用路径,以 markdown 链接的形式




    #![globals]
    影响整个 crate 和程序的属性:
    Opt-Out'sOn解释
    #![no_std] C 不会自动导入std,将使用core代替
    #![no_implicit_prelude] CM 不会添加prelude,需要手动导入None Vec ...
    #![no_main] C 如果是这种情况则不要在程序中写入 main()
    Opt-In'sOn解释
    #![feature(a, b, c)] C 依赖可能永远不会稳定的特性,Unstable Book.
    BuildsOn解释
    #![windows_subsystem = "x"] C 在 Windows 上,做一个控制台或者应用程序
    #![crate_name = "x"] C 指定当前 crate 的名称,如 当在不使用 cargo 的时候
    #![crate_type = "bin"] C 指定当前 crate 的类型,如 bin lib dylib cdylib ...
    #![recursion_limit = "123"] C 为 deref 和 macros 设置编译期间的递归限制
    #![type_length_limit = "456"] C 限制类型替换的最大数量
    HandlersOn解释
    #[panic_handler] F 创建一些应用程序的panic handler,如 fn f(&PanicInfo) -> !
    #[global_allocator] S 创建一些静态项目的实现,GlobalAlloc 全局分配器

    #[code]
    主要控制写出代码的属性:
    Developer UXOn解释
    #[non_exhaustive] T Future-proof structenum,暗示它以后数据将会扩大
    #[path = "x.rs"] M 从非标准库中获取模块
    CodegenOn解释
    #[inline] F 建议编译器应该在调用点时使用内联函数
    #[inline(always)] F 强烈要求编译器使用内联调用
    #[inline(never)] F 如果仍然内联函数,则指示编译器不起作用
    #[cold] F 提示该函数可能不会被调用
    #[target_feature(enable="x")] F 对于 unsafe fn 的代码强制 CPU 开启特性
    #[track_caller] F 对于更友好的 panic 输出信息,允许 fn 查找caller
    #[repr(X)] T 使用另一种表示而不是默认的rust
    #[repr(C)] T 使用 C-compatible (f. FFI)、可预测 (f. transmute) 的布局
    #[repr(C, u8)] enum enum判别器指定类型
    #[repr(transparent)] T 为单个元素类型提供与包含字段相同的布局
    #[repr(packed(1))] T 结构和包含字段的较低对齐,轻度的 UB 倾向
    #[repr(align(8))] T 将结构对齐提高到给定值,如 SIMD 类型
    *可以组合一些表示修饰符,如 #[repr(C, packed(1))]
    LinkingOn解释
    #[no_mangle] * 直接使用项目名称作为符号名称而不修改
    #[no_link] X 当想要使用宏时,不去链接extern crate
    #[link(name="x", kind="y")] X 当查找符号时,要链接的本地库
    #[link_name = "foo"] F 要查找以解析 extern fn 的符号名称
    #[link_section = ".sample"] FS 应放置项目的目标文件的部分名称
    #[export_name = "foo"] FS 以不同的名称导出 fnstatic
    #[used] S 不要优化掉 static 变量,尽管它看起来并未使用

    #[quality]
    Rust 工具用于提高代码质量的属性:
    代码模式On解释
    #[allow(X)] * 指示 rustc 或 clippy 去忽略 class X 可能的问题
    #[warn(X)] * 发出警告,与 clippy lints 在一起使用
    #[deny(X)] * 编译失败
    #[forbid(X)] * 编译失败并且阻止后续的重写
    #[deprecated = "msg"] * 让你用户知道你制造了一个设计错误
    #[must_use = "msg"] FTX 使编译器检查由调用者处理的返回值
    *对于保证高质量 crates 的最佳选择,存在一些争论。积极维护的多个开发版本的 crates 可能会受益于更具侵略性denyforbid lints;不经常更新的可能更多来自保守使用warn,因为未来的编译器或 clippy 更新可能会突然破坏其他工作代码并出现些小问题
    测试On解释
    #[test] F 将函数标记为测试,使用 cargo test
    #[ignore = "msg"] F 编译但暂时不执行一些 #[test]
    #[should_panic] F 测试必须触发 panic!() 才能实际成功
    #[bench] F 标记函数在 bench/ 作为基准测试,使用 cargo bench
    格式化On解释
    #[rustfmt::skip] * 防止 cargo fmt 清理项目
    #![rustfmt::skip::macros(x)] CM ... 从清理宏 x 开始
    #![rustfmt::skip::attributes(x)] CM ... 从清理属性 x 开始
    文档On解释
    #[doc = "Explanation"] * 与添加 /// 文档注释相同
    #[doc(alias = "other")] * 提供用户可以在文档中搜索的另一个名称
    #[doc(hidden)] * 防止项目出现在文档中
    #![doc(html_favicon_url = "")] C 为文档设置 favicon
    #![doc(html_logo_url = "")] C 文档中使用的 logo
    #![doc(html_playground_url = "")] C 生成 Run 按钮并使用给定的服务
    #![doc(html_root_url = "")] C 指向外部 crate 的链接的基本 URL
    #![doc(html_no_source)] C 防止源包含在文档中

    #[macros]
    与宏的创建和使用相关的属性:
    宏示例On解释
    #[macro_export] ! 导出 macro_rules! 作为公开的在 crate 级别
    #[macro_use] MX 让宏持久化的模块,或者从 extern crate 导入
    过程宏On解释
    #[proc_macro] F fn 标记为function-like的过程宏,可作为 m!() 调用
    #[proc_macro_derive(Foo)] F 将可以 #[derive(Foo)]fn 标记为derive macro
    #[proc_macro_attribute] F 将可以理解成新的 #[x]fn 标记为attribute macro
    DerivesOn解释
    #[derive(X)] T 让一些过程宏提供一些好的 trait X 的实现

    #[cfg]
    控制条件编译的属性:
    配置属性On解释
    #[cfg(X)] * 如果配置 X 成立,则包含项目
    #[cfg(all(X, Y, Z))] * 如果所有的选项都成立,则包含项目
    #[cfg(any(X, Y, Z))] * 如果至少有一个选项成立,则包含项目
    #[cfg(not(X))] * 如果 X 不成立,则包含项目
    #[cfg_attr(X, foo = "msg")] * 如果配置 X 成立,则应用 #[foo = "msg"]
    *注意,options 一般可以设置多次,即同一个 key 可以出现多个 value。可以同时期望 #[cfg(target_feature = "avx")]#[cfg(target_feature = "avx2")] 为真。
    已知选项On解释
    #[cfg(target_arch = "x86_64")] * 为了去编译 CPU 架构的 crate
    #[cfg(target_feature = "avx")] * 某一类特性类别的指令是否可用
    #[cfg(target_os = "macos")] * 你的代码将在其操作系统上运行
    #[cfg(target_family = "unix")] * 属于家族操作系统
    #[cfg(target_env = "msvc")] * DLLs 和函数怎样与操作系统接口连接
    #[cfg(target_endian = "little")] * 你的 zero-cost 协议失败的主要原因
    #[cfg(target_pointer_width = "64")] * 有多少位指针、usize 和 CPU
    #[cfg(target_vendor = "apple")] * 目标生产商
    #[cfg(debug_assertions)] * debug_assert!()是否会发生 panic
    #[cfg(panic = "unwind")] * 是否 unwind 或 abort 将发生恐慌
    #[cfg(proc_macro)] * 是否将 crate 编译为过程宏
    #[cfg(test)] * 是否与 cargo test一起编译
    #[cfg(feature = "serde")] * 是否 feature serde 与你的 crate 被一起编译

    build.rs
    与预构建脚本关联的环境变量与输出
    输入环境解释
    CARGO_FEATURE_X 为每一个激活的特性 X 设置环境变量
    CARGO_FEATURE_SERDE 如果特性 serde 被强制启用
    CARGO_FEATURE_SOME_FEATURE 如果有一些特性被强制启用,- 会转成 _
    CARGO_CFG_X 暴露 cfg's,加入多个选项通过,并且会把- 转换成 _
    CARGO_CFG_TARGET_OS=macos 如果 target_os 设置为了 macos
    CARGO_CFG_TARGET_FEATURE=avx,avx2 如果 target_feature 设置为了 avx 和 avx2
    OUT_DIR 输出应该放到哪个目录
    TARGET 正在编译的目标三重
    HOST 主机三重(运行这个构建脚本)
    PROFILE 可以是 debug 或 release
    通过 env::var()?build.rs中是可用的,但并未详尽列出
    输出字符串On解释
    cargo:rerun-if-changed=PATH 仅在如果 PATH 变更了,再次运行这个 build.rs
    cargo:rerun-if-env-changed=VAR 仅在如果环境变量 VAR 变更了,再次运行这个 build.rs
    cargo:rustc-link-lib=[KIND=]NAME 就像通过-l选项链接本机库一样
    cargo:rustc-link-search=[KIND=]PATH 就像通过-l选项,为本地库查找路径
    cargo:rustc-flags=FLAGS 向编译器添加指定的 flags
    cargo:rustc-cfg=KEY[="VALUE"] 发出给定的 cfg 选项以用于以后的编译
    cargo:rustc-env=VAR=VALUE 在 crate 编译期间发出可通过env!()访问的变量
    cargo:rustc-cdylib-link-arg=FLAG 当构建一个cdylib时,传递链接器标志
    cargo:warning=MESSAGE 发出编译器警告
    通过 println!()build.rs写出,但并未详尽列出

    *对于属性中的On列:

    • C 表示在 crate 级别(通常 #![my_attr] 在顶级文件中给出)
    • M 意味着在模块上
    • F 指功能上
    • S 意味着静态
    • T 表示类型
    • X 意味着一些特殊的东西
    • ! 意味着在宏上
    • * 意味着几乎任何项目