知识点:关联类型
关联类型让 trait 的使用者不需要指定泛型参数:
// 用泛型:每次实现都要指定具体类型traitContainer<T>{fnget(&self)->&T;}// 用关联类型:trait 内部声明类型,实现时指定traitIterator{typeItem;// 关联类型fnnext(&mutself)->Option<Self::Item>;}structCounter{count:usize,max:usize,}implCounter{fnnew(max:usize)->Counter{Counter{count:0,max}}}// 实现时指定 Item 为 usizeimplIteratorforCounter{typeItem=usize;fnnext(&mutself)->Option<Self::Item>{ifself.count<self.max{self.count+=1;Some(self.count)}else{None}}}fnmain(){letmutcounter=Counter::new(5);whileletSome(val)=counter.next(){print!("{} ",val);}println!();// 1 2 3 4 5}知识点:默认泛型参数和运算符重载
usestd::ops::Add;// Add trait 的定义(标准库中)// trait Add<Rhs = Self> { // Rhs 有默认值 Self// type Output;// fn add(self, rhs: Rhs) -> Self::Output;// }// 默认 Rhs = Self,所以同类型相加不需要指定#[derive(Debug, Clone, Copy)]structPoint{x:f64,y:f64,}implAddforPoint{typeOutput=Point;fnadd(self,other:Point)->Point{Point{x:self.x+other.x,y:self.y+other.y,}}}// 不同类型相加:指定 Rhs#[derive(Debug, Clone, Copy)]structMm(f64);#[derive(Debug, Clone, Copy)]structCm(f64);// Cm + Mm => CmimplAdd<Mm>forCm{typeOutput=Cm;fnadd(self,rhs:Mm)->Cm{Cm(self.0+rhs.0/10.0)}}fnmain(){letp1=Point{x:1.0,y:2.0};letp2=Point{x:3.0,y:4.0};println!("{:?}",p1+p2);// Point { x: 4.0, y: 6.0 }letlength=Cm(3.0);letextra=Mm(50.0);println!("{:?}",length+extra);// Cm(8.0)}知识点:完全限定语法 — 消除歧义
当一个类型实现了多个有同名方法的 trait 时,需要完全限定:
traitPilot{fnfly(&self);}traitWizard{fnfly(&self);}structHuman;implPilotforHuman{fnfly(&self){println!("飞行员在飞");}}implWizardforHuman{fnfly(&self){println!("巫师在飞");}}implHuman{fnfly(&self){println!("人类在扑腾翅膀");}}fnmain(){leth=Human;// 默认调用 Human 自身的方法h.fly();// 人类在扑腾翅膀// 完全限定语法调用 trait 方法Pilot::fly(&h);// 飞行员在飞Wizard::fly(&h);// 巫师在飞// 关联函数(没有 self 参数)的完全限定traitAnimal{fnname()->String;}structDog;implDog{fnname()->String{String::from("小狗")}}implAnimalforDog{fnname()->String{String::from("犬类")}}println!("{}",Dog::name());// 小狗(调用自身方法)println!("{}",<DogasAnimal>::name());// 犬类(调用 trait 方法)}知识点:超 trait(Supertrait)
一个 trait 可以要求实现者同时实现另一个 trait:
usestd::fmt;// OutlinePrint 要求实现者必须也实现 fmt::DisplaytraitOutlinePrint:fmt::Display{fnoutline(&self){lettext=self.to_string();// 因为实现了 Display,所以可以用 to_string()letlen=text.len();println!("{}","*".repeat(len+4));println!("* {} *",text);println!("{}","*".repeat(len+4));}}structMessage(String);// 必须先实现 Display,才能实现 OutlinePrintimplfmt::DisplayforMessage{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{write!(f,"{}",self.0)}}implOutlinePrintforMessage{}// 另一个例子:要求能比较 + 能显示traitPrintable:fmt::Display+PartialOrd{fnprint_comparison(&self,other:&Self){ifself<other{println!("{} < {}",self,other);}elseifself>other{println!("{} > {}",other,self);}else{println!("{} == {}",self,other);}}}#[derive(PartialEq, PartialOrd)]structScore(i32);implfmt::DisplayforScore{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{write!(f,"分数({})",self.0)}}implPrintableforScore{}fnmain(){letmsg=Message(String::from("Hello"));msg.outline();// *********// * Hello *// *********lets1=Score(80);lets2=Score(95);s1.print_comparison(&s2);// 分数(80) < 分数(95)}知识点:newtype 模式
用元组结构体包装外部类型,为它实现本地 trait:
usestd::fmt;// Wrapper 包装了 Vec<String>structWrapper(Vec<String>);// 标准库的 Display trait 和 Vec<String> 都在外部 crate 中// 不能直接为 Vec<String> 实现 Display(孤儿规则)// 但可以为 Wrapper 实现!implfmt::DisplayforWrapper{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{write!(f,"[{}]",self.0.join(", "))}}// 也可以实现其他方法implWrapper{fnpush(&mutself,item:String){self.0.push(item);}fnget(&self,index:usize)->Option<&String>{self.0.get(index)}fnlen(&self)->usize{self.0.len()}}fnmain(){letmutw=Wrapper(vec![String::from("hello"),String::from("world"),]);println!("{}",w);// [hello, world]w.push(String::from("rust"));println!("{}",w);// [hello, world, rust]println!("长度: {}",w.len());// 3println!("第一个: {:?}",w.get(0));// Some("hello")}核心规则
概念 写法
关联类型 trait T { type Item; }
实现关联类型 impl T for X { type Item = Y; }
默认泛型参数 trait Add<Rhs = Self>
运算符重载 impl Add for Type { … }
完全限定(方法) Trait::method(&instance)
完全限定(关联函数) ::function()
超 trait trait A: B { … }
newtype 模式 struct Wrapper(ExternalType);
动手试试
补全下面的代码:
usestd::fmt;usestd::ops::{Add,Mul};// 补全:定义一个 trait Matrix,包含:// 1. 关联类型 Element(要求实现 Add + Mul + Copy + Default + fmt::Display)// 2. 方法 rows(&self) -> usize// 3. 方法 cols(&self) -> usize// 4. 方法 get(&self, row: usize, col: usize) -> Self::Element// 5. 方法 set(&mut self, row: usize, col: usize, value: Self::Element)// 补全:定义结构体 SimpleMatrix<E: Copy + Default>,内部用 Vec<Vec<E>> 存储// 补全:为 SimpleMatrix<E> 实现 Matrix trait// 其中 Element = E// 补全:为 SimpleMatrix<E> 实现 Display trait// 格式:每行用空格分隔,行间用换行// 例如 2x3 矩阵:// 1 2 3// 4 5 6// 补全:实现函数 transpose// 接受一个实现了 Matrix 的引用,返回转置后的 SimpleMatrix// 转置:行列互换fntranspose<M:Matrix>(matrix:&M)->SimpleMatrix<M::Element>whereM::Element:Copy+Default,{// 补全todo!()}fnmain(){// 创建一个 2x3 矩阵letdata=vec![vec![1,2,3],vec![4,5,6],];letmatrix=SimpleMatrix{data};// 补全:打印矩阵// 期望输出:// 1 2 3// 4 5 6// 补全:打印行数和列数// 期望: 行数: 2, 列数: 3// 补全:转置并打印// 期望输出:// 1 4// 2 5// 3 6}期望输出:
1 2 3
4 5 6
行数: 2, 列数: 3
1 4
2 5
3 6
写好了贴过来,没问题就进入第22课:宏 Macros 👋
贴代码
usestd::fmt;usestd::ops::{Add,Mul};// Matrix trait,带关联类型 ElementtraitMatrix{typeElement:Add+Mul+Copy+Default+fmt::Display;fnrows(&self)->usize;fncols(&self)->usize;fnget(&self,row:usize,col:usize)->Self::Element;fnset(&mutself,row:usize,col:usize,value:Self::Element);}// SimpleMatrix 结构体#[derive(Debug)]structSimpleMatrix<E:Copy+Default>{data:Vec<Vec<E>>,}// 为 SimpleMatrix 实现 Matrix traitimpl<E>MatrixforSimpleMatrix<E>whereE:Add+Mul+Copy+Default+fmt::Display,{typeElement=E;fnrows(&self)->usize{self.data.len()}fncols(&self)->usize{ifself.data.is_empty(){0}else{self.data[0].len()}}fnget(&self,row:usize,col:usize)->Self::Element{self.data[row][col]}fnset(&mutself,row:usize,col:usize,value:Self::Element){self.data[row][col]=value;}}// Display 实现impl<E>fmt::DisplayforSimpleMatrix<E>whereE:Copy+Default+fmt::Display,{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{forrowin&self.data{letline:Vec<String>=row.iter().map(|x|format!("{}",x)).collect();writeln!(f,"{}",line.join(" "))?;}Ok(())}}// 矩阵转置fntranspose<M:Matrix>(matrix:&M)->SimpleMatrix<M::Element>whereM::Element:Copy+Default,{letr=matrix.rows();letc=matrix.cols();letmuttransposed_data=vec![vec![M::Element::default();r];c];foriin0..r{forjin0..c{transposed_data[j][i]=matrix.get(i,j);}}SimpleMatrix{data:transposed_data,}}fnmain(){// 创建一个 2x3 矩阵letdata=vec![vec![1,2,3],vec![4,5,6]];letmatrix=SimpleMatrix{data};// 打印矩阵println!("{}",matrix);// 打印行数和列数println!("行数: {}, 列数: {}",matrix.rows(),matrix.cols());// 转置并打印lett=transpose(&matrix);println!("{}",t);}运行输出
plaintext
1 2 3
4 5 6
行数: 2, 列数: 3
1 4
2 5
3 6
知识点解析
1. Trait + 关联类型(associated type)
traitMatrix{typeElement:Add+Mul+Copy+Default+fmt::Display;...}- 关联类型:trait 内部定义一个类型占位符,实现的时候指定具体类型;
- 和泛型 trait Matrix 对比:关联类型意味着一个实现只能对应一种元素类型,语义上更加自然(一个矩阵只有一类元素)。
2. SimpleMatrix
底层存储 Vec<Vec> ,二维向量。
- rows() :外层 vector 的长度;
- cols() :第一行的长度;空矩阵返回0。
3. get / set
通过索引直接读写矩阵元素;本题简化版,没有边界检查(越界会panic)。工业代码需要做越界判断,返回 Result 。
4. Display 格式化矩阵
遍历每一行,每行元素转字符串、空格拼接,每行末尾 writeln! 输出换行。
5. transpose 转置函数
- 新建一个 c × r 的二维数组,初始值为 Default;
- 双重循环: transposed_data[j][i] = matrix.get(i,j) ,行列下标互换;
- 构造并返回新的 SimpleMatrix 。
约束条件解释
fntranspose<M:Matrix>(matrix:&M)->SimpleMatrix<M::Element>whereM::Element:Copy+DefaultM::Element 已经在Matrix trait上有一系列约束;但是构造SimpleMatrix需要 Copy + Default ,所以where子句再次补充约束。