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

    浏览量

    7383
  • rust语言
    +关注

    关注

    0

    文章

    57

    浏览量

    3009
收藏 人收藏

    评论

    相关推荐

    详解Rust的

    所有的编程语言都致力于将重复的任务简单化,并为此提供各种各样的工具。在 Rust 中,(generics)就是这样一种工具,它是具体类型或其它属性的抽象替代。在编写代码时,我们可以直接描述
    发表于 11-12 09:08 1065次阅读

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

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

    Java的背景和作用

    )只能存储Object类型的对象,这使得在使用集合时需要进行强制类型转换,容易出现类型错误。 的背景:在Java 5版本之前,Java的类型是静态的,在编译时确定,并且在运行时擦除类型信息。这种情况下,编译器无法对集合的元素类型进行验证,因此可能会导致运行时类型错误。
    的头像 发表于 09-20 14:30 1084次阅读
    Java<b class='flag-5'>泛</b><b class='flag-5'>型</b>的背景和作用

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

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

    冒泡排序法的实现

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

    iOS中关于的解析

    文章围绕这五点: 1. 是什么 2. 为什么要用 3. 怎么用 4.
    发表于 09-25 10:01 0次下载

    java 编程

    一。 概念的提出(为什么需要)? 首先,我们看下下面这段简短的代码: publicclassGenericTest { publicstaticvoidmain(String[
    发表于 09-27 11:15 0次下载

    聊聊java实现的原理与好处

    摘要: 和C++以模板来实现静多态不同,Java基于运行时支持选择了,两者的实现原理大相庭径。C++可以支持基本类型作为模板参数,Java却只能接受类作为参数;Java可以在
    发表于 09-27 16:50 0次下载

    Java的工作原理和案例

    是Java语言一个非常重要的概念,在Java集合类框架中被广泛应用。在介绍之前先看一个例子。
    的头像 发表于 07-01 10:14 2658次阅读

    trait中使用 `async fn`

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

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

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

    rust语言基础学习: Default trait

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

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

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

    C语言是否支持编程?

    C语言是否支持编程?毫无疑问,答案是不支持。
    的头像 发表于 10-16 10:02 685次阅读

    鸿蒙语言TypeScript学习第18天:【

    (Generics)是一种编程语言特性,允许在定义函数、类、接口等时使用占位符来表示类型,而不是具体的类型。
    的头像 发表于 04-16 14:56 379次阅读
    鸿蒙语言TypeScript学习第18天:【<b class='flag-5'>泛</b><b class='flag-5'>型</b>】