为什么不能将一个实现了特定trait的struct分配给该trait绑定

8

我正在尝试理解Rust的多态性。从我的面向对象编程(OOP)背景来看,我期望以下Rust代码可以运行:

use std::io::{stdin, Read};

fn main() {
    let r: Read = stdin();
    println!("ok");
}

但实际上它并不会:
4 |     let r: Read = stdin();
  |                   ^^^^^^^ expected trait std::io::Read, found struct `std::io::Stdin`

我知道有一个适用于 StdInRead 实现,那么我该如何使其正常工作呢?换句话说,如何在需要使用 Read 的地方使用 StdinFile 或者如果可能的话是 String(找不到它的实现)?
我认为我不能在这里使用泛型,因为我需要将实现了 Readr 实例传递给后面的另一个方法,但如果我错了,请告诉我。
1个回答

10

你可能想在这里使用特质对象。你可以基本上用两种方式使用特质:

  • 静态分派: fn foo<T: Trait>(x: T)。这意味着"对于一个任意的,但是固定的实现了TraitT"。
  • 动态分派: fn foo(x: &Trait)。这通过使用特质对象消除了第一种版本中的"但是固定"限制。

如果你想要一个变量,它既可以持有对StdinFile或其他任何实现Read的东西的引用,那么你不能选择静态分派解决方案,因为你的实现类型不是固定的。


那么你的变量类型是什么呢?Read?很遗憾,情况并不那么简单。特质对象是不定长的,不能直接在堆栈上使用。相反,您只能通过引用/指针与特质对象交互,例如&Read&mut ReadBox<Read>等等。现在我们使用借用-东西,可能会遇到更多与此相关的问题。幸运的是,你不是第一个遇到这个问题的人:请参阅这个问题以详细了解这个特定用例。

简化一下,在大多数情况下,使用Box<Trait>是可以的。在您的情况下,它看起来像这样:

use std::io::{stdin, Read};

fn main() {
    let r: Box<Read> = Box::new(stdin());
    println!("ok");
}

我在考虑使用一个函数指针,指向Read::read方法,也许这样会更高效(并且对于字符串情况也适用)? - Renato
1
@Renato,你需要一个指向适当具体类型的Read :: read实现的指针,以及一个指向该具体类型特定数据的指针。这将是两个指针,Rust将它们粘合在一起并调用fat pointer,而a trait object is that。在链接的重复项中,有一个使用trait object reference&Read)的示例,这就是你所要求的。Box<Read>是一个boxed trait object - Shepmaster
2
Trait对象使用所谓的vtables来实现运行时多态。这些vtable包含指向所有trait方法的函数指针!因此,您不需要手动执行此操作。此外,LLVM知道vtable并可能执行优化。因此,鼓励使用trait对象。关于您的String问题:请使用Cursor<String>,它实现了Read - Lukas Kalbertodt
1
@Renato 即使现在有点跑题,但是请记住如何编写快速程序。1.正确性,2.测量,3.优化瓶颈。具体来说,这意味着:不要过早考虑微观优化。从文件或stdin准备可能比文件描述符的小堆分配要昂贵得多。关于Java风格的多态性:在编写Rust时尝试使用Java思维非常恼人,是的。但是不用担心,您很快就会习惯Rust的思维方式,然后一切都变得非常好和合理 :) - Lukas Kalbertodt
1
@Renato 说句实话,“Java风格的多态”并没有太大的区别:在Java中,每个对象都可能是一个引用,并且每个多态方法调用总是通过vtable进行。Java主要缺少的是&MyStruct(指向数据的指针)和&MyTrait(带有vtable的指向数据的指针)之间的区别。Rust只是让你更明确地处理额外的间接性。 - trent
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接