Rust的泛型和特型

我爱海鲸 2024-02-01 17:20:46 rust学习

简介trait

1、提取函数消除重复

重复代码

fn main() {
    let number_list = vec![34,50,25,100,65];
    let mut largest = number_list[0];
    for number in number_list {
        if number > largest {
            largest = number
        }
    }

    println!("The largest number is {}",largest);

    let number_list = vec![34,50,25,100,65,646,871,69];
    let mut largest = number_list[0];
    for number in number_list {
        if number > largest {
            largest = number
        }
    }

    println!("The largest number is {}",largest);
}    

这两部分的代码是一样的,那么我们如何进行优化来消除重复代码呢?

fn main() {
    let number_list = vec![34,50,25,100,65];
    let result = largest(&number_list);

    println!("The largest number is {}",result);

    let number_list = vec![34,50,25,100,65,646,871,69];
    let result = largest(&number_list);

    println!("The largest number is {}",result);
}    


fn largest(list:&[i32]) ->i32{
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

消除重复的步骤

   识别重复代码

   提取重复代码到函数体中,并在函数签名中指定函数的输入和返回值

   将重复的代码使用函数调用进行替代

2、泛型

泛型:提高代码复用能力

   处理重复代码的问题

   泛型是具体类型或其它属性的抽象代替:

   你编写的代码不是最终的代码,而是一种模板,里面有一些“占位符”。

   编译器在编译时将“占位符”替换为具体的类型。

例如: fn largest<T>(list: &[T]) ->T{ ...}类型参数:

   很短,通常一个字母- CamelCase

   T: type的缩写

函数定义中的泛型

   泛型函数:

   参数类型

   返回类型

fn main() {
    let number_list = vec![34,50,25,100,65];
    let result = largest(&number_list);

    println!("The largest number is {}",result);

    let number_list = vec!['a','e','b','d','r','q'];
    let result = largest(&number_list);

    println!("The largest number is {}",result);
}    


fn largest<T>(list:&[T]) ->T{
    let mut largest = list[0];
    for &item in list {
        if item > largest {
            largest = item;
        }
    }
    largest
}

这里我们演示了泛型的使用,但是上面的代码会报错的,后面我们在说这些问题怎么解决

Struct定义中的泛型

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

可以使用多个泛型的类型参数

太多类型参数:你的代码需要重组为多个更小的单元

Enum定义中的泛型

可以让枚举的变体持有泛型数据类型

例如Option<T>,Result<T,E>

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

enum Result<T,E> {
    ok<T>,
    Err<E>,
}

fn main() {
    
}

方法定义中的泛型

为struct或enum实现方法的时候,可在定义中使用泛型

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

注意:
   把T放在 impl关键字后,表示在类型T上实现方法

      例如: impl<T> Point<T>

只针对具体类型实现方法(其余类型没实现方法):

例如: impl Point<f32>

struct里的泛型类型参数可以和方法的泛型类型参数不同

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

impl<T,U> Point<T,U>  {
    fn mixup<V,W>(self,other:Point<V,W>) -> Point<T,W> {
        Point {
            x:self.x,
            y:other.y
        }
    }
}

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

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

泛型代码的性能

使用泛型的代码和使用具体类型的代码运行速度是一样的。

单态化(monomorphization)

   在编译时将泛型替换为具体类型的过程

fn main() {
    let integer = Some(5);
    let float = Some(5.0);
}

enum Option_i32{
    Some(i32),
    None,
}

enum Option_f64{
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

3、trait(特型)

Trait告诉Rust 编译器:

   某种类型具有哪些并且可以与其它类型共享的功能

   Trait:抽象的定义共享行为

Trait bounds(约束):泛型类型参数指定为实现了特定行为的类型

Trait与其它语言的接口(interface)类似,但有些区别。

定义一个Trait

Trait的定义:把方法签名放在一起,来定义实现某种目的所必需的一组行为。

   关键字: trait

   只有方法签名,没有具体实现

   trait可以有多个方法:每个方法签名占一行,以;结尾

   实现该trait的类型必须提供具体的方法实现

pub trait Summary {
    fn summarize(&self) -> String;
    fn summarize1(&self) -> String;
}

// NewsArticle
// Tweet

fn main() {
    
}

在类型上实现 trait

与为类型实现方法类似。不同之处:

  • impl Xxxx for Tweet { ...}

在impl的块里,需要对Trait里的方法签名进行具体的实现

lib.rs:

pub trait Summary {
    fn summarize(&self) -> String;
}

// NewsArticle
pub struct NewsArticle {
    pub headline:String,
    pub location:String,
    pub author:String,
    pub content:String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{},by {} ({})",self.headline,self.author,self.location)
    }
}


// Tweet

pub struct Tweet {
    pub username:String,
    pub content:String,
    pub reply:bool,
    pub retweet:bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{},{}",self.username,self.content)
    }
}

