简体   繁体   English

为什么 Rust 编译器需要 Option<&impl Trait> 的类型注释?

[英]Why does the rust compiler require a type annotation for Option<&impl Trait>?

Given this MCVE:鉴于此 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();
}

The rust compiler is not happy: 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`.

Using type annotations makes this compile:使用类型注释使这个编译:

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

If we use Trait instead of Struct in the type annotation, there is a bit more information given to us:如果我们在类型注解中使用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`.

I understand this as "You shall not use a trait here, because then I do not know how much memory I need to allocate for this parameter".我将其理解为“您不应在这里使用特征,因为那样我不知道我需要为此参数分配多少内存”。

But why is that relevant when I am passing None ?但是当我通过None时,为什么这很重要?
Of course, passing any concrete instance of a type implementing Trait (ie Struct ) is okay to the compiler.当然,传递实现Trait (即Struct )的类型的任何具体实例对编译器来说都是可以的。


Sidenote:边注:
I have read this answer on the difference between &dyn Trait and &impl Trait .我已经阅读了有关&dyn Trait&impl Trait之间区别的答案 I'm unsure when to use which, but since my program does compile with &impl Trait (when using type annotations as above) it seems like the safe choice.我不确定何时使用 which,但由于我的程序确实使用&impl Trait编译(当使用上述类型注释时),它似乎是安全的选择。

If instead we make the function parameter be of type Option<&dyn Trait> , my program compiles without type annotations within main() :相反,如果我们使函数参数的类型为Option<&dyn Trait> ,我的程序将在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"

This:这个:

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

is just syntactic sugar for this:只是语法糖:

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

Which means that the compiler will generate many foo functions, one for every T (type implementing Trait ) that you are going to use it with.这意味着编译器将生成许多foo函数,每个T (实现Trait类型)一个。 So even if you call it with None , the compiler needs to know which is the T in that case, so it can pick/generate the right function.因此,即使您使用None调用它,编译器也需要知道在这种情况下哪个是T ,以便它可以选择/生成正确的函数。

The way Option<T> type is represented in memory depends on how the T type is represented. Option<T>类型在内存中的表示方式取决于T类型的表示方式。 The compiled assembly of the function foo depends on that.函数foo的编译程序集依赖foo For different T the resulting assembly may look differently.对于不同的T ,生成的组件可能看起来不同。 (For example the enum tag that defines whether it is Some or None may be at different byte offset. It may use different registers, it may decide differently whether to unroll loops, inline functions, vectorize, ...) This is the strength of static dispatch - even that you write code with lot of abstraction, you get code fully optimized for the concrete types you actually use. (例如,定义是Some还是None的 enum 标签可能处于不同的字节偏移量。它可能使用不同的寄存器,它可能会以不同的方式决定是否展开循环、内联函数、向量化……)这就是静态分派 - 即使您编写了大量抽象的代码,您也可以获得针对您实际使用的具体类型完全优化的代码。

With the upcoming specialization feature you can actually manually write different implementation of foo for different subsets of T , so it is really important for the compiler to know which foo are you calling.使用即将推出的专业化功能,您实际上可以为T不同子集手动编写不同的foo实现,因此编译器知道您正在调用哪个foo非常重要。 Each may do something different with None .每个人都可以用None做一些不同的事情。


On the other hand this:另一方面这个:

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

Means that there is exactly one function foo that takes Option containing a fat pointer to some type implementing Trait .意味着只有一个函数foo接受 Option ,该函数包含一个指向实现Trait某种类型的胖指针。 If you call some method on maybe_trait inside the function, the call goes thru dynamic dispatch.如果您在maybe_trait内部调用maybe_trait上的某个方法,该调用将通过动态调度。

Since there is exactly one function foo , you don't have to say anything about the type when using None , there is only one.因为只有一个函数foo ,所以在使用None时你不必说任何关于类型的信息,只有一个。

But dynamic dispatch comes at cost - this one function is not optimized for any specific T , it works with every T dynamically.但是动态调度是有代价的——这个函数没有针对任何特定的T进行优化,它动态地与每个T

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM