【Rust自学】10.4. trait Pt.2:trait作为参数和返回类型、trait bound
10.4 trait Pt.2trait作为参数和返回类型、trait bound10.4.1. 把trait作为参数继续以 10.3 trait Pt.1trait的定义、约束与实现 中的内容为例pub trait Summary { fn summarize(self) - String; } 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) } } 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) } }如果我们新定义一个函数notify它以NewsArticle和Tweet这两种类型作为参数并打印Breaking news!后面跟上在参数上调用Summary中summarize方法的返回值就会遇到一个问题这个函数接收的是两个不同的结构体类型。怎样才能让参数同时适用于这两种类型呢细想一下这两个结构体有什么共同点没错——它们都实现了Summarytrait。Rust 为这种情况提供了解决方案pub fn notify(item: impl Summary) { println!(Breaking news! {}, item.summarize()); }只要把参数类型写成impl 某个trait即可。因为这两个结构体都实现了Summarytrait所以写成impl Summary。又因为这个函数不需要数据的所有权所以写成引用impl Summary。如果还有其他数据类型也实现了Summary同样可以作为参数传入。impl trait语法适用于简单情况。对于更复杂的情况通常使用 trait bound 语法。同样的代码用 trait bound 来写pub fn notifyT: Summary(item: T) { println!(Breaking news! {}, item.summarize()); }这两种写法是等价的。不过在有两个参数时这两种写法的差别会更明显。假设我要设计一个新的notify1函数。它接收两个参数Breaking news!后面的内容是分别在每个参数上调用summarize的返回值。trait bound 写法pub fn notify1T: Summary(item1: T, item2: T) { println!(Breaking news! {} {}, item1.summarize(), item2.summarize()); }impl trait写法pub fn notify1(item1: impl Summary, item2: impl Summary) { println!(Breaking news! {} {}, item1.summarize(), item2.summarize()); }显然这两种写法并不等价。用 trait bound 时item1和item2必须是同一个具体类型都是T。用impl Trait时只要各自都实现了Summaryitem1和item2可以是不同类型例如一个是NewsArticle另一个是Tweet。当你需要两个参数共用同一类型时用 trait bound当允许不同类型、且签名仍然简单时用impl Trait即可。在简单情况下impl Trait相当于带有 trait bound 的匿名泛型写起来更省事。对于更复杂的签名——例如多个参数必须是同一类型或约束很多——具名的 trait bound或where子句通常更清晰。那么如果notify函数需要其参数同时实现Displaytrait 和Summarytrait 呢换句话说两个或更多 trait bound 该怎么写例如pub fn notify_with_displayT: Summary std::fmt::Display(item: T) { println!(Breaking news! {}, item); }使用连接各个 trait bound。还有一点因为Display不在预导入模块中写它时需要写出完整路径。也可以先在代码开头引入Display像这样use std::fmt::Display。然后就可以在 trait bound 中直接写Displayuse std::fmt::Display; pub fn notify_with_displayT: Summary Display(item: T) { println!(Breaking news! {}, item); }别忘了impl trait也是语法糖在这种语法糖中同样用连接 trait bounduse std::fmt::Display; pub fn notify_with_display(item: (impl Summary Display)) { println!(Breaking news! {}, item); }这种写法有一个缺点如果 trait bound 太多大量约束信息会降低函数签名的可读性。为了解决这个问题Rust 提供了一种替代语法在函数签名之后使用where子句来写 trait bound。下面是多个 trait bound 的普通写法use std::fmt::Display; use std::fmt::Debug; pub fn special_notifyT: Summary Display, U: Summary Debug(item1: T, item2: U) { println!(Breaking news! {} and {}, item1.summarize(), item2.summarize()); }同样的代码用where子句重写use std::fmt::Display; use std::fmt::Debug; pub fn special_notifyT, U(item1: T, item2: U) where T: Summary Display, U: Summary Debug, { println!(Breaking news! {} and {}, item1.summarize(), item2.summarize()); }这种语法与 C# 很相似。10.4.2. 把trait作为返回类型和把 trait 作为参数一样把 trait 作为返回值也可以使用impl 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, } }这种语法有一个缺点如果返回类型实现了某个 trait那么必须保证这个函数/方法所有可能的返回值都只能是同一种类型。这是因为impl形式在工作方式上有一些限制所以 Rust 并非在所有情况下都支持它。但 Rust 支持动态派发之后会讲。例如fn returns_summarizable(flag:bool) - impl Summary { if flag { Tweet { username: String::from(horse_ebooks), content: String::from( of course, as you probably already know, people, ), reply: false, retweet: false, } } else { NewsArticle { headline: String::from(Penguins win the Stanley Cup Championship!), location: String::from(Pittsburgh, PA, USA), author: String::from(Iceburgh, Scotland), content: String::from( The Pittsburgh Penguins once again are the best \ hockey team in the NHL., ), } } }根据flag的值可能有两种返回类型Tweet和NewsArticle。这时编译器会报错error[E0308]: if and else have incompatible types -- src/lib.rs:42:9 | 32 | / if flag { 33 | | / Tweet { 34 | | | username: String::from(horse_ebooks), 35 | | | content: String::from( 36 | | | of course, as you probably already know, people, ... | | 39 | | | retweet: false, 40 | | | } | | |_________- expected because of this 41 | | } else { 42 | | / NewsArticle { 43 | | | headline: String::from(Penguins win the Stanley Cup Championship!), 44 | | | location: String::from(Pittsburgh, PA, USA), 45 | | | author: String::from(Iceburgh, Scotland), ... | | 49 | | | ), 50 | | | } | | |_________^ expected Tweet, found NewsArticle 51 | | } | |_______- if and else have incompatible types | help: you could change the return type to be a boxed trait object | 31 - fn returns_summarizable(flag:bool) - impl Summary { 31 fn returns_summarizable(flag:bool) - Boxdyn Summary { | help: if you change the return type to expect trait objects, box the returned expressions | 33 ~ Box::new(Tweet { 34 | username: String::from(horse_ebooks), ... 39 | retweet: false, 40 ~ }) 41 | } else { 42 ~ Box::new(NewsArticle { 43 | headline: String::from(Penguins win the Stanley Cup Championship!), ... 49 | ), 50 ~ }) |报错信息说的是if和else的返回类型不兼容也就是它们不是同一种类型。使用trait bounds的实例还记得在 10.2. 泛型 中提到的比大小代码吗我把它粘在这里fn largestT(list: [T]) - T{ let mut largest list[0]; for item in list{ if item largest{ largest item; } } largest }当时出现的错误我也粘在这里error[E0369]: binary operation cannot be applied to type T -- src/main.rs:4:17 | 4 | if item largest{ | ---- ^ ------- T | | | T | help: consider restricting type parameter T with trait PartialOrd | 1 | fn largestT: std::cmp::PartialOrd(list: [T]) - T{ | 现在学了 trait 之后对这段代码及其报错信息的理解是不是又不一样了先从报错信息开始分析。错误说比较运算符不能应用于类型T。下面的help行说考虑限制类型参数T再往下给出了具体做法在T后面加上std::cmp::PartialOrd在 trait bound 中只需要写PartialOrd因为它在预导入模块中所以不需要写完整路径。这实际上就是用于比较的 trait。试试按照提示修改fn largestT: PartialOrd(list: [T]) - T{ let mut largest list[0]; for item in list{ if item largest{ largest item; } } largest }仍然会报错error[E0508]: cannot move out of type [T], a non-copy slice -- src/main.rs:2:23 | 2 | let mut largest list[0]; | ^^^^^^^ | | | cannot move out of here | move occurs because list[_] has type T, which does not implement the Copy trait | help: if T implemented Clone, you could clone the value -- src/main.rs:1:12 | 1 | fn largestT: PartialOrd(list: [T]) - T{ | ^ consider constraining this type parameter with Clone 2 | let mut largest list[0]; | ------- you could clone this value help: consider borrowing here | 2 | let mut largest list[0]; | error[E0507]: cannot move out of a shared reference -- src/main.rs:3:18 | 3 | for item in list{ | ---- ^^^^ | | | data moved here because item has type T, which does not implement the Copy trait | help: consider removing the borrow | 3 - for item in list{ 3 for item in list{ |但这次错误不同了无法从list中移出元素因为list中的T没有实现Copytrait。下面的help说如果T实现了Clonetrait可以考虑克隆该值。再下面还有一个help建议使用借用。根据以上信息有三种解决方案- 为泛型类型添加Copytrait- 使用克隆也就是为泛型类型添加Clonetrait- 使用借用该选择哪个方案呢这取决于你的需求。我想让这个函数处理数字和字符的集合。由于数字和字符都存储在栈上它们都实现了Copytrait所以只要给泛型类型加上Copy就够了fn largestT: PartialOrd Copy(list: [T]) - T{ let mut largest list[0]; for item in list{ if item largest{ largest item; } } largest } fn main() { let number_list vec![34, 50, 25, 100, 65]; let result largest(number_list); println!(The largest number is {}, result); let char_list vec![y, m, a, q]; let result largest(char_list); println!(The largest char is {}, result); }输出The largest number is 100 The largest char is y如果我想让这个函数比较String集合呢由于String存储在堆上它没有实现Copytrait所以给泛型类型加上Copy的思路行不通。那就试试克隆也就是给泛型类型加上Clonetraitfn largestT: PartialOrd Clone(list: [T]) - T{ let mut largest list[0].clone(); for item in list.iter() { if item largest{ largest item; } } largest } fn main() { let string_list vec![String::from(dev1ce), String::from(Zywoo)]; let result largest(string_list); println!(The largest string is {}, result); }输出error[E0507]: cannot move out of a shared reference -- src/main.rs:3:18 | 3 | for item in list.iter() { | ---- ^^^^^^^^^^^ | | | data moved here because item has type T, which does not implement the Copy trait | help: consider removing the borrow | 3 - for item in list.iter() { 3 for item in list.iter() { |错误说无法移动数据因为这种写法要求实现Copy而String做不到。该怎么办呢那就不要移动数据不要使用模式匹配。去掉item前面的这样item就从T变成了不可变引用T。然后在比较时使用解引用运算符*把T解引用为T再与largest比较下面的代码就是这种做法或者在largest前面加使其变成T。总之被比较的两个值必须类型一致fn largestT: PartialOrd Clone(list: [T]) - T{ let mut largest list[0].clone(); for item in list.iter() { if *item largest{ largest item.clone(); } } largest } fn main() { let string_list vec![String::from(dev1ce), String::from(Zywoo)]; let result largest(string_list); println!(The largest string is {}, result); }记住T没有实现Copytrait所以给largest赋值时需要使用clone方法。输出The largest string is dev1ce之所以这样写是因为返回值是T。如果把返回值改成T就不再需要克隆了fn largestT: PartialOrd(list: [T]) - T{ let mut largest list[0]; for item in list.iter() { if item largest{ largest item; } } largest } fn main() { let string_list vec![String::from(dev1ce), String::from(Zywoo)]; let result largest(string_list); println!(The largest string is {}, result); }但要记住初始化largest时必须把它设为T所以需要在list[0]前面加使其成为引用。另外比较时两边应是同一种值这里item和largest都是T因此可以直接写item largest。10.4.3. 使用trait bound有条件地实现方法如果在带有泛型类型参数的impl块上使用 trait bound就可以有条件地为实现了特定 trait 的类型实现方法。例如use std::fmt::Display; struct PairT { x: T, y: T, } implT PairT { fn new(x: T, y: T) - Self { Self { x, y } } } implT: Display PartialOrd PairT { 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); } } }无论T的具体类型是什么new函数都会存在于Pair上。但只有当T同时实现了Display和PartialOrd时才会有cmp_display方法。也可以为实现了另一个 trait 的任意类型有条件地实现某个 trait。为所有满足某个 trait bound 的类型实现一个 trait叫做覆盖实现blanket implementation。以标准库中的to_string函数为例implT: Display ToString for T { // ...... }这意味着对所有满足Displaytrait 的类型都实现了ToString这就是覆盖实现任何实现了Display的类型都可以调用ToString上的方法。以整数为例let s 3.to_string();之所以能这样做是因为i32实现了Displaytrait所以可以调用ToString上的to_string方法。

