Rust常用特型之Borrow和BorrowMut特型

在Rust标准库中,存在很多常用的工具类特型,它们能帮助我们写出更具有Rust风格的代码。

std::borrow::BorrowAsRef有点相似,如果一个类型实现了Borrow<T>,那么你可以从它的borrow函数里高效的借出一个&T。但是Borrow施加了一些限制,就是借出的&T必须和该类型拥有相同的哈希和比较算法。注意,Rust并不强制这一点,只是标准库注明了该特型的限制。这使得Borrow主要用于哈希表和结构树中的key和处理其它需要比较或者哈希值的场景。

举一个实际例子,对于String来说,它实现了AsRef<str>AsRef<[u8]>AsRef<Path>。但是这三种实现的目标类型都会产生不同的哈希值。只有&strString会产生相同的哈希值,所以String 只实现了Borrow<str>

Borrow特型的定义和AsRef是相等的,只有名称上的区别:

trait Borrow<Borrowed: ?Sized> {fn borrow(&self) -> &Borrowed;
}

Borrow特型被设计用来解决一个特定的问题,那就是哈希表和关联集合类型的通过不同类型的Key进行查询的问题。

例如,假定你有一个std::collections::HashMap<String,i32>用来做从字符串到i32的映射。这个HashMap的键是String类型,每个键值对都拥有自己的键。那么查询表中的键值对的函数定义是什么样子的呢?我们先从简单的看起:

impl<K, V> HashMap<K, V> where K: Eq + Hash
{fn get(&self, key: K) -> Option<&V> { ... }
}

这样的定义的确有意义,当你查询一个entry时,你必须提供一个key,当然这个key的类型必须是表中的K。但是此例中,K是String,这个函数定义会强制你在每次函数调用时传递一个String的值,这有些小题大作了。你所需要的只是对这个Key的一个引用而已,于是,代码可以稍微修改成下面这样:

impl<K, V> HashMap<K, V> where K: Eq + Hash
{fn get(&self, key: &K) -> Option<&V> { ... }
}

上最初的定义相比,仅是将key的类型改成了&K,但是对上例来说,你必须传递一个&String。假定你有一个&str,你必须先创建一个临时String,然后再将它的引用传递过去。你需要写出类似如下代码:hashtable.get(&"twenty-two".to_string()).

从语法上看,上面的写法问题不大,但是从性能上看,上面的写法并不可取,它首先在堆上会分配一个String的缓冲区并且将所有文本复制进去,然后你再借出一个引用,然后将引用 传递到get函数中去,在函数调用结束后,再销毁这个String。

更好的解决方案是函数参数可以传递一个和我们的key具有相同的hash和比较算法的类型,例如&str,它很好的符合了这个要求。于是,我们需要继续改进代码,下面是标准库中的版本:

impl<K, V> HashMap<K, V> where K: Eq + Hash
{fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>where K: Borrow<Q>,Q: Eq + Hash{ ... }
}

换句话说,如果你能从一个entry的key中借出一个&Q,并且它的哈希和比较算法实现和key的结果一样,那么&Q也应该是可以接受的key的类型。由于String实现了Borrow<str>Borrow<String>, 这个最终版本的get函数允许你传递&String或者&str来作为一个key.

这里的意思是如果哈希表的key的哈希与比较结果与另一种类型相同,并且Key的类型可以借出另一种类型的引用,则另一种类型的引用也是有效的key类型(对于get函数来讲,插入时肯定是严格的类型匹配)。这也侧面反映了哈希表需要进行key的哈希和比较,只要哈希和比较后的结果一样,就可以用作key。那是不是所有符合这个条件的类型都可以作为key呢?这里需要进一步缩小范围进行限定,因此约束了K: Borrow<Q>,也就是K可以借出Q,这也是有意义的。两个无关的类型可以相互替换在设计上是有瑕疵的,必须满足这个Borrow特型才可以。如果K实现了Borrow<Q>,那么根据Borrow的定义,K和Q的哈希及比较结果必须是相同的(虽然Rust并不强制这一点,但Borrow就是为了解决这个问题而引入的协议,你非要自己违反它别人是没有办法的)。

从这里也可以看出,部分转换特型其实就是添加了一个君子协定,约定一个公共的方法。

Vec<T>[T:N]实现了Borrow<[T]>,也就是向量和数据可以借出底层切片的引用(虽然AsRef也这么做了,但是两者应用的场景不同)。每个像字符串这样的类型都允许错出它的底层切片类型,例如String实现了Borrow<str>PathBuf实现了Borrow<Path>等。所有的标准库关联集合类型(例如HashSet)都使用了Borrow来决定某个类型是否能传递给他们的查询函数。

标准库同时包含了一个空实现,所有的类型都可以借出自己,这是显而易见的。这样,&K 传递给get函数也是没有问题的。

