Rust的泛型

文章目录

  • Rust的泛型
    • 在函数中使用泛型
    • 特性与特性绑定
    • 在结构体中使用泛型

Rust的泛型

在函数中使用泛型

项目经理总是善变的,有一天项目经理告诉我,替客户计算一个圆形的面积。客户要求很简单,半径只会是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中,通常使用表示,当然,不一定要是T,它也可以是A、B、C……

使用泛型并不容易,在这个例子中,我感受到了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>(r: T) -> T {,于是我的第二版程序诞生了:

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));
}

终于可以得到正确结果了。

回过头来解释一下刚才的过程。泛型,指定的是任意类型,而并不是所有的类型都能进行乘法运算的。因此,我们需要对泛型加以限制。这被称为特性绑定,或泛型约束,意思是只能满足条件(实现了某特性)的泛型才被允许传到函数中来。

第二版的>就是约束只有实现了std::ops::Mul特性的泛型才可以通过编译器的检查,而在这个例子中,因为r是泛型,在进行乘法时所有权发生了转移,还要进行第二个约束,T必须同时实现std::ops::Mul和Copy两个特性。Rust中可以使用“+”来实现多重约束,这就是 + Copy>的来历。

上面的写法无疑使得第一行很长,可读性不好,为些Rust设计了where子句,用来实现泛型约束:

fn area(r: T) -> T 
where T: std::ops::Mul + Copy
{
    r * r
}

fn main() {
    println!("{}", area(3));
    println!("{}", area(3.2));
}

在结构体中使用泛型

这下足足过了1个月,我都没见到项目经理的身影,直到有一天,项目经理满面春风来到我的工位,说上次的程序写得太棒了,客户发现不管什么时候,我的程序都能正常工作。客户对我们公司非常肯定,决定再给我们一个新的项目:计算长方形面积,此类项目前景非常好,为了便于扩展,最好能抽象成结构体。我吐血。

于是我一棋呵成:

use std::ops::Mul;  // 这么写可以简化代码

struct Rect      // 为结构体添加泛型
{
    width: T,       // 宽和高都是泛型
    height: T
}

impl Rect {   // 为泛型实现方法,impl后也要添加
    fn area(&self) -> T     
    where T: Mul + Copy {       // 泛型约束
        self.height * self.width
    }
}

fn main() {
    // 整型
    let rect1 = Rect{width:3, height:4};
    println!("{}", rect1.area());

    // 浮点型
    let rect2 = Rect{width:3.5, height:4.3};
    println!("{}", rect2.area());
}

正在洋洋自得时,测试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 + Copy,     // Mul的第一个参数,表示让这个类型和自身相乘,Output表示输出值的类型
          U: Into + Copy {
        self.width.mul(self.height.into())
    }
}

fn main() {
    let rect2 = Rect{width:3.1, height:4};
    println!("{}", rect2.area());
}

这一点让刚学Rust的我很是郁闷,换成别的语言,都是很简单的事件,怎么在Rust里这么难。

你可能感兴趣的:(Rust语言学习笔记,rust)