相关新闻

【Rust自学】10.5. 生命周期 Pt.1:生命周期的定义与意义、借用检查器与泛型生命周期

【Rust自学】10.5. 生命周期 Pt.1:生命周期的定义与意义、借用检查器与泛型生命周期

10.5 生命周期 Pt.1:生命周期的定义与意义、借用检查器与泛型生命周期 10.5.1. 什么是生命周期 Rust 中的每个引用都有自己的生命周期。生命周期的作用是让引用保持有效;换句话说,它就是引用保持有效的作用域。 在大多数情况下&#xff0c…

2026/7/23 18:51:03阅读更多 →
【Rust自学】7.5. use关键字 Pt.2 :重导入与换国内镜像源教程

【Rust自学】7.5. use关键字 Pt.2 :重导入与换国内镜像源教程

7.5 use关键字 Pt.2 :重导出与换国内镜像源教程 7.5.1. 使用pub use重新导出名称 使用use将路径导入作用域内后,该名称在词法作用域内是私有的。 以 7.4. use关键字 Pt.1:use的使用与as关键字 的代码为例: mod front_of_house…

2026/7/23 18:49:03阅读更多 →
硬件限制下的AI训练场景与优化方案

硬件限制下的AI训练场景与优化方案

一、先明确你的硬件核心限制 配置:i7-4710MQ(4 核 8 线程)+16G DDR3+GTX960M 4G 移动端显存 致命瓶颈:仅 4G 独立显存,移动端 GPU 算力弱、CUDA 最高仅支持 10.2/11.1,新版 PyTorch 不兼容;全量训练极易 OOM 显存溢出。 仅能做小型模型从零训练、轻量大模型 LoRA 微调、…

