【Rust中级教程】2.4. API设计原则之不意外性(unsurprising) Pt.4:“人体工程学”的trait实现、包装类型(Wrapper Types)、`Borrow` trait
2.4. API设计原则之不意外性(unsurprising) Pt.4“人体工程学”的trait实现、包装类型(Wrapper Types)、Borrow trait2.4.1. “人体工程学”的trait实现Rust不会自动为实现某一trait的类型的引用提供对应的实现。比如说Bar实现了Trait但不能将Bar传递给fn fooT: Trait(t: T)。因为为Bar实现Trait并不会自动为Bar实现Trait。看代码例trait Trait { fn name(self) - static str; } struct Bar; impl Trait for Bar { fn name(self) - static str { Bar } } fn fooT: Trait(t: T) { println!({}, t.name()); } fn main() { let bar Bar; foo(bar); // OK let bar_ref Bar; foo(bar_ref); // 报错[E0277]the trait bound Bar: Trait is not satisfied }而用户看到某个trait的方法只接收self而不接收self或mut self时仍然可能惊讶于Bar不能满足T: Trait不符合不意外(unsurprising)原则。为了解决这一问题我们需要在定义新的trait时通常在方法签名允许的情况下典型是self/mut self方法为下列提供相应的全局实现-T where T: Trait ?Sized-mut T where T: Trait ?Sized-BoxT where T: Trait ?Sized接着上面的代码例为了不让foo(bar_ref);报错我们需要手动提供T的Trait实现implT: Trait ?Sized Trait for T { fn name(self) - static str { (**self).name() } }注意如果 trait 方法按值接收self消耗所有权一般无法写一个把调用转发给T的impl Trait for T因为共享引用不能把T移出去。对于迭代器来说如果某个类型可以迭代那么它的引用也应该添加相应的trait实现。也就是说对于任何可迭代的类型考虑为MyType和mut MyType实现IntoIterator。这样在循环中我们就可以直接使用借用的实例符合用户预期。看个代码例struct MyCollection { items: Veci32, } // 为 MyCollection 实现 IntoIterator impl IntoIterator for MyCollection { type Item i32; type IntoIter std::vec::IntoIterSelf::Item; fn into_iter(self) - Self::IntoIter { self.items.into_iter() } } // 为 MyCollection 实现 IntoIterator impla IntoIterator for a MyCollection { type Item a i32; type IntoIter std::slice::Itera, i32; fn into_iter(self) - Self::IntoIter { self.items.iter() } } // 为 mut MyCollection 实现 IntoIterator impla IntoIterator for a mut MyCollection { type Item a mut i32; type IntoIter std::slice::IterMuta, i32; fn into_iter(self) - Self::IntoIter { self.items.iter_mut() } } fn main() { let mut collection MyCollection { items: vec![1, 2, 3] }; // 使用所有权迭代 for item in collection { println!(Owned: {}, item); } let collection MyCollection { items: vec![4, 5, 6] }; // 使用不可变借用迭代 for item in collection { println!(Borrowed: {}, item); } let mut collection MyCollection { items: vec![7, 8, 9] }; // 使用可变借用迭代 for item in mut collection { *item * 2; } // 确保修改生效 for item in collection { println!(Modified: {}, item); } }2.4.2. 包装类型(Wrapper Types)Rust没有面向对象传统意义上的继承但是Deref和AsRef提供了类似继承的东西。比如说你有一个类型为T的值并满足DerefTarget U那就可以在T类型值上直接调用类型U的方法。看代码例use std::ops::Deref; // 定义一个包装类型 Wrapper它内部存储一个 String struct Wrapper(String); // 实现 Deref使得 Wrapper 的 Deref 目标是 String impl Deref for Wrapper { type Target String; fn deref(self) - Self::Target { self.0 } } fn main() { let my_wrapper Wrapper(String::from(Hello, Rust!)); // 由于 Wrapper 实现了 DerefTarget String // 这里可以直接调用 String 的方法而不需要手动解引用 let len my_wrapper.len(); let uppercased my_wrapper.to_uppercase(); println!(Length: {}, len); println!(Uppercased: {}, uppercased); }输出Length: 12 Uppercased: HELLO, RUST!如果你提供了相对透明的类型例如ArcT那么实现Deref允许你的包装类型在使用点运算符时自动解引用为内部类型从而可以直接调用内部类型的方法。如果访问内部类型不需要任何复杂或潜在的低效逻辑应考虑实现AsRef这样用户就可以轻松将WrapperType作为InnerType使用。对于大多是包装类型还应在可能的情况下为包装类型实现FromInnerType并为内部类型实现FromWrapper这样会免费得到Into以便用户可以轻松地添加或移除包装。看代码例use std::ops::Deref; use std::sync::Arc; // 定义一个包装类型 struct Wrapper(ArcString); // 实现 Deref 以允许透明地访问内部 String impl Deref for Wrapper { type Target String; fn deref(self) - Self::Target { self.0 } } // 实现 AsRefString允许用户获取 String impl AsRefString for Wrapper { fn as_ref(self) - String { self.0 } } // 实现 FromString 以便用户轻松创建 Wrapper impl FromString for Wrapper { fn from(s: String) - Self { Wrapper(Arc::new(s)) } } // 实现 FromWrapper for String允许用户将 Wrapper 转回 String克隆字符串 impl FromWrapper for String { fn from(w: Wrapper) - Self { (*w).clone() // Deref 使 Wrapper 可以当作 String 使用 } } fn main() { let wrapped Wrapper::from(Hello, Rust!.to_string()); // 由于实现了 Deref我们可以直接调用 String 的方法 println!(Length: {}, wrapped.len()); println!(Uppercased: {}, wrapped.to_uppercase()); // 使用 AsRef可以获取 String 引用 let str_ref: String wrapped.as_ref(); println!(AsRef: {}, str_ref); // 通过 Into 获取 String克隆 let original: String wrapped.into(); println!(Converted back: {}, original); }输出Length: 12 Uppercased: HELLO, RUST! AsRef: Hello, Rust! Converted back: Hello, Rust!2.4.3.BorrowtraitBorrowtrait与Deref和AsRef有些类似不过它针对的是更为狭窄的使用情况并且进行了定制。Borrowtrait允许调用者提供统一类型的多个本质上相同的变体中的任意一个这些变体叫做Equivalent。注意Borrowtrait仅适用于当你的类型本质上与另一个类型等价时(Equivalent)。也就是说Borrow适用于“等价”的情况而AsRef和Deref适用于“充当”的情况。比如说对于一个HashSetStringBorrow允许调用者提供str或String。看一个代码例use std::collections::HashSet; fn main() { let mut set: HashSetString HashSet::new(); set.insert(hello.to_string()); set.insert(world.to_string()); // 直接使用 str 进行查找而不需要创建 String。 // 这能工作是因为标准库已经提供了 impl Borrowstr for String。 let exists set.contains(hello); let not_exists set.contains(rust); println!(Contains hello: {}, exists); println!(Contains rust: {}, not_exists); }输出Contains hello: true Contains rust: false与AsRef的对比当然使用AsRef也是可以实现上面的代码效果的:use std::collections::HashSet; // 泛型函数接受任何 AsRefstr 的类型如 str 和 String fn containsS: AsRefstr(set: HashSetString, value: S) - bool { set.contains(value.as_ref()) // AsRefstr 使 value 转换为 str } fn main() { let mut set: HashSetString HashSet::new(); set.insert(hello.to_string()); set.insert(world.to_string()); // 直接使用 str 进行查找 let exists contains(set, hello); // 也可以使用 String 进行查找 let string_value world.to_string(); let exists_string contains(set, string_value); println!(Contains hello: {}, exists); println!(Contains world: {}, exists_string); }使用AsRef能达到同样的效果但是如果没有Borrow的额外要求这种实现对哈希表查找是不安全的因为Borrow要求借用形式的Hash、Eq和Ord必须与拥有类型保持一致。潜在的问题是AsRefU甚至不会在文档层面要求源类型与U之间的Hash、Eq和Ord保持一致。比如说use std::collections::HashSet; #[derive(Hash, Eq, PartialEq)] struct CustomType { value: String, } // 实现 AsRefstr但这本身并不能让 HashSet 用 str 查找合法 impl AsRefstr for CustomType { fn as_ref(self) - str { self.value } } fn main() { let mut set: HashSetCustomType HashSet::new(); set.insert(CustomType { value: hello.to_string() }); // 这里不会通过编译error[E0308]contains 依赖 Borrow 而不是 AsRef // 因此没有 CustomType: Borrowstr 时 str 无法匹配 let exists set.contains(hello); println!(Exists: {}, exists); }contains的类型是按Borrow写的所以AsRefstr并不参与即便你写了借助AsRef再查找的辅助函数类型系统也不会强制CustomType的Hash/Eq与str一致相比之下BorrowU才是HashMap/HashSet查找使用的 trait其文档要求类型与借用形式之间的Hash、Eq和Ord保持一致编译器不会证明这一点实现者必须遵守use std::collections::HashSet; use std::borrow::Borrow; #[derive(Hash, Eq, PartialEq)] struct CustomType { value: String, } // Borrowstr 才让 contains(hello) 成为可能并且你必须让 Hash/Eq 与 str 对齐 impl Borrowstr for CustomType { fn borrow(self) - str { self.value } } fn main() { let mut set: HashSetCustomType HashSet::new(); set.insert(CustomType { value: hello.to_string() }); let exists set.contains(hello); // 在 Hash/Eq 与 str 一致时查找是安全的 println!(Exists: {}, exists); }其余特性Borrowtrait还为BorrowT、T和mut T提供了通用实现。这使得在trait约束中使用它来给接收给定类型的拥有值或引用值非常方便。Rust 标准库中为所有 T 提供了以下BorrowT的通用实现这些实现意味着- 类型T本身可以BorrowT即T可以直接作为BorrowT的参数-T也可以BorrowT这允许我们用一个不可变引用来满足BorrowT的约束-mut T也可以BorrowT这允许我们用一个可变引用来满足BorrowT的约束假设我们有一个find_item函数它在HashMapK, V中查找某个键use std::borrow::Borrow; use std::collections::HashMap; use std::hash::Hash; fn find_itema, K, V, Q(map: a HashMapK, V, key: Q) - Optiona V where K: Eq Hash BorrowQ, // 关键点K 可以借用为 Q Q: ?Sized Eq Hash, { map.get(key) } fn main() { let mut map: HashMapString, i32 HashMap::new(); map.insert(hello.to_string(), 42); // 由于 String: Borrowstr我们可以用 str 直接查找 HashMapString, i32 let value find_item(map, hello); println!(Value: {:?}, value); // Output: Value: Some(42) }这里的BorrowT提供的便利有这些-String可以作为str的BorrowT实现因此HashMapString, i32允许用str作为键进行查找-find_item(map, hello)直接传入str而不需要转换成String-find_item(map, hello.to_string())也可以工作因为String也满足Borrowstr

相关新闻

【Rust中级教程】2.3. API设计原则之不意外性(unsurprising) Pt.3:实现serde下的Serialize和Deserialize trait、不建议实现Copy trait

【Rust中级教程】2.3. API设计原则之不意外性(unsurprising) Pt.3:实现serde下的Serialize和Deserialize trait、不建议实现Copy trait

2.3. API设计原则之不意外性(unsurprising) Pt.3:实现serde下的Serialize和Deserialize trait、不建议实现Copy trait 2.3.1. 建议实现serde下的Serialize和Deserialize trait serde是Rust中用于序列化(serialization)和反序列化(…

2026/7/24 22:44:59阅读更多 →
猫抓浏览器扩展终极指南:如何高效获取网页视频资源

猫抓浏览器扩展终极指南:如何高效获取网页视频资源

猫抓浏览器扩展终极指南:如何高效获取网页视频资源 【免费下载链接】cat-catch 猫抓 浏览器资源嗅探扩展 / cat-catch Browser Resource Sniffing Extension 项目地址: https://gitcode.com/GitHub_Trending/ca/cat-catch 猫抓(cat-catch&#xf…

2026/7/24 22:44:59阅读更多 →
微信单向好友检测终极指南:3步找出谁偷偷删除了你

微信单向好友检测终极指南:3步找出谁偷偷删除了你

微信单向好友检测终极指南:3步找出谁偷偷删除了你 【免费下载链接】WechatRealFriends 微信好友关系一键检测,基于微信ipad协议,看看有没有朋友偷偷删掉或者拉黑你 项目地址: https://gitcode.com/gh_mirrors/we/WechatRealFriends 你…

2026/7/24 22:44:59阅读更多 →
基于YOLO实例分割的管道缺陷智能检测系统

基于YOLO实例分割的管道缺陷智能检测系统

1. 项目背景与核心价值排水管道作为城市基础设施的重要组成部分,其健康状况直接影响着城市运转效率和公共安全。传统的人工检测方式存在效率低、主观性强、危险系数高等问题。我们团队基于YOLO实例分割技术开发的这套管道缺陷识别系统,能够实现管道内部裂…

2026/7/25 3:09:47阅读更多 →
多尺度形态学在眼前节组织分割中的应用与实践

多尺度形态学在眼前节组织分割中的应用与实践

1. 眼前节组织分割的临床需求与挑战在眼科临床诊断和手术规划中,眼前节组织的精确分割一直是个关键问题。眼前节包括角膜、虹膜、晶状体等结构,这些组织的形态参数对于青光眼、白内障等疾病的诊断至关重要。传统的手工分割方法不仅耗时耗力,而…

2026/7/25 3:09:47阅读更多 →
54岁C语言杀疯了!2026年仍霸占TIOBE第二,这5个理由让永不消亡

54岁C语言杀疯了!2026年仍霸占TIOBE第二,这5个理由让永不消亡

2026 年 4 月 TIOBE 榜单刚发布,一个惊人数据让整个编程圈炸开锅:诞生于 1972 年的 C 语言以 12.34% 的市场占有率稳居第二,环比增长 2.39%,力压 Java、C 等一众热门语言。在 Python 称霸、AI 框架横行的时代,这个 54 …

2026/7/25 3:09:47阅读更多 →
Windows下小龙虾与WSL2高效开发环境配置指南

Windows下小龙虾与WSL2高效开发环境配置指南

1. Windows环境下小龙虾快速安装指南作为长期在Windows和Linux双环境下工作的开发者,我最近在配置新机器时重新梳理了一套高效的小龙虾安装方案。小龙虾(Crayfish)作为一款轻量级开发环境管理工具,能显著提升Windows下的开发体验。…

2026/7/25 3:09:47阅读更多 →
Claude AI 开发者实战指南:从 API 集成到 IDE 扩展

Claude AI 开发者实战指南:从 API 集成到 IDE 扩展

最近在技术圈里,一个现象引起了我的注意:不少开发者为了能顺畅使用Claude,不惜尝试各种方法,甚至有人开玩笑说“肉身部署到美国”。这背后反映的,其实是开发者们对一款强大、安全、专注于助力的AI工具的迫切需求。Claude作为Anthropic公司推出的新一代AI助手,以其在代码生…

2026/7/25 3:09:47阅读更多 →
AI时代人类身份重构:技术哲学与社会伦理的碰撞

AI时代人类身份重构:技术哲学与社会伦理的碰撞

1. 项目背景与核心命题"玄机说AI-我们正在成为谁"这个标题直指当下最值得深思的命题:人工智能技术爆发式发展背景下的人类身份重构。作为一名长期观察AI与人类关系演变的从业者,我注意到这个标题至少包含三个关键维度:技术哲学层面…

2026/7/25 3:07:46阅读更多 →
Go语言静态资源打包方案对比与实践指南

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

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

2026/7/25 1:01:14阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

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

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

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

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

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

2026/7/25 1:01:14阅读更多 →
突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档,但是相关网站浏览体验不好各种广告,各种登录验证,需要很多步骤才能下载文档,该脚本就是为了解决您的…

2026/7/25 0:01:16阅读更多 →
C++ string类模拟实现:从深拷贝到内存管理的完整指南

C++ string类模拟实现:从深拷贝到内存管理的完整指南

1. 项目概述:为什么我们要“手撕”string类?在C的学习道路上,尤其是从C语言过渡到C的“初阶”阶段,string类绝对是一个绕不开的核心。标准库里的std::string用起来太方便了,、find、substr,几个操作符和函数…

2026/7/25 0:01:16阅读更多 →
三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

1. 先搞清楚“三角洲寻宝鼠”到底是什么工具从名称来看,“三角洲寻宝鼠”更像是一个资源查找或文件检索类工具,而不是游戏或娱乐软件。这类工具的核心价值在于帮助用户快速定位特定资源,比如文档、图片、压缩包或特定格式的文件。如果你经常需…

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

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

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

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

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

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

2026/7/24 19:00:40阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

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

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

2026/7/24 19:00:40阅读更多 →