main.rs:

use guessing_game::{Summary, Tweet};





fn main() {
    let tweet = Tweet {
        username:String::from("horse_book"),
        content:String::from("of course,as you probably already know,people"),
        reply:false,
        retweet:false,
    };

    println!("I new tewwt: {}",tweet.summarize())
}

实现 trait的约束

   可以在某个类型上实现某个trait的前提条件是:

      这个类型或这个trait是在本地crate里定义的

无法为外部类型来实现外部的trait:

   这个限制是程序属性的一部分(也就是一致性)

   更具体地说是孤儿规则:之所以这样命名是因为父类型不存在。

   此规则确保其他人的代码不能破坏您的代码。反之亦然。

如果没有这个规则,两个crate可以为同一类型实现同一个trait,Rust就不知道应该使用哪个实现了。

默认实现

   lib.rs:

pub trait Summary {
    // fn summarize(&self) -> String;
    fn summarize(&self) -> String {
        String::from("(Read more ..)") 
    }
}

// NewsArticle
pub struct NewsArticle {
    pub headline:String,
    pub location:String,
    pub author:String,
    pub content:String,
}

impl Summary for NewsArticle {
    // fn summarize(&self) -> String {
    //     format!("{},by {} ({})",self.headline,self.author,self.location)
    // }
}


// Tweet

pub struct Tweet {
    pub username:String,
    pub content:String,
    pub reply:bool,
    pub retweet:bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{},{}",self.username,self.content)
    }
}

main.rs:

use guessing_game::{NewsArticle, Summary};





fn main() {

    let news_article:NewsArticle  = NewsArticle {
        headline:String::from("快过年了"),
        content:String::from("快过年了"),
        author:String::from("我爱海鲸"),
        location:String::from("在广州"),
    };

    println!("I new tweet: {}",news_article.summarize())
}

默认实现的方法可以调用trait中其它的方法,即使这些方法没有默认实现。

lib.rs:

pub trait Summary {
    fn summarize_author(&self) -> String;
    fn summarize(&self) -> String {
        format!("(Read more ..),{}",self.summarize_author()) 
    }
}

// NewsArticle
pub struct NewsArticle {
    pub headline:String,
    pub location:String,
    pub author:String,
    pub content:String,
}

impl Summary for NewsArticle {
    fn summarize_author(&self) -> String {
        format!("@{}",self.author) 
    }
}


// Tweet

pub struct Tweet {
    pub username:String,
    pub content:String,
    pub reply:bool,
    pub retweet:bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{},{}",self.username,self.content)
    }
    fn summarize_author(&self) -> String {
        format!("@{}",self.username) 
    }
}

main.rs:

use guessing_game::{NewsArticle, Summary};





fn main() {

    let news_article:NewsArticle  = NewsArticle {
        headline:String::from("快过年了"),
        content:String::from("快过年了"),
        author:String::from("我爱海鲸"),
        location:String::from("在广州"),
    };

    println!("I new tweet: {}",news_article.summarize())
}

注意:无法从方法的重写实现里面调用默认的实现。

Trait作为参数

impl Trait 语法:适用于简单情况

pub fn notify(item: impl Summary) {
    println!("Break news! {}",item.summarize())
}

