Rust潜规则
一些类型系统之外的规则,比如Box::new()
是在堆上创建变量,但如果只看函数签名pub fn new(x: T) -> Self
,其实是看不出来这个规则的,这些规则只会在文档中说明。
此外,所有 unsafe 都需要程序员给予某些保证,因此有些关于unsafe的内容可能也会在本文中出现。
1. memory
1.1 Box
虽然Box本身看上去好像只是普通的struct,但其实非常特殊。
Box<T>
会在堆上申请内存,在其drop函数中会通过 dealloc 释放内存,且释放前会调用 T 的drop函数.
Box<T>
有DerefMove语义,详见 https://manishearth.github.io/blog/2017/01/10/rust-tidbits-box-is-special/
1.2 ptr
- read 无论 T 有没有实现Copy,都会按位拷贝数据,要小心double free的问题,比如下面
fn main() {
let s = String::from("hello");
unsafe {
let s1: String = ptr::read(&s);
println!("{}", s1); // print "hello"
}
// free(): double free detected in tcache 2
// 因为String中的数据实际是一个u8指针数组,所以同一个指针被free了两次
}
也不是所有时候都要考虑这种指向同一数据的问题,即使没有实现Copy,比如
struct Node {
val: i32,
}
fn main() {
let s = Node{val: 10};
unsafe {
let mut s1: Node = ptr::read(&s);
println!("{} {}", s.val, s1.val); // 10 10
s1.val = 11;
println!("{} {}", s.val, s1.val); // 10 11
}
}
alloc
GlobalAlloc不允许size为0的layout,视为UB,但不是所有的alloc都如此,需要看注释。但是一般指针都操作都允许ZST,但要保证对齐。
2. 类型匹配
2.1 ? operator
在Result<T, E>结尾加?,如果是个error e, 并不只是简单return E(e),而是return E(From:from(e))
,所以下面代码可以编译通过
struct MyError {}
impl From<ParseIntError> for MyError {
fn from(value: ParseIntError) -> Self {
MyError{}
}
}
fn f() -> Result<i32, MyError> {
let z: i32 = "a".parse()?; // 会有类型转换
Ok(z)
}
2.2 trait object lifetime
下面的代码可以通过编译
trait Module {}
struct Foo {
module_box: Box<dyn Module + 'static>,
}
impl Foo {
fn mut_box<'s>(&'s mut self) -> &'s mut (dyn Module + 's) {
// this works
self.module_box.as_mut() // : &'s mut (dyn Module + 'static)
}
}
但是根据显式规则,&mut T 关于 T 是 invariant 的,但是这里(dyn Module + 'static)
可以被当做 (dyn Module + 's)
用,详见https://github.com/rust-lang/rust/issues/108999
2.3 Coercions
coercions 可能会和variance有点混淆,coercion实际类似于隐式类型转换,且这种转换与子类型无关。例如下面
fn f(_i : &i32) {
}
fn main() {
let mut x = 12;
f(&mut x); // &mut i32 被转成 &i32
}
更多转换规则见上面链接。
2.4 Method-call
编译器选择执行哪些method是有顺序的,简单说是先 T
,&T
, &mut T
然后不断dereference T
,对得到的V = deref T
再尝试V
, &V
, &mut V
依次类推。到最后尝试Unsized Coercions 。 可以看https://dtolnay.github.io/rust-quiz/31
3. lifetime
3.1 drop
除了熟知的在函数体内先声明的后drop等规则外,要注意下面 let _
语句会立即调用drop函数,所以要避免类似 let _ = xx.lock()
的写法
fn main() {
let _ = MyStruct{}; // 会立即调用MyStruct::drop()
xxx;
}
3.2 temporary lifetime extension
允许类似这样的语法,下面可以编译成功,String生命周期在block结束
{
let x = &String::new();
}
https://doc.rust-lang.org/reference/destructors.html#temporary-lifetime-extension
super let blog https://blog.m-ou.se/super-let/
if let Some(x) = vec.lock().unwrap().pop() {
// The mutex is still locked here. :(
// This is unnecessary, because we don't borrow anything from the `Vec`. (`x` is a `T`)
println!("popped item from the vec: {x}");
}