为什么在取消引用的特征对象或切片上调用方法会编译?

Why does calling a method on a dereferenced trait object or slice compile?

给定以下代码:

trait Function {
    fn filter (&self);
}

#[derive(Debug, Copy, Clone)]
struct Kidney {}

impl Function for Kidney {
    fn filter (&self)  {
        println!("filtered");
    }  
}

fn main() {
    let k = Kidney {};
    let f: &Function = &k;
    //let k1 = (*f);   //--> This gives a "size not satisfied" error
    (*f).filter();     //--> Works; what exactly happens here?
}

我不确定为什么会编译。我原以为最后一条语句会失败。我想我在学习 Rust 时忽略了一些基础知识,因为我不明白为什么取消引用一个特征(存在于指针后面)应该编译。

这个问题是不是和下面的案例类似?

let v = vec![1, 2, 3, 4];
//let s: &[i32] = *v;
println!("{}", (*v)[0]);

*v 给出了一个切片,但是切片没有大小,所以我还是不清楚它是如何编译的。如果我取消注释第二条语句,我会得到

   |     let s:&[i32]= *v;
   |                   ^^
   |                   |
   |                   expected &[i32], found slice
   |                   help: consider borrowing here: `&*v`
   |
   = note: expected type `&[i32]`
              found type `[{integer}]`

expected type &[i32]是否意味着"expected a reference of slice"?

从第一段代码产生的MIR判断,(*f).filter()等价于f.filter();编译器似乎知道,由于 filter&self 上的一个方法,取消引用它没有任何作用,因此被完全省略。

然而,第二种情况不同,因为取消引用切片会引入 bounds-checking 代码。在我看来,编译器还应该能够判断此操作(取消引用)不会引入任何有意义的更改(and/or 不会出现 out-of-bounds 错误)并将其视为常规切片索引,但这背后可能有一些原因。

解除对特征对象的引用没有问题。事实上,它必须在某个时候被取消引用,否则它会毫无用处。

let k1 = (*f); 失败不是因为取消引用,而是因为您试图将原始特征对象放在堆栈上(这是局部变量所在的位置)。堆栈上的值必须具有在编译时已知的大小,特征对象不是这种情况,因为任何类型都可以实现该特征。

这是一个示例,其中具有不同大小的结构实现了特征:

trait Function {
    fn filter (&self);
}

#[derive(Debug, Copy, Clone)]
struct Kidney {}

impl Function for Kidney {
    fn filter (&self)  {
        println!("filtered");
    }  
}

#[derive(Debug, Copy, Clone)]
struct Liver {
    size: f32
}

impl Function for Liver {
    fn filter (&self)  {
        println!("filtered too!");
    }  
}

fn main() {
    let k = Kidney {};
    let l = Liver {size: 1.0};

    let f: &Function;
    if true {
        f = &k;
    } else {
        f = &l;
    }

    // Now what is the size of *f - Kidney (0 bytes) or Liver (4 bytes)?
}

(*f).filter(); 起作用是因为暂时取消引用的对象没有放在堆栈上。其实这和f.filter()是一样的。 Rust 会根据需要自动应用尽可能多的解引用以获取实际对象。这已记录在案 in the book

第二种情况发生的是Vec implements Deref到切片,所以它免费获得切片实现的所有方法。 *v 给你一个取消引用的切片,你将它分配给一个切片。这是一个明显的类型错误。