P.GEN.02 不要随便使用 impl Trait
语法替代泛型限定
【描述】
impl Trait
语法 和 泛型限定,虽然都是静态分发,且效果类似,但是它们的语义是不同的。
在类型系统层面上的语义:
impl Trait
是 存在量化类型。意指,存在某一个被限定的类型。- 泛型限定 是 通用量化类型。意指,所有被限定的类型。
要根据它们的语义来选择不同的写法。
另外,impl Trait
可以用在函数参数位置和返回值位置,但是不同位置意义不同。
函数参数位置
等价于 泛型参数。
但要注意:
#![allow(unused)] fn main() { fn f(b1: impl Bar, b2: impl Bar) -> usize }
等价于:
#![allow(unused)] fn main() { fn f<B1: Bar, B2: Bar>(b1: B1, b2: B2) -> usize }
而不是
#![allow(unused)] fn main() { fn f<B: Bar>(b1: B, b2: B) -> usize }
证明示例:
use std::fmt::Display; // 函数参数可以传入 整数,但是函数返回值是 String fn func(arg: impl Display) -> impl Display { format!("Hay! I am not the same as \"{}\"", arg) } // 很明显不等价于下面这类 // fn somefunc2<T: Display>(arg: T) -> T { // // 需要指定同一个类型 T 的行为 // } fn main(){ let a = 42; let a = func(42); }
函数返回值
在返回值位置上,如果是泛型参数,则是由调用者来选择具体类型,比如 parse::<i32>("32")
; 如果是 impl Trait
,则是由被调用者来决定具体类型,但只能有一种类型。
在返回值位置上的 impl Trait
会根据函数体的返回值自动推断实现了哪些 auto trait。这意味着你不必在 impl Trait
后面再 加 Sync + Send
这种auto trait。
注意下面代码:
#![allow(unused)] fn main() { // Error: 这里只允许有同一种具体类型,Foo 和 Baz 都实现了 Bar 也是错的。 fn f(a: bool) -> impl Bar { if a { Foo { ... } } else { Baz { ... } } } }