impl Trait
轻松返回复杂的类型
impl Trait
是指定实现特定特征的未命名但有具体类型的新方法。 你可以把它放在两个地方:参数位置和返回位置。
trait Trait {}
// argument position
fn foo(arg: impl Trait) {
}
// return position
fn foo() -> impl Trait {
}
参数位置
在参数位置上,这个特性是十分简单的,下面这两种写法几乎相同:
trait Trait {}
fn foo<T: Trait>(arg: T) {
}
fn foo(arg: impl Trait) {
}
也就是说,它是泛型类型参数的简短的语法。这意味着,“ arg
是一个参数,它可以是实现了 Trait
特征的任何类型。”
但是,在技术上,T: Trait
和 impl Trait
有着一个很重要的不同点。
当你编写前者时,可以使用turbo-fish语法在调用的时候指定T
的类型,如 foo::<usize>(1)
。
在 impl Trait
的情况下,只要它在函数定义中使用了,不管什么地方,都不能再使用turbo-fish。
因此,您应该注意,更改两者和切换到 impl Trait
都会对代码的用户构成重大变化。
返回参数
在返回位置,此功能更有趣。这意味着“我正在返回一些实现了 Trait
特征的类型,但我不打算告诉你究竟是什么类型。”
在 impl Trait
之前,你可以用特征对象做到这一点:
#![allow(unused)] fn main() { trait Trait {} impl Trait for i32 {} fn returns_a_trait_object() -> Box<dyn Trait> { Box::new(5) } }
但是,这会产生一些开销: Box <T>
表示这里有堆分配,这将使用动态分配。
有关此语法的说明,请参阅 dyn Trait
部分。 但是我们在这里只返回一个可能的东西,即 Box <i32>
。
这意味着我们即使我们不使用动态分配,但是依旧为它而付出了代价!
使用 impl Trait
,上面的代码如下:
#![allow(unused)] fn main() { trait Trait {} impl Trait for i32 {} fn returns_a_trait_object() -> impl Trait { 5 } }
在这里,我们没有 Box <T>
,没有特征对象,也没有动态分配。但我们仍然可以实现 i32
的返回类型。
使用 i32
,这看起来并不是非常有用。但 Rust 中有一个主要运用的地方,它更有用: 闭包。
impl Trait
和闭包
如果你想要了解闭包,参阅 their chapter in the book.
在 Rust 中,闭包具有独特的,不可写的类型。然而,他们确实实现了 Fn
系列的特征。
这意味着,在以前,从函数处返回闭包的唯一方法是,使用trait对象:
#![allow(unused)] fn main() { fn returns_closure() -> Box<dyn Fn(i32) -> i32> { Box::new(|x| x + 1) } }
你不能写明闭包的类型,仅仅是使用 Fn
特性。这意味着特征对象是必须的,但是,利用 impl Trait
呢:
#![allow(unused)] fn main() { fn returns_closure() -> impl Fn(i32) -> i32 { |x| x + 1 } }
我们现在可以直接返回闭包类型,就像其他的返回值那样!
更多的细节
以上是你需要了解和使用 impl Trait
的所有内,但是还有一些更细微的细节: 类型参数和参数位置 impl Trait
的普遍性(普遍量化的类型)。
同时,impl Trait
在返回位置是存在的(存在量化的类型)。 好吧,也许这有点太行话了。 我们退一步吧。
考虑下面这个函数:
fn foo<T: Trait>(x: T) {
当你调用它时,你设置类型,T
。 “你”是这里的调用者。 这个签名说“我接受任何实现Trait的类型”。(“任何类型” == 行话中的通用)
这个版本:
fn foo<T: Trait>() -> T {
相似但是有些不同,你这个调用者,提供一个你想要的返回类型 T
, 你可以看一下现在 Rust 中的 parse 和 collect :
let x: i32 = "5".parse()?;
let x: u64 = "5".parse()?;
这里, .parse
有如下的签名:
pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err> where
F: FromStr,
如你所想,虽然结果类型和 FromStr
有一个相关的类型... 无论如何,你可以看到 F
在这里的返回位置。所以你有能力去选择了。
使用 impl Trait
,你会说“嘿,有些类型存在实现这个特性,但我不会告诉你它是什么。” (术语中的“存在主义”,“某种类型存在”)。
所以现在,调用者无法选择,函数本身可以选择。如果我们尝试使用 Result <impl F,...
定义解析作为返回类型,它将无效。
使用 impl Trait
在更多的地方
如前所述,作为一个开始,您将只能使用 impl Trait
作为自由或固有函数的参数或返回类型。
但是,现在 impl Trait
不能在traits的实现中使用,也不能用作let绑定的类型或类型别名。未来其中一些限制将被取消。
获取更多的信息,查看tracking issue on impl Trait
.