![](/img/trans.png)
[英]Why does Rust require generic type declarations after the “impl” keyword?
[英]Why does the rust compiler require a type annotation for Option<&impl Trait>?
鉴于此 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
)的类型的任何具体实例对编译器来说都是可以的。
边注:
我已经阅读了有关&dyn Trait
和&impl Trait
之间区别的答案。 我不确定何时使用 which,但由于我的程序确实使用&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"
这个:
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
的编译程序集依赖foo
。 对于不同的T
,生成的组件可能看起来不同。 (例如,定义是Some
还是None
的 enum 标签可能处于不同的字节偏移量。它可能使用不同的寄存器,它可能会以不同的方式决定是否展开循环、内联函数、向量化……)这就是静态分派 - 即使您编写了大量抽象的代码,您也可以获得针对您实际使用的具体类型完全优化的代码。
使用即将推出的专业化功能,您实际上可以为T
不同子集手动编写不同的foo
实现,因此编译器知道您正在调用哪个foo
非常重要。 每个人都可以用None
做一些不同的事情。
另一方面这个:
fn foo(maybe_trait: Option<&dyn Trait>) -> String {
意味着只有一个函数foo
接受 Option ,该函数包含一个指向实现Trait
某种类型的胖指针。 如果您在maybe_trait
内部调用maybe_trait
上的某个方法,该调用将通过动态调度。
因为只有一个函数foo
,所以在使用None
时你不必说任何关于类型的信息,只有一个。
但是动态调度是有代价的——这个函数没有针对任何特定的T
进行优化,它动态地与每个T
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.