闭包
在函数中返回闭包:
-
为了从函数返回一些东西,Rust 需要知道返回类型的大小,而不能直接返回一个trait,因此可以返回一个Box指针
-
在函数中定义的变量放入闭包中返回,会因为移出作用域而失效,因此采用move关键字使得闭包获得它环境的所有权
1 | fn factory() -> Box<Fn(i32) -> i32> { |
通用函数调用
不同的trait中有同名函数:
1 | trait Foo { |
结构体本身和trait中有同名函数,使用 <Type as Trait>
指明用的是哪个trait中的函数:
1 | trait Foo { |
包(crate)和模块(module)
基本调用:
-
结构:
1
2
3
4
5foo
├── Cargo.toml
└── src
└── aaa.rs
└── main.rs -
aaa.rs
:1
2
3pub fn print_aaa() {
println!("{}", 25);
} -
main.rs
:1
2
3
4
5
6
7mod aaa;
use self::aaa::print_aaa;
fn main () {
print_aaa();
}
多层模块
- 优先查找
xxx.rs
文件main.rs
、lib.rs
、mod.rs
中的mod xxx
: 默认优先查找同级目录下的xxx.rs
文件;- 其他文件
yyy.rs
中的mod xxx
:默认优先查找同级目录的yyy
目录下的xxx.rs
文件;
- 如果
xxx.rs
不存在,则查找xxx/mod.rs
文件,即xxx
目录下的mod.rs
文件。
例子:
-
结构:
1
2
3
4
5
6
7
8
9src
├── a
│ ├── b
│ │ ├── c
│ │ │ ├── d.rs
│ │ │ └── mod.rs
│ │ └── mod.rs
│ └── mod.rs
└── main.rs -
a/b/c/d.rs
:1
2
3pub fn print_ddd() {
println!("i am ddd.");
} -
a/b/c/mod.rs
:1
pub mod d;
-
a/b/mod.rs
:1
pub mod c;
-
a/mod.rs
:1
pub mod b;
-
main.rs
:1
2
3
4
5
6
7mod a;
use self::a::b::c::d;
fn main() {
d::print_ddd();
}
**注:**若在 a/mod.rs
中进行重导出(Re-exporting),即:
1 | pub mod b; |
则可以直接在 main.rs
中使用 use a::d;
来调用
路径
self
的含义:
use self::xxx
表示,加载当前模块中的xxx
。此时 self 可省略;use xxx::{self, yyy}
,表示,加载当前路径下模块xxx
本身,以及模块xxx
下的yyy
;
super
表示父模块:use super::xxx;
表示引用父模块中的 xxx
数组转基本类型
将包含4个u8的数组转换为u32类型
1 | use std::mem; |
关联类型
将多个类型归为一类,如下,构建一个图(Graph),需要的类型包括点(Node)和边(Edge)
1 | trait Graph { |
宏
$开头的变量是语法元素,指示符用于限定愈发元素的类型,包括:
- ident: 标识符,用来表示函数或变量名
- expr: 表达式
- block: 代码块,用花括号包起来的多个语句
- pat: 模式,普通模式匹配(非宏本身的模式)中的模式,例如
Some(t)
,(3, 'a', _)
- path: 路径,注意这里不是操作系统中的文件路径,而是用双冒号分隔的限定名(qualified name),如
std::cmp::PartialOrd
- tt: 单个语法树
- ty: 类型,语义层面的类型,如
i32
,char
- item: 条目,
- meta: 元条目
- stmt: 单条语句,如
let a = 42;
重复
1 | macro_rules! vector { |
递归
1 | macro_rules! find_min { |
导入和导出
宏导入导出用 #[macro_use]
和 #[macro_export]
。父模块中定义的宏对其下的子模块是可见的,要想子模块中定义的宏在其后面的父模块中可用,需要使用 #[macro_use]
。
1 | macro_rules! m1 { () => (()) } |
crate 之间只有被标为 #[macro_export]
的宏可以被其它 crate 导入。假设上面例子是 foo
crate 中的部分代码,则只有 m2
和 m3
可以被其它 crate 导入。导入方式是在 extern crate foo;
前面加上 #[macro_use]
1 |
|
如果只想导入 foo
crate 中某个宏,比如 m3
,就给 #[macro_use]
加上参数
1 |
|
unsafe
- 解引用一个裸指针
*const T
和*mut T
1 | let x = 5; |
- 读写一个可变的静态变量
static mut
1 | static mut N: i32 = 5; |
- 调用一个不安全函数
1 | unsafe fn foo() { |
迭代器
消费者
-
collect()
1
let one_to_one_hundred = (1..101).collect::<Vec<i32>>();
-
find()
1
let greater_than_forty_two = (0..100).find(|x| *x > 42);
-
fold()
1
2// 计算从迭代器的第0个元素开始,所有元素的累加
let sum = (1..4).fold(0, |sum, x| sum + x);
迭代适配器
-
map
1
(1..100).map(|x| println!("{}", x));
-
take
1
2
3for i in (1..).take(5) {
println!("{}", i);
} -
filter
1
2
3for i in (1..100).filter(|&x| x % 2 == 0) {
println!("{}", i);
}
FFI
调用ffi函数
引入libc库
Cargo.toml
:
1 | [dependencies] |
rs文件:
1 | extern crate libc |
声明ffi函数
1 | use libc::c_int; |
调用ffi函数
由于此函数来自外部的c库,所以rust并不能保证该函数的安全性。因此,调用任何一个ffi
函数需要一个unsafe
块。
1 | let result: size_t = unsafe { |
封装unsafe,暴露安全接口
在一个叫ffi.rs
之类的文件中写上所有的extern块
用以声明ffi函数。在一个叫wrapper.rs
之类的文件中进行包装:
1 | // ffi.rs |
1 | // wrapper.rs |
数据结构对应
结构体/Enum
1 |
|
回调函数
将一个rust函数转变成c可执行的回调函数非常简单:在函数前面加上extern "C"
:
1 | extern "C" fn callback(a: c_int) { // 这个函数是传给c调用的 |
对应c库代码:
1 | typedef void (*rust_callback)(int32_t); |
字符串
CStr
对于产生于c的字符串(如在c程序中使用malloc
产生),rust使用CStr
来表示,和str
类型对应,表明我们并不拥有这个字符串
1 | use std::ffi::CStr; |
在这里get_string
使用CStr::from_ptr
从c的char*
获取一个字符串,并且转化成了一个String.
- 注意to_string_lossy()的使用:因为在rust中一切字符都是采用utf8表示的而c不是, 因此如果要将c的字符串转换到rust字符串的话,需要检查是否都为有效
utf-8
字节。to_string_lossy
将返回一个Cow<str>
类型, 即如果c字符串都为有效utf-8
字节,则将其0开销地转换成一个&str
类型,若不是,rust会将其拷贝一份并且将非法字节用U+FFFD
填充。
CString
和CStr
表示从c中来,rust不拥有归属权的字符串相反,CString
表示由rust分配,用以传给c程序的字符串。
1 | use std::ffi::CString; |
注意c字符串中并不能包含\0
字节(因为\0
用来表示c字符串的结束符),因此CString::new
将返回一个Result
, 如果输入有\0
的话则为Error(NulError)
。
空指针
另一种很常见的情况是需要一个空指针。请使用0 as *const _
或者 std::ptr::null()
来生产一个空指针。
将Rust编译成库
调用约定和mangle
由于rust支持重载,所以函数名会被编译器进行混淆,就像c++一样。因此当你的函数被编译完毕后,函数名会带上一串表明函数签名的字符串。
比如:fn test() {}
会变成_ZN4test20hf06ae59e934e5641haaE
. 这样的函数名为ffi调用带来了困难,因此,rust提供了#[no_mangle]
属性为函数修饰。 对于带有#[no_mangle]
属性的函数,rust编译器不会为它进行函数名混淆。
1 |
|
使用Any
由于在跨越ffi
过程中,rust
类型信息会丢失,比如当用rust
提供一个OpaqueStruct
给别的语言时:
1 | use std::mem::transmute; |
以上代码中完全不知道foo
是一个什么东西。安全也无从说起了,只能靠文档。 因此在ffi
调用时往往会丧失掉rust
类型系统带来的方便和安全。在这里提供一个小技巧:使用Box<Box<Any>>
来包装你的类型。
rust
的Any
类型为rust
带来了运行时反射的能力,使用Any
跨越ffi
边界将极大提高程序安全性。
1 | use std::any::Any; |
这样一来,就非常不容易出错了。
属性
属性只能应用于Item(元素、项), 例如 use
声明、模块、函数等。
在Rust中,Item是Crate(库)的一个组成部分。它包括
extern crate
声明use
声明- 模块(模块是一个Item的容器)
- 函数
type
定义- 结构体定义
- 枚举类型定义
- 常量定义
- 静态变量定义
- Trait定义
- 实现(Impl)
这些Item是可以互相嵌套的,比如在一个函数中定义一个静态变量、在一个模块中使用use
声明或定义一个结构体。这些定义在某个作用域里面的Item跟你把 它写到最外层作用域所实现的功能是一样的,只不过你要访问这些嵌套的Item就必须使用路径(Path),如a::b::c
。但一些外层的Item不允许你使用路径去访问它的子Item,比如函数,在函数中定义的静态变量、结构体等,是不可以通过路径来访问的。
属性语法
1 |
它是由一个#
开启,后面紧接着一个[]
,里面便是属性的具体内容,它可以有如下几种写法:
- 单个标识符代表的属性名,如
#[unix]
- 单个标识符代表属性名,后面紧跟着一个
=
,然后再跟着一个字面量(Literal),组成一个键值对,如#[link(name = "openssl")]
- 单个标识符代表属性名,后面跟着一个逗号隔开的子属性的列表,如
#[cfg(and(unix, not(windows)))]
在#
后面还可以紧跟一个!
,比如#![feature(box_syntax)]
,这表示这个属性是应用于它所在的这个Item。而如果没有!
则表示这个属性仅应用于紧接着的那个Item。
例如:
1 | // 为这个crate开启box_syntax这个新特性 |
Crate的属性
-
crate_name
- 指定Crate的名字。如#[crate_name = "my_crate"]
则可以让编译出的库名字为libmy_crate.rlib
。 -
crate_type
- 指定Crate的类型,有以下几种选择"bin"
- 编译为可执行文件;"lib"
- 编译为库;"dylib"
- 编译为动态链接库;"staticlib"
- 编译为静态链接库;"rlib"
- 编译为Rust特有的库文件,它是一种特殊的静态链接库格式,它里面会含有一些元数据供编译器使用,最终会静态链接到目标文件之中。
例
#![crate_type = "dylib"]
。 -
feature
- 可以开启一些不稳定特性,只可在nightly版的编译器中使用。 -
no_builtins
- 去掉内建函数。 -
no_main
- 不生成main
这个符号,当你需要链接的库中已经定义了main
函数时会用到。 -
no_start
- 不链接自带的native
库。 -
no_std
- 不链接自带的std
库。 -
plugin
- 加载编译器插件,一般用于加载自定义的编译器插件库。用法是1
2
3
4// 加载foo, bar两个插件
// 或者给插件传入必要的初始化参数 -
recursive_limit
- 设置在编译期最大的递归层级。比如自动解引用、递归定义的宏等。默认设置是#![recursive_limit = "64"]
模块的属性
-
no_implicit_prelude
- 取消自动插入use std::prelude::*
。 -
path
- 设置此mod
的文件路径。如声明
mod a;
,则寻找- 本文件夹下的
a.rs
文件 - 本文件夹下的
a/mod.rs
文件
1
2
3
4
5
6
7
mod sys;
mod sys; - 本文件夹下的
函数的属性
main
- 把这个函数作为入口函数,替代fn main
,会被入口函数(Entry Point)调用。plugin_registrar
- 编写编译器插件时用,用于定义编译器插件的入口函数。start
- 把这个函数作为入口函数(Entry Point),改写start
language item。test
- 指明这个函数为单元测试函数,在非测试环境下不会被编译。should_panic
- 指明这个单元测试函数必然会panic。cold
- 指明这个函数很可能是不会被执行的,因此优化的时候特别对待它。
1 | // 把`my_main`作为主函数 |
全局静态变量的属性
thread_local
- 只可用于static mut
,表示这个变量是thread local的。
FFI的属性
extern
块可以应用以下属性
-
link_args
- 指定链接时给链接器的参数,平台和实现相关。 -
link
- 说明这个块需要链接一个native库,它有以下参数:name
- 库的名字,比如libname.a
的名字是name
;kind
- 库的类型,它包括dylib
- 动态链接库static
- 静态库framework
- OS X里的Framework
1
2
3
4
5
6
7
8
9
extern {
}
extern {
}
在extern
块里面,可以使用
link_name
- 指定这个链接的外部函数的名字或全局变量的名字;linkage
- 对于全局变量,可以指定一些LLVM的链接类型( http://llvm.org/docs/LangRef.html#linkage-types )。
对于enum
类型,可以使用
repr
- 目前接受C
,C
表示兼容C ABI。
1 |
|
对于struct
类型,可以使用
repr
- 目前只接受C
和packed
,C
表示结构体兼容C ABI,packed
表示移除字段间的padding。
宏的属性
-
macro_use
- 把模块或库中定义的宏导出来-
应用于
mod
上,则把此模块内定义的宏导出到它的父模块中 -
应用于
extern crate
上,则可以接受一个列表,如1
2
extern crate log;则可以只导入列表中指定的宏,若不指定则导入所有的宏。
-
-
macro_reexport
- 应用于extern crate
上,可以再把这些导入的宏再输出出去给别的库使用。 -
macro_export
- 应于在宏上,可以使这个宏可以被导出给别的库使用。 -
no_link
- 应用于extern crate
上,表示即使我们把它里面的库导入进来了,但是不要把这个库链接到目标文件中。
其他属性
-
export_function
- 用于静态变量或函数,指定它们在目标文件中的符号名。 -
link_section
- 用于静态变量或函数,表示应该把它们放到哪个段中去。 -
no_mangle
- 可以应用于任意的Item,表示取消对它们进行命名混淆,直接把它们的名字作为符号写到目标文件中。 -
simd
- 可以用于元组结构体上,并自动实现了数值运算符,这些操作会生成相应的SIMD指令。 -
doc
- 为这个Item绑定文档,跟///
的功能一样,用法是1
2
struct Foo {}
条件编译属性
有时候,我们想针对不同的编译目标来生成不同的代码,比如在编写跨平台模块时,针对Linux和Windows分别使用不同的代码逻辑。
条件编译基本上就是使用cfg
这个属性,直接看例子
1 |
|
其中,cfg
可接受的条件有
debug_assertions
- 若没有开启编译优化时就会成立。target_arch = "..."
- 目标平台的CPU架构,包括但不限于x86
,x86_64
,mips
,powerpc
,arm
或aarch64
。target_endian = "..."
- 目标平台的大小端,包括big
和little
。target_env = "..."
- 表示使用的运行库,比如musl
表示使用的是MUSL的libc实现,msvc
表示使用微软的MSVC,gnu
表示使用GNU的实现。 但在部分平台这个数据是空的。target_family = "..."
- 表示目标操作系统的类别,比如windows
和unix
。这个属性可以直接作为条件使用,如#[unix]
,#[cfg(unix)]
。target_os = "..."
- 目标操作系统,包括但不限于windows
,macos
,ios
,linux
,android
,freebsd
,dragonfly
,bitrig
,openbsd
,netbsd
。target_pointer_width = "..."
- 目标平台的指针宽度,一般就是32
或64
。target_vendor = "..."
- 生产商,例如apple
,pc
或大多数Linux系统的unknown
。test
- 当启动了单元测试时(即编译时加了--test
参数,或使用cargo test
)。
还可以根据一个条件去设置另一个条件,使用cfg_attr
,如
1 |
这表示若a
成立,则这个就相当于#[cfg(b)]
。
条件编译属性只可以应用于Item,如果想应用在非Item中怎么办呢?可以使用cfg!
宏,如
1 | if cfg!(target_arch = "x86") { |
这种方式不会产生任何运行时开销,因为不成立的条件相当于里面的代码根本不可能被执行,编译时会直接被优化掉。
内联参数
内联函数即建议编译器可以考虑把整个函数拷贝到调用者的函数体中,而不是生成一个call
指令调用过去。这种优化对于短函数非常有用,有利于提高性能。
编译器自己会根据一些默认的条件来判断一个函数是不是应该内联,若一个不应该被内联的函数被内联了,实际上会导致整个程序更慢。
可选的属性有:
#[inline]
- 建议编译器内联这个函数#[inline(always)]
- 要求编译器必须内联这个函数#[inline(never)]
- 要求编译器不要内联这个函数
内联会导致在一个库里面的代码被插入到另一个库中去。
自动实现Trait
编译器提供一个编译器插件叫作derive
,它可以帮你去生成一些代码去实现(impl)一些特定的Trait,如
1 |
|
编译器会自动为你生成以下的代码
1 | impl<T: PartialEq> PartialEq for Foo<T> { |
目前derive
仅支持标准库中部分的Trait。
编译器rustc参数
Rust编译器程序的名字是rustc
,使用它的方法很简单:
1 | $ rustc [OPTIONS] INPUT |
其中,[OPTIONS]
表示编译参数,而INPUT
则表示输入文件。而编译参数有以下可选:
-
-h, --help
- 输出帮助信息到标准输出; -
--cfg SPEC
- 传入自定义的条件编译参数,使用方法如1
2
3
4
5fn main() {
if cfg!(hello) {
println!("world!");
}
}如上例所示,若
cfg!(hello)
成立,则运行程序就会输出"world"
到标准输出。我们把这个文件保存为hello.rs
然后编译它1
$ rustc --cfg hello hello.rs
运行它就会看到屏幕中输出了
world!
。 -
-L [KIND=]PATH
- 往链接路径中加入一个文件夹,并且可以指定这个路径的类型(Kind),这些类型包括dependency
- 在这个路径下找依赖的文件,比如说mod
;crate
- 只在这个路径下找extern crate
中定义的库;native
- 只在这个路径下找Native库;framework
- 只在OS X下有用,只在这个路径下找Framework;all
- 默认选项。
-
-l [KIND=]NAME
- 链接一个库,这个库可以指定类型(Kind)static
- 静态库;dylib
- 动态库;framework
- OS X的Framework。
如果不传,默认为
dylib
。此处举一个例子如何手动链接一个库,我们先创建一个文件叫
myhello.rs
,在里面写一个函数1
2
3
4
5
6
7// myhello.rs
/// 这个函数仅仅向标签输出打印 Hello World!
/// 不要忘记要把它标记为 pub 哦。
pub fn print_hello() {
println!("Hello World!");
}然后把这个文件编译成一个静态库,
libmyhello.a
1
$ rustc --crate-type staticlib myhello.rs
然后再创建一个
main.rs
,链接这个库并打印出"Hello World!"1
2
3
4
5
6
7
8
9// main.rs
// 指定链接库 myhello
extern crate myhello;
fn main() {
// 调用库函数
myhello::print_hello();
}编译
main.rs
1
$ rustc -L. -lmyhello main.rs
运行
main
,就会看到屏幕输出"Hello World!"啦。 -
--crate-type
- 指定编译输出类型,它的参数包括bin
- 二进行可执行文件lib
- 编译为库rlib
- Rust库dylib
- 动态链接库staticlib
- 静态链接库
-
--crate-name
- 指定这个Crate的名字,默认是文件名,如main.rs
编译成可执行文件时默认是main
,但你可以指定它为foo
1
$ rustc --crate-name foo main.rs
则会输出
foo
可执行文件。 -
--emit
- 指定编译器的输出。编译器默认是输出一个可执行文件或库文件,但你可以选择输出一些其它的东西用于Debugasm
- 输出汇编llvm-bc
- LLVM Bitcode;llvm-ir
- LLVM IR,即LLVM中间码(LLVM Intermediate Representation);obj
- Object File(就是*.o
文件);link
- 这个是要结合其它--emit
参数使用,会执行Linker再输出结果;dep-info
- 文件依赖关系(Debug用,类似于Makefile一样的依赖)。
以上参数可以同时使用,使用逗号分割,如
1
$ rustc --emit asm,llvm-ir,obj main.rs
同时,在最后可以加一个
=PATH
来指定输出到一个特定文件,如1
$ rustc --emit asm=output.S,llvm-ir=output.ir main.rs
这样会把汇编生成到
output.S
文件中,把LLVM中间码输出到output.ir
中。 -
--print
- 打印一些信息,参数有crate-name
- 编译目标名;file-names
- 编译的文件名;sysroot
- 打印Rust工具链的根目录地址。
-
-g
- 在目标文件中保存符号,这个参数等同于-C debuginfo=2
。 -
-O
- 开启优化,这个参数等同于-C opt-level=2
。 -
-o FILENAME
- 指定输出文件名,同样适用于--emit
的输出。 -
--out-dir DIR
- 指定输出的文件夹,默认是当前文件夹,且会忽略-o
配置。 -
--explain OPT
- 解释某一个编译错误,比如若你写了一个
main.rs
,使用了一个未定义变量f
1
2
3fn main() {
f
}编译它时编译器会报错:
1
2
3
4
5main.rs:2:5: 2:6 error: unresolved name `f` [E0425]
main.rs:2 f
^
main.rs:2:5: 2:6 help: run `rustc --explain E0425` to see a detailed explanation
error: aborting due to previous error虽然错误已经很明显,但是你也可以让编译器解释一下,什么是
E0425
错误:1
2$ rustc --explain E0425
// 编译器打印的说明 -
--test
- 编译成一个单元测试可执行文件 -
--target TRIPLE
- 指定目标平台,基本格式是cpu-manufacturer-kernel[-os]
,例如1
2## 64位OS X
$ rustc --target x86_64-apple-darwin -
-W help
- 打印Linter的所有可配置选项和默认值。 -
-W OPT, --warn OPT
- 设置某一个Linter选项为Warning。 -
-A OPT, --allow OPT
- 设置某一个Linter选项为Allow。 -
-D OPT, --deny OPT
- 设置某一个Linter选项为Deny。 -
-F OPT, --forbit OPT
- 设置某一个Linter选项为Forbit。 -
-C FLAG[=VAL], --codegen FLAG[=VAL]
- 目标代码生成的的相关参数,可以用-C help
来查看配置,值得关注的几个是linker=val
- 指定链接器;linker-args=val
- 指定链接器的参数;prefer-dynamic
- 默认Rust编译是静态链接,选择这个配置将改为动态链接;debug-info=level
- Debug信息级数,0
= 不生成,1
= 只生成文件行号表,2
= 全部生成;opt-level=val
- 优化级数,可选0-3
;debug_assertion
- 显式开启cfg(debug_assertion)
条件。
-
-V, --version
- 打印编译器版本号。 -
-v, --verbose
- 开启啰嗦模式(打印编译器执行的日志)。 -
--extern NAME=PATH
- 用来指定外部的Rust库(*.rlib
)名字和路径,名字应该与extern crate
中指定的一样。 -
--sysroot PATH
- 指定工具链根目录。 -
-Z flag
- 编译器Debug用的参数,可以用-Z help
来查看可用参数。 -
--color auto|always|never
- 输出时对日志加颜色auto
- 自动选择加还是不加,如果输出目标是虚拟终端(TTY)的话就加,否则就不加;always
- 给我加!never
- 你敢加?
Cargo.toml参数配置
[package]段落
啥也不多说了,直接上例子,大家注意我在例子中的中文解释,个人觉得这样比较一目了然:
1 | [package] |
依赖的详细配置
最直接的方式:
1 | [dependencies] |
与平台相关的依赖定义格式不变,不同的是需要定义在[target]字段下。例如:
1 | # 注意,此处的cfg可以使用not、any、all等操作符任意组合键值对。 |
自定义编译器调用方式模板详细参数
cargo内置五种编译器调用模板,分别为dev、release、test、bench、doc,分别用于定义不同类型生成目标时的编译器参数,如果我们自己想改变这些编译模板,可以自己定义相应字段的值,例如(注意:下述例子中列出的值均为此模板字段对应的系统默认值):
1 | # 开发模板, 对应`cargo build`命令 |
需要注意的是,当调用编译器时,只有位于调用最顶层的软件包的模板文件有效,其他的子软件包或者依赖软件包的模板定义将被顶层软件包的模板覆盖。
[features]段落
[features]段落中的字段被用于条件编译选项或者是可选依赖。例如:
1 | [package] |
如果其他软件包要依赖使用上述awesome软件包,可以在其描述文件中这样写:
1 | [dependencies.awesome] |
使用features时需要遵循以下规则:
- feature名称在本描述文件中不能与出现的软件包名称冲突
- 除了default feature,其他所有的features均是可选的
- features不能相互循环包含
- 开发依赖包不能包含在内
- features组只能依赖于可选软件包
features的一个重要用途就是,当开发者需要对软件包进行最终的发布时,在进行构建时可以声明暴露给终端用户的features,这可以通过下述命令实现:
1 | $ cargo build --release --features "shumway pdf" |
关于测试
当运行cargo test命令时,cargo将会按做以下事情:
- 编译并运行软件包源代码中被#[cfg(test)] 所标志的单元测试
- 编译并运行文档测试
- 编译并运行集成测试
- 编译examples
配置构建目标
所有的诸如[[bin]], [lib], [[bench]], [[test]]以及 [[example]]等字段,均提供了类似的配置,以说明构建目标应该怎样被构建。例如(下述例子中[lib]段落中各字段值均为默认值):
1 | [lib] |
no_std
OS提供系统调用来支持IO、网络、文件系统等的操作,Rust利用OS的特性来实现如发送消息到控制台、读取文件、打开URL等标准库(std)的操作。
若OS不提供这些系统调用,则这种特性被称为no_std,对于Rust的no_std我们只能使用不依赖OS的特性,如core crate
no_std与std的区别在于:
- no_std下,不能使用std crate,但能使用大部分core crate中的模块
- 不能使用与堆相关的模块(box, collections, string等),因为Rust内存分配机制默认依赖于OS系统调用实现堆的分配
- 如果写一个二进制crate,则必须实现一些lang items(rustc允许用户通过lang item来定制语言特性,而不是将所有操作都嵌入到编译器中)
https://justjjy.com/Rust-no-std
外部crate
lazy_static
给静态变量延迟赋值的宏。
使用这个宏,所有 static
类型的变量可在执行的代码在运行时被初始化。 这包括任何需要堆分配,如vector
或hash map
,以及任何非常量函数调用。
由于const
和static
在初始化时,必须赋予一个常量表达式的值,如果想要初始化动态的数组,vector,map,结果是编译不通过:
1 | static VEC:Vec<u8> = vec![0x18u8, 0x11u8]; |
使用 lazy_static 消除上面所有问题,在使用时需要解引用:
1 |
|
创建全局静态可变变量
1 | use std::{collections::HashMap, sync::Mutex}; |
使用:
1 | ENCLAVE_ID_MAP.lock().unwrap().insert(eid1, 1); |