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.rs
cargo new hello_world --bin
- 如果是已经存在的目录
cargo init --bin
- 新建一个hello world库, 新建
src/lib.rs
cargo 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格式