【问题标题】:Why does the rust compiler require a type annotation for Option<&impl Trait>?为什么 rust 编译器需要 Option<&impl Trait> 的类型注解?
【发布时间】:2019-11-23 21:22:09
【问题描述】:

鉴于此 MCVE:

fn main() {
    println!("{}", foo(None));
}

trait Trait {}
struct Struct {}
impl Trait for Struct {}

fn foo(maybe_trait: Option<&impl Trait>) -> String {
    return "hello".to_string();
}

rust 编译器不高兴:

error[E0282]: type annotations needed
 --> src\main.rs:2:20
  |
2 |     println!("{}", foo(None));
  |                    ^^^ cannot infer type for `impl Trait`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0282`.

使用类型注释可以编译:

fn main() {
    let nothing: Option<&Struct> = None;
    println!("{}", foo(nothing));
}

trait Trait {}
struct Struct {}
impl Trait for Struct {}

fn foo(maybe_trait: Option<&impl Trait>) -> String {
    return "hello".to_string();
}

如果我们在类型注解中使用Trait 而不是Struct,我们会得到更多信息:

warning: trait objects without an explicit `dyn` are deprecated
 --> src\main.rs:2:26
  |
2 |     let nothing: Option<&Trait> = None;
  |                          ^^^^^ help: use `dyn`: `dyn Trait`
  |
  = note: #[warn(bare_trait_objects)] on by default

error[E0277]: the size for values of type `dyn Trait` cannot be known at compilation time
 --> src\main.rs:3:20
  |
3 |     println!("{}", foo(nothing));
  |                    ^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `dyn Trait`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
note: required by `foo`
 --> src\main.rs:10:1
  |
10| fn foo(maybe_trait: Option<&impl Trait>) -> String {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

我将其理解为“您不应在此处使用特征,因为那时我不知道需要为该参数分配多少内存”。

但是当我通过 None 时,为什么这有关系?
当然,传递实现Trait(即Struct)的类型的任何具体实例对编译器来说都是可以的。


旁注:
我已经阅读了this answer 关于&amp;dyn Trait&amp;impl Trait 之间的区别。我不确定何时使用哪个,但由于我的程序确实使用 &amp;impl Trait 编译(当使用上述类型注释时),它似乎是安全的选择。

如果我们将函数参数设为Option&lt;&amp;dyn Trait&gt; 类型,我的程序将在main() 中不带类型注释的情况下编译:

fn main() {
    println!("{}", foo(None));
}

trait Trait {}
struct Struct {}
impl Trait for Struct {}

fn foo(maybe_trait: Option<&dyn Trait>) -> String {
    return "hello".to_string();
}

$ cargo --version
cargo 1.37.0 (9edd08916 2019-08-02)  

$ cat Cargo.toml
[package]
name = "rdbug"
version = "0.1.0"
authors = ["redacted"]
edition = "2018"

【问题讨论】:

  • 这是因为编译器必须推断类型,即使只有一个可以猜测,它也无法猜测它们(除了某些特定的不包括这个场景)。 None 可以是 Option&lt;Struct&gt;::NoneOption::&lt;AnotherTypeImplementingTrait&gt;::None。参数位置的impl trait 与泛型一样工作,因此编译器必须知道要实例化的foo 的版本,None 可以是任何东西。
  • 这很重要,Option 的大小取决于实现 trait 的类型。
  • 我认为你错过了 foo::HugeThing 的行为可能与 foo::OtherThing 不同 即使通过 None,编译器必须知道使用正确的.
  • (此外,并非所有引用的大小都相同,因为对未指定大小类型的引用是胖指针,但即使是胖指针,编译器仍然不会为您选择类型。)

标签: rust


【解决方案1】:

这个:

fn foo(maybe_trait: Option<&impl Trait>) -> String {

只是语法糖:

fn foo<T: Trait>(maybe_trait: Option<&T>) -> String {

这意味着编译器将生成许多foo 函数,每个T(实现Trait 的类型)对应一个您要使用它的函数。因此,即使您使用 None 调用它,编译器也需要知道在这种情况下哪个是 T,以便它可以选择/生成正确的函数。

Option&lt;T&gt; 类型在内存中的表示方式取决于T 类型的表示方式。函数foo 的编译程序集依赖于此。对于不同的T,生成的程序集可能看起来不同。 (例如,定义它是Some 还是None 的枚举标记可能位于不同的字节偏移量。它可能使用不同的寄存器,它可能以不同的方式决定是否展开循环、内联函数、矢量化......)这是静态调度的优势——即使你编写了很多抽象的代码,你也会得到针对你实际使用的具体类型完全优化的代码。

借助即将推出的specialization 功能,您实际上可以为T 的不同子集手动编写foo 的不同实现,因此编译器知道您调用的是哪个foo 非常重要。每个人都可以使用None 做不同的事情。


另一方面:

fn foo(maybe_trait: Option<&dyn Trait>) -> String {

意味着恰好有一个函数foo接受包含指向实现Trait的某种类型的胖指针的Option。如果你在函数内部调用maybe_trait 的某个方法,调用会通过动态调度进行。

由于foo只有一个函数,所以在使用None时就不用说类型了,只有一个。

但是动态调度是有代价的——这个函数没有针对任何特定的T进行优化,它可以动态地与每个T一起工作。

【讨论】:

  • 谢谢!与@trenctl 的 cmets 一起,这是完美的。我会在接受之前稍等片刻,这样他也有机会写一个答案,因为他也解释了一些好点
  • @lucidbrot 谢谢,但除非我的 cmets 中有一些重要的东西你认为在这个答案中没有解释,否则我会让 michalsrb 有复选标记 :) 很高兴我能提供帮助!
  • 这终于让我点击了,很好的答案。
猜你喜欢
  • 1970-01-01
  • 2021-02-01
  • 2019-03-20
  • 1970-01-01
  • 1970-01-01
  • 2021-04-11
  • 1970-01-01
相关资源
最近更新 更多