作为一个约定,每个&mut T类型同时也实现了Borrow<T>,用来返回一个平常使用的共享的引用。这允许你向集合查询函数传递一个可变引用而不需要重新把它借出为一个共享引用。模拟了Rust隐式从可变引用到共享引用的引用转换。

这里我们看一下源码:

#[stable(feature = "rust1", since = "1.0.0")]
impl<T: ?Sized> Borrow<T> for &mut T {fn borrow(&self) -> &T {&**self}
}

这里的self其实为 &&mut T,所以需要两次解引用 得到T,然后再前面加&得到&T,这是没有问题的。

结合上面的例子,如果传进去一个&mut String, 这里K为String ,那么key是什么呢? 按定义是K能借出Q,也就是String能借出什么类型?能借出自己和str,所以 key 可以是 &String和&str。但是涉及到mut到底是什么会事呢?

我们写了如下测试代码:

use std::borrow::Borrow;
use std::hash::Hash;pub fn get<K,V,Q>(k: &Q) -> Option<&V>
whereQ: ?Sized,K: Borrow<Q>,Q: Hash + Eq, 
{   println!("type is {}", std::any::type_name::<Q>());None
}fn main() {let mut key = String::from("key");let v = get::<String,i32,String>(&mut key);println!("v:{v:?}");
}

这里我们打印出来Q的类型是String. 也就是说,我们传递进去一个&mut T,它自动变成了一个&T ,这个自动引用转换是通过borrow进行的还是Rust自动转换的,不得而知。猜想是Rust自动转换的。那个 &mut T的空实现估计是为了让Borrow更加完美。

但是看书中的意思是使用了Borrow特型的borrow函数进行转换的。

相应的,BorrowMut特型允许借出一个可变引用。除此外和Borrow用法没有区别

