赞
踩
Rust 程序由 crate 组成。在 Rust 中,crate 是最小的编译单元。
crate 可以分为:
main
函数作为程序的入口。main
函数。可使用 cargo build --verbose
可以看到构建过程中 Cargo
正在运行的命令。
在使用 Cargo
时候,可以在 Cargo.toml
文件的 dependencies
中指定引入项目的库 crate 的版本。运行 cargo build
时,Cargo 会从 crates.io
中下载这些 crate 的指定版本的源代码。它会读取这些 crate 的 Cargo.toml
文件,并下载它们的依赖项,并递归的进行。所有这些依赖关系的集合,会告诉 Cargo 关于要构建什么 crate 以及按什么顺序构建,这叫做该 crate 的依赖图。
此时,有了源代码,Cargo 就会编译所有的 crate。它会为项目依赖图中的每个 crate 都运行一次 rustc。
在编译库的时候,会使用 --crate-type lib
选项。会告诉 rustc 不用寻找 main
函数,而是生成一个 .rlib
文件,其中包含已编译代码,可用于创建二进制文件和其他的 .rlib
文件。
在编译二进制程序时,Cargo 会使用 --crate-type bin
,结果是目标平台的二进制可执行文件。
对于每一个 rustc 命令,Cargo 都会传入 --extern
选型,给出 crate 将使用的每个库的文件名。这样,当 rustc 看到一行代码(例如 use image::png::PNGEncoder
)时,就可以确定 image
是一个 crate 的名称。Cargo 知道在哪里可以找到磁盘上已编译的 crate。Rust 编译器需要访问这些 .rlib
文件,因为它们包含库的已编译代码。Rust 会将代码静态链接到最终的可执行文件中。.rlib
包含一些类型信息,这样 Rust 就可检查我们在代码中使用的库特性是否确实存在于 crate 中,以及我们是否正确使用了它们。.rlib
文件中还包含此 crate 的公共内联函数、泛型和宏这三者的副本,在 Rust 知道如何使用它们之前,这些特性无法完全编译为机器码。
cargo build
支持很多选项。最常用的是 cargo build --release
,会生成优化过的程序。
Cargo.toml
文件中 [package]
部分会使用 edition
来指定 Rust 的版本。
edition = "2021"
Rust 承诺编译器始终接受该语言的所有现存版本,并且程序可以自由混用以不同版本编写的 crate。2015 版的 crate 甚至可以依赖 2021 版的 crate。crate 的版本只影响其源代码的解释方式,编译代码时,版本的差异已然消失。当想在自己的代码中使用新的语言特性时,只需要改版本就可以了。
如果有一个旧版本的 Rust 编写的 crate,则 cargo fix
能帮助将代码升级到新版本。
模块是关于项目内代码组织的。扮演着 Rust 命名空间的角色,是构成 Rust 程序或库的函数、类型、常量等的容器。
模块是一组语法项的集合,这些语法项具有命名的特性。需要使用 mod
关键词,默认为私有的。
可以使用 pub
关键字使某个语法项声明为公共的。
mod spores { use cells::{Cell, Gene}; pub struct Spore { // ... } pub fn produce_spore(factory: &mut Sporanium) -> Spore { // ... } pub(crate) fn genes(spore: &Spore) -> Vec<Gene> { // ... } fn recombine(parent: &mut Cell) { // ... } }
pub(crate)
表示在这个 crate 中的任何地方都能使用,但不会作为外部接口的一部分公开,不会被其他 crate 使用。
未标记为 pub 的内容都是私有的,只能在定义它的模块及其任意子模块中使用。
模块可以嵌套。如果希望嵌套模块中的语法项对其他 crate 可见,需要将它和它所在的模块标记为 pub
。
pub(super)
表示语法项只对其父模块可见。
pub(in <path>)
让语法项在特定的父模块及其后代中可见。这对于深度嵌套的模块有用。
mod plant_structures {
pub mod roots {
pub mod products {
pub(in crate::plant_structures::roots) struct Cytokinin {
// ...
}
}
use products::Cytokinin; // 正确: 在 roots 模块可见
}
use roots::products::Cytokinin; // 错误: 只在 roots 模块中可见
}
use plant_structures::roots::procucts::Cytokinin; // 错误
模块还可以这样写:
mod spores;
然后将 spores
模块的主体代码,也就是花括号里的,保存到一个单独的名为 spores.rs
的文件中。
模块可以有自己的目录。当 Rust 看到 mod spores;
时,会同时检查 spores.rs
和 spores/mod.rs
,如果这两个文件都存在或都不存在,就会报错。如果 spores
模块还有子模块,那么考虑使用 spores/mod.rs
的方式。
fern_sim/
├── Cargo.toml
└── src/
├── main.rs
├── spores.rs
└── plant_structures/
├── mod.rs
├── leaves.rs
├── roots.rs
└── stems.rs”
::
运算符用于访问模块中的各项特性。项目中任何位置的代码都可通过写出其路径来引用:
if s1 > s2 {
std::mem::swap(&mut s1, &mut s2);
}
或者使用 use
将这些特性导入到当前模块中:
use std::mem;
if s1 > s2 {
mem::swap(&mut s1, &mut s2);
}
可一次性导入多个名称:
use std::collections::{HashMap, HashSet};
use std::fs::{self, File};
use std::io::prelude::*; // 导入所有语法项目
可使用 as
在导入的时候重新命名。
use std::io::Result as IOResult;
模块不能自动访问其父模块的内容。例如 proteins
模块的 proteins/mod.rs
文件有如下代码:
// proteins/mod.rs
pub enum AminoAcid { ... }
pub mod synthesis;
那么 synthesis
子模块中不能直接使用 AminoAcid
类型。需要使用 super
显式导入。
默认情况下,路径是相对于当前模块的。self
也是当前模块的同义词。
关键字 super
和 crate
在路径中有着特殊的含义:super
指父模块,crate
指当前模块所在的 crate
。使用 crate
根路径而不是当前模块的路径可以更容易地在项目中移动代码,因为如果当前模块的路径发生了变化,不会破坏任何导入。
子模块可使用 use super::*
访问父模块中的私有语法项。
Rust 中有一种特殊路径,称为绝对路径,该路径以 ::
开头。
标准库 std
会自动链接到每个项目。意味着可以使用 use std::whatever
,或者就按名称引用 std
中的语法项,例如 std::mem::swap()
。
还有一些特别便捷的名称(Vec 和 Result)会包含在标准库预导入中并自动导入。Rust 的行为就好像每个模块都用以下导入语句开头一样:
use std::prelude::v1::*;
结构体声明为 pub
时,其字段还是私有的。要暴露其字段的话,其字段也需要声明为 pub
。
pub struct Fern {
pub roots: RootSet,
pub stems: StemSet
}
使用 cargo new --lib <项目名>
。
创建的项目没有 src/main.rs
文件,而是创建一个 src/lib.rs
文件。不需要要更改 Cargo.toml
中的任何内容。此时会让 Cargo 保持默认行为。默认设定下,cargo build
会查看源目录中的文件并根据文件名确定要构建的内容。当它发现存在文件 src/lib.rs
时,就知道要构建一个库。
src/lib.rs
中的代码构成了库的根模块。其他使用这个库的 crate 只能访问这个根模块的公共语法项。
src/bin
目录也可将程序和库放在一个 crate 中。Cargo
本身就是用这样的方式编写的。它的大部分代码在一个 Rust 库中。使用的 cargo
命令行程序只是一个很薄的包装程序,它会调用库来完成所有繁重的工作。库和命令行程序都位于同一个源代码存储库中。
可以将需要运行的程序放置在 src/bin
目录下。例如取名为 efern.rs
,里面包含了 main
函数用来运行程序。
当执行 cargo build
时,会编译库和可执行程序。
运行的时候执行 cargo run --bin efern
即可。
如果程序比较大,可以在 src/bin
下创建目录,然后这个目录里定义 main.rs
文件作为可执行程序的入口文件。
src/bin
适合小型的项目使用。
如果定义了一个库,然后在一个大型项目中使用这个库。那么可以将这个库放置在完全独立的目录中,然后在 Cargo.toml
中将 fern_sim
列为依赖项。
属性是 Rust 的通用语法,用于向编译器提供各种指令和建议。
如果收到了 #[warn(non_camel_case_type)]
警告。可以通过在此类型上添加#[allow(non_camel_case_type)]
属性来禁用这条警告。
#[allow(non_camel_case_types)]
pub struct git_revspec {
...
}
可使用 #[cfg]
的属性编写条件编译。
// Android 时候才构建项目中包含此模块
#[cfg(target_os = "android)]
mod mobile;
下面是常用的 #[cfg]
选项:
可以使用 #[inline]
属性对函数的内联展开进行微观管理。通常会把这种优化留给编译器。
Rust 默认 #[inline(always)]
要求函数在每个调用点内联展开。#[inline(never)]
要求函数永不内联。
#[cfg]
和 #[allow]
可附着在整个模块上对其中的所有内容生效。#[test]
和 #[inline]
则必须附着到耽搁语法项。
要将属性附着到整个 crate,需要将其添加到 **main.rs**
或 **lib.rs**
文件顶部。并写成 **#!**
。
#![allow(non_camel_case_types)]
#![feature]
属性可启用 Rust 语言和库中的不稳定特性。
Rust 中内置了一个简单的单元测试框架。测试是使用了 #[test]
属性的普通函数。
cargo test
会运行项目中的所有测试。
无论你的 crate 是可执行文件还是库,都可以通过将参数传给 Cargo 来运行特定测试:cargo test match
会运行名称中包含 match
的所有测试。
测试经常使用 assert!
和 assert_eq!
两个 Rust 标准库中的宏。它们两个会包含在发布构建中。如果仅仅在调试中使用断言,可以使用 debug_assert!
和 debug_assert_eq!
。
使用 #[should_panic]
属性来测试各种出错情况。
#[test]
#[allow(unconditional_painc, unused_must_use)]
#[should_panic(expected="divide by zero")]
fn test_divide_by_zero_error() {
1 / 0;
}
可以从测试中返回 Result<(), E>
。只要错误类型实现了 Debug 特型,就可以简单的使用 ?
抛弃 Ok
变体以返回 Result
:
use std::num::ParseIntError;
// 如果 1024 是一个有效的数值,那么本测试就会通过
#[test]
fn explicit_radix() -> Result<(), ParseIntError> {
i32::from_str_radix("1024", 10)?;
Ok(())
}
标记为 #[test]
的函数是有条件编译的。cargo build
和 cargo build --release
会跳过测试代码。但是当运行 cargo test
时,Cargo 会分开两次来构建你的程序:一次是以普通的方式,一次带着你的测试和已启用的测试工具。意味着单元测试可以与它们所测试的代码一起使用,按需访问内部实现细节,而且没有运行期成本。但是,可能会导致一些警告。
所以,如果测试变的庞大需要支撑性代码时,应该将它们放在 tests
模块中,并使用 #[cfg]
属性声明整个模块仅用于测试:
#[cfg(test)]
mod tests {
fn roughly_equal(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-6
}
#[test]
fn trig_works() {
use std::f64::consts::PI;
assert!(roughly_equal(PI.sin(), 0.0));
}
}
Rust 测试工具默认会使用多个线程同时运行好几个测试。要禁用这个功能,运行单个测试 cargo test testname
或运行 cargo test --test-threads 1
。
通常测试工具只会显示失败测试的输出。如果要展示成功测试的输出,需要运行 cargo test -- --nocapture
。
集成测试就是使用 .rs
文件,位于 src
目录同层级的 tests
目录中。
cargo test
会运行单元测试和集成测试。要运行某个特定文件(如 tests/unfurl.rs
)中的集成测试,请使用 cargo test --test unfurl
命令。
cargo doc
会创建 HTML
文档:
cargo doc --no-deps --open
--no-deps
只会为本 crate 生成文档,不会为依赖的所有 crate 生成文档。--open
选项会要求 Cargo 随后在浏览器中打开此文档。///
表示文档型注释。它和 #[doc]
属性一致。
//!
和 #![doc]
通常是模块或 crate 上使用。
可以添加别名以便使用内置搜索功能更轻松的查找内容。
#[doc(alias = "route")]
pub struct VascularPath {
// ...
}
在此 crate 文档中搜索 path
或 route
都能找到 VasularPath
。
可以在文档中包含外部文件。例如,存储库的 README.md
文件中包含与准备用作 crate 的顶层文档相同的文本,那么可以将下面的这句话放在 lib.rs
或 main.rs
的顶部。
#![doc = incluce_str!("../README.md")]
在 Rust 库 crate 中运行测试时,Rust 会检查文档中出现的所有代码是否真能如预期般工作。Rust 会获取文档型注释中出现的每个代码块,然后将其编译为单独的可执行包,再与你的库链接在一起,最后运行。
要告诉 Rust 编译你的示例,但是不实际运行它,使用 no_run
注释。
/// 将本地玻璃栽培箱的所有照片上传到在线画廊
///
/// ```no_run
/// let mut session = fern_sim::connect();
/// session.upload_all();
/// ```
pub fn upload_all(&mut self) {
...
}
指定版本号。
image = "0.6.1"
如果想要使用没有发布在 crates.io 上的依赖项。指定 Git 存储库 URL 和修订号。
image = { git = "https://github.com/Piston/image.git", rev = "528f19c" }
或者指定一个包含 crate 源代码的目录(本地):
image = { path = "vendor/image" }
如果 Cargo.toml
文件中写的是 image = "0.13.0"
时,Cargo 会使用与版本 0.13.0
兼容的最新版本的 image。
兼容性规则:
可以使用一些运算符来指定确切的版本或版本范围。
image = "=0.10.0"
,使用确切的 0.10.0
版本。image = ">=1.0.5"
,任何大于 1.0.5
的版本,甚至是大于 1.0
的版本。imagge = ">1.0.5 <1.1.9"
,限定了版本的范围。image = "<=2.7.10"
,使用小于 2.7.10
的版本。有时候会使用 *
通配符,它会告诉 Cargo 任何版本都可以。
为了项目依赖的稳定性,不希望每次构建时都将依赖库升级到最新的版本。Cargo 内置一种机制来防止发生这种情况。第一次构建时候,会输出一个 Cargo.lock
文件,会记录它使用的每个 crate 的确切版本。以后的构建都将参考此文件并继续使用相同的版本。仅当要求 Cargo 升级时才会升级到最新版本,方法是手动修改 Cargo.toml
文件中的版本号或运行 cargo update
。
但是 cargo update
只能升级到兼容的最新版本。如果有大版本的更改,需要自己手动修改。
首先打包 crate。
$ cargo package
它会创建一个 .crate
文件,其中包含了所有库的源文件。可以使用 cargo package --list
来查看包含了哪些文件。
之后登录 crates.io
并获取密钥。
使用 cargo login ***
登录。
最后使用 cargo publish
进行发布。
随着项目不断成长,最终会写出很多 crate。它们并存于同一个源代码存储库中:
Cargo 的工作方式是,每个 crate
都有自己的构建目录 target
,其中包含该 crate
的所有依赖项的单独构建。这些构建目录是完全独立的。即使两个 crate
具有共同的依赖项,它们也不能共享任何已编译的代码。这有点浪费。
可以使用 Cargo 工作空间来节省编译时间和磁盘空间。Cargo 工作空间是一组 crate
,它们共享着公共构建目录和 Cargo.lock
文件。需要在根目录中创建一个 Cargo.toml 文件,并将下面的代码放入其中:
[workspace]
members = ["fern_sim", "fern_img", "fern_video"]
fern_sim
这些包含了你的 crate 的子目录名。这些子目录中所有残存的 Cargo.lock
文件和 target
目录都需要删除。
完成这些操作后,任何 crate
中的 cargo build
都会自动在根目录下创建和共享构建目录。
cargo build --workspace
会构建当前工作空间中的所有 crate。cargo test
和 cargo doc
也能接受 --workspace
选项。
参考链接:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。