12.泛型、trait和生命周期(中)

news2024/11/24 12:35:17

标题

  • 五、定义共享的行为
    • 5.1 定义trait
    • 5.2 实现trait
    • 5.4 将trait作为参数
    • 5.5 Trait Bound
    • 5.6 返回实现了trait的类型
    • 5.7 使用trait bounds修复get_max_value_from_vector的错误
    • 5.8 使用trait bound有条件地实现方法

五、定义共享的行为

  • 如果告诉Rust编译器某个特定类型拥有可能与其他类型共享的功能,可以通过trait实现;
  • train类似于其他语言中的接口功能;

5.1 定义trait

  • 一个类型的行为由其可供调用的方法构成;
  • 对不同类型调用相同的方法,这些类型就可以共享相同的行为;
  • trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合;

有两个存放了不同类型和属性文件的结构体

  • 结构体NewsArticle用于存放发生于世界各地的新闻故事;
  • 结构体Tweet最多只能存放 280 个字符的内容,以及一些其他内容的元数据;

现存要创建一个多媒体聚合库用来显示可能储存在NewsArticle或Tweet 实例中的数据的总结。每一个结构体都需要的行为是他们是能够被总结的,这样的话就可以调用实例的summarize方法来请求总结。

pub trait Summary {
    fn summarize(&self) -> String;
}
  • 使用trait关键字进行定义,后面跟trait的名字;
  • 大括号内部声明这个trait的方法;
  • 不需要写函数体,而直接跟上分号结尾;
  • 也可以有多个方法声明,每个声明以分号结尾;

5.2 实现trait

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

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

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

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

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

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

    println!("{}", tweet.summarize()); //horse_ebooks: of course, as you probably already know, people
}
  • 采用impl 结构体名 for trait名 {}的方式实现;
  • 大括号内实现trait内声明的所有行为
  • 实现trait后,就可以在main方法中在Tweet 实例中调用 trait 方法;
  • 公有pub的trait可以使得其他crate可以实现此trait;

为trait增加默认实现

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

impl Summary for NewsArticle{}

fn main() {
    let article = NewsArticle {
        headline: String::from("Penguins win the Stanley Cup Championship!"),
        location: String::from("Pittsburgh, PA, USA"),
        author: String::from("Iceburgh"),
        content: String::from("The Pittsburgh Penguins once again are the best
        hockey team in the NHL."),
    };
    
    println!("New article available! {}", article.summarize()); //New article available! (Read more...)
}

代码所做的改变如下

  • 为Summary trait 的summarize方法指定一个默认的字符串值;
  • 将NewsArticle对该trait的实现置空(如果不置空调用的时候不会采用默认的实现)

5.4 将trait作为参数

  • 定义一个函数调用参数item上的summarize方法,该参数是实现了Summary trait的某种类型;
  • 使用impl Trait语法实现
pub fn notify(item: impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
  • 对于item参数,指定了impl关键字和trait名称,而不是具体的类
  • item参数支持任何实现了指定trait的类型
  • 在notify函数体中,可以调用任何来自Summary trait的方法(比如 summarize);
  • 可以传递任何NewsArticle或Tweet的实例来调用notify;
  • 任何用其它如 String 或 i32 的类型调用该函数的代码都不能编译,因为它们没有实现 Summary;

5.5 Trait Bound

  • impl Trait语法适用于直观的例子,它实际上是一种较长形式的语法糖,称之为trait bound,看起来像
pub fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}
  • trait bound 与泛型参数声明在一起,位于尖括号中的冒号后面;
  • impl Trait适用于短小的例子,trait bound则适用于更复杂的场景;

例如获取两个实现了Summary的参数,使用impl Trait语法实现

pub fn notify(item1: impl Summary, item2: impl Summary) {}
  • 上述代码适用于item1和item2允许是不同类型的情况,如果强制它们都是相同的类型呢?那就只能使用trait bound
pub fn notify<T: Summary>(item1: T, item2: T) {]
  • 泛指类型T被指定为item1和item2,因此这两个参数的具体类型必须一致

通过 + 指定多个trait bound

  • notify需要显示item的格式化形式,同时也要使用summarize方法;
  • 因此item就需要同时实现两个不同的trait:Display和Summary;
  • 可以通过+语法实现;
  • 通过指定两个trait bound,nofity的函数体就可以调用summarize并使用{}格式化item;
pub fn notify(item: impl Summary + Display) {}
  • 也适用于泛型的trait bound
pub fn notify<T: Summary + Display>(item: T) {}

通过where简化trait bound

  • 每个泛型有其自己的 trait bound;
  • 有多个泛型参数的函数在名称和参数列表之间会有很长的 trait bound 信息;
  • 这就会使得函数参数多到难以阅读;
  • Rust可以在函数之后的where从句中指定trait bound的语法;
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {}

等价于

fn some_function<T, U>(t: T, u: U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{}

5.6 返回实现了trait的类型

可以在返回值中使用impl Trait语法来返回实现了某个trait的类型

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from("of course, as you probably already know, people"),
        reply: false,
        retweet: false,
    }
}
  • 指定了returns_summarizable函数返回某个实现了 Summary trait 的Tweet,但是调用方并不知情;
  • 返回一个只是指定了需要实现的 trait 的类型的能力在闭包和迭代器场景十分的有用;
  • 闭包和迭代器创建只有编译器知道的类型,或者是非常非常长的类型;
  • impl Trait允许简单的指定函数返回一 Iterator而无需写出实际的冗长的类型;

