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}");
}