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

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

3天内不再提示

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

冬至子 来源:山川与湖水 作者:山川与湖水 2023-05-22 15:57 次阅读

Borrow和BorrowMut trait 是Rust标准库std:borrow 模块中用于处理借用数据的trait,通过实现Borrow 和BorrowMut trait可以让一个类型被借用成不同的引用。

1、AsRef VS Borrow 转换与借用

Borrow trait的定义如下:

pub trait Borrow

对比一下 AsRef:

pub trait AsRef

可以看出AsRef的定义和Borrow的定义十分相像,那么既然有了AsRef trait,为啥还有Borrow trait的存在呢?

AsRef trait用来表示**类型转换, **Borrow trait用来表示 借用数据, 在Rust中,为不同的语义不同的使用情况提供不同的类型表示是很常见的。

一个类型通过实现 Borrow trait,在 borrow()方法中提供对 T 的引用/借用,表达的语义是可以作为某个类型 T被借用,而非转换。一个类型可以自由地借用为几个不同的类型,也可以用可变的方式借用。

Borrow trait这类特性存在的意义旨在于解决特定领域的问题,例如在 Hashset,HashMap,BTreeSet,BtreeMap 中使用 &str 查询 String 类型的键。

所以 Borrow 和 AsRef 如何选呢?

  • 当你想把不同类型的借用进行统一抽象,或者当你要建立一个数据结构,以同等方式处理自拥有值(ownered)和借用值(borrowed)时,例如散列(hash)和比较(compare)时,选择Borrow。
  • 当你想把某个类型直接转换为引用,并且你正在编写通用代码时,选择AsRef。比较简单的情况。

2、Borrow 和 BorrowMut 实现借用数据

BorrowMut trait的定义如下:

pub trait BorrowMut

BorrowMut trait类似于Borrow,用于可变借用。BorrowMut trait继承自Borrowed trait。因此,一个类型如果实现了BorrowMut trait,则它也实现了Borrowed trait。

从Borrow trait的文档中看,它对类型实现借用数据强加了更多的限制:

如果一个类型U实现了Borrow,在为U实现额外的trait(特别是实现Eq, Ord, Hash)的时候应该实现与T相同的行为。

这句话可以从例子理解,例如String实现了Borrow,那么在为String实现Eq, Ord, Hash等trait时,实现的行为应该与str实现相同。

Borrow trait的文档中给了一个HashMap的例子,HashMap利用了String实现Borrow时,String和str对Eq, Hash的实现是相同的这一点,可以让我们可以使用&str作为Key来访问一个HashMap。HashMap的定义如下:

use std::borrow::Borrow;
use std:#:Hash;

pub struct HashMap

可以看到get方法的参数k类型是&Q,而不是&K。Q的trait bound是Hash + Eq + ?Sized,而K的trait bound是Borrow。这里K实现了Borrow, Hash, Eq等作为额外的trait,Borrow trait约定的限制是K和Q对这些额外trait的实现行为是相同的。

上面例子说明在写通用的代码时,如果依赖了Hash, Eq等这些额外的trait的相同的行为,会使用Borrow trait。这些trait作为trait bounds出现。

再看一个例子:

// 这个结构体能不能作为 HashMap 的 key?
pub struct CaseInsensitiveString(String);


// 它实现 Eq 没有问题
impl PartialEq for CaseInsensitiveString {
    fn eq(&self, other: &Self) -> bool {
       // 但这里比较是要求忽略了 ascii 大小写
        self.0.eq_ignore_ascii_case(&other.0)
    }
}