上述只适用于返回单一类型的情况,要返回不同的类型就无法编译通过;

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from("Penguins win the Stanley Cup Championship!"),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from("The Pittsburgh Penguins once again are the best
            hockey team in the NHL."),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from("of course, as you probably already know, people"),
            reply: false,
            retweet: false,
        }
    }
}

编译报错
在这里插入图片描述

5.7 使用trait bounds修复get_max_value_from_vector的错误

      在 get_max_value_from_vector函数体中我们想要使用大于运算符(>)比较两个T类型的值。这个运算符被定义为标准库中 trait std::cmp::PartialOrd 的一个默认方法。所以需要在 T 的 trait bound 中指定 PartialOrd,这样get_max_value_from_vector函数可以用于任何可以比较大小的类型的 slice。因为 PartialOrd 位于 prelude 中所以并不需要手动将其引入作用域。将get_max_value_from_vector函数修改如下

fn get_max_value_from_vector<T: PartialOrd>(src: &[T]) -> T {
    let mut max = src[0];

    for &item in src.iter(){
        if item > max {
            max = item;
        }
    }

    max
}

编译报错
在这里插入图片描述

  • i32 和 char 这样的类型是已知大小的并可以储存在栈上,所以他们实现了 Copy trait;
  • 当我们将 get_max_value_from_vector函数改成使用泛型后,现在src参数的类型就有可能是没有实现 Copy trait 的。这意味着我们可能不能将 src[0] 的值移动到max变量中,这导致了上面的错误;
  • 为了只对实现了 Copy 的类型调用这些代码,可以在 T 的 trait bounds 中增加 Copy;
fn get_max_value_from_vector<T: PartialOrd + Copy>(src: &[T]) -> T {}

然而

  • 代码实现在功能上没问题;
  • 数据对比时增加值的拷贝功能会增大程序的运行时长,如果是String类型则增加的运行时长更加多;
  • 最好的解决方法是直接返回最大值的引用,这样就避免了拷贝操作;
    最终代码
fn get_max_value_from_vector<T: PartialOrd>(src: &[T]) -> &T {
    let mut max = &src[0];

    for item in src.iter(){
        if item > max {
            max = item;
        }
    }
    
    max
}

fn main() {
    let x = vec![12, 21, 78, 56, 77];
    let y = vec![11.5, 100.3, 1.0, 90.1];

    println!("max x: {}", get_max_value_from_vector(&x));   //max x: 78
    println!("max y: {}", get_max_value_from_vector(&y)); //max y: 100.3
}

5.8 使用trait bound有条件地实现方法

  • 通过使用带有 trait bound 的泛型参数的 impl 块,可以有条件地只为那些实现了特定 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 cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}
  • 类型Pair<T>总是实现了 new 方法,不过只有那些为T类型实现了PartialOrd trait允许比较和Display trait启用打印的Pair<T>才会实现 cmp_display 方法;

也可以对任何实现了特定 trait 的类型有条件地实现 trait

  • 对任何满足特定 trait bound 的类型实现 trait 被称为 blanket implementations;
  • 他们被广泛的用于 Rust 标准库中;

