项目经理总是善变的,有一天项目经理告诉我,替客户计算一个圆形的面积。客户要求很简单,半径只会是u8类型。好,我写了如下代码:
fn area_u8(r: u8) -> u8 {
r * r
}
fn main() {
println!("{}", area_u8(3));
}
可第二天项目经理又来了,说客户说的不对,半径某种情况下还会是u16。唉,客户就是上帝,项目经理也没有办法。于是我又添加了一个函数:
fn area_u8(r: u8) -> u8 {
r * r
}
fn area_u16(r: u16) -> u16 {
r * r
}
fn main() {
println!("{}", area_u8(3));
println!("{}", area_u16(10));
}
可第三天、第四天,项目经理总是气喘吁吁的跑来说半径还会是u32、u64,甚至,还可能是浮点数。我的天啊,我到底要写多少个函数才行!我意识到是时候叫出超级飞侠了。不,不对,是泛型了。
泛型,顾名思义,就是广泛的类型,在Rust中,通常使用
使用泛型并不容易,在这个例子中,我感受到了Rust编译器的强大。我的第一版程序:
fn area(r: T) -> T {
r * r
}
fn main() {
println!("{}", area(3));
println!("{}", area(3.2));
}
然后编译器告诉我:
error[E0369]: cannot multiply `T` to `T`
--> src\main.rs:2:7
|
2 | r * r
| - ^ - T
| |
| T
|
help: consider restricting type parameter `T`
|
1 | fn area>(r: T) -> T {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: aborting due to previous error
不能对两个T类型的数做乘法!那我该怎么办?
别急,看第11行。它直接告诉我应该这么写:fn area
fn area>(r: T) -> T {
r * r
}
fn main() {
println!("{}", area(3));
println!("{}", area(3.2));
}
再编译:
error[E0382]: use of moved value: `r`
--> src\main.rs:2:9
|
1 | fn area>(r: T) -> T {
| - move occurs because `r` has type `T`, which does not implement the `Copy` tr
ait
2 | r * r
| - ^ value used here after move
| |
| value moved here
|
help: consider further restricting this bound
|
1 | fn area + Copy>(r: T) -> T {
| ^^^^^^
error: aborting due to previous error
还是不对,在第14行编译器又告诉我应该这么写,于是,第三版:
fn area + Copy>(r: T) -> T {
r * r
}
fn main() {
println!("{}", area(3));
println!("{}", area(3.2));
}
终于可以得到正确结果了。
回过头来解释一下刚才的过程。泛型,指定的是任意类型,而并不是所有的类型都能进行乘法运算的。因此,我们需要对泛型加以限制。这被称为特性绑定,或泛型约束,意思是只能满足条件(实现了某特性)的泛型才被允许传到函数中来。
第二版的
上面的写法无疑使得第一行很长,可读性不好,为些Rust设计了where子句,用来实现泛型约束:
fn area(r: T) -> T
where T: std::ops::Mul
这下足足过了1个月,我都没见到项目经理的身影,直到有一天,项目经理满面春风来到我的工位,说上次的程序写得太棒了,客户发现不管什么时候,我的程序都能正常工作。客户对我们公司非常肯定,决定再给我们一个新的项目:计算长方形面积,此类项目前景非常好,为了便于扩展,最好能抽象成结构体。我吐血。
于是我一棋呵成:
use std::ops::Mul; // 这么写可以简化代码
struct Rect // 为结构体添加泛型
{
width: T, // 宽和高都是泛型
height: T
}
impl Rect { // 为泛型实现方法,impl后也要添加
fn area(&self) -> T
where T: Mul
正在洋洋自得时,测试MM微笑着来到我的面前。我以为要步入人生的新阶段时,测试MM开口了:“你这个函数不对啊,如果宽和高一个是整数一个是浮点数……”我瞬间石化,后面的我再也听到。
也不知道测试MM什么时候走的,回过神来的我马上试验:
use std::ops::Mul; // 这么写可以简化代码
struct Rect // 为结构体添加两个泛型
{
width: T, // 宽和高是不同的泛型
height: U
}
impl Rect { // 为泛型实现方法,impl后也要添加
fn area(&self) -> T
where T: Mul + Copy, // Mul的第一个参数,表示让这个类型和自身相乘,Output表示输出值的类型
U: Mul + Copy {
self.height * self.width
}
}
fn main() {
// 整型
let rect1 = Rect{width:3, height:4.3};
println!("{}", rect1.area());
// 浮点型
let rect2 = Rect{width:3.5, height:4};
println!("{}", rect2.area());
}
编译器报错:
error[E0277]: cannot multiply `{float}` to `{integer}`
--> src\main.rs:20:26
|
20 | println!("{}", rect1.area());
| ^^^^ no implementation for `{integer} * {float}`
|
= help: the trait `std::ops::Mul<{float}>` is not implemented for `{integer}`
error[E0277]: cannot multiply `{integer}` to `{float}`
--> src\main.rs:20:26
|
20 | println!("{}", rect1.area());
| ^^^^ no implementation for `{float} * {integer}`
|
= help: the trait `std::ops::Mul<{integer}>` is not implemented for `{float}`
error[E0277]: cannot multiply `{integer}` to `{float}`
--> src\main.rs:24:26
|
24 | println!("{}", rect2.area());
| ^^^^ no implementation for `{float} * {integer}`
|
= help: the trait `std::ops::Mul<{integer}>` is not implemented for `{float}`
error[E0277]: cannot multiply `{float}` to `{integer}`
--> src\main.rs:24:26
|
24 | println!("{}", rect2.area());
| ^^^^ no implementation for `{integer} * {float}`
|
= help: the trait `std::ops::Mul<{float}>` is not implemented for `{integer}`
error: aborting due to 4 previous errors
Rust竟然不能把整型和浮点数相乘,这就不得不吐槽一下Rust了,因为Rust不像C语言那样有隐式类型转换,C语言遇到这样的问题时,会把两个值都转换成double来计算,而Rust不会。编译器告诉我,要想实现这个问题,需要使用From和Into特性,我查了很多资料,加群问了大佬,也只能写出来第一个参数是浮点第二个参数是整数的解决方案。
use std::ops::Mul; // 这么写可以简化代码
use std::convert::{Into};
struct Rect // 为结构体添加两个泛型
{
width: T, // 宽和高是不同的泛型
height: U
}
impl Rect { // 为泛型实现方法,impl后也要添加
fn area(&self) -> T
where T: Mul
这一点让刚学Rust的我很是郁闷,换成别的语言,都是很简单的事件,怎么在Rust里这么难。