Rust泛型详解

泛型可以看作构建函数和类型定义的模板。标准库中有很多常见类型例如Result<T, E>Option<T>Vec<T>等都是泛型实现的。主要用于代码的特化。Rust是静态类型语言,因此泛型的类型参数(T)需要在编译期解析为具体类型,这种特性就是参数化多态性。泛型的实现还采用单态化技术,会根据实际传入的类型参数,将泛型实例化为唯一的专用类型。
泛型的优点:

  • 代码复用:只需编写一套代码,可以用于不同的类型。
  • 重构:重构变得更简单,因为泛型只有一份源码,不需要维护多份类型不同但功能相同的代码
  • 扩展性:可扩展性强,未来即使出现新的数据类型,泛型代码依然适用。
  • 更不容易犯错:使用泛型减少了大量重复代码的实现,潜在的错误也变少。
  • 独特功能:Rust中的泛型机制带来了独特的能力->函数重载

泛型函数

泛型函数是使用类型参数的函数模板,用于创建具体函数,

  • 类型参数要在函数名后使用<>声明
  • 命名惯例使用大驼峰命名,第一个类型参数一般用T,接着是U、V等
fn function_name<T>(param: T)->T {
    let variable: T;
}

示例

fn swap<T, U>(tuple: (T, U))->(U, T) {
    (tuple.1, tuple.0)
}
fn main() {
    let tuple = (1, "hello");
    let tuple1 = ("hello".to_string(), "world".to_string());
    let tuple2 = (3.14, 98);

    let t_swap = swap(tuple);
    let t1_swap = swap(tuple1);
    let t2_swap = swap(tuple2);

    println!("{:?}", t_swap);
    println!("{:?}", t1_swap);
    println!("{:?}", t2_swap);
    
}

编译器自动推导出了具体的类型,编译器会根据函数定义,选择调用对应的函数版本。

编译器通常能从参数类型推导出对应的类型参数,但如果无法推导出具体类型,就需要显式指定类型

function_name::<type,...>(arg,...)

示例

fn do_something<T: Default>()->T {
    let value: T = T::default();
    value
}

fn main() {
    let ret = do_something::<i8>();
    println!("{}", ret);
}

泛型约束

在编译期,编译器对这些参数类型的实际类型并不了解,因此,编译器需要对使用类型参数的代码添加合理的限制。
可以把类型参数看作一个装着某种工具的黑盒子,你不知道里面具体是什么,但是你想安全地执行一些操作,这个时候会看有关于盒子内容的提示会非常有用。
trait约束就是用来限制类型参数的行为,可以限定单个或多个trait,每个trait都会告诉编译器类型参数应该具备哪些能力。

使用(:)用于添加trait约束,如<T: Trait>

use std::fmt::Display;

fn do_something<T: Display>(t: T) {
    println!("{}",t);
}

fn main() {
    do_something(20);
}

因为格式化字符串中的{}占位符需要实现Display trait,编译器不能假设类型参数T具有此特性。

可以给类型参数指定多个限制

<T: Trait1+Trait2+...>

示例

use std::{cmp::Ordering, fmt::Display};

fn largest<T: Ord+Display>(arg1: T, arg2: T) {
    match arg1.cmp(&arg2) {
        Ordering::Less => println!("{arg1} < {arg2}"),
        Ordering::Greater => println!("{arg1} > {arg2}"),
        Ordering::Equal => println!("{arg1} == {arg2}"),
    }
}

fn test() {
    largest(1, 2);
    largest("arg1", "arg2");
    largest('a', 'b');
    largest(100, 99);

}
fn main() {
    test();
}

largest是一个泛型函数,用于比较并输出结果。
要支持比较,这个类型必须实现Ord trait; 使用占位符{},要求这个类型必须实现Display,这两个trait都被应用与T的约束。

where

where是约束的另一种表示方法,这种方法的表达力更强

use std::fmt::Debug;

fn do_something<T>(t: T)
where T: Debug {
    println!("{:?}", t);
}
fn main() {
    let tuple = (1,2,3,5);

    do_something(tuple);
}

泛型结构体

除了函数之外,结构体同样支持泛型,在定义结构体时,可以在结构体名称后面加上<T> 之后该参数可以被用于结构体定义的任何位置(字段和方法)

struct Wrapper<T> {
    internal: T
}
fn main() {
    let obj = Wrapper {internal: 24};

    /* 会被单态化成i32类型
        struct wrapper {
            internal: i32
        }
    */

}

由于单态化机制,编译器会用i32类型实例化Wrapper结构体

泛型结构体不仅可以使用类型参数定义字段,还可以将类型参数用于方法定义。

use std::fmt::Debug;

#[derive(Debug)]
struct Wrapper<T> {
    internal: T
}

impl<T: Copy> Wrapper<T> {
    fn get(&self) -> T {
        self.internal
    }
}

fn main() {
    let obj = Wrapper { internal: 20 };
    let obj1 = Wrapper {internal: "hello".to_string() };

    let ret = obj.get();
    println!("{}",ret);
}

只有字段类型实现了Copy的T,才能调用该impl块内的所有方法。由于set方法需要创建T类型的拷贝,所以需要T实现Copy trait。而String类型不属于实现了Copy trait的类型,因此实例化后不具备get方法。

除了从结构体那里继承的类型参数,方法也可以定义专属于自己的类型参数。

use std::fmt::{Debug, Display};

#[derive(Debug)]
struct Wrapper<T> {
    internal: T
}

impl<T: Copy+Display> Wrapper<T> {
    fn display<U: Display>(&self, prefix: U, suffix: U) {
        println!("{prefix} {} {suffix}", self.internal);
    }
}

fn main() {
    let obj = Wrapper { internal: 20 };
    obj.display("(",")");
}

关联函数

你可以将泛型与关联函数一起使用

use std::fmt::{Debug, Display};

#[derive(Debug)]
struct Wrapper<T> {
    internal: T
}
impl<T: Display> Wrapper<T> {
    fn hello(name: T) {
        println!("hello {}",name)
    }
}

fn main() {
    Wrapper::hello("张三");
}

由于hello方法传入了一个&str类型的参数,编译器可以推断出T的类型,因此就不需要手动指定。
而下面这种情况需要手动指定类型:

use std::fmt::{Debug, Display};

#[derive(Debug)]
struct Wrapper<T> {
    internal: T
}
impl<T> Wrapper<T> {
    fn hello() {
        println!("helloworld");
    }
}

fn main() {
    Wrapper::<&str>::hello();
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值