RUST 每日一省:泛型约束——trait
使用泛型编程时, 很多情况下的行为并不是针对所有类型都实现的,用trait作为泛型的约束。例如,我们编写一个判断两个变量大小的泛型函数,编译时,会运行如下错误。
fn max<T>(a: T, b: T) -> T {
if a < b {
b
} else {
a
}
}
fn main() {
let m = max(1, 2);
}
error[E0369]: binary operation `<` cannot be applied to type `T`
--> src/main.rs:14:10
|
14 | if a < b {
| - ^ - T
| |
| T
|
help: consider restricting type parameter `T`
|
13 | fn max<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
| ++++++++++++++++++++++
这个编译错误是说, 由于泛型参数T没有任何约束——如果我们填入两个Struct类型,如何比较呢, 因此编译器认为a<b这个表达式是不合理的, 因为它只能作用于支持比较运算符的类型。编译器也进行了提示,只有impl了PartialOrd的类型, 才能支持比较运算符。修正如下。
fn max<T:PartialOrd>(a: T, b: T) -> T {
if a < b {
b
} else {
a
}
}
fn main() {
let m = max(1, 2);
}
trait约束语法
trait约束的语法如下:
fn generic<T: MyTrait + MyOtherTrait + SomeStandarTrait>(t: T)
{
todo!()
}
如果泛型参数有多个trait约束,通过+语法来指定多个trait约束;拥有多个泛型参数的函数,在函数名和参数列表之间会有很长的trait约束信息,使得函数签名可读性差。Rust提供where关键字来处理这种情况。代码如下,
fn max<T>(a: T, b: T) -> T
where T: PartialOrd
{
if a < b {
b
} else {
a
}
}
总结,泛型参数约束有两种语法:
(1) 在泛型参数声明的时候使用冒号: 指定;
(2) 使用where子句指定。
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {}
fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,
U: Clone + Debug
{}
在有了“泛型约束”之后, 编译器不仅会在声明泛型的地方做类型检查, 还会在调用的地方做类型检查。例如当用户调用时,编译器会分析泛型函数当场检查类型的合法性,此时泛型的类型检查就完成了。
fn max<T:PartialOrd>(a: T, b: T) -> T {
if a < b {
b
} else {
a
}
}
struct T1 {
value: i32
}
#[derive(PartialOrd,PartialEq)]
struct T2 {
value: i32
}
fn main() {
let m = max(1, 2);
let t11 = T1{value:1};
let t12 = T1{value:1};
let m = max(t11, t12);
let t21 = T2{value:1};
let t22 = T2{value:1};
let m = max(t21, t22);
}
= help: the trait `PartialOrd` is not implemented for `T1`
note: required by a bound in `max`
--> src/main.rs:31:10
|
31 | fn max<T:PartialOrd>(a: T, b: T) -> T {
| ^^^^^^^^^^ required by this bound in `max`
help: consider annotating `T1` with `#[derive(PartialOrd)]`
|
3 | #[derive(PartialOrd)]
|
我们看到定时泛型函数max时指定了PartialOrd,没有错误;但是在我们调用的时候,由于T1没有实现PartialOrd,导致调用失败了。