trait BorrowMut<Borrowed: ?Sized>: Borrow<Borrowed> {fn borrow_mut(&mut self) -> &mut Borrowed;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/1308.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

synchronized和ReentrantLock傻傻分不清楚

synchronized和ReentrantLock都是用于线程间同步的机制&#xff0c;都是可重入锁&#xff08;同一个线程可以多次获取同一个锁&#xff09;&#xff0c;它们的异同点如下&#xff1a; 一、应用场景 1.synchronized可应用于实例方法、静态方法和代码块。 2.ReentrantLock 是 jav…

使用Docker搭建Redis主从集群

文章目录 ☃️前言☃️搭建❄️❄️架构❄️❄️实例说明❄️❄️搭建第一个服务器上的两个实例❄️❄️搭建第二个服务器上的一个实例 ☃️开启主从❄️❄️改配置❄️❄️重启从节点 ☃️验证 ☃️前言 单节点 Redis 的并发能力是有上限的&#xff0c;要进一步提高Redis的并…

了解监控易(33):工单管理

在复杂的IT运维环境中&#xff0c;高效、规范地处理各种事务请求至关重要。监控易的工单管理功能&#xff0c;作为一款轻量化的运维协同工具&#xff0c;为团队提供了一个集中化、标准化的平台&#xff0c;以创建、接单、转交、挂起和重启工单&#xff0c;从而确保客户设备故障…

搜维尔科技:SenseGlove 的 Nova 被用于组装卫星接收器的虚拟现实培训项目中

SenseGlove 的 Nova 被用于组装卫星接收器的虚拟现实培训项目中 搜维尔科技&#xff1a;SenseGlove 的 Nova 被用于组装卫星接收器的虚拟现实培训项目中 得益于 SenseGlove 的力反馈专利&#xff0c;学员可以感受到他们正在组装的零件的形状、尺寸和密度。学员可以通过运动跟踪…

[大模型]TransNormerLLM-7B FastApi 部署调用

TransNormerLLM-7B FastApi 部署调用 1. TransNormer 介绍 TransNormerLLM 是一个基于线性注意力的 LLM&#xff0c;在准确性和效率方面均优于传统的基于 softmax 注意力的模型。它是在包含多达1.4 万亿个令牌的高质量语料库上进行训练的&#xff0c;TransNormerLLM 从之前的…

K-means和DBSCAN

目录 一、K-means和DBSCAN之间的主要区别 二、DBSCAN聚类算法 2.1DBSCAN聚类算法实现点集数据的聚类 2.2DBSCAN聚类算法实现鸢尾花数据集的聚类 三、K-means聚类算法 3.1K-means聚类算法实现随机数据的聚类 3.2K-means聚类算法实现鸢尾花数据集的聚类 一、K-means和DBSC…

014Node.js时间格式包silly-datetime安装与使用

下载&#xff1a; https://www.npmjs.com/网站上下载silly-datetime 安装 npm i silly-datetime --save var sd require(silly-datetime);console.log(new Date()); //2024-04-18T04:40:38.505Zvar dsd.format(new Date(), YYYY-MM-DD HH:mm);console.log(d); //2024…

未加权的相位解包裹算法DCT-0基础入门(含matlab代码和详细教程解析)

后面会更新c++ 的代码 ! phi = phase_unwrap(psi, weight) % get the wrapped differences of the wrapped valuesdx = [zeros([size(psi,1),1]), wrapToPi(diff(psi, 1, 2)), zeros([size(psi,1),1])];dy = [zeros([1,size(psi,2)]); wrapToPi(diff(psi, 1, 1)); zeros([1,s…

idea在controller或者service使用ctrl+alt+b进入方法后,如何返回到 进入前的那一层

idea在controller或者service使用ctrlaltb进入方法后&#xff0c;如何返回到进入方法的最外层 解决方案使用 ctrlalt ← /→← /→ 键盘上的左右键盘

jQuery 选择器有几种,分别是什么

jQuery选择器是用于“选择”&#xff08;或查找&#xff09;HTML元素的强大工具。jQuery选择器基于元素的名称、ID、类、类型、属性等进行选择。以下是jQuery选择器的几种主要类型&#xff0c;以及它们的详细代码示例&#xff1a; 基本选择器&#xff1a; 元素选择器&#xff1…

2024.4.20力扣每日一题——组合总和

2024.4.20 题目来源我的题解方法一 回溯 题目来源 力扣每日一题&#xff1b;题序&#xff1a;39 我的题解 方法一 回溯 以每一个位置开始深搜&#xff0c;直到target等于0或者小于0或者遍历完结束。 关键在于&#xff1a;注意去重 巧妙方法&#xff1a;传入一个index&#x…

stm32中断发送接收数据

配置hal库 1配置时钟 2配置uart 3打开中断 程序结构 uart中断函数 中断接收和发送函数 HAL_UART_Receive_IT()&#xff1a;启动中断驱动的接收过程&#xff0c;当接收到指定数量的字节后会产生中断&#xff0c;并调用HAL_UART_RxCpltCallback()回调函数。 HAL_StatusTypeD…

Qt : 如何解决重载引起的歧义

一、引子 在Qt中编写代码&#xff0c;进行信号和槽函数的连接时&#xff0c;如果采用新的语法&#xff0c;如&#xff1a; connect(ui->doubleSpinBox, &QDoubleSpinBox::valueChanged,this,&App::minValueChanged);当你准备快乐编译时&#xff0c;突然被背刺。卧槽…

【Redis(6)】Redis集群模式配置示例

Redis的三种高可用方案对比参考上一篇博客&#xff1a;深入理解Redis三种高可用方案&#xff0c;以做出明智的选择&#xff0c;下面要探讨的是三种方案其中的一种。 在构建高性能缓存系统时&#xff0c;Redis以其卓越的速度和灵活性成为首选。然而&#xff0c;传统的哨兵模式和…

STL分解

效果图 ref&#xff1a;时序数据分析的利器——STL分解 - 知乎 (zhihu.com)

2024化工制造企业数字化白皮书

来源&#xff1a;蓝凌研究院 中国石油和化学工业联合会发布2023年中国石油和化工行业经济运行情况。数据显示&#xff0c;2023年&#xff0c;我国石化行业实现营业收入15.95万亿元&#xff0c; 同比下降1.1%&#xff0c;利润总额8733.6亿元&#xff0c;行业经济运行总体呈现低…

2024蓝桥杯每日一题(数学期望)

备战2024年蓝桥杯 -- 每日一题 Python大学A组 试题一&#xff1a;收集卡牌 试题二&#xff1a;爬树的甲壳虫 试题三&#xff1a;绿豆蛙的归宿 试题四&#xff1a;扑克牌 试题一&#xff1a;收集卡牌 【题目描述】 小林在玩一个抽卡游戏&#xff0c;其…

MySQL-笔记-06.数据高级查询

目录 6.1 连接查询 6.1.1 交叉连接&#xff08;cross join&#xff09; 6.1.2 内连接&#xff08;inner join&#xff09; 6.1.3 外连接&#xff08;outer join&#xff09; 6.1.3.1 左外连接&#xff08;left [outer] join&#xff09; 6.1.3.2 右外连接&#xff08;rig…

LeetCode 1378、1277、2944

1378 二级排序&#xff0c;compare函数必须是static的 class Solution { public:struct node {int val;int priority;};static bool compare(const node &n1, const node &n2) {if (n1.priority n2.priority) {return n1.val < n2.val;}return n1.priority < n…

VSCode 目录折叠展开、缩进深度设置

1、VSCode 目录折叠展开设置 运行 Visual Studio Code &#xff0c;按 Ctrl &#xff0c;打开设置 输入Explorer:Compact Folders&#xff0c;取消勾选 或者在设置文件上添加 "explorer.compactFolders": false2、VSCode 目录缩进深度设置 输入Workbench Tree:…