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.toml | clippy lints 的指定配置,通过使用 cargo clippy |
build.rs | Pre-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导入的使用
模块树 |
模块和源文件的使用方式: |
|
实际模块的定义如下: |
mod m {} ,当使用 mod m; 时,将会先去读 m.rs 或者 m/mod.rs .rs 的路径是基于嵌套的,如 mod a { mod b { mod c; }}} 、a/b/c.rs 、a/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 |
- 在类型和函数中,定义类型
X
和常量X
- 在类型和函数中,定义类型
X
和函数X
- 在任何给定的作用域中,例如在一个模块中,每个名称空间只能存在一个项目
enum X {}
和fn X() {}
可以共存struct X;
和const X
可以共存
- 使用
use my_mod::X;
时,所有被X
调用的目录都会被导入
*由于命名约定,如 按照约定 fn
和 mod
是小写,并且大多数开发者不会把所有东西都命名为 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 |
一个表达式,如 x 、1 + 1 、String::new() 、vec![] |
$x:pat |
一种分支模式,如 Some(t) |
$x:ty |
一种类型,如 String |
$x:ident |
一个标识符,如 在 let x = 0; 中,标识符是 x |
$x:path |
一个路径,如 foo 、std::mem::replace 、transmute::<_, int> |
$x:literal |
一个字面值,如 3 、"foo" 、 b"bar" 等等 |
$x:lifetime |
一个生命周期,如 'a 、'static 等等 |
$x:meta |
一个meta item ,在这些 #[...] 、#![...] 里面的属性 |
$x:vis |
一个可见的修饰符,如 pub 、pub(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's | On | 解释 |
#![no_std] |
C |
不会自动导入std,将使用core代替 |
#![no_implicit_prelude] |
CM |
不会添加prelude,需要手动导入None Vec ... |
#![no_main] |
C |
如果是这种情况则不要在程序中写入 main() |
Opt-In's | On | 解释 |
#![feature(a, b, c)] |
C |
依赖可能永远不会稳定的特性,Unstable Book. |
Builds | On | 解释 |
#![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 |
限制类型替换的最大数量 |
Handlers | On | 解释 |
#[panic_handler] |
F |
创建一些应用程序的panic handler,如 fn f(&PanicInfo) -> ! |
#[global_allocator] |
S |
创建一些静态项目的实现,GlobalAlloc 全局分配器 |
#[code] | ||
主要控制写出代码的属性: | ||
Developer UX | On | 解释 |
#[non_exhaustive] |
T |
Future-proof struct 或 enum,暗示它以后数据将会扩大 |
#[path = "x.rs"] |
M |
从非标准库中获取模块 |
Codegen | On | 解释 |
#[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))] |
||
Linking | On | 解释 |
#[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 |
以不同的名称导出 fn 或 static |
#[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 可能会受益于更具侵略性deny或forbid 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 |
Derives | On | 解释 |
#[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
意味着一些特殊的东西!
意味着在宏上*
意味着几乎任何项目