rust 泛型、多态

本文详细介绍了 Rust 语言中的泛型编程概念,包括泛型函数、数据结构、常数泛型参数以及 trait 对象等内容。同时,阐述了 Rust 的隐式转换机制,如 Deref 和 DerefMut 的实现原理及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一,泛型

1,泛型函数

2,特征约束

(1)特征约束

(2)多重约束

(3)where

(4)子特征的约束推导出父特征

(5)模板类型的默认特征约束

(6)trait类型的入参

3,泛型数据结构

(1)泛型结构体

(2)泛型结构体的特化实例实现trait

(3)泛型结构体实现trait、带type的特征约束

(4)泛型枚举

4,常数泛型参数

5,泛型trait

6,trait内的泛型函数

7,trait类型的返回值

8,trait对象

(1)用法

(2)前提条件

(3)trait对象列表

(4)trait对象调用同名函数的优先级

二,隐式转换

0,背景知识

1,Deref

(1)定义

(2)示例:Vec转切片

(3)隐式转换

(4)自定义例子1

(5)编译取向

(6)自定义例子2

(7)转换链

(8)隐式转换导致拥有多个同名函数

(9)总结

2,DerefMut


一,泛型

1,泛型函数

下面是一个手动实现vec翻转的例子:

fn vector_reverse<T:Clone> (v:&Vec<T>)->Vec<T>{
    let mut ans = Vec::new();
    let mut i = v.len();
    loop {
        if i==0{
            break;
        }
        i-=1;
        ans.push(v[i].clone());
    }
    return ans;
}

这里一共有3处类型参数T

第一个vector_reverse<T:Clone>表示这个泛型函数的的参数就是T

第二个v:&Vec<T>表示入参是T类型的vec

第三个->Vec<T>表示返回值是T类型的vec

也可以使用多个模板参数:

fn f<T:Clone, S:Copy> (v:&Vec<T>,s:&Vec<S>)->i32
{
    return 0;
}

2,特征约束

(1)特征约束

vector_reverse的例子中,要求模板参数T具有Clone特征,这就是一个特征约束。

泛型函数要保证自身是能编译的,而不取决于调用代码。所以,泛型函数内部对T的约束条件,都通过指明T所包含的trait的方式进行说明。

(2)多重约束

如果T需要多个trait,采用加号把trait连接起来。

(3)where

如果特征约束比较多,为了不影响阅读,可以把约束提到函数头的末尾:

fn f<T, S> (v:&Vec<T>,s:&Vec<S>)->i32
    where T:Clone, S:Copy
{
    return 0;
}

(4)子特征的约束推导出父特征

fn f2<T:Ord+Clone>(arr:Vec<T>)->T{
    if(arr[0]==arr[1]){
        return arr[1].clone();
    }
    return arr[0].clone();
}

Ord特征中并没有eq函数,但是Ord特征间接继承了PartialOrd,所以有Ord特征的类型肯定是可以使用==的。

(5)模板类型的默认特征约束

对于绝大部分trait,模板类型都是默认不包含该trait的,需要特征约束才能说明该类型具有该trait。

而Sized是个反例,模板类型是默认包含Sized这个特征的,需要 ?Sized 才能说明可以不具有该特征。

pub trait Borrow<Borrowed: ?Sized> {
......
}

(6)trait类型的入参

fn exec(x:impl Display){
    print!("{}",x);
}

fn main() {
    exec(6);
}

这是一个语法糖,等价于:

fn exec<T:Display>(x:T){
    print!("{}",x);
}

3,泛型数据结构

数据结构要想好用,都得是泛型的。

无论是c++ STL还是rust std,里面所有的数据结构都是泛型的,c++和rust的结构体也类似,可以是泛型的也可以是非泛型的。

(1)泛型结构体

例如 Vec

pub struct Vec<T, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global> {
    buf: RawVec<T, A>,
    len: usize,
}

(2)泛型结构体的特化实例实现trait