impl Eq for CaseInsensitiveString { }
// 实现 Hash 没有问题
// 但因为 eq 忽略大小写,那么 hash 计算也必须忽略大小写
impl Hash for CaseInsensitiveString {
    fn hash

但是 CaseInsensitiveString 可以实现 Borrow吗?

很显然,CaseInsensitiveString 和 str 对 Hash 的实现不同,str 是不会忽略大小写的。因此,CaseInsensitiveString不能实现Borrow,所以 CaseInsensitiveString 不能作为 HashMap 的 key,编译器就可以通过 Borrow trait 来识别这种情况了。但是 CaseInsensitiveString 完全可以实现 AsRef 。这就是 Borrow 和 AsRef 的区别,Borrow 更加严格一些,并且表示的语义和 AsRef 完全不同。

3、Borrow和BorrowMut的blanket implement

对于BorrowBorrowMut,Rust为泛型T和&T自动实现了这两个trait。

#[stable(feature = "rust1", since = "1.0.0")]
impl

4. 为什么Borrow和BorrowMut被定义为泛型trait

被定义为泛型trait,这样就可以让同一个类型同时有多个Borrow和BorrowMut trait的实现, 这样这个类型就可以同时让多个不同的引用类型作为它的借用。

例2:

fn main() {
    let s = String::from("hello");
    let s1: &str = s.borrow();
    let s2: &String = s.borrow();
    println!("s1: {s1:p}, s2: {s2:p}"); // s1: 0x7ff58ec05bc0, s2: 0x7ffee9169fe0
}

例2中引用类型&str&String都可以作为String类型的借用。即通过实现Borrow trait可以让一个类型被借用成不同的引用。

总结

Borrow trait是用来表示 借用数据 ,一个类型通过实现 Borrow trait,在 borrow()方法中提供对 T 的引用/借用,表达的语义是可以作为某个类型 T被借用,而非转换。一个类型可以自由地借用为几个不同的类型,也可以用可变的方式借用。

1 如果一个类型实现了Borrow,那么这个类型的borrow方法可以从其借用一个&T

2 可以将 BorrowBorrowMut视作 AsRefAsMut的严格版本,其返回的引用 &T 具有与 Self 相同的 Eq,Hash 和 Ord 的实现。

注:理解Borrow trait这类特性存在的意义,有助于我们揭开 HashSet,HashMap,BTreeSet 和 BTreeMap 中某些方法的实现的神秘面纱。但是在实际应用中,几乎没有什么地方需要我们去实现这样的特性,因为再难找到一个需要我们对一个值再创造一个“借用”版本的类型的场景了。对于某种类型 T ,&T 就能解决 99.9% 的问题了,且 T: Borrow 已经被一揽子泛型实现对 T 实现了,所以我们无需手动实现它,也无需去实现某种的对 U 有 T: Borrow 了。

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

    关注

    0

    文章

    43

    浏览量

    7362
  • rust语言
    +关注

    关注

    0

    文章

    58

    浏览量

    2958
收藏 人收藏

    评论

    相关推荐

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

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

    华教程

    华的入门教程,适合初学者
    发表于 08-15 11:29

    Mutich_vcap_vdis中的grpxId只定义了没有使用,请问这是为什么?如何解决?

    ]源码中开头注释画了usecase的diagram 显示的是grpx与SW mosaic都被送到了on-chip HDMI可是我看完源码后发现这个grpxId只定义了 根本没有使用。请问这是为何》?这个grpxId是干什么用
    发表于 06-22 02:59

    为什么要将队列控制块定义双重指针

    **OSQOut; INT16UOSQSize; INT16UOSQEntries;} OS_Q;如上队列控制块的定义,其中OSQStart和OSQEnd指向消息指针数组的起始地址和结束地址,但是它们并不关心地址里的内容,只是标记起始和结束,为什么还要
    发表于 09-04 21:20

    在网是什么?

    随着信息通信技术日新月异地发展,信息社会一步步走向现实,一种强调“无所不在”或“ 在”通信理念的特征正日渐清晰, “在”将是信息社会重要的特征,在网将成为信息社会的重要载体并已经成为信息通信业
    发表于 10-10 09:12

    为什么我定义的两个长度1200的double数组打印总是出错?

    定义了两个长度1200的double数组用程序对它进行赋值打印总是出现nan要么就是数据不对怎么解决啊是不是数据超出了代码段的长度怎么解决希望原子哥解惑哦
    发表于 10-18 02:21

    labview连接mongdb问题,找到不.NET类中的

    有没有人用labview连接mongodb数据库的?已下载mongodb的c#驱动,利用labview中的.net控件调用相关函数,但是驱动中有部分函数在类中, labview能调用c#中的
    发表于 04-08 13:38

    详细介绍了Java、注解、并发编程

    介绍了Java、注解、并发编程、数据传输与序列化、高效IO、容器集合、反射与类加载以及JVM重点知识线程、内存模型、JVM运行时内存、垃圾回收与算法、Java中四种引用类型、GC 分代收集算法
    发表于 08-20 06:09

    冒泡排序法的实现

    冒泡排序法的实现,自用笔记!
    发表于 01-20 07:22

    C语言编程是如何实现对数组中元素的线性搜索的

    C语言编程,实现对数组中元素的线性搜索。
    发表于 01-20 06:36

    python偏函数和型函数

    )4>>> power_2(3)9python型函数根据传入参数类型的不同而调用不同的函数逻辑体,这种实现我们称之为。在 Python 中叫做 singledispatch。它
    发表于 03-03 16:44

    trait中使用 `async fn`

    trait 中使用 async fn async 工作组很高兴地宣布 async fn 现在可以在 nightly 版本的 traits 中使用。在 playground 上有一个完整的工作示例
    的头像 发表于 11-23 15:40 528次阅读

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

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

    rust语言基础学习: Default trait

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

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

    如果你在一个类型上实现了一个trait,然后引用了这个类型,那么类型的引用也实现了这个trait吗?
    的头像 发表于 08-28 15:25 466次阅读