陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

14|类型系统:有哪些必须掌握的trait?

其它
操作符相关
类型转换相关
标记 trait
内存相关
Rust Trait 知识总结

该思维导图由 AI 生成,仅供参考

你好,我是陈天。
开发软件系统时,我们弄清楚需求,要对需求进行架构上的分析和设计。在这个过程中,合理地定义和使用 trait,会让代码结构具有很好的扩展性,让系统变得非常灵活。
之前在 get hands dirty 系列中就粗略见识到了 trait 的巨大威力,使用了 From<T> / TryFrom<T> trait 进行类型间的转换(第 5 讲),还使用了 Deref trait (第 6 讲)让类型在不暴露其内部结构代码的同时,让内部结构的方法可以对外使用。
经过上两讲的学习,相信你现在对 trait 的理解就深入了。在实际解决问题的过程中,用好这些 trait,会让你的代码结构更加清晰,阅读和使用都更加符合 Rust 生态的习惯。比如数据结构实现了 Debug trait,那么当你想打印数据结构时,就可以用 {:?} 来打印;如果你的数据结构实现了 From<T>,那么,可以直接使用 into() 方法做数据转换。

trait

Rust 语言的标准库定义了大量的标准 trait,来先来数已经学过的,看看攒了哪些:
Clone / Copy trait,约定了数据被深拷贝和浅拷贝的行为;
Read / Write trait,约定了对 I/O 读写的行为;
Iterator,约定了迭代器的行为;
Debug,约定了数据如何被以 debug 的方式显示出来的行为;
Default,约定数据类型的缺省值如何产生的行为;
From<T> / TryFrom<T>,约定了数据间如何转换的行为。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Rust语言中的trait是非常强大的特性,能够让代码结构具有很好的扩展性,使系统变得非常灵活。本文深入介绍了一些必须掌握的trait,包括Clone、Copy、Read、Write、Iterator、Debug、Default、From和TryFrom等。通过深入研究这些trait的定义和使用场景,读者可以更好地理解Rust语言的设计理念,并写出更加优雅的Rust代码。文章还探讨了Drop trait在内存管理中的应用,以及标记traitSized、Send、Sync和Unpin的作用。通过本文的阅读,读者可以深入了解Rust语言中trait的重要性和灵活性,为编写高效、安全的Rust代码提供了重要的指导和启发。同时,文章还提到了Send/Sync trait在并发安全中的基础作用,以及类型转换相关的traitFrom\<T> / Into\<T>/AsRef\<T> / AsMut\<T>的使用方法。通过合理地使用这些trait,读者可以让代码变得简洁,符合Rust可读性强的风格,更符合开闭原则。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(34)

  • 最新
  • 精选
  • 记事本
    老师,你讲得太通透,太详细了,太负责任了,全网最好的教程了

    作者回复: 谢谢夸奖!

    2021-09-25
    31
  • c4f
    1. 不行。因为 Vec 和 Copy 都不是用户自己 crate 中定义的,所以根据孤儿原则无法为 Vec 实现 Copy trait 2. 因为 Arc 实现了 Deref 和 DerefMut trait,解应用可以直接访问内部的 Mutex 3. 实现的时候遇到了一个问题:对于非法的 index (比如测试用例中的 128)该如何返回,没找到解决方法于是只针对 List<u32> 实现了 Index trait,这样在遇到非法 index 时返回 &0 即可。 针对 Vec 测试了一下非法 index 的情形,发现会直接终止进程。具体代码如下 ```rust fn index(&self, index: isize) -> &Self::Output { // todo!(); if let Some(idx_abs) = if index >= 0 { Some(index as usize) } else { self.len().checked_sub(index.abs() as usize) } { let mut iter = self.iter(); for _i in 0..idx_abs { iter.next(); } iter.next().unwrap_or(&0) } else { &0 } } ```

    作者回复: 很棒! 对于 3,你可以看我的参考代码: ```Rust impl<T> Index<isize> for List<T> { type Output = T; fn index(&self, index: isize) -> &Self::Output { let len = self.len() as isize; // 我们需要先对负数,以及 index 超出范围的数字进行处理 // 使其落在 0..len 之间 let n = (len + index % len) % len; let iter = self.iter(); // 由于 n 一定小于 len 所以这里可以 skip n 取 next // 此时一定有值,所以可以放心 unwrap iter.skip(n as usize).next().unwrap() } } ``` playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=9f354aaed64c4b97f3b80c3be9c4b59a

    2021-09-24
    4
    16
  • noisyes
    刚才的小例子中要额外说明一下的是,如果你的代码出现 v.as_ref().clone() 这样的语句,也就是说你要对 v 进行引用转换,然后又得到了拥有所有权的值,那么你应该实现 From,然后做 v.into()。 这句话怎么理解呀

    作者回复: 就是如果你获得某个类型T 到其它类型的引用 U,然后又把这个引用 U clone 出一个 U 的带所有权的数据。那么为何不直接实现 From<T> for U 呢?

    2021-09-27
    11
  • 彭亚伦
    第3题, 同样使用标准库的2个方法, `checked_rem_euclid`取得合理索引值, `iter().nth()`获得实际值 ```rust use std::{ collections::LinkedList, ops::{Deref, DerefMut, Index}, }; struct List<T>(LinkedList<T>); impl<T> Deref for List<T> { type Target = LinkedList<T>; fn deref(&self) -> &Self::Target { &self.0 } } impl<T> DerefMut for List<T> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl<T> Default for List<T> { fn default() -> Self { Self(Default::default()) } } impl<T> Index<isize> for List<T> { type Output = T; fn index(&self, index: isize) -> &Self::Output { let len = self.0.len(); //标准库的checked_rem_euclid方法, 如果len=0 则返回None //这里直接把i进行unwrap, 如果链表长度不为0, 则i一定在0..len范围内, 可以放心使用, //如果长度为零, 意味这对一个空链表进行索引, 那么我panic应该也是合情合理的吧 let i = (index as usize).checked_rem_euclid(len).unwrap(); &self.0.iter().nth(i).unwrap() } } #[test] fn it_works() { let mut list: List<u32> = List::default(); for i in 0..16 { list.push_back(i); } assert_eq!(list[0], 0); assert_eq!(list[5], 5); assert_eq!(list[15], 15); assert_eq!(list[16], 0); assert_eq!(list[-1], 15); assert_eq!(list[128], 0); assert_eq!(list[-128], 0); } ``` playground 链接: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=73b4129f1a6608892691da92d501ba15

    作者回复: 👍

    2021-11-17
    3
    4
  • 核桃
    老师,这里有一些概念没搞清晰。 1. * 这个符号,有时表示解引用,有时表示获取变量地址的值,对吗?有点搞混场景了。 2. trait继承这里,经常看到一句话,组合优于继承,怎么理解。同时对于实现和继承来说,可能基础不扎实,一直没理解好什么时候继承什么时候实现,学java的时候,那些抽象类和接口也迷糊的很。 另外这里隐藏了很多东西,看老师代码的时候经常用unwrap,其实生产环境代码是非常危险的。例如今天写hashmap替换里面内容时,最好用containkeys判断一下,如果没有则插入一个空的,再使用get_mut和unwarp,这样就保证安全不会panic了。

    作者回复: 1. * 代表解引用,&是获取变量引用(地址) 2. trait "继承" 是打引号的继承,只是 trait A "继承" trait B 的方法(和关联类型),是行为的继承,也是一种组合,和面向对象继承的概念是不一样的,它们之间并无数据的继承,也没有类别继承的关系。 3. unwrap() 在示例代码中会常常用到,我在之前的内容中介绍过这样会导致 panic,在生产环境中的代码除非你在上下文中确保它不会 panic,否则不宜使用。

    2021-10-27
    4
  • 周烨
    1. 不能,因为不能确定T是否实现了Copy trait。 2. 因为Arc实现了Deref trait 3. ```impl<T> Index<isize> for List<T> { type Output = T; fn index(&self, index: isize) -> &Self::Output { let len = self.len() as isize; let i = if index % len >= 0 { index % len } else { len + index % len } as usize; let it = self.iter(); return it.skip(i).next().unwrap(); } }```

    作者回复: 正确!

    2021-10-08
    3
    4
  • 夏洛克Moriaty
    let a = *list; let b = list.deref(); 老师请问下这两种方式有什么区别,为什么a和b的类型不同?

    作者回复: 区别是: let a = &*list; let b = list.deref(); // a == b 注意看 deref 的返回值。

    2021-09-26
    3
    2
  • GE
    1. 不能,但是这里和T关系无关,而是因为Vec本身已经实现了Drop trait,所以和Copy trait是冲突的 ``` // source code unsafe impl<#[may_dangle] T, A: Allocator> Drop for Vec<T, A> ```

    作者回复: 👍

    2022-01-04
    1
  • Taozi
    第三题里面给List<T>实现DerefMut时,为什么不需要加type Target = LinkedList,那返回的Self::Target是什么。

    作者回复: DerefMut 依赖 Deref,也就是说要实现 DerefMut 必须县实现 Deref,所以 Target 就复用了。

    2021-09-26
    3
    1
  • 0@1
    老师,想提前问下unsafe相关的问题,这个目前比较困扰我进一步学习Rust. 比如这个 std::mem::forget(t), 看了下源码,是直接调用 ManuallyDrop::new(t), 看文档,好像这2个又不是直接等价的。forget的源码如下,多了些属性宏修饰,编译器是不是多加了处理,从而跟直接调用 ManuallyDrop::new(t)起到的效果不一样? 如果是的话,这些宏的文档在哪里可以看到,类似的这些编译器处理的宏有哪些,他们的文档在哪里,谢谢。 Note: 我学rust陆续2年了,看源码时对这些需要编译器额外处理的东西比较困惑,不知道如何去进一步的理解他们, rust中很多隐含规则貌似都有他们的影子。 #[inline] #[rustc_const_stable(feature = "const_forget", since = "1.46.0")] #[stable(feature = "rust1", since = "1.0.0")] #[cfg_attr(not(test), rustc_diagnostic_item = "mem_forget")] pub const fn forget<T>(t: T) { let _ = ManuallyDrop::new(t); }

    作者回复: 这里没有额外的 secret,forget 和 ManuallyDrop::new() 不同的地方是一个有返回值,一个没有返回值。文档中的例子也是使用这一点的不同来表示它们不同的使用场景的。你可以再仔细看看两个例子调用 forget 和 ManuallyDrop::new() 的位置的不同,想想为什么使用 forget 不能一开始就调用。:)

    2021-09-26
    2
    1
收起评论
显示
设置
留言
34
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部