赞
踩
impl的时候也可以使用泛型。在implfor{}这个语法结构中,泛型
类型既可以出现在位置,也可以出现在位置。
与其他地方的泛型一样,impl块中的泛型也是先声明再使用。在impl块中出现
的泛型参数,需要在impl关键字后面用尖括号声明。
当我们希望为某一组类型统一impl某个trait的时候,泛型就非常有用了。有了
这个功能,很多时候就没必要单独为每个类型去重复impl了。以标准库中的代码为
例:
impl<T, U> Into for T
where U: From
{
fn into(self) -> U {
U::from(self)
}
}
上面这段代码中,impl关键字后面的尖括号<T,U>意思是先声明两个泛型参
数,后面会使用它们。这跟类型、函数中的泛型参数规则一样,先声明、后使用。
标准库中的Into和From是一对功能互逆的trait。如果A:Into,意味着B:
From。因此,标准库中写了这样一段代码,意思是针对所有类型T,只要满足
U:From,那么就针对此类型impl Into。有了这样一个impl块之后,我们如
果想为自己的两个类型提供互相转换的功能,那么只需impl From这一个trait就可以
了,因为反过来的Into trait标准库已经帮忙实现好了。
泛型参数约束
Rust的泛型和C++的template非常相似,但也有很大不同。它们的最大区别在于
执行类型检查的时机。在C++里面,模板的类型检查是延迟到实例化的时候做的。
而在Rust里面,泛型的类型检查是当场完成的。示例如下:
// C++ template 示例
template
const T& max (const T& a, const T& b) {
return (a<b)?b:a;
}
void instantiateInt() {
int m = max(1, 2); // 实例化1
}
struct T {
int value;
};
void instantiateT() {
T t1 { value: 1};
T t2 { value: 2};
//T m = max(t1, t2); // 实例化2
}
int main() {
instantiateInt();
instantiateT();
return 0;
}
在这个例子中:如果我们把“实例化2”处的代码先注释掉,使用g+±std=c++11
test.cpp命令编译,可以通过;如果取消注释,则编译不通过。编译错误为:
error: no match for ‘operator<’ (operand types are ‘const T’ and ‘const T’)
出现编译错误的原因是我们没有给T类型提供比较运算符重载。此处的关键在
于,如果我们用int类型来实例化max函数,它就可以通过;如果我们用自定义的T
类型来实例化max函数,它就通不过。max函数本身一直都是没有问题的。也就是
说,编译器在处理max函数的时候,根本不去管a<b是不是一个合理的运算,而是将
这个检查留给后面实例化的时候再分析。
Rust采取了不同的策略,它会在分析泛型函数的时候当场检查类型的合法性。
这个检查是怎样做的呢?它要求用户提供合理的“泛型约束”。在Rust中,trait可以用
于作为“泛型约束”。在这一点上,Rust跟C#的设计是类似的。上例用Rust来写,大
致是这样的逻辑:
fn max(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
–> test.rs:2:8
|
2 | if a < b {
| ^^^^^
|
= note: an implementation of std::cmp::PartialOrd
might be missing for T
这个编译错误说得很清楚了,由于泛型参数T没有任何约束,因此编译器认为
a<b这个表达式是不合理的,因为它只能作用于支持比较运算符的类型。在Rust中,
只有impl了PartialOrd的类型,才能支持比较运算符。修复方案为泛型类型T添加泛
型约束。
泛型参数约束有两种语法:
(1)在泛型参数声明的时候使用冒号:指定;
(2)使用where子句指定。
use std::cmp::PartialOrd;
// 第一种写法:在泛型参数后面用冒号约束
fn max<T: PartialOrd>(a: T, b: T) -> T {
// 第二种写法,在后面单独用 where 子句指定
fn max(a: T, b: T) -> T
where T: PartialOrd
在上面的示例中,这两种写法达到的目的是一样的。但是,在某些情况下(比
如存在下文要讲解的关联类型的时候),where子句比参数声明中的冒号约束具有
更强的表达能力,但它在泛型参数列表中是无法表达的。我们以Iterator trait中的函
数为例:
trait Iterator {
type Item; // Item 是一个关联类型
// 此处的where子句没办法在声明泛型参数的时候写出来
fn max(self) -> OptionSelf::Item
where Self: Sized, Self::Item: Ord
{
…
}
…
}
它要求Self类型满足Sized约束,同时关联类型Self::Item要满足Ord约束,这
是用冒号语法写不出来的。在声明的时候使用冒号约束的地方,一定都能换作
where子句来写,但是反过来不成立。另外,对于比较复杂的约束条件,where子句
的可读性明显更好。
在有了“泛型约束”之后,编译器不仅会在声明泛型的地方做类型检查,还会在
实例化泛型的地方做类型检查。接上例,如果向我们上面实现的那个max函数传递
自定义类型参数:
struct T {
value: i32
}
fn main() {
let t1 = T { value: 1};
let t2 = T { value: 2};
let m = max(t1, t2);
}
编译,可见编译错误:
error[E0277]: the trait bound T: std::cmp::PartialOrd
is not satisfied
–> test.rs:20:13
|
20 | let m = max(t1, t2);
| ^^^ can’t compare T
with T
|
= help: the trait std::cmp::PartialOrd
is not implemented for T
= note: required by max
这说明,我们在调用max函数的时候也要让参数符合“泛型约束”。因此我们需
要impl PartialOrd for T。完整代码如下:
use std::cmp::PartialOrd;
use std::cmp::Ordering;
fn max(a: T, b: T) -> T
where T: PartialOrd
{
if a < b {
b
} else {
a
}
}
struct T {
value: i32
}
impl PartialOrd for T {
fn partial_cmp(&self, other: &T) -> Option {
self.value.partial_cmp(&other.value)
}
}
impl PartialEq for T {
fn eq(&self, other: &T) -> bool {
self.value == other.value
}
}
fn main() {
let t1 = T { value: 1};
let t2 = T { value: 2};
let m = max(t1, t2);
}
由于标准库中的PartialOrd继承了PartialEq,因此单独实现PartialOrd还是会产生
编译错误,必须同时实现PartialEq才能编译通过。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。