1. 测试和文档测试
rust原生支持测试, 还支持对文档中的example代码进行测试. https://www.cs.brandeis.edu/~cs146a/rust/doc-02-21-2015/book/testing.html
2. 详细解释crate和mod的文档
https://www.cs.brandeis.edu/~cs146a/rust/doc-02-21-2015/book/crates-and-modules.html
cargo build会根据约定俗成的规则来编译bin或者lib, 大体上是通过分析目录结构, 和特殊的文件名, 比如main.rs, lib.rs, mod.rs等.
$ tree .
.
├── Cargo.lock
├── Cargo.toml
├── src
│ ├── english
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ ├── japanese
│ │ ├── farewells.rs
│ │ ├── greetings.rs
│ │ └── mod.rs
│ └── lib.rs
└── target
├── deps
├── libphrases-a7448e02a0468eaa.rlib
└── native
3. 项目和模块
Rust用了两个概念来管理项目:一个是crate,一个是mod。
- crate简单理解就是一个项目。crate是Rust中的独立编译单元。每个 crate对应生成一个库或者可执行文件(如.lib .dll .so .exe等)。官方有一 个crate仓库https://crates.io/ ,可以供用户发布各种各样的库,用户也可 以直接使用这里面的开源库。
- mod简单理解就是命名空间。mod可以嵌套,还可以控制内部元素的可见性。
crate和mod有一个重要区别是:crate之间不能出现循环引用;而mod是无所谓的,mod1要使用mod2的内容,同时mod2要使用mod1的内容,是完全没问题的。在Rust里面,crate才是一个完整的编译单元 (compile unit)。也就是说,rustc编译器必须把整个crate的内容全部读进去才能执行编译,rustc不是基于单个的.rs文件或者mod来执行编译的。作为对比,C/C++里面的编译单元是单独的.c/.cpp文件以及它们所有的include文件。每个.c/.cpp文件都是单独编译,生成.o文件,再把这些.o文件链接起来。
3.1. cargo
Cargo是官方的项目管理工具
- 新建一个hello world工程, 新建
src/main.rscargo new hello_world --bin - 如果是已经存在的目录
cargo init --bin - 新建一个hello world库, 新建
src/lib.rscargo new hello_world --lib - 编译
cargo build --release
3.1.1. cargo.toml
举例:
[package]
name = "seccompiler"
version = "1.1.0"
authors = ["Amazon Firecracker team <firecracker-devel@amazon.com>"]
edition = "2018"
build = "../../build.rs"
description = "Program that compiles multi-threaded seccomp-bpf filters expressed as JSON into raw BPF programs, serializing them and outputting them to a file."
homepage = "https://firecracker-microvm.github.io/"
license = "Apache-2.0"
[[bin]]
name = "seccompiler-bin"
path = "src/seccompiler_bin.rs"
[dependencies]
bincode = "1.2.1"
libc = ">=0.2.39"
serde = { version = ">=1.0.27", features = ["derive"] }
serde_json = ">=1.0.9"
utils = { path = "../utils" }
The Cargo.toml file for each package is called its manifest. It is written in the TOML format. Every manifest file consists of the following sections:
参考: https://doc.rust-lang.org/cargo/reference/manifest.html
cargo-features— Unstable, nightly-only features.[package]— Defines a package.name— The name of the package.version— The version of the package.authors— The authors of the package.edition— The Rust edition.rust-version— The minimal supported Rust version.description— A description of the package.documentation— URL of the package documentation.readme— Path to the package's README file.homepage— URL of the package homepage.repository— URL of the package source repository.license— The package license.license-file— Path to the text of the license.keywords— Keywords for the package.categories— Categories of the package.workspace— Path to the workspace for the package.build— Path to the package build script.links— Name of the native library the package links with.exclude— Files to exclude when publishing.include— Files to include when publishing.publish— Can be used to prevent publishing the package.metadata— Extra settings for external tools.default-run— The default binary to run bycargo run.autobins— Disables binary auto discovery.autoexamples— Disables example auto discovery.autotests— Disables test auto discovery.autobenches— Disables bench auto discovery.resolver— Sets the dependency resolver to use.
- Target tables: (see configuration for settings)
[lib]— Library target settings.[[bin]]— Binary target settings.[[example]]— Example target settings.[[test]]— Test target settings.[[bench]]— Benchmark target settings.
- Dependency tables:
[dependencies]— Package library dependencies.[dev-dependencies]— Dependencies for examples, tests, and benchmarks.[build-dependencies]— Dependencies for build scripts.[target]— Platform-specific dependencies.
[badges]— Badges to display on a registry.[features]— Conditional compilation features.[patch]— Override dependencies.[replace]— Override dependencies (deprecated).[profile]— Compiler settings and optimizations.[workspace]— The workspace definition.
3.1.2. toml语法
cargo.toml是toml语法:
[package]是toml table的语法, 是hash map类型- inline table的语法是
name = { first = "Tom", last = "Preston-Werner" }
- inline table的语法是
- 还有双方括号的用法, 比如
[[products]]
name = "Hammer"
sku = 738594937
[[products]] # empty table within the array
[[products]]
name = "Nail"
sku = 284758393
color = "gray"
相当于json的
{
"products": [
{ "name": "Hammer", "sku": 738594937 },
{ },
{ "name": "Nail", "sku": 284758393, "color": "gray" }
]
}
3.2. 错误处理
比如使用Option表示some和none两种可能, 返回Result既有值又有错误
impl str {
pub fn find<'a, P: Pattern<'a>>(&'a self, pat: P) -> Option<usize> {}
}
impl File {
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<File> {}
}
比如
use std::mem::{size_of, size_of_val};
use std::str::FromStr;
use std::string::ParseError;
fn main() {
let r: Result<String, ParseError> = FromStr::from_str("hello");
println!("Size of String: {}", size_of::<String>());
println!("Size of `r`: {}", size_of_val(&r));
}
3.3. 问号运算符
问号运算符意思是,如果结果是Err,则提前返回一个Result类型,否则继续执行。 标准库的Option、Result实现了问号需要的trait
fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, String> {
let mut file = File::open(file_path).map_err(|e| e.to_string())?;
let mut contents = String::new();
file.read_to_string(&mut contents)
.map_err(|err| err.to_string())?;
let n = contents
.trim()
.parse::<i32>()
.map_err(|err| err.to_string())?;
Ok(2 * n)
}
进一步简化:
use std::fs::File;
use std::io::Read;
use std::path::Path;
fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, Box<dyn std::error::Error>> {
let mut file = File::open(file_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;
let n = contents.trim().parse::<i32>()?;
Ok(2 * n)
}
fn main() {
match file_double("foobar") {
Ok(n) => println!("{}", n),
Err(err) => println!("Error: {:?}", err),
}
}
跟其他很多运算符一样,问号运算符也对应着标准库中的一个trait std::ops::Try
trait Try {
type Ok;
type Error;
fn into_result(self) -> Result<Self::Ok, Self::Error>;
fn from_error(v: Self::Error) -> Self;
fn from_ok(v: Self::Ok) -> Self;
}
3.4. 和C的ABI兼容
Rust有一个非常好的特性,就是它支持与C语言的ABI兼容. 所以,我们可以用Rust写一个库,然后直接把它当成C写的库来使用。或者反过来,用C写的库,可以直接在Rust中被调用。而且这个过程是没有额外性能损失的。C语言的ABI是这个世界上最通用的ABI,大部分编程语言都支持与C的ABI兼容。这也意味着,Rust与其他语言之间的交互是没问题的,比如用Rust为Python/Node.js/Ruby写一个模块等。
rust编译选项有--crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]
其中,cdylib和staticlib就是与C的ABI兼容的
Rust 中有泛型,C语言里面没有,所以泛型这种东西是不可能暴露出来给C语言使用的,这就不是C语言的ABI的一部分。只有符合C语言的调用方式的函数,才能作为FFI的接口。这样的函数有以下基本要求:
- 使用
extern "C"修饰,在Rust中extern fn默认等同于extern "C" fn; - 使用
#[no_mangle]修饰函数,避免名字重整; - 函数参数、返回值中使用的类型,必须在Rust和C里面具备同样的内存布局。
3.4.1. 从C调用Rust库
假设我们要在Rust中实现一个把字符串从小写变大写的函数,然后 由C语言调用这个函数: 在Rust侧:
#[no_mangle]
pub extern "C" fn rust_capitalize(s: *mut c_char) {
unsafe {
let mut p = s as *mut u8;
while *p != 0 {
let ch = char::from(*p);
if ch.is_ascii() {
let upper = ch.to_ascii_uppercase();
*p = upper as u8;
}
p = p.offset(1);
}
}
}
我们在Rust中实现这个函数,考虑到C语言调用的时候传递的是char*类型,所以在Rust中我们对应的参数类型是*mut std::os:: raw::c_char。这样两边就对应起来了。
用下面的命令生成一个c兼容的静态库:
rustc --crate-type=staticlib capitalize.rs
在C里面调用:
#include <stdlib.h>
#include <stdio.h>
// declare
extern void rust_capitalize(char *);
int main() {
char str[] = "hello world";
rust_capitalize(str);
printf("%s\n", str);
return 0;
}
用下面的命令链接:
gcc -o main main.c -L. -l:libcapitalize.a -Wl,--gc-sections -lpthread -ldl
3.4.2. 从Rust调用C库
比如c的函数
int add_square(int a, int b)
{
return a * a + b * b;
}
在rust里面调用:
use std::os::raw::c_int;
#[link(name = "simple_math")]
extern "C" {
fn add_square(a: c_int, b: c_int) -> c_int;
}
fn main() {
let r = unsafe { add_square(2, 2) };
println!("{}", r);
}
//编译:
//rustc -L . call_math.rs
3.5. 文档
特殊的文档注释 是///、//!、/**…*/、/*!…*/,它们会被视为文档
mod foo {
//! 这块文档是给 `foo` 模块做的说明
/// 这块文档是给函数 `f` 做的说明
fn f() {
// 这块注释不是文档的一部分
}
}
文档还支持markdown格式