13|类型系统:如何使用trait来定义接口?
该思维导图由 AI 生成,仅供参考
什么是 trait?
- 深入了解
- 翻译
- 解释
- 总结
Rust中的Trait是一种接口定义方式,类似于其他编程语言中的interface、protocol或type class。通过trait,可以将数据结构中的行为抽取出来,在多个类型之间共享,也可以作为泛型编程中的约束。文章介绍了trait的基本定义和实现方式,以及如何使用trait来处理特设多态和子类型多态。通过示例代码展示了如何定义和实现trait,以及trait内部方法的调用方式。文章还介绍了如何为Parse trait添加关联类型Error,使得在出错时返回合理的错误。总体来说,文章深入浅出地介绍了Rust中的trait,对于想要了解Rust类型系统和多态特性的读者具有很好的参考价值。 文章主要介绍了Rust中的Trait的定义和实现方式,以及如何使用Trait来处理特设多态和子类型多态。通过示例代码展示了Trait的基本用法,以及如何为Trait添加关联类型。此外,文章还介绍了Trait的“继承”特性,以及泛型Trait的应用场景。通过深入浅出的讲解,读者可以快速了解Rust中Trait的特点和应用,对于想要深入了解Rust类型系统和多态特性的读者具有很好的参考价值。
《陈天 · Rust 编程第一课》,新⼈⾸单¥68
全部留言(31)
- 最新
- 精选
- GengTeng1. 不可以。关联类型无法就只能impl一次了,我们需要为Complex实现多个Add<Rhs>。 2. 不能。返回类型中的 Self 需要是Sized,而 dyn Write 不是Sized。 3. #[derive(Debug, Copy, Clone)] 4. impl<'a> Iterator for SentenceIter<'a> { type Item = &'a str; fn next(&mut self) -> Option<Self::Item> { match self.s.find(self.delimiter) { None => None, Some(i) => { let s = &self.s[..i + self.delimiter.len_utf8()]; *self.s = &self.s[i + self.delimiter.len_utf8()..]; if let Some((start, _)) = s.as_bytes().iter().enumerate().find(|(_, b)| **b != b' ') { Some(&s[start..]) } else { None } } } } } 这个SentenceIter的功能定义不明确,分割出来的每个sentence如果都需要包括delimiter的话,那剩余部分没有delimiter的情况该返回None吗?或者返回一个不带delimiter的剩余部分?都很别扭。
作者回复: 非常棒! 对于第 4 题,如果没有 delimiter 的,要返回剩余部分。可以看看我的参考答案: ```rust struct SentenceIter<'a> { s: &'a mut &'a str, delimiter: char, } impl<'a> SentenceIter<'a> { pub fn new(s: &'a mut &'a str, delimiter: char) -> Self { Self { s, delimiter } } } impl<'a> Iterator for SentenceIter<'a> { type Item = &'a str; fn next(&mut self) -> Option<Self::Item> { // 如果内部的字符串引用指向空,则提前结束 if self.s.is_empty() { return None; } match self.s.find(self.delimiter) { Some(pos) => { // 注意对于 utf8 char,取它的长度需要用 c.len_utf8() let len = self.delimiter.len_utf8(); let s = &self.s[..pos + len]; let suffix = &self.s[pos + len..]; // 更改内部字符串引用,指向剩余部分 *self.s = suffix; Some(s.trim()) } None => { // 没有找到句号时,有可能后续还有最后一句内容 let s = (*self.s).trim(); *self.s = ""; if s.len() == 0 { None } else { Some(s) } } } } } #[test] fn it_works() { let mut s = "This is the 1st sentence. This is the 2nd sentence."; let mut iter = SentenceIter::new(&mut s, '.'); assert_eq!(iter.next(), Some("This is the 1st sentence.")); assert_eq!(iter.next(), Some("This is the 2nd sentence.")); assert_eq!(iter.next(), None); } fn main() { let mut s = "a。 b。 c"; let sentences: Vec<_> = SentenceIter::new(&mut s, '。').collect(); println!("sentences: {:?}", sentences); } ``` playground:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4590211f7542553e7feef960e814ab0b
2021-09-22511 - lisiur1. 不应该这么做。如果这么做的话,同一个类型对同一个 trait 只能有一个实现, Rhs 也之能有一种可能,这样就不能既实现 String + String 又实现 String + &str,没有扩展性。 2. 不能编译通过,因为 by_ref 返回值含有 Self,不能作为 trait object 的方法使用。 3. 对 Complex 实现 Copy 和 Clone ``` #[derive(Debug, Copy, Clone)] struct Complex { real: f64, imagine: f64, } ``` 4. ```rust impl<'a> Iterator for SentenceIter<'a> { type Item = &'a str; fn next(&mut self) -> Option<Self::Item> { self.s.find(self.delimiter).map(|index| { let next = &self.s[..index + self.delimiter.len_utf8()]; *self.s = &self.s[index + self.delimiter.len_utf8()..]; next.trim_start() }) } } ``` 有个小小的疑问想请教下老师,在 **Trait Object 的实现机理** 这一小节开始给的配图中, formatter 这个 trait object 的 ptr 为啥会指向参数 text 呢?不是指向 HtmlFormatter 这个实例数据吗?
作者回复: 非常棒!1,2,3 答案正确!4 需要考虑最后的部分。对 main() 里的实例代码,这个只返回["a。", "b。"]。你可以看我之前在其他回答下的回复。 谢谢指正,对,trait object 那个图是有问题,ptr 应该指向 HtmlFormatter。有时候图 copy & paste 时,就回忘了把不用的部分删除并修改,lol。我回头让编辑更新!
2021-09-226 - Marvichov``` // 指向同一种类型的同一个 trait 的 vtable 地址相同 // 这里都是 String + Display assert_eq!(vtable1, vtable3); // 这里都是 String + Debug assert_eq!(vtable2, vtable4); ``` 我原本以为String有个method call table, Display也有一个单独的; 那么说, 我自己写了个trait给String type, 岂不是编译的时候, 我自己提供的method需要append到String之前已经有的vtable? 1. vtable和method call table有啥区别呢? 2. 那么call `fn fmt(&self, f: &mut Formatter<'_>) -> Result;`的时候, 编译器是知道这个call在各个不同的实现中vtable的offset? 而且, 这些offset都要一样? 因为我记得, 编译的时候, 因为类型被erase了, 只能通过addr + offset (ptr, *args) 来call metho 例如, vtable1 + offset 和 vtable3 + offset 的地址都是call table中 fmt method 对应地址?
作者回复: 不是 append 的关系。每一个 trait 和某个数据结构对该 trait 的实现都会生成一个静态的 vtable。vtable 是彼此独立的。 1. method call 不需要 table,通过类型就可以得到类型固定的 method call 的地址(编译期就可以完成了)。vtable 是一个运行期的概念。在 trait object 生成的时候,才指向具体的 vtable。 2. 你可以再仔细看看关于 vtable 的那几个图。并不是通过 addr + offset 来调 trait 的 method 的。是通过 vtable。比如一个 trait object 指向了 String Display 的 vtable,那么重这个 vtable 里就能找到 fmt 方法的地址。
2021-09-2933 - QY请问第四题为什么要用&mut &s 呢? 是性能更好吗? 感觉&s使用起来自由度更高。宣言s时不需要mut s。
作者回复: 因为 strtok 会修改传入的参数,使其指向 &str 的 tokenize 后面的位置,所以需要 &mut &str。你可以仔细看我画的图去理解。
2021-09-2632 - 夏洛克Moriaty今天内容很多,也很细,看来需要常来温故才行
作者回复: 👍
2021-09-222 - 王鹏飞内容言简意赅, 只讲核心, 含金量十足; 但是需要自己补充相对基础的东西,才能理解
编辑回复: 恩是的,最好对编程语言相关基础概念有理解,这也是为什么“适合人群”中老师会标注掌握任意一门编程语言。另外,Rust基础语法,老师的想法是官方资料就很齐全了,大家可以配合学习
2022-12-02归属地:北京1 - 核桃老师,这里关于静态分发和动态分发,有个例子没搞懂 #[drive(Debug)] struct Foo; trait Bar{ fn baz(&self); } impl Bar for Foo{ fn baz(&self) { println!("{:?}",self)}; } fn static_dispatch<T>(t: &T) where T:Bar{ t.baz(); } //为什么这里是动态分发就无法确定类型大小 //这里t也是限制为&Bar对象呀. fn dynamic_dispatch(t: &Bar){ t.baz(); } 这里不能理解的是动态分发,参数t也是要求为&Bar的呀,那么这里到底和静态分发的核心区分是什么?凭什么就无法确定类型大小而需要用虚表呢?这个实在不太理解,多谢了。
作者回复: 首先这里应该是 &dyn Bar,其次,Bar 是一个 trait,它的类型是不确定的,而 &T 是一个确定的类型,在编译时会根据使用编译成 Foo 或者其他类型。这是本质的区别。对于 trait,只能通过虚表来获得原始类型的能力和一些属性。
2021-10-2821 - Marvichov> C++ / Java 指向 vtable 的指针,在编译时放在类结构里, 现在C++的vtable也是分开放的 (可能看编译器, clang是分开的); 不过对 cpp, 对interface的实现大家都和original class definition放在一起. 没有试过在分开的文件中impl一个cpp的class的interface (parent inheritance). 可能也行, 但是不符合cpp的convention. rust相对而言, 对某个类型的impl可以到处放. ``` vtable for Foo: .quad 0 .quad typeinfo for Foo // RTTI for foo .quad Foo::method1() // where vtable starts .quad Bar::method2() // for method2 in vtable .quad Foo::~Foo() [complete object destructor] .quad Foo::~Foo() [deleting destructor] ``` https://guihao-liang.github.io/2020/05/30/what-is-vtable-in-cpp
作者回复: 对,我是说指向 vtable 的指针是放在类结构里的。文中最后 summary 的图里也是这个意思吧?
2021-10-071 - hughieyu1. 不可以。 Complex不能实现Add多次,只能实现对一种类型的计算。 2. 不能。 对于trait object来说, Self信息已经被擦除了。 3. #[derive(Debug, Copy, Clone)] 或者 用自定义类型包装Rc实现Add 如RcComplex(Rc<Complex>) 4. impl<'a> Iterator for SentenceIter<'a> { type Item = &'a str; // 想想 Item 应该是什么类型? fn next(&mut self) -> Option<Self::Item> { match self.s.find(self.delimiter) { None => None, Some(idx) => { let slice = &self.s[..idx+self.delimiter.len_utf8()]; *self.s = &self.s[idx+self.delimiter.len_utf8()..]; match slice.as_bytes().iter().enumerate().find(|(_,b)|**b != b' '){ None => None, Some((start,_)) => Some(&slice[start..]), } } } } } 问题: trait泛型和关联类型的使用场景还是有些模糊
作者回复: 1/2/3 正确。4 需要考虑 main() 中的场景,要处理不带 delimiter 的后续部分,可以看我之前的回答。 trait 泛型是对同一个数据结构你需要有多个不同的实现,trait 的关联类型是,在某个实现里,我需要设定和这个实现相关的类型。其实关联类型就和关联函数一样的。
2021-09-221 - overheat陈天老师的这部分是目前看到的最生动的教程,the book对有些内部机制bi而不谈,其他书面教程感觉我自己的语文没学好。我现在是看了第20章再回头看的,终于明白了trait object这个之前差不多直接跳过的概念。
作者回复: 谢谢!:)
2021-12-13