标准库为任何实现了 Display trait 的类型实现了ToString trait,这个 impl 块看起来像这样

impl<T: Display> ToString for T {
    // --snip--
}

由于标准库有了这些 blanket implementation,我们可以对任何实现了 Display trait 的类型调用由 ToString 定义的 to_string 方法。例如,可以将整型转换为对应的 String 值(因为整型实现了 Display)

let s = 3.to_string()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1828042.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【办公类-04-03】华为助手导出照片视频分类(根据图片、视频的文件名日期分类导出)

背景需求&#xff1a; 用华为手机助手导出的照片视频&#xff0c;只能将jpg照片&#xff08;exifread读取图片的exif拍摄日期&#xff0c;Png、JPEG、mp4都无法识别到exif信息&#xff09; 【办公类-04-02】华为助手导出照片&#xff08;jpg&#xff09;读取拍摄时间分类导出…

Linux——ansible的应用

要让ansible管理业务里的主机 1.得先知道&#xff0c;有哪些主机 用IP地址&#xff0c;用主机名 2.知道了有哪些主机以后&#xff0c;精细、细分管理 主机要用某些办法&#xff0c;分组管理 在ansible里&#xff0c;要用一个东西&#xff1a;清单->inventory inventory …

C学习自学笔记-会陆续完善对应章节编程经典例子

C学习笔记 0>C语言概述 为什么学习C语言 1&#xff09;C的起源和发展------了解即可 B语言、C语言、C语言的产生地&#xff1a;都出自 美国贝尔实验室 2&#xff09;C的特点 优点&#xff1a;代码量小、速度快、功能强大 缺点&#xff1a;危险性高、开发周期长、可移植性…

POC EXP | woodpecker插件编写

woodpecker插件编写 目录 woodpecker介绍woodpecker使用插件编写 安装环境 woodpecker-sdkwoodpecker-request 创建Maven项目 Confluence OGNL表达式注入漏洞插件编写 创建Package包和Class类编写POC 漏洞POC代码编写导出jar包将jar包放入woodpecker的plugin目录运行woodpeck…

2.2 抽头

目录 为什么要抽头 什么是抽头 接入系数 怎么抽头 信号源端抽头 负载端抽头 例题分析 要点总结 为什么要抽头 阻抗转换&#xff0c;使信号源内阻Rs与负载电阻RL变得很大&#xff0c;分流小&#xff0c;再使用并联方式。 什么是抽头 接入系数 电容越大&#xff0c;分压越…

Hadoop 2.0:主流开源云架构(四)

目录 五、Hadoop 2.0访问接口&#xff08;一&#xff09;访问接口综述&#xff08;二&#xff09;浏览器接口&#xff08;三&#xff09;命令行接口 六、Hadoop 2.0编程接口&#xff08;一&#xff09;HDFS编程&#xff08;二&#xff09;Yarn编程 五、Hadoop 2.0访问接口 &am…

最大连续子序列和问题详解

最大连续子序列和问题如下&#xff1a;给定一个数字序列&#xff0c;求i,j&#xff0c;使得最大&#xff0c;输出这个最大和。 这个问题如果用暴力来做&#xff0c;枚举左端点和右端点&#xff0c;需要的复杂度&#xff0c;而计算需要的复杂度&#xff0c;因此总时间复杂度为。…

表面声波滤波器——压电材料(2)

声表面波压电材料 压电效应&#xff1a; 1880年&#xff0c;法国物理学家居里兄弟(PCunie 和J.Curie)发现将重物置于α石英晶体上,品体部分表面会产生电荷&#xff0c;电荷量与所受压力成正比正压电效应&#xff1a;外加压力作应下在表面间产生电位差。 和逆压电效&#xff1a…

14 学习PID--步进电机梯形加减速实现原理

步进电机加减速使用的场景有那些呢&#xff1f;为什么要使用加减速呢&#xff1f; 硬件驱动细分器与软件的细分参数或定时器分频参数设置不当时启动电机时&#xff0c;会遇见步进电机有啸叫声但是不会转动&#xff0c;这是因为软件产生脉冲的频率大于步进电机的启动频率&#x…

Iptables深入浅出

1、iptables的基本概念 众所周知iptables是Linux系统下自带免费的包过滤防火墙。其实不然&#xff0c;iptables其实不是真正的防火墙&#xff0c;我们可以把它理解成一个客户端代理&#xff0c;用户通过iptables这个代理&#xff0c;将用户的安全设定执行到对应的”安全框架”…

