为什么将特征作为函数参数传递时需要 impl ?
Why is `impl` needed when passing traits as function parameters?
在下面传递一个特征作为参数的例子中,函数签名中发送impl
的必要性是什么?
我知道 traits 是更通用的类型而不是具体类型,但是由于 Rust 编译器不允许跨结构和 traits 共享名称,为什么需要在函数签名中提供 impl
代表类型?
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
文档提到上面的签名只是下面签名的语法糖。使用 trait Summary
而不是 impl Summary
是否有意义,因为 impl
也可用于定义结构上的方法?
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
是否有我遗漏的隐藏概念?
与 Go 或 Java 等语言相反,Rust 允许静态和动态调度,并且需要一些语法让程序员在两者之间进行选择。
由于动态分派必须对可能不是 Sized
的对象起作用,您需要引用才能使用它。也就是说,您将使用 &dyn Trait
或 Box<dyn Trait>
(注意:由于历史原因,dyn
关键字不是必需的,但现代 Rust 使用它)。在 C++ 中,动态调度也需要引用或指针。
Go 或 Java 没有静态调度。在 C++ 中,它与模板和鸭子类型一起使用。在 Rust 中,它适用于泛型和特征,其原始语法为:
fn some_function<T: Trait>(foo: T) { … }
后来,语言中添加了以下语法:
fn some_function(foo: impl Trait) { … }
相当于上面的
此语法最初是为了在 return 类型中使用而发明的,其中没有通用的等效项:
fn some_function() -> impl Trait { … }
这意味着 some_function
可以 return 实现 Trait
的任何单个类型,但必须在编译时知道该类型。例如,这比 returning Box<Trait>
有一些性能优势。在 C++ 中,最接近的等价物是 returning auto
或 decltype(auto)
.
为对称添加了参数位置的语法。
您可能想知道为什么不简单地使泛型隐式并具有:
fn some_function(foo: Trait) { … }
但这会有点令人困惑。 Trait
本身没有大小,因此不能用作参数,除非它们是通用的。这将使特征在未确定大小的类型领域中脱颖而出。例如,如果 (foo: Trait)
有效,您可能想知道为什么 (foo: str)
无效,但那是什么意思?使泛型隐式化还有其他问题,例如,特征中的泛型使特征非对象安全。
稍后,Rust 可能会扩展这些存在类型并在模块级别允许这样做:
type Foo = impl Bar;
(目前允许在夜间播放,由 type_alias_impl_trait
功能保护)
最后,您问的是为什么语法是 impl Foo
,而不是 trait Foo
。这读起来很好 "a type that implements Foo"。 original RFC doesn't discuss alternative syntaxes much. Another RFC discusses the syntax more,特别是语法是否应该在参数位置any Foo
,以及return位置some Foo
。据我所知,语法 trait Foo
从未被考虑过。
在下面传递一个特征作为参数的例子中,函数签名中发送impl
的必要性是什么?
我知道 traits 是更通用的类型而不是具体类型,但是由于 Rust 编译器不允许跨结构和 traits 共享名称,为什么需要在函数签名中提供 impl
代表类型?
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
文档提到上面的签名只是下面签名的语法糖。使用 trait Summary
而不是 impl Summary
是否有意义,因为 impl
也可用于定义结构上的方法?
pub fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
是否有我遗漏的隐藏概念?
与 Go 或 Java 等语言相反,Rust 允许静态和动态调度,并且需要一些语法让程序员在两者之间进行选择。
由于动态分派必须对可能不是 Sized
的对象起作用,您需要引用才能使用它。也就是说,您将使用 &dyn Trait
或 Box<dyn Trait>
(注意:由于历史原因,dyn
关键字不是必需的,但现代 Rust 使用它)。在 C++ 中,动态调度也需要引用或指针。
Go 或 Java 没有静态调度。在 C++ 中,它与模板和鸭子类型一起使用。在 Rust 中,它适用于泛型和特征,其原始语法为:
fn some_function<T: Trait>(foo: T) { … }
后来,语言中添加了以下语法:
fn some_function(foo: impl Trait) { … }
相当于上面的
此语法最初是为了在 return 类型中使用而发明的,其中没有通用的等效项:
fn some_function() -> impl Trait { … }
这意味着 some_function
可以 return 实现 Trait
的任何单个类型,但必须在编译时知道该类型。例如,这比 returning Box<Trait>
有一些性能优势。在 C++ 中,最接近的等价物是 returning auto
或 decltype(auto)
.
为对称添加了参数位置的语法。
您可能想知道为什么不简单地使泛型隐式并具有:
fn some_function(foo: Trait) { … }
但这会有点令人困惑。 Trait
本身没有大小,因此不能用作参数,除非它们是通用的。这将使特征在未确定大小的类型领域中脱颖而出。例如,如果 (foo: Trait)
有效,您可能想知道为什么 (foo: str)
无效,但那是什么意思?使泛型隐式化还有其他问题,例如,特征中的泛型使特征非对象安全。
稍后,Rust 可能会扩展这些存在类型并在模块级别允许这样做:
type Foo = impl Bar;
(目前允许在夜间播放,由 type_alias_impl_trait
功能保护)
最后,您问的是为什么语法是 impl Foo
,而不是 trait Foo
。这读起来很好 "a type that implements Foo"。 original RFC doesn't discuss alternative syntaxes much. Another RFC discusses the syntax more,特别是语法是否应该在参数位置any Foo
,以及return位置some Foo
。据我所知,语法 trait Foo
从未被考虑过。