rust 泛型、多态

目录

一,泛型

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是靠入参自动推导出来的。

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参数,不能直接借用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是比&更高级的引用的原则。

  • 21
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值