trait VecEx {
    fn get_zeros_num(&self) -> i32;
}
impl VecEx for Vec<i32> {
    fn get_zeros_num(&self) -> i32{
        let mut ans = 0;
        for i in 0..self.len() {
            if (self[i] == 0) {
                ans+=1;
            }
        }
        return ans;
    }
}

(3)泛型结构体实现trait、带type的特征约束

use std::ops::Sub;
trait VecEx {
    fn get_zeros_num(&self) -> i32;
}
impl<T:Clone+Sub<Output=T>+PartialEq> VecEx for Vec<T> {
    fn get_zeros_num(&self) -> i32{
        let mut ans = 0;
        for i in 0..self.len() {
            if (self[i] == self[0].clone() - self[0].clone()) {
                ans+=1;
            }
        }
        return ans;
    }
}

(4)泛型枚举

例如option、result

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

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

4,常数泛型参数

和c++类型,常数也可以作为泛型参数

fn f<T:Clone,const N:usize>(arr:[T;N])->T{
    return arr[0].clone();
}

fn main() {
    let x = [1,2,3];
    let y = [1.5,2.5];
    assert_eq!(f(x),1);
    assert_eq!(f(y),1.5);
}

作为泛型参数的常数类型,可以是所有整数类型、bool类型、char类型

5,泛型trait

给结构体实现泛型trait,会遇到一些比较复杂的情况

(1)同名且返回值相同

struct S<T, T2>
{
    x:T,
    y:T2
}
trait MyTrait<T>{
    fn f(&self)->i32;
}

impl<T> MyTrait<T> for T{
    fn f(&self)->i32{
        -1
    }
}

impl<T,T2> MyTrait<T> for S<T,T2>{
    fn f(&self)->i32{
        1
    }
}

fn main() {
    let x=S{x:1, y:1};
    assert_eq!(<S<i32,i32> as MyTrait<i32>>::f(&x), 1);
    assert_eq!(<S<i32,i32> as MyTrait<S<i32,i32>>>::f(&x), -1);
    println!("end");
}

这样,实际上给S实现了2份MyTrait,必须用trait名调用函数。

(2)同名但返回值不同

参考Rc中的borrow函数

6,trait内的泛型函数

struct S{
    x:i32,
    y:f32
}

trait Tr {
    fn f<T:Display>(x:& T){}
}

impl Tr for S{
    fn f<T:Display>(x:& T)
    {
        println!("data = {}", x);
    }
}

fn main() {
    let mut s=S{x:5, y:7.7};
    S::f(&s.x);
    S::f(&s.y);
}

这个例子中,类型T是靠入参自动推导出来的。

其中一个特例就是,Self类型也可以用于泛型函数,只有满足条件的Self类型才能实例化对应的函数。

示例:

use std::mem::size_of_val;
trait MyTrait {
    fn my_func(&self) where Self:Eq {
        print!("{}", size_of_val(self));
    }
}

impl MyTrait for f32 {
}
impl MyTrait for i32 {
}
fn main() {
    let x:f32=1.0;
    // x.my_func(); //f32没有my_func
    let x:i32=1;
    x.my_func(); 
}

7,trait类型的返回值

fn f()->impl Display{
    4546
}
fn exec<T:Display>(x:T){
    print!("{}",x);
}

fn main() {
    let x = f();
    exec(x);
}

f可以返回任意具有Display特征的类型的数据。

8,trait对象

(1)用法

用dyn关键字,可以把一个trait当做一个数据类型。

struct S{
    x:i32
}
struct S2{
    y:f32
}

trait Ft{
    fn f(&self){}
    fn f2(&self){}
}
impl Ft for S{
    fn f(&self){
        print!("{}",self.x);
    }
}
impl Ft for S2{
    fn f(&self){
        print!("{}",self.y);
    }
}

fn exec(x: &dyn Ft) {
    x.f();
}

fn main() {
    let s1 = S{x:5};
    let s2=S2{y:2.3};
    exec(&s1);
    exec(&s2);
}

(2)前提条件

