版本

Rust在推出了不兼容的更新之后,会发布一个edition,在cargo.toml里著名edition,编译的时候就按这个edition编译,所以程序依赖了旧的版本也不会有问题。甚至2015edition依赖了2018也没问题。
在cargo.toml里相应的段落可以添加编译参数

  1. [profile.release]
  2. debug = true # enable debug symbols in release builds

模块

pub(crate)声明让整个crate都可以用,但并不对crate外显示。
pub(super)声明让该项对其父模块可见。
pub(in <path>)让该项对指定的模块可见。

模块的私有项可以被该模块和该模块的子模块可见。

模块的结构分三种:

  1. 单独一个文件,模块名就是文件名,引用该模块的时候:mod .
  2. 一个目录是一个模块,模块名是目录名,子模块是目录里的文件,目录里要有一个文件叫mod.rs,这个文件里要写明要导出的子模块:pub mod ,这种情况不能存在和目录名相同的文件。
  3. 也是目录,但目录里不放mod.rs,而在目录同级放一个同名的文件:.rs。

use ::image::Pixels; 以::开始的导入都会被编译器认为是外部crate。

标准库自动导入的实际是use std::prelude::v1::*;
有一些库里会有模块叫prelude,但这个并不会像标准库一样自动导入,而是一个习惯告诉用户用*导入。

模块里可以声明常量和静态变量。常量的作用是编译器会把代码里用到该常量的地方替换成常量的值,静态变量是一个变量生命周期从程序的开始到结束,一般用作保存一个比较大的数据或者保存一个常量的引用。静态变量可以是mut的,但mut的静态变量只能在unsafe代码里使用。

lib和bin

编译器默认按文件结构决定是编译成库还是可执行文件,如果是有src/lib.rs则编译成库,项目里的模块都要通过这个跟节点对外暴露。
可执行文件出来放在src/main.rs,可以可以放在目录src/bin/下。bin下的每个rs文件都会编译成可执行程序。bin下每个程序也可以建一个目录,目录里的main.rs作为程序入口,其它文件作为其模块。无论是在那种可执行程序中引用lib.rs都要用本项目的名作为crate:use <项目名>::...
也可以把lib和可执行分成两个单独的项目,在引用lib的时候要在依赖中配置:

  1. [dependencies]
  2. fern_sim = { path = "../fern_sim" }

属性attributes

常用 #[cfg]配置项

#[cfg(…)] option Enabled when
test Tests are enabled (compiling with cargo test or rustc —test).
debug_assertions Debug assertions are enabled (typically in nonoptimized builds).
unix Compiling for Unix, including macOS.
windows Compiling for Windows.
target_pointer_width = “64” Targeting a 64-bit platform. The other possible value is “32”.
target_arch = “x86_64” Targeting x86-64 in particular. Other values: “x86”, “arm”, “aarch64”, “powerpc”, “powerpc64”, “mips”.
target_os = “macos” Compiling for macOS. Other values: “windows”, “ios”, “android”, “linux”, “freebsd”, “openbsd”, “netbsd”, “dragonfly”.
feature = “robots” The user-defined feature named “robots” is enabled (compiling with cargo build —feature robots or rustc —cfg feature=’”robots”‘). Features are declared in the [features]section ofCargo.toml
.
not(A) A is not satisfied. To provide two different implementations of a function, mark one with #[cfg(X)] and the other with #[cfg(not(X))].
all(A,B) Both A and B are satisfied (the equivalent of &&).
any(A,B) Either A or B is satisfied (the equivalent of ||).


有点属性只能设置在某个单个的item上,例如#[test]#[inline]。有些属性是可以设置在任何地方的,例如#[cfg]#[allow]。当使用#!,设置在main.rs或者lib.rs的时候,这个设置针对的是整个crate。有些属性只能用#!的形式设置例如#![feature]用来控制打开某个实验的特性。

测试

assert_eq!(v1, v2)和assert!(v1 == v2)的区别是,前者如果失败了会打印参数的值。
assert再release编译也有,如果只想debug用可以用debug_assert。

  1. #[test]
  2. #[allow(unconditional_panic, unused_must_use)]
  3. #[should_panic(expected="divide by zero")]
  4. fn test_divide_by_zero_error() {
  5. 1 / 0; // should panic!
  6. }

should_panic用来测试应该出错的用例,因为除以0是编译的时候能检查出来的错误,所以要用allow告诉编译器,允许我们这样做。
也可以用返回Result的形式判断对错。