【Oracle APEX开发小技巧1】转换类型实现显示小数点前的 0 以 及常见类型转换

在 apex 交互式式网格中&#xff0c;有一数值类型为 NUMBER&#xff0c;保留小数点后两位的项&#xff0c;在 展示时小数点前的 0 不显示。 效果如下&#xff1a; 转换前&#xff1a; m.WEIGHT_COEFFICIENT 解决方案&#xff1a; 将 NUMBER&#xff08;20&#xff0c;2&#xf…

Python进阶:从函数到文件的编程艺术!!!

第二章&#xff1a;Python进阶 模块概述 函数是一段可重复使用的代码块&#xff0c;它接受输入参数并返回一个结果。函数可以用于执行特定的任务、计算结果、修改数据等&#xff0c;使得代码更具模块化和可重用性。 模块是一组相关函数、类和变量的集合&#xff0c;它们被封…

Android Media Framework(六)插件式编程与OMXStore

OpenMAX IL Spec阅读到上一节就结束了&#xff0c;这一节开始正式进入到Framework阅读阶段&#xff0c;我们将了解OpenMAX框架是如何与Android Framework连接的。 1、插件式编程 插件式编程&#xff08;Plugin-based Programming&#xff09;是一种软件开发模式&#xff0c;它…

docker login 报错: http: server gave HTTP response to HTTPS client

环境&#xff1a; 自建 Harbor、Docker 1. 问题分析 # 命令&#xff0c;这里用的是 IP&#xff0c;可以为域名 docker login -u test 172.16.51.182:31120 # 输入密码 Password:# 报错如下&#xff1a; Error response from daemon: Get "https://172.16.51.182:31120/…

Flutter IOS 打包上架踩坑

前言 Flutter 作为一款跨平台的移动应用开发框架&#xff0c;凭借其高效、灵活和美观的特性&#xff0c;受到了越来越多开发者的青睐。 然而&#xff0c;当开发者们倾注心血完成 Flutter iOS 应用开发后&#xff0c;如何将应用成功上架至苹果商店&#xff08;App Store&#…

关于QTcreator,19年大学时写的文章了,之前写在印象笔记现在拉过来,往事如烟呐

1.初来乍到&#xff0c;先按照书本写一个基础列程理解一下原理。 这里创建工程的时候选择Qdialog基类&#xff0c;dialog.h头文件&#xff0c;并且勾选了创建界面 &#xff08;勾选之后可以通过手动添加组块并且可以自生成他们的函数定义&#xff0c;如果没有勾选&#xff0c;…

R 初级教程之一

IT的发展目前已经相当的内卷&#xff0c;到处都在说24年是将来4年最难的一年&#xff01;确实是&#xff0c;眼下各大厂商都在疯狂的裁员砍掉不营利的业务&#xff0c;收紧业务&#xff0c;不再盲目的扩张。小公司更是水深火热&#xff0c;无以言表。近期有个医院联系让使用R给…

uniapp中unicloud接入支付宝订阅消息完整教程

经过无数次的尝试,终于还是让我做出来了 准备工作 设置接口加签方式 使用支付宝小程序订阅消息,首先要设置接口加签方式,需要下载支付宝开放平台密钥工具,按照步骤生成秘钥,然后按照支付宝设置密钥加签方式添加接口加签方式。 有一点需要注意的,因为要在云函数中使用,…

MyBatis源码--04:MyBatis缓存实现

缓存作用&#xff1a; 在程序访问数据库这个过程中&#xff0c;存在几个性能瓶颈&#xff1a; 网络通信非关系型数据库将数据存储在硬盘当中&#xff0c;需要进行硬盘操作Java对象复用问题&#xff08;Connection连接池&#xff0c;Statement对象&#xff09; 缓存在程序和数…

Python猫周刊赠书规则与书单

△△请给“Python猫”加星标 &#xff0c;以免错过文章推送 你好&#xff0c;我是猫哥。在创作《Python潮流周刊》一年多的时间里&#xff0c;我已累计通过抽奖赠书 80 本&#xff01;早期周刊是免费分享&#xff0c;所以参与人数很多&#xff0c;现在周刊转为付费后&#xff0…