Rust泛型

使用泛型定义函数签名或结构体,枚举,方法等,就可以在不同数据类型中使用,而不需要重复定义。

在函数定义中使用泛型🔗

考虑下面代码:

fn largest_i32(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> &char {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest_i32(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest_char(&char_list);
    println!("The largest char is {}", result);
}

可见上面两个函数的代码几乎一致,只是类型不同,因此可以写一个新函数使用泛型来简化代码。
参数化这个新函数,可以定义一个类型参数,并在函数签名中使用它,在这里使用T,将参数声明在函数名称与参数列表中间的尖括号中,如下:

fn largest<T>(list: &[T]) -> &T {}

这里表示,函数largest有泛型类型T,他的参数list是T的切片,返回值也是T的引用。

考虑下面代码:

fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn main() {
    let number_list = vec![34, 50, 25, 100, 65];
    let result = largest(&number_list);
    println!("The largest number is {}", result);

    let char_list = vec!['y', 'm', 'a', 'q'];
    let result = largest(&char_list);
    println!("The largest char is {}", result);
}

编译器会根据传入的参数类型,自动推断出T的类型,因此不需要显式地声明T的类型,但是代码目前不能编译,因为>并不能使用于所有可能类型,std::cmp::PartialOrd这个trait定义了>这个方法,因此需要将T限制为实现了PartialOrd这个trait的类型才能编译。

结构体定义中使用泛型🔗

考虑下面代码:

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

这里定义了一个结构体Point,它有两个字段x和y,他们的类型都是T,T是一个泛型类型参数,因此Point可以用于任何类型,如i32,f64,char等。

💡 提示

这里只使用了一个泛型类型,表明x和y的类型相同,如果创建一个x和y类型不同的Point,编译器会报错。

枚举定义中使用泛型🔗

考虑下面代码:

enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}

这里定义了两个枚举,Option和Result,他们都有一个泛型类型参数,Option的泛型类型参数T表示Some的值,Result的泛型类型参数T表示Ok的值,泛型类型参数E表示Err的值。

方法定义中使用泛型🔗

考虑下面代码:

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

这里定义了一个x方法用来返回Point的x字段的引用,这个方法使用了泛型类型参数T,因此它可以用于任何类型的Point,如Point,Point等。

在定义方法时也可以指定限制,如:

impl Point<f32> {
    fn distance_from_origin(&self) -> f32 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

这段代码表示只有Point类型的Point结构体才能调用distance_from_origin方法,而其他类型的实例没有这个方法。

ℹ️ 注意

需要在impl后面加上<T>来指定泛型类型参数T,编译器才能知道尖括号中的类型是泛型而不是具体类型。

泛型并不是总和结构体方法签名中的泛型是同一类型:

struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' };

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

这里定义了一个mixup方法,它接受一个Point类型的参数other,返回一个Point类型的值,可见一些泛型通过impl声明,而一些通过方法声明。

泛型代码的性能🔗

Rust会在编译时进行代码的单态化(monomorphization),将泛型代码转换为针对具体类型的代码,因此泛型代码没有额外的性能开销。