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

luc*_*rot 4 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();
}
Run Code Online (Sandbox Code Playgroud)

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`.
Run Code Online (Sandbox Code Playgroud)

使用类型注释使这个编译:

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();
}
Run Code Online (Sandbox Code Playgroud)

如果我们在类型注解中使用了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`.
Run Code Online (Sandbox Code Playgroud)

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

但是当我经过时,为什么这很重要None
当然,向编译器传递实现Trait(ie Struct)类型的任何具体实例都是可以的。


旁注:
我看过这个答案上的区别&dyn Trait&impl Trait。我不确定何时使用 which,但由于我的程序确实编译&impl Trait(当使用上述类型注释时)它似​​乎是安全的选择。

相反,如果我们将函数参数设为 type 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();
}
Run Code Online (Sandbox Code Playgroud)
$ cargo --version
cargo 1.37.0 (9edd08916 2019-08-02)  

$ cat Cargo.toml
[package]
name = "rdbug"
version = "0.1.0"
authors = ["redacted"]
edition = "2018"
Run Code Online (Sandbox Code Playgroud)

mic*_*srb 5

这个:

fn foo(maybe_trait: Option<&impl Trait>) -> String {
Run Code Online (Sandbox Code Playgroud)

只是语法糖:

fn foo<T: Trait>(maybe_trait: Option<&T>) -> String {
Run Code Online (Sandbox Code Playgroud)

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

Option<T>类型在内存中的表示方式取决于T类型的表示方式。函数的编译程序集foo取决于此。对于不同T的结果组件可能看起来不同。(例如 enum 标签定义了它是否SomeNone可能处于不同的字节偏移量。它可能使用不同的寄存器,它可能会以不同的方式决定是否展开循环、内联函数、向量化,...)这就是静态调度的优势- 即使您编写了大量抽象的代码,您也可以获得针对您实际使用的具体类型完全优化的代码。

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


另一方面这个:

fn foo(maybe_trait: Option<&impl Trait>) -> String {
Run Code Online (Sandbox Code Playgroud)

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

由于只有一个 function foo,因此在使用 时无需说明类型None,只有一个。

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


归档时间:

查看次数:

1564 次

最近记录:

6 年,3 月 前