Rust Cheat Sheet in Practice
这篇博客主要记录一下,在实际的Rust编程过程中,会涉及到的一些Rust语言相关知识。
1. ()
unit type
()
类似C语言中的void fn func(){}
和fn func()->(){}
是一样的 分号;
可以丢弃块末尾的表达式结果,使表达式(以及块)计算为 ()
fn returns_i64() -> i64 {
1i64
}
let is_i64 = {
returns_i64()
};
let is_unit = {
returns_i64();
};
2. Option type vs. Result type
// Rust中使用Option类型来处理缺失值
enum Option<T> {
None,
Some(T),
}
//Result是加强版Option,可以用来处理错误值--unexpected value(缺失值可以理解为特殊的一种错误值)
enum Result<T, E> {
Ok(T),
Err(E),
}
Option
和Result
的关系相当于type Option<T> = Result<T, ()>;
例子:
fn double_number(number_str: &str) -> Result<i32, ParseIntError> {
match number_str.parse::<i32>() {
Ok(n) => Ok(2 * n),
Err(err) => Err(err),
}
}
3. unwrap
vs. expect
vs. ?
这个知识点涉及Rust中的error handling,要明白unwrap,expect和“?”操作符号,需要知道Rust中的两个类型Option
和Result
(见2)
Method | Behaviour |
---|---|
unwrap | 返回计算结果,如果出现error,就通过panic 终止程序 |
expect | 返回计算结果,如果出现error,就通过panic 终止程序,且可以自定义panic message |
? | 返回计算结果,如果出现error,返回/传递error而不是终止程序 |
通过一个例子来说明
// File::open returns Result: Ok(file) or Err(error)
// Unwrap means:
// - "if result is Ok: store value inside enum in `file`
// - "if result is Err (opening file failed): panic (crash program)"
// Panic if opening a file fails:
let mut file = File::open(filename).unwrap();
// expect is the same as unwrap but allows you to print a
// more descriptive error message when panicking.
let mut file = File: :open(filename).expect ("Failed to open file");
// ? means:
// - "if result is Ok: store value inside enum in `file`
// - "if result is Err (opening file failed): return Err(error) to caller"
let mut file = File::open(filename)?;
对于Option
类型来说,默认的unwrap
相当于
impl<T> Option<T> {
fn unwrap(self) -> T {
match self {
Option::Some(val) => val,
Option::None =>
panic!("called `Option::unwrap()` on a `None` value"),
}
}
}
unwrap_or
在Rust标准库中表示,出现缺失值时,把缺失值设置为默认值
对于Result
类型来说,默认的unwrap
相当于
impl<T, E: ::std::fmt::Debug> Result<T, E> {
fn unwrap(self) -> T {
match self {
Result::Ok(val) => val,
Result::Err(err) =>
panic!("called `Result::unwrap()` on an `Err` value: {:?}", err),
}
}
}
4. as_ref()
vs. &
reference
类型 | 功能 | |
---|---|---|
as_ref() | Option::as_ref method | 把&Option<T> 转换为 Option<&T> |
Result::as_ref method | 把&Result<T,E> 转化为 Result<&T, &E> | |
对Box | 把Box<T> 转化为&T | |
AsRef::<Target>::as_ref trait implementation | 如果类型U 实现了AsRef<T> ,则as_ref 可以实现&U 到&T 的转换 | |
& | 对Option | 把Option<T> 转换为 &Option<T> |
对Result | 把Result<T,E> 转化为 &Result<T,E> | |
对Box | 把Box<T> 转化为&T | |
取地址运算符 | 只能将类型T 转换为其自身的引用类型&T |
Further reading:
5. &str
vs. String
, &[]
vs. Vec
请查看Rust裸指针,引用,slice,box,vec,string的内存布局及转换这篇博客。
6. as vs. from/Into
https://www.lurklurk.org/effective-rust/casts.html
7. pub extern “C” & #[no_mangle]
通过 extern
来说明跨语言的函数调用
提供Rust函数给其他语言调用
#[no_mangle]
pub extern "C" fn call_from_c() {
println!("Just called a Rust function from C!");
}
#[no_mangle]
告诉Rust编译器不要混淆函数名,以便在C语言中调用时可以找到对应的函数。
mangle是指Rust编译器会改变函数的名字,在其中加入更多的信息变得不可读。
比如dragonos_kernel::syscall::Syscall::handle ()
这样一个函数名会被编译器编译为_ZN15dragonos_kernel7syscall7Syscall6handle17hd25633fe74647baeE
在Rust中调用其他语言函数
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
8. core.rs vs. lib.rs vs. mod.rs vs. main.rs
If a package contains src/main.rs
and src/lib.rs
, it has two crates: a library and a binary, both with the same name as the package.
9. 智能指针–Rc, Arc, Weak
Rust 规则 | 智能指针带来的额外规则 |
---|---|
一个数据只有一个所有者 | Rc/Arc 让一个数据可以拥有多个所有者 |
要么多个不可变借用,要么一个可变借用 | RefCell 实现编译期可变、不可变引用共存 |
违背规则导致编译错误 | 违背规则导致运行时panic |
智能指针往往都实现了 Deref
和 Drop
特征
-
Deref
可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如*T
-
Drop
允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作
Box<T>
将一个值分配到堆上
enum List {
Cons(i32, Box<List>),
Nil,
}
fn main() {
let a = Box::new(3);
println!("a = {}", a); // a = 3
}
Rc<T>
reference counting,即统计指向堆上变量的指针数量。允许一个数据资源在同一时刻拥有多个所有者,适用于单线程,指向底层数据的不可变的引用,因此你无法通过它来修改数据
Rc::clone就是克隆一个Rc指针,指向的同一个底层数据
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("test ref counting"));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Rc::clone(&a);
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Rc::clone(&a);
println!("count after creating c = {}", Rc::strong_count(&c));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
-
Rc<T>
是一个智能指针,实现了Deref
特征,因此你无需先解开Rc
指针,再使用里面的T
,而是可以直接使用T
,例如上例中的gadget1.owner.name
Arc<T>
atomic reference counting 允许一个数据资源在同一时刻拥有多个所有者,适用于多线程,同样Arc也是指向底层数据的不可变的引用
在多线程编程中,Arc
跟 Mutex
锁的组合使用非常常见,它们既可以让我们在不同的线程中共享数据,又允许在各个线程中对其进行修改。
use std::sync::{Arc, Mutex};
fn main() {
let my_number = Arc::new(Mutex::new(0));
let mut handle_vec = vec![]; // JoinHandles will go in here
for _ in 0..2 { // do this twice
// Make the clone before starting the thread
let my_number_clone = Arc::clone(&my_number);
let handle = std::thread::spawn(move || { // Put the clone in
for _ in 0..10 {
*my_number_clone.lock().unwrap() += 1;
}
});
// save the handle so we can call join on it outside of the loop
// If we don't push it in the vec, it will just die here
handle_vec.push(handle);
}
handle_vec.into_iter().for_each(|handle| handle.join().unwrap()); // call join on all handles
println!("{:?}", my_number);
}
This looks complicated but Arc<Mutex<SomeType>>>
is used very often in Rust, so it becomes natural.
Weak<T>
Weak
相当于一种不持有所有权的Rc
,它仅仅保存一份指向数据的弱引用,用来解决循环引用(reference cycle)的问题。比如,如果a中包含一个Rc指向b,b中也包含一个Rc指向a,这样引用计数就不可能变成0,最终就会导致内存泄漏的发生。如果使用Weak Rc,那么当一个内存只有弱引用指向时,改内存会被释放。
-
Rc<T>
调用downgrade
方法转换成Weak<T>
-
Weak<T>
可使用upgrade
方法转换成Option<Rc<T>>
,如果资源已经被释放,则Option
的值是None
对于父子引用关系,可以让父节点通过 Rc
来引用子节点,然后让子节点通过 Weak
来引用父节点。
use std::rc::Rc;
fn main(){
let five = Rc::new(5);
// 通过Rc,创建一个Weak指针
let weak_five = Rc::downgrade(&five);
// 通过Weak引用访问数据,需要通过upgrade实现,返回一个类型为 Option<Rc<T>>
let strong_five: Option<Rc<_>> = weak_five.upgrade();
assert_eq!(*strong_five.unwrap(), 5);
// 手动释放资源`five`
drop(five);
// Weak引用的资源已不存在,因此返回None
let strong_five: Option<Rc<_>> = weak_five.upgrade();
assert_eq!(strong_five, None);
}
Also, you use Rc::weak_count(&item)
to see the weak count.
对比Weak, Rc, Arc
Weak | Rc | Arc |
---|---|---|
不计数 | 引用计数 | 引用计数 |
不拥有所有权 | 拥有值的所有权 | 拥有值的所有权 |
不阻止值被释放(drop) | 所有权计数归零,才能 drop | 所有权计数归零,才能 drop |
引用的值存在返回 Some ,不存在返回 None | 引用的值必定存在 | 引用的值必定存在 |
通过 upgrade 取到 Option<Rc<T>> ,然后再取值 | 通过 Deref 自动解引用,取值无需任何操作 | 通过 Deref 自动解引用,取值无需任何操作 |
/ | 单线程 | 多线程 |
10. 智能指针–Cell, RefCell, Ref, RefMutW
通过Cell<T>
以及RefCell<T>
可以在拥有不可变引用的同时修改目标数据–即对一个不可变的值进行可变借用,也称为内部可变性(内部通过unsafe实现,不安全代码都被封装到了安全的 API )。
-
Cell<T>
适用于T
实现Copy
特征的值类型,比如&str, u8等 -
RefCell
用于引用,实现编译期可变、不可变引用共存
// Cell 的使用
let c = Cell::new(5);
let five = c.get();
c.set(10);
在 Rust 中,一个常见的组合就是 Rc
和 RefCell
在一起使用,前者可以实现一个数据拥有多个所有者,后者可以实现数据的可变性,从而实现多个所有者共享同一份数据。
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let s = Rc::new(RefCell::new("我很善变,还拥有多个主人".to_string()));
let s1 = s.clone();
let s2 = s.clone();
// let mut s2 = s.borrow_mut();
s2.borrow_mut().push_str(", oh yeah!");
println!("{:?}\n{:?}\n{:?}", s, s1, s2);
// Output:
//RefCell { value: "我很善变,还拥有多个主人, oh yeah!" }
//RefCell { value: "我很善变,还拥有多个主人, oh yeah!" }
//RefCell { value: "我很善变,还拥有多个主人, oh yeah!" }
}
Ref<T>
Wraps a borrowed reference to a value in a RefCell
box. A wrapper type for an immutably borrowed value from a RefCell<T>
.
RefMut<T>
Mutex<T>
11. clone vs. copy
12. Path
vs. PathBuf
10. dyn
dyn
is a prefix of a trait object’s type. The dyn
keyword is used to highlight that calls to methods on the associated Trait
are dynamically dispatched. To use the trait this way, it must be ‘object safe’. 相当于动态绑定
Reference
Enjoy Reading This Article?
Here are some more articles you might like to read next: