全球最实用的IT互联网信息网站!

AI人工智能P2P分享&下载搜索网页发布信息网站地图

当前位置:诺佳网 > 电子/半导体 > 嵌入式技术 >

实现Rust Trait类型 那么该类型的引用也实现了tr

时间:2023-08-28 15:25

人气:

作者:admin

标签: rust语言 

导读:如果你在一个类型上实现了一个trait,然后引用了这个类型,那么类型的引用也实现了这个trait吗?...

如果你在一个类型上实现了一个trait,然后引用了这个类型,那么类型的引用也实现了这个trait吗?

有一段时间我是这么想的!但实际上并不是,有时候Rust为你做的事情可能会混淆幕后真正发生的事情。

为了演示,让我从一个名为Speaker的trait和一个实现该trait的空struct开始:

///定义一个trait,有一个speak方法。
traitSpeaker{
fnspeak(&self);
}

/// BasicSpeaker是一个空结构体,只是为了实现Speaker。
structBasicSpeaker;

///BasicSpeakers实现speak方法
implSpeakerforBasicSpeaker{
fnspeak(&self){
println!("Hello!");
}
}
现在,在main函数中,以下代码应该可以工作:
//创建BasicSpeaker结构体
letspeaker=BasicSpeaker;
//调用在BasicSpeaker上定义的speak方法
speaker.speak();

确实如此,它就会输出“Hello!”。

如果我引用了一个BasicSpeaker,我仍然可以对它调用speak,因为Rust会自动解除对变量的引用。所以下面的代码也可以工作:

//定义一个BasicSpeaker的引用
letspeaker_ref:&BasicSpeaker=&speaker;
//通过引用调用在BasicSpeaker上定义的speak方法
speaker_ref.speak();

这可能会让你认为BasicSpeaker实现了Speaker,引用&BasicSpeaker也实现了Speaker。也许是Rust的魔法?

让我们更具体地测试一下,定义一个接受impl Speaker类型形参的函数。

fnspeak_to(s:implSpeaker){
s.speak();
}

fnmain(){
//创建BasicSpeaker结构体
letspeaker=BasicSpeaker;
//将speaker传递给新函数
speak_to(speaker);
}

这是可行的,因为BasicSpeaker实现了Speaker特性。

让我们尝试同样的事情,但这次是传递BasicSpeaker的引用:

//定义一个BasicSpeaker的引用
letspeaker_ref:&BasicSpeaker=&speaker;
//将引用传递给'speak_to'
speak_to(speaker_ref);
这行不通!错误信息如下所示:
error[E0277]:thetraitbound`&BasicSpeaker:Speaker`isnotsatisfied
-->src/main.rs:31:14
|
31|speak_to(speaker_ref);
|--------^^^^^^^^^^^thetrait`Speaker`isnotimplementedfor`&BasicSpeaker`
||
|requiredbyaboundintroducedbythiscall
|
=help:thetrait`Speaker`isimplementedfor`BasicSpeaker`
note:requiredbyaboundin`speak_to`
-->src/main.rs:16:21
|
16|fnspeak_to(s:implSpeaker){
|^^^^^^^requiredbythisboundin`speak_to`

Formoreinformationaboutthiserror,try`rustc--explainE0277`.

最初的错误消息是模糊的,但是第一个代码块旁边的消息更清晰:“&BasicSpeaker没有实现trait Speaker”。

前面的代码示例演示了你可以在引用上调用没有在该引用上实现的方法,因为Rust会默默地为你解引用该值。Rust是这样做的:

//Rust将'speaker_ref.speak()'转换为
(*speaker_ref).speak();

这并不意味着&BasicSpeaker(一个引用)实现了Speaker。

直接的解决方案

最直接的解决方案是在BasicSpeaker的引用上实现Speaker,如下所示:

implSpeakerfor&BasicSpeaker{
fnspeak(&self){
println!("Hello!");
}
}
将其添加到代码中后,就可以编译和运行了。所以这是一种解决方案,但这并不理想。首先,这基本上是先前实现的重复代码。下面是一个稍微改进的实现,它只调用底层结构体的方法:
implSpeakerfor&BasicSpeaker{
fnspeak(&self){
return(**self).speak();
}
}

很明显,我必须对self进行两次解引用,因为该函数接受&self,而self是&BasicSpeaker。这意味着参数是一个&&BasicSpeaker,必须对其进行两次解引用才能获得实现speak()的BasicSpeaker。

好了,现在没有那么多代码复制了,但是还有另一个问题,如果我想定义另一个Speaker,比如NamedSpeaker,那么我必须编写两次代码——一次为NamedSpeaker,一次为&NamedSpeaker。

用泛型Trait解决这个问题

我可以写一个泛型的实现:“如果一个struct T实现了Speaker,那么写一个用于&T的Speaker实现。”

///所有实现Speaker的事物的引用也必须是Speaker的。
implSpeakerfor&T
where
T:Speaker,
{
fnspeak(&self){
return(**self).speak();
}
}
或者,如果你喜欢,你可以使用下面的,稍微短一点的语法,意思是一样的:
implSpeakerfor&T{
fnspeak(&self){
return(**self).speak();
}
}
即使我现在已经为&BasicSpeaker编写了Speaker的实现,但这并不适用于&mut BasicSpeaker!所以这行不通:
//获取一个对BasicSpeaker的可变引用
letspeaker_mut_ref:&mutBasicSpeaker=&mutspeaker;
//通过可变引用,调用在BasicSpeaker上定义的speak方法
speak_to(speaker_mut_ref);
这需要另一个泛型实现:
///所有实现Speaker的事物的可变引用也必须是Speaker的。
implSpeakerfor&mutT
where
T:Speaker,
{
fnspeak(&self){
return(**self).speak();
}
}
为了完整起见,这里对于Box也是一样的,当你想把Speaker实现放到堆上时:
implSpeakerforBox
where
T:Speaker,
{
fnspeak(&self){
return(**self).speak();
}
}
一旦添加了这些覆盖实现,就意味着Speaker的任何新类型(该类型本身、对该类型的任何引用以及包含该类型的任何Box)都自动实现了Speaker trait。

总结

因为Rust会自动解除对trait的引用,它看起来就像引用本身也实现了trait。但事实并非如此。幸运的是,在许多情况下,你可以使用一些泛型trait来修复这个问题。

如果你的trait接口允许,你应该为&T, &mut T和Box提供trait实现,这样你就可以将这些类型传递给任何接受trait实现的函数。






审核编辑:刘清

温馨提示:以上内容整理于网络,仅供参考,如果对您有帮助,留下您的阅读感言吧!
相关阅读
本类排行
相关标签
本类推荐

CPU | 内存 | 硬盘 | 显卡 | 显示器 | 主板 | 电源 | 键鼠 | 网站地图

Copyright © 2025-2035 诺佳网 版权所有 备案号:赣ICP备2025066733号
本站资料均来源互联网收集整理,作品版权归作者所有,如果侵犯了您的版权,请跟我们联系。

关注微信