2026/7/23 18:49:03阅读更多 →
嵌入式音频降噪:双二阶IIR滤波器在步进电机噪声抑制中的应用

嵌入式音频降噪:双二阶IIR滤波器在步进电机噪声抑制中的应用

1. 项目概述:嵌入式音频处理中的噪声攻坚战 在嵌入式多媒体设备,比如我们常见的数码相机或智能手机里,录制视频时同步收录清晰的人声是一个看似简单却充满挑战的任务。想象一下,你正在用相机记录一段重要的家庭聚会发言&#xff0…

2026/7/23 20:05:28阅读更多 →
【AI数字人变现黄金法则】:20年实战总结的7大落地路径与避坑指南

【AI数字人变现黄金法则】:20年实战总结的7大落地路径与避坑指南

更多请点击: https://intelliparadigm.com 第一章:AI数字人变现的核心逻辑与市场定位 AI数字人并非单纯的技术炫技,其商业价值根植于“可替代性劳动可规模化触达高情感/专业溢价”的三维乘积模型。当一个数字人能稳定承接真人难以高频重复的…

2026/7/23 20:05:28阅读更多 →
OpenWrt在VMWare中的实战:从镜像转换到网络调试全记录

OpenWrt在VMWare中的实战:从镜像转换到网络调试全记录

