Rust常用特型之Borrow和BorrowMut特型

2024-04-21 09:36

本文主要是介绍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;
}

这篇关于Rust常用特型之Borrow和BorrowMut特型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/922748

相关文章

MySQL常用字符串函数示例和场景介绍

《MySQL常用字符串函数示例和场景介绍》MySQL提供了丰富的字符串函数帮助我们高效地对字符串进行处理、转换和分析,本文我将全面且深入地介绍MySQL常用的字符串函数,并结合具体示例和场景,帮你熟练... 目录一、字符串函数概述1.1 字符串函数的作用1.2 字符串函数分类二、字符串长度与统计函数2.1

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

MySQL字符串常用函数详解

《MySQL字符串常用函数详解》本文给大家介绍MySQL字符串常用函数,本文结合实例代码给大家介绍的非常详细,对大家学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录mysql字符串常用函数一、获取二、大小写转换三、拼接四、截取五、比较、反转、替换六、去空白、填充MySQL字符串常用函数一、

Java中Arrays类和Collections类常用方法示例详解

《Java中Arrays类和Collections类常用方法示例详解》本文总结了Java中Arrays和Collections类的常用方法,涵盖数组填充、排序、搜索、复制、列表转换等操作,帮助开发者高... 目录Arrays.fill()相关用法Arrays.toString()Arrays.sort()A

Spring Boot中WebSocket常用使用方法详解

《SpringBoot中WebSocket常用使用方法详解》本文从WebSocket的基础概念出发,详细介绍了SpringBoot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消... 目录一、WebSocket基础概念1.1 什么是WebSocket1.2 WebSocket与HTTP

golang中reflect包的常用方法

《golang中reflect包的常用方法》Go反射reflect包提供类型和值方法,用于获取类型信息、访问字段、调用方法等,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值... 目录reflect包方法总结类型 (Type) 方法值 (Value) 方法reflect包方法总结

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

python常用的正则表达式及作用

《python常用的正则表达式及作用》正则表达式是处理字符串的强大工具,Python通过re模块提供正则表达式支持,本文给大家介绍python常用的正则表达式及作用详解,感兴趣的朋友跟随小编一起看看吧... 目录python常用正则表达式及作用基本匹配模式常用正则表达式示例常用量词边界匹配分组和捕获常用re

gitlab安装及邮箱配置和常用使用方式

《gitlab安装及邮箱配置和常用使用方式》:本文主要介绍gitlab安装及邮箱配置和常用使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1.安装GitLab2.配置GitLab邮件服务3.GitLab的账号注册邮箱验证及其分组4.gitlab分支和标签的