Trait bound语法:可用于复杂情况

pub fn notify<T:Summary>(item:T) {
    println!("Break news! {}",item.summarize())
}

   impl Trait语法是Trait bound的语法糖

使用+指定多个Trait bound

pub fn notify1(item1: impl Summary + Display) {
    println!("Break news! {}",item1.summarize())
}

pub fn notify<T:Summary + Display>(item1:T,item2:T) {
    println!("Break news! {}",item1.summarize())
}

Trait bound使用where子句

pub fn notify<T:Summary + Display,U:Debug + Display>(a:T,b:U) ->String{
    format!("Break news! {}",a.summarize())
}

pub fn notify2<T:Summary + Display,U:Debug + Display>(a:T,b:U) ->String
    where T:Summary + Display,
            U:Debug + Display 
{
    format!("Break news! {}",a.summarize())
}

   在方法签名后指定where子句

实现Trait作为返回类型

impl Trait语法

pub fn notify1(s:&str) -> impl Summary{
    NewsArticle {
        headline:String::from("快过年了"),
        content:String::from("快过年了"),
        author:String::from("我爱海鲸"),
        location:String::from("在广州"),
    }
}

注意:impl Trait只能返回确定的同一种类型,返回可能不同类型的代码会报错

pub fn notify1(flag:bool) -> impl Summary{
    if flag {
        NewsArticle {
            headline:String::from("快过年了"),
            content:String::from("快过年了"),
            author:String::from("我爱海鲸"),
            location:String::from("在广州"),
        }
    } else {
        Tweet {
            username:String::from("我爱海鲸"),
            content:String::from("快过年了"),
            reply:false,
            retweet:false,
        }
    }

}

在上面的代码中就会报错

使用Trait Bound 的例子:

在之前的largest的例子中我们的代码会报错,现在我们可以来进行修复了

fn main() {
    let number_list = vec![34,50,25,100,65];
    let result = largest(&number_list);

    println!("The largest number is {}",result);

    let number_list = vec!['a','e','b','d','r','q'];
    let result = largest(&number_list);

    println!("The largest number is {}",result);

    let number_list = vec![String::from("frwqas"),String::from("gfdsfas"),String::from("ffdsas")];
    let result = largest(&number_list);

    println!("The largest number is {}",result);
}    


fn largest<T:PartialOrd+Clone>(list:&[T]) ->&T{
    let mut largest = &list[0];
    for item in list {
        if item > &largest {
            largest = item;
        }
    }
    largest
}

使用Trait Bound有条件的实现方法

在使用泛型类型参数的impl块上使用Trait bound,我们可以有条件的为实现了特定Trait的类型来实现方法

use std::fmt::Display;


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

impl<T> Pair<T> {
    fn new(x:T,y:T)->Self {
        Self{x,y}
    }
}

impl<T:Display + PartialOrd> Pair<T> {
    fn cmd_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x={}",self.x);
        } else {
            println!("The largest member is y={}",self.y);
        }
    }
}


fn main() {
    
}

也可以为实现了其它Trait的任意类型有条件的实现某个Trait

为满足Trait Bound的所有类型上实现Trait叫做覆盖实现(blanket implementations)

我们直接看标准库里的方法:

impl<T: fmt::Display + ?Sized> ToString for T {
    // A common guideline is to not inline generic functions. However,
    // removing `#[inline]` from this method causes non-negligible regressions.
    // See <https://github.com/rust-lang/rust/pull/74852>, the last attempt
    // to try to remove it.
    #[inline]
    default fn to_string(&self) -> String {
        let mut buf = String::new();
        let mut formatter = core::fmt::Formatter::new(&mut buf);
        // Bypass format_args!() to avoid write_str with zero-length strs
        fmt::Display::fmt(self, &mut formatter)
            .expect("a Display implementation returned an error unexpectedly");
        buf
    }
}

这就是所谓的覆盖实现,就是我们可以为满足Display这个约束的ToString trait调用to_string这个方法

 

你好:我的2025

上一篇:Rust的错误处理

下一篇:Rust的生命周期