定义某个trait对象的前提条件是,该trait的所有函数都有self参数,且该trait不继承Sized

PS :该trait不继承Sized的前提条件是,该trait的所有函数没有Self类型的入参(除了self)和返回值。

(3)trait对象列表

不同实际类型的trait对象,可以用“trait对象”类型组成一个列表。

trait Ft{
  fn f(&self){}
}
struct S{
}
struct S2{
}
struct S3{
}


impl Ft for S{
  fn f(&self){
    println!("S");
  }
}
impl Ft for S2{
  fn f(&self){
    println!("S2");
  }
}
impl Ft for S3{
  fn f(&self){
    println!("S3");
  }
}

fn exec(v: Vec<&dyn Ft>) {
  for opt in v {
    opt.f();
  }
}

fn main() {
  let v=Vec::from([&S{} as &dyn Ft,&S2{} as &dyn Ft,&S3{} as &dyn Ft]);
  exec(v);
}

(4)trait对象调用同名函数的优先级

rust trait一文中,我总结了同名函数的优先级:

trait中的默认函数(低级)
给结构体实现的trait中的函数(中级)
直接实现的函数(高级)

对于trait对象,只能调用trait中的默认函数、给结构体实现的trait中的函数这2种,且优先级规律不变:对于一个结构体来说,实现了某个trait中的函数,trait中的默认函数就彻底失去了。

二,隐式转换

0,背景知识

解引用操作符* 和 引用操作符&或者&mut 是相反的。

所以,只要编译器遇到*&,或者*&mut,会直接抵消掉。

只要在类型T1中定义了1个函数f,把T1数据转换成&T2或者&mut T2类型,那么就有如下的代码:

let x:T1=T1{};
let y:T2=*x.f();

如果这个函数是deref或者deref_mut,那么就直接写成*x就行了。

可以说这是自定义解引用,也可以理解成一种隐式转换。

rust中只能自定义解引用,不能自定义引用。

1,Deref

rust语言对类型检查比较严格,不同类型之间是没有隐式转换的,除非实现了Deref这个trait

只要实现了Deref,就自动拥有了对应的隐式转换能力。

(1)定义

pub trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

(2)示例:Vec转切片

impl<T, A: Allocator> ops::Deref for Vec<T, A> {
    type Target = [T];

    #[inline]
    fn deref(&self) -> &[T] {
        unsafe { slice::from_raw_parts(self.as_ptr(), self.len) }
    }
}

使用示例:

    let c:Vec<u8>=Vec::new();
    let d:&[u8]=&c;

这里,&c本来是&Vec的类型,但是由于隐式转换,所以可以赋值给&[u8]类型的变量。

(3)隐式转换

实际上没有*操作符,deref也能发挥隐式转换的作用,而且场景很多,包括函数参数传递、赋值语句、解引用等

(4)自定义例子1

use std::ops;
use ops::Deref;

#[derive(Clone,Copy)]
struct S1{
}
struct S2{
}

impl ops::Deref for S2{
    type Target = S1;
    fn deref(&self)->&S1{
        &S1{}
    }
}

fn show(x:S1){
    println!("show");
}

fn main() {
    let x:S1=S1{};
    show(x);
    let x2=S2{};
    show(*x2.deref()); //展示了deref的原始语义,即*x2.deref()可以省略成*x2
    show(*x2); //x2转换成&S1类型
    show(*(&x2).deref()); //既可以用x2调用函数,也可以用&x2来调用
    //show(*&x2.deref()); error,等价于x2.deref(),不能转换成S1类型
    //show(*(&x2)); error,等价于x2,不能转换成S1类型
    show(*(&x2 as &S1)); //&x2先转换成&S1类型,再调用as
    println!("end");
}

看起来&x2可以变成&S1类型,x2也可以变成&S1类型,这不是有点矛盾吗?

我个人是这么理解的,deref提供的隐式转换,并不等同于把什么类型转换成什么类型,而是会自动判断要不要隐式地调用deref做隐式转换。

(5)编译取向

