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

时间:2019-11-23 21:22:09

标签: rust

给出此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关于&dyn Trait&impl Trait之间的区别。我不确定何时使用它,但是由于我的程序确实使用&impl Trait进行编译(当使用上述类型注释时)似乎是安全的选择。

如果相反,我们将函数参数的类型设为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"

1 个答案:

答案 0 :(得分:3)

此:

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

只是语法糖:

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

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

Option<T>类型在内存中的表示方式取决于T类型的表示方式。函数foo的编译程序集与此相关。对于不同的T,所得程序集的外观可能会有所不同。 (例如,定义它是Some还是None的enum标签可以处于不同的字节偏移处。它可以使用不同的寄存器,可以决定是否展开循环,内联函数,向量化...。 ..)这就是静态分派的优势-即使您编写了很多抽象代码,也可以针对实际使用的具体类型对代码进行完全优化。

借助即将推出的specialization功能,您实际上可以为foo的不同子集手动编写T的不同实现,因此对于编译器来说,知道哪个{{1} }您在打电话。每个人对foo可能做不同的事情。


另一方面:

None

意味着有一个恰好功能fn foo(maybe_trait: Option<&dyn Trait>) -> String { ,该功能带有一个包含用于实现foo的某种类型的胖指针的Option。如果您在函数内的Trait上调用了某个方法,则该调用将通过动态分派进行。

由于只有一个函数maybe_trait,因此在使用foo时不必说任何有关类型的信息,

但是动态分配是有代价的-这个功能并未针对任何特定的None进行优化,而是动态地与每个T一起使用。