OpenWrt在VMWare中的实战:从镜像转换到网络调试全记录 最近在捣鼓家庭网络,想搞个软路由玩玩,但直接上物理设备成本高,调试也麻烦。于是想到了在VMWare虚拟机里先跑个OpenWrt试试水,既能熟悉系统,又能模拟真实网络环境。这个想法听起来简单,但实际操作起来,从下载镜像…

2026/7/23 20:05:28阅读更多 →
OpenWrt软路由新玩法:用树莓派CM5实现4G网络共享(含USB网卡驱动安装指南)

OpenWrt软路由新玩法:用树莓派CM5实现4G网络共享(含USB网卡驱动安装指南)

树莓派CM5软路由实战:打造高性能4G移动网络中枢 在移动办公、户外项目部署或是家庭网络临时扩展的场景里,我们常常需要一个稳定、灵活且性能足够的网络接入点。传统的便携式路由器功能单一,性能有限,而专业的工业网关又往往价格不菲。有没有一种方案,能让我们用消费级硬件…

2026/7/23 20:05:28阅读更多 →
深度解析TMS570LS12x安全架构与内存映射:从原理到嵌入式开发实战

深度解析TMS570LS12x安全架构与内存映射:从原理到嵌入式开发实战

1. 项目概述在汽车电子、工业控制这些对可靠性要求近乎苛刻的领域,选对一颗微控制器只是第一步,真正理解它的“五脏六腑”和“运行法则”才是项目成败的关键。最近在做一个基于功能安全的电机控制项目,主控芯片选用了德州仪器(TI&…

2026/7/23 20:05:28阅读更多 →
13 - VRAM驱逐与恢复

13 - VRAM驱逐与恢复

难度: 🔴 高级 预计学习时间: 2.5小时 前置知识: 第7章(内存迁移机制)、第10章(MMU Notifier集成)、dma-fence基础 📋 概述 VRAM驱逐(Eviction)是SVM中最复杂的内存管理场景之一。当GPU显存不足,或CPU需要访问已迁移到VRAM的页面时,必须将VRAM中的数据迁移回系统内…

2026/7/23 20:03:28阅读更多 →
Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/23 0:56:31阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/23 0:56:31阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 0:56:31阅读更多 →
Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具

Chitchatter完整指南:免费开源的终极点对点安全聊天工具 【免费下载链接】chitchatter Secure peer-to-peer chat that is serverless, decentralized, and ephemeral 项目地址: https://gitcode.com/gh_mirrors/ch/chitchatter Chitchatter是一款革命性的安…

2026/7/23 0:00:28阅读更多 →
从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表)

更多请点击: https://intelliparadigm.com 第一章:从单点好评到指数级传播:AI副业主理人必须掌握的4层口碑渗透模型(含ROI测算表) 当AI副业主理人不再仅满足于单次服务交付,而是主动构建可复用、可裂变、可…

2026/7/23 0:00:28阅读更多 →
油泥处理设备哪里能买到

油泥处理设备哪里能买到

油泥处理设备哪里有?这是许多从事油田、炼化、清罐业务的从业者最关心的问题。根据河南三丰环保设备有限公司的行业经验,选购油泥处理设备的核心在于设备能否适配当地环保法规与原料特性,而非单纯看价格。该公司总经理王钦田先生指出&#xf…

2026/7/23 0:00:28阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/22 22:56:18阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/23 18:58:18阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/23 18:58:18阅读更多 →