同步操作将从 deepinwiki/wiki 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
特征(trait)是 rust 类似接口的概念,但是它和其他语言还是有不少差异的。本文来总结一下这些差一点。
概念:rust 里面的所有东西,都是有明确类型的,有一些类型可以写出来,有一些写不出来,但是编译器可以打印出来。作为一个强类型语言,rust 编程很大程度上就是要书写很多类型标注,还要去研究类型转换,类型继承扩展这些事情。这和动态语言不一样。
特征处于类型系统的一个顶端(root),我们可以使用它来扩展库里面的 api。
trait A{
fn f1(&self);
fn f2(&self){}
fn f3(){}
}
特征,就是提供一组方法的抽象类
trait B{
fn f1(&self);
fn f2(&self){}
}
struct Oa; // 注意,和其他语言不同,实体在定义的时候不需要关联特征
struct Ob;
impl A for Oa{// 而可以在后续添加关联,即实现特征的定义
fn f1(&self){}// 必须实现没有实现的函数
}
实体和特征之间的关系并不是捆绑的
impl B for Ob{// 一、实现 B 特征
fn f1(&self){}
}
impl Oa{
fn f3(&self){}
}
impl Ob{ // 二、自身的实现
fn f1(&self, _:i32){}
fn f2(&self, _:i32){}
}
对于实体而言:
// impl B for Oa{} // 冲突
impl A for dyn B{}
impl<T:B> A for T{}
对特征而言,它可以选择不同对象来实现:
T 的限制手段:
&[X]
...Oc<T>:B
: 存在 Oc<T>:B
时,注意:左边是实体,右边是特征&T
: 只读借用 / 共享借用&mut T
: 可写借用 / 独占借用* const T
: 只读指针* mut T
: 指针[T]
: 切片fn(T)
: 函数[T;32]
: 数组(T,T,T)
: 元组S<T>
: 使用 T 做参数的类型特征的限制手段:
Self:Unsize<Self>
动态大小,不能产生实体?(还不是正式版)特征对象对特征的要求:
trait C{
fn f1(&self){println!("C::F1")}
}
trait D
where Self : C
{}
trait E:C{}
trait F:Sized{}
trait G:Unsize<Self>{}
trait H/*:?Sized*/{}
struct Oc;
struct Od;
struct Oe;
struct Of;
struct Og;
struct Oh;
impl C for Oc{} // ok
impl D for Od{} // 因为 D:C
impl C for Od{} // 所以 Od 必须也同时实现 C
impl E for Oe{}
impl C for Oe{} // 同上
impl C for dyn B{} // B 特征满足 dyn B 的要求, dyn A 不行, dyn C 默认实现 C,所以不能重复定义。
impl F for Of{} // Sized 静态大小
// impl F for dyn B{} // 不允许,因为 dyn B 是动态大小
impl G for dyn B{} // 允许
// impl G for Og{} // Unsize 针对动态大小
impl H for Oh{}
特征实现限制:
Box<T>
除外),这就是所谓的孤儿原则,这个限制比想象中要大,因为它很大层度限制了库的扩展
Vec<T>
impl<p1..pm> Trait<t1..,ti,..tn> for T0
(ti,tn]
,即不能落在 [t0..ti]
区间。Box<T>
例外调用方法:
fn test(){
<Od as D>::f1(Od); // 用实际类型来调用特征接口,特征必须要和实际类型关联才能调用接口
let a = Od;
(&a as &dyn D).f1(); // 将引用转换成特征对象来动态调用接口
}
Result<T,E> -> T
最近在网上看到一个网友说不想对 Result 做 match 判断,希望实现:
fn f()->T{
let a = OK("")?;
let b = Ok(a)?;
let c:T = Err(b)?
c
}
也就是它希望,不管Result里面是 T,还是E 都能返回 T.
trait MyInto<U>
{
fn into2(self)->U;
}
impl<E,U> MyInto<U> for Result<U, E>
where U:From<E>,
{
fn into2(self)->U{
match self{
Ok(t)=>t,
Err(e)=>e.into(),
}
}
}
fn f(){
let a:&str = Ok("").into2();
let a:String = Err(a).into2(); // &str->String
a
}
std 自带 Into 和 From 用来转换,但是因为孤儿原则,导致要扩展非常难。
经验总结:
std::any::* 下面实现了动态类型识别的基础框架。
is<T>(&self)->bool
: 判断是否为 T 类型downcast_ref<T>(&self)->Option<&T>
: 转换到 &Tdowncast_mut<T>(&self)->Option<&mut T>
: 转换到 &mut Tof<T>()->TypeId
: 获取 T 的类型 id因为实现了 impl<T> Any for T
,所以所有类型都可以使用 Any 的接口,不需要特地转换成 Any。
所有类型的 id 都是不一样的,哪怕多一个 & 也是不同的类型。
rust 的特征对象是一个胖指针,包含两个指针,一个指向数据,一个指向虚表(函数的表格),也就是指向虚表的指针是静态编译的。每一个特征,和具体的类型,都会生成一个虚表,所以编译器可以很容易上具体的类型转换成特征对象,但是特征对象转换到基类的特征对象,是做不到的。因为特征对象并不知道具体的对象是什么,所以它无法确定要找那个具体对象的虚表(记住,虚表是根据具体对象和特征这两个因素来生成的)。一个特征,有多个实现它的具体对象,因此它不可能在编译期就知道应该选择那个具体对象。
这就是为什么 rust 特征对象不像其他语言那么轻而易举的转换成基类的特征对象。有没有办法实现这种转换,这就需要手动提供这种转换信息,将静态编译的转换为动态提供。
这是一个例子:
trait A{fn f1(&self){println!("A::f1()")}}
trait B:A{fn f2(&self){println!("B::f2()")}}
struct C;
let mut a:&dyn A;
let b:&dyn B = &C;
a = b; // 不能转换,B (在类型角度)没有转换到 A 的必要信息。
// 解决方案
trait B:A
where for<'a> &'a dyn A:From<&'a dyn B>
{
fn getA(&self)->&dyn A; // 通过多态动态获取正确的虚表
}
impl<'a> From<&'a dyn B> for &'a dyn A{
fn from(item: &'a dyn B)->Self{
item.getA()
}
}
// impl C
impl A for C{}
impl B for C{
fn getA(&self)->&dyn A{
self // 因为在 C 实现的接口,自然能取得正确的虚表
}
}
a = b.into();
不要滥用特征。任何支持多态的语言,都会有一个毛病,就是让对象去实现它本来不应该去实现的 api。
特征的设计来源:
两种方式都很依赖经验,方式一容易陷入细节、条理不清、高度耦合,方式二容易想当然、难以实现或效率低下。总之经验不足,api 必然是要修修补补,这很正常。但是应该避免:
因为这样必然会导致滥用,迫使对象去实现它不应该具备的接口,也迫使对象之间形成它不应该具备的对象网络拓扑。
对面向对象最大的批评,就是子类要完全实现父类,这是很强的自我限制。
如何判断是否过度设计了?首先就要看对象是否编写了很煞笔的 api 实现。这些实现完全是为了实现接口而去实现的,并没有什么意义。我们应该尽量的精简接口,而不是反过来去实现多余的接口,这是设计的首要原则,不要暴露无意义的公开接口,哪怕你代码质量存在问题,那也是内部的事情,通过自我迭代,代码质量可以得到提高。
但一旦成为外部接口,就不可避免的连累到客户。所以这种封闭性是应该首先倡导的。
因此,我们完全没必要去追求高度抽象,根据具体的对象来编程,往往能得到最精炼的api,最简要的设计,这样也便于我们在实际项目中快速更新迭代。
特征更适合成熟的产品,如 rust 的官方库。我们实现 rust 库中的特征,或者在自己的 api 中添加这些特征,这是没有毛病的。他们很成熟,相当于免费提供了优秀的方案给我们,让我们的代码变得规范而优秀,同时也利于和客户代码整合。
当然不是说逃避抽象,只是说抽象应该是自然而然的。在重复代码出现到一定的层度,那么就算别人不要求,你也会厌倦,而使用抽象来隔离具体的对象。关键是掌握这个技术,然后在必要的时候使用,审慎的检查对象是否契合这个接口。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。