我们看一下这2行:

    //show(*(&x2)); error,等价于x2,不能转换成S1类型
    show(*(&x2 as &S1)); //&x2先转换成&S1类型,再调用as

这里面体现了编译器的取向:在最内层消除类型歧义,如果最内层2种类型都能编译,则默认不发生隐式转换

第一行中,&x2既可以是&S2类型,也可以是&S1类型,*(&x2)对于2种类型都能编译,于是采用了默认类型,*(&x2)变成了S2类型,消除了歧义,即*(&x2)不可能是S1类型。

第二行中,&x2 as &S1消除了歧义,一定是&S1类型了。

(6)自定义例子2

use std::ops;
use ops::Deref;

//#[derive(Clone,Copy)]
struct S1{
}
struct S2{
}

impl ops::Deref for S2{
    type Target = S1;
    fn deref(&self)->&S1{
        &S1{}
    }
}

fn show(x:&S1){
    println!("show");
}

fn main() {
    let x:S1=S1{};
    show(&x);
    let x2=S2{};
    show(x2.deref()); //展示了deref的原始语义,即*x2.deref()可以省略成*x2
    //show(x2); //error
    show(&*x2);
    show((&x2).deref()); //既可以用x2调用函数,也可以用&x2来调用
    show(*&x2.deref()); //等价于x2.deref()
    show(&x2);
    show(&x2 as &S1); //&x2先转换成&S1类型,再调用as

    let x3=&x2;
    show(x3);
    let x3:&S1=&x2;
    show(x3);
    //let x3:&S1=x2; //error
    println!("end");
}

首先,例1传的是S1类型,有几个调用形式需要S1有Copy,例2传的是&S1类型,不需要S1有Copy。

其次,一个很重要的区别是,在这个例子中&x2可以隐式转换成&S1类型,x2却不行。

&*x2和x2显然是不等价的,首先*x2是S1类型,然后&*x2是&S1类型。

(7)转换链

struct S1{
}
struct S2{
}
struct S3{
}
 
impl ops::Deref for S2{
    type Target = S1;
    fn deref(&self)->&S1{
        &S1{}
    }
}
impl ops::Deref for S3{
    type Target = S2;
    fn deref(&self)->&S2{
        &S2{}
    }
}
fn show(x:&S1){
    println!("show");
}

fn main() {
    let x:S1=S1{};
    show(&x);
    let x2=S2{};
    show(&x2);
    let x3=S3{};
    show(&x3);
    println!("end");
}

这里的&x3隐式转换成&S2类型,然后又隐式转换成&S1类型。

(8)隐式转换导致拥有多个同名函数

struct S1{
}
struct S2{
}
 
impl ops::Deref for S2{
    type Target = S1;
    fn deref(&self)->&S1{
        &S1{}
    }
}

trait MyTrait{
    fn f(&self);
}
impl MyTrait for S1 {
    fn f(&self){
        println!("show 1");
    }
}
impl MyTrait for S2 {
    fn f(&self){
        println!("show 2");
    }
}

fn main() {
    let x:S1=S1{};
    x.f();
    let x2=S2{};
    x2.f();
    (&x2 as &S1).f();
    println!("end");
}

其实上文的“编译取向”中已经有结论了,默认不发生隐式转换。

(9)总结

隐式转换包括函数参数传递、赋值语句、解引用等。

*x2会触发x2隐式转换成&S1类型,参数传递、赋值语句不会触发。

&x2隐式转换成&S1类型则比较容易,各种场景都可以。

任何可以隐式转换的场景,如果导致有类型歧义,总会在最内层消除类型歧义。

2,DerefMut

DerefMut和Deref类似,Deref是自定义不可变解引用,DerefMut是自定义可变解引用。

  • 当T: Deref<Target=U>时,允许&T转换为&U。
  • 当T: DerefMut<Target=U>时,允许&mut T转换为&mut U。
  • 当T: Deref<Target=U>时,允许&mut T转换为&U。

仍然遵从&mut是比&更高级的引用的原则。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值