0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

实现Rust Trait类型 那么该类型的引用也实现了trait吗?

jf_wN0SrCdH 来源:coding到灯火阑珊 2023-08-28 15:25 次阅读

如果你在一个类型上实现了一个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实现的函数。






审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • rust语言
    +关注

    关注

    0

    文章

    57

    浏览量

    3009

原文标题:实现了Rust Trait的类型,那么该类型的引用也实现了trait吗?

文章出处:【微信号:Rust语言中文社区,微信公众号:Rust语言中文社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    基于Rust语言Hash特征的基础用法和进阶用法

    Rust语言是一种系统级编程语言,具有高性能、安全、并发等特点,是近年来备受关注的新兴编程语言。在Rust语言中,Hash是一种常用的数据结构,用于存储键值对。Rust语言提供一系列
    的头像 发表于 09-19 16:02 1444次阅读

    Rust语言中的反射机制

    Rust语言的反射机制指的是在程序运行时获取类型信息、变量信息等的能力。Rust语言中的反射机制主要通过 Any 实现。 std::any::Any
    的头像 发表于 09-19 16:11 2417次阅读

    Rust中的From和Into trait的基础使用方法和进阶用法

    、可靠和安全的系统级应用的首选语言。 Rust中的From和Into是两个重要的trait,它们可以帮助我们进行类型转换。From trait允许我们从一个
    的头像 发表于 09-20 10:55 1776次阅读

    “布尔2”的引用 和“Bool引用句柄”为什么不是同一个数据类型

    请教 改变了 按钮的机械方式, 右图中“布尔2”的引用 和“Bool引用句柄”为什么不是同一个数据类型
    发表于 04-04 23:21

    子vi如何使其变为非严格类型

    请教各位大神一个问题,在一个vi中调用子vi时,通过“右键>选择服务器类型>浏览”方式选择一个子vi后其为严格类型,要想实现引用还得使用下
    发表于 05-04 20:29

    Windows -编程-数据类型

    Windows -编程-数据类型Rust 中的每个值都有特定的数据类型,它告诉 Rust 指定什么样的数据,以便它知道如何处理这些数据。我
    发表于 08-24 14:30

    引用数据类型的概念_引用数据类型有哪几种

    引用类型类型的实际值引用(类似于指针)表示的数据类型。如果为某个变量分配一个引用
    发表于 11-17 18:29 2.6w次阅读
    <b class='flag-5'>引用</b>数据<b class='flag-5'>类型</b>的概念_<b class='flag-5'>引用</b>数据<b class='flag-5'>类型</b>有哪几种

    C#良好兼容类型/引用类型

    反观历史,C#曾经因为 值类型/引用类型 保守诟病,“拆箱”和“装箱”一直是个招黑的设计。但后来我们看到,随着泛型的成熟和普及,随着泛型容器代替通用容器,装箱和拆箱的问题已经在很大程
    的头像 发表于 11-20 10:14 1698次阅读
    C#良好兼容<b class='flag-5'>了</b>值<b class='flag-5'>类型</b>/<b class='flag-5'>引用</b><b class='flag-5'>类型</b>

    Rust原子类型和内存排序

    原子类型在构建无锁数据结构,跨线程共享数据,线程间同步等多线程并发编程场景中起到至关重要的作用。本文将从Rust提供的原子类型和原子类型的内存排序问题两方面来介绍。
    的头像 发表于 10-31 09:21 940次阅读

    Rust中GAT和高阶类型

    Rust类型系统级别上与Haskell,Scala有许多相似之处。
    的头像 发表于 11-07 10:21 1170次阅读

    trait中使用 `async fn`

    { ... } } 更多请看官方blog:https://blog.rust-lang.org/inside-rust/2022/11/17/async-fn-in-trait-nightly.html 社区受
    的头像 发表于 11-23 15:40 779次阅读

    重点讲解Send与Sync相关的并发知识

    Send与Sync在Rust中属于marker trait,代码位于marker.rs,在标记模块中还有Copy、Unpin等trait
    的头像 发表于 01-16 09:54 928次阅读

    为什么Borrow和BorrowMut被定义为泛型trait呢?

    Borrow和BorrowMut traitRust标准库std:borrow 模块中用于处理借用数据的trait,通过实现Borrow 和BorrowMut
    的头像 发表于 05-22 15:57 901次阅读

    rust语言基础学习: 智能指针之Cow

    Rust中与借用数据相关的三个trait: Borrow, BorrowMut和ToOwned。理解了这三个trait之后,再学习Rust中能够实现
    的头像 发表于 05-22 16:13 2905次阅读

    rust语言基础学习: Default trait

    Default trait 顾名思义是默认值,即Rust为特定类型实现 Default 特性时,可以为该类型赋予
    的头像 发表于 05-22 16:16 1278次阅读