Trait
trait
定义了某个特定类型拥有可能与其他类型共享的功能。通过 trait
以抽象的方式定义共同行为。
定义 trait
一个类型的行为由其可供调用的方法构成。对不同类型调用相同的方法,这些类型就可以共享相同的行为了。trait
定义是一种将方法签名组合起来的方法。
这里使用 trait
关键字来声明一个 trait
,后面跟着的是 trait
的名字,同样可以设置 trait
是否公有。
trait
有点类似于接口,在方法签名后跟分号,而不是在大括号中提供其实现。每一个实现这个 trait
的类型都需要提供其自定义行为的方法体。
trait
中可以有多个方法签名,一行一个方法签名且都以分号结尾。
实现 trait
impl
关键字后跟着的是需要实现 trait
的名称,for
后跟着的是需要实现 trait
的类型的名称,也即在某个类型上实现某个 trait
。
默认实现
可以为 trait
中的某些或全部方法提供默认的行为。这样当为某个特定类型实现 trait
时,可以选择保留或重载每个方法的默认行为。
这里给 GetInfo trait
的 value
方法提供了一个默认的字符串值,而不只是定义方法签名了。
如何使用默认实现?
通过指定一个空的 impl
块即可。
通过 impl GetInfo for NormalInfo {}
指定了一个空的 impl
块,以此使用默认实现。
使用了默认实现,并不代表将原来的 trait
方法签名给覆盖掉了,而是实例依然能够使用方法签名。
默认实现允许调用相同的 trait
中的其他方法,哪怕这些方法没有默认实现。
trait
作为参数
使用 trait
来接受多种不同类型的参数。
参数 item
指定了 impl
关键字和 GetInfo trait
,该参数支持任何实现了指定 trait
的类型。在 console
函数体中,可以调用任何来自 GetInfo trait
的方法。
Trait Bound
trait bound
实际上是一种较长形式的语法糖。
相较于前面的例子,trait bound
与泛型参数声明在一起,位于尖括号中的冒号后面。
与 impl Trait
相比,trait bound
更适合用于复杂的场景,而 impl Trait
适用于短小的场景:
从代码中可以看出,impl Trait
语法在函数参数的角度来说,确实是更长一点,并且 item1
和 item2
允许是不同类型的情况,只要它们都实现了 GetInfo
。而 trait bound
语法在函数参数的角度则更短一点,并且 item1
和 item2
是相同的类型。
通过 +
指定多个 trait bound
+
也可以适用于 impl Trait
语法。
通过 +
指定多个 trait bound
看着还是很清晰的~
由于 trait bound
是一种较长形式的语法糖,所以当过多的 trait bound
也有缺点,每个泛型有自己的 trait bound
,所以有多个泛型参数的函数在函数名称和参数列表之间会有很长的 trait bound
信息,这导致函数签名难以阅读。于是,Rust 给出了一个解决办法——使用 where
简化过多的 trait bound
使用 where
简化 trait bound
未使用 where
简化前:
可以看到 trait bound
非常长,这还是只有 3 个 trait
的时候…
使用 where
简化 trait bound
:
看起来好像复杂了,其实也还好,但是使用 where
简化 trait bound
确实达到了目的,确实解决了之前提到的问题。
返回实现了 trait
的类型
可以在返回值中使用 impl Trait
语法,来返回实现了某个 trait
的类型:
通过 impl GetInfo
作为返回值类型,指定了 return
函数返回某个实现了 GetInfo trait
的类型,但是不确定具体的类型。
对任何实现了特定的 trait
的类型有条件地实现 trait
,好绕啊?你可以理解为在 trait
上实现 trait
,只不过某个 trait
是有类型限制的。
在实现了 GetInfo trait
的类型上实现了 ToString trait
。
对于任何满足特定 trait bound
的类型实现 trait
,也即在 trait bound
上实现 trait
,这种行为被称为 blanket implementations
(一揽子实现或覆盖实现)。
这个的作用就是可以对任何实现了 GetInfo trait
的类型调用 ToString
类型的 to_string
方法。
总结
trait
和 trait bound
使用泛型类型参数来减少重复,并且泛型还拥有单态化的特性,因此泛型可以使得代码更具通用性和灵活性,以及更好的性能。编译器利用 trait bound
在编译时检查代码中所用到的具体类型是否提供了正确的行为,这避免了在运行时出现错误。