如果测试的代码依赖了别的函数,这些函数在非test编译的时候会警告没被使用。所以一般当测试代码变得复杂的时候就把所有相关的代码放到一个单独的测试模块并标记#[cfg(test)]

默认Rust会用多线程同时跑多个测试,如果想要单线程测试就指定只跑一个测试,或者用参数让rust只用一个线程:cargo test -- --test-threads 1。默认Rust会只输出错误的测试结果,用参数可输出全部:cargo test -- --no-capture

集成测试:创建和src同级的目录test,该目录下的文件可以将项目当作一个外部crate来测试。cargo test会运行单元测试和集成测试,如果只运行集成测试使用cargo test --test unfurl,会运行test目录下的unfurl.rs里的测试。

文档

cargo doc --no-deps --open生产项目的文档,--no-deps指定不生成依赖的文档。--open让浏览器打开刚生成的文档。

三个斜线///开头的注释(被称为文档注释,doc comments)才会出现在生成的文档里。///等同于#[doc],这个属性在编译代码时没有任何作用,只在生成文档时有用。//!或者#![doc]用来给整个功能(feature,一般是一个模块或者crate)加文档。
文档的内容可以是markdown。markdown中的链接除了可以用正常的url,还可以用rust的引用路径。
#[doc(alias = “route”)]可以加搜索别名,在文档中可以按模块的真实名称的部分,或者别名进行搜索。
除了markdown的格式,缩进4格也会被当作代码渲染。

文档里的代码在cargo test的时候会被当作测试用例运行。实际上运行文档里代码的是rustdoc --test
在文档的代码里加上#(#和一个空格)可以隐藏一些琐碎的代码。rustdoc在运行文档里的代码的时候会把其放到一个main函数里单独编译成一个程序。但如果想要在文档里写一个完整的例子,已经包含了fn main那rustdoc就不会加任何代码了。
在代码块加no_run告诉rust编译但不运行,ignore告诉rust不编译。

依赖

image = { git = "[https://github.com/Piston/image.git",](https://github.com/Piston/image.git",) rev = "528f19c" }可以指定任意git repo的代码,并用rev,tag,branch等指明想要的版本。
image = { path = "vendor/image" }可以指定本地目录的代码。

cargo使用语义版本,但和semver标准不太一样:

  • 如果指定0.0,cargo不会找任何其它版本。
  • 如果指定0.x版本,cargo会找0.x.y中y最大的,x不会变。
  • 如果指定了大于1.0的,cargo会找兼容的最新版本。如:x.y如果x相同则认为兼容,会找y最大的。

版本没有完全按指定的找的一个原因是如果依赖的多个crate用了另外一个相同的crate A,如果两者指定的crate A的版本不一样,则不利于cargo编译。

多种指定版本的方法:

Cargo.toml line Meaning
image = “=0.10.0” Use only the exact version 0.10.0
image = “>=1.0.5” Use 1.0.5 or any higher version (even 2.9, if it’s available)
image = “>1.0.5 <1.1.9” Use a version that’s higher than 1.0.5, but lower than 1.1.9
image = “<=2.7.10” Use any version up to 2.7.10

但并不是每次编译cargo都会按照这个方式去找最新的版本。cargo在第一次编译的时候会生成Cargo.lock,如果不说明,cargo每次都会按Cargo.lock里的纪录的详细版本去编译。只有手动修改了cargo.toml里的版本,或者在运行cargo update的时候才会升级,升级也是在cargo.toml里指定的版本范围内升级。
使用git repo做依赖的时候也一样。假如用branch做为版本,build的时候并不会去拉去最新的代码,只有update才会。
一般来说,如果项目是可执行的程序,需要讲lock文件提交的版本控制,这样使用者可以编译出一样的程序。如果项目是一个库,则用户是不关心的,因为他们有自己的lock文件。如果项目是共享库,则这个库不是被cargo用户使用的,则需要提交。

发布到crate.io

workspace

workspace可以将相关联的多个项目的编译和依赖放到一起,节省编译时间和磁盘空间。
方法:在repo的根目录下新建一个Cargo.toml。

  1. [workspace]
  2. members = ["fern_sim", "fern_img", "fern_video"]

members就是各个项目的子目录。在任意子项目里cargo build都会在workspace里创建一个共享的target目录。cargo build —workspace会编译所有子项目。cargo test和cargo doc都有—workspace参数。