Rust常用特型之ToOwned特型

2024-04-20 16:44
文章标签 rust 常用 toowned 特型

本文主要是介绍Rust常用特型之ToOwned特型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

ToOwned

这次我们来学一个和Borrow特型相关的特型,叫ToOwned类型。看字面意思Borrow是代表借出,而ToOwned代表去拥有它。

在Rust中,假定某类型实现了Clone特型,如果给你一个对它引用,那我们得到它指向内容的备份的最常见方式是调用其clone()函数。但是如果你想克隆&str或者&[i32]时会发生什么呢?你的目的可能是想得到一个String或者Vec<i32>。但是根据Clone特型的定义,你无法得到它们。根据定义,对一个&T调用clone() 会返回一个T的值,也就是说会返回str或者[i32]。而我们前面学习Sized特型的时候提到过,str或者[i32] 是切片类型,无固定大小,是不能保存在变量中或者作为函数结果返回的。

std::borrow::ToOwned 特型提供了一个稍微宽松的方法将一引用转换为可拥有的值。

trait ToOwned {type Owned: Borrow<Self>;fn to_owned(&self) -> Self::Owned;
}

上面的定义中,to_owned函数返回一个类型为Self::Owned的新鲜值,但是这个Owned可不是任意类型,它有个类型约束,也就是实现了Borrow<Self>. 也就是说A能借出B/&B(实现了Borrow<B>),B才能拥有A.

例如,你可以从Vec<T>中借出一个&[T](这里的泛型U 为 [T] ),因此[T]可以实现ToOwned<Owned=Vec<T>>,只要T实现了Clone特型。这里为什么要对T限制呢?毕竟你要得到一个备份,如果一个T不能克隆,那么这个备份是无法实现的,因为需要把切片的元素复制到新的向量中去。相似的,str实现了ToOwned<Owned=String>,因此我们可以调用&strto_owned函数得到一个全新的字符串。Path也实现了ToOwned<Owned=PathBuf>,我们也可以从Path引用中得到一个全新的PathBuf值。

Humble Cow

BorrowToOwned联动可以实现一个很有意思的类型,Cow,注意它不是奶牛的意思,而是指 clone on write 我们趁热来学习它。

充分利用 Rust 需要深思熟虑所有权问题,例如某个函数是否应该通过引用或值接收参数。通常你能确定使用其中的一种或者另一种(使用引用还是值),函数的参数类型代表了你的决定。但是存在这样一些场景,你只有在运行时才知道到底是需要借用还是引用,这时,std::borrow::Cow类型就派上用场了,它的定义如下:

enum Cow<'a, B: ?Sized>where B: ToOwned{Borrowed(&'a B),Owned(<B as ToOwned>::Owned),
}

这里可以看到,Cow是一个枚举,有两个变量,分别代表借用和拥有。其Borrow变量绑定了一个&B(这里先忽视生命周期标记),这个B是个泛型,它的约束为B: ToOwned。它的目标类型我们先假定为U,那么U 必定实现了Borrow<B>

它的第二个枚举变量为Owned,绑定了一个<B as ToOwned>::Owned的值,也就是U的值,所以Owned变量可以写成Owned<U>。其中可以从U借出B,当然,也可以从B拥有U的新值。

第一个枚举变量,是绑定了&B,因此我们可以很方便的得到&B,第二个变量,是绑定了U,然而U又可以借出B,因此我们仍然可以很容易的得到&B( 通过U的borrow() 函数)。两个变量都可以方便的得到&B,因此它也实现了Deref特型,这样你可以直接在Cow上调用B的相关函数,而不管Cow是借用了B还是拥有了U。

你还可以在Cow类型的值上调用to_mut函数得到一个&mut B。 如果Cow变量刚好好Borrowed,则to_mut函数会先调用&Bto_owned方法得到它自己拥有的一个U的Copy,并对原来的变量进行重新赋值,这样就从Borrowed变量转换成了Owned变量,然后再从新拥有的U的值中借出一个mut 引用。这里正是clone on write的含义所在(写时clone).

我们来看一下这个Deref的实现代码:

#[stable(feature = "rust1", since = "1.0.0")]
impl<B: ?Sized + ToOwned> Deref for Cow<'_, B>
whereB::Owned: Borrow<B>,
{type Target = B;fn deref(&self) -> &B {match *self {Borrowed(borrowed) => borrowed,Owned(ref owned) => owned.borrow(),}}
}

你代码中我们可以看到,如果Cow是引用 ,直接将这个引用返回,如果是拥有的U值,则从U值借出,这里有一个细节:

Owned(ref owned) => owned.borrow(), 因为我们的deref函数接收参数为&self,因此我们无法在函数内部消耗掉Cow本身,而match直接匹配时便会消耗这个值,因此为了阻止这种行为,添加了ref owned,代表这个owned只是获取一个引用 ,因此这里的owned的类型其实为&U,所以直接调用其borrow()函数也就得到了一个&B. 注意borrow函数也是接收一个引用而非值作为参数。

to_mut函数的解释为:

Acquires a mutable reference to the owned form of the data.

Clones the data if it is not already owned.

使用示例为:

use std::borrow::Cow;let mut cow = Cow::Borrowed("foo");
cow.to_mut().make_ascii_uppercase();assert_eq!(cow,Cow::Owned(String::from("FOO")) as Cow<'_, str>
);

通过上面的示例我们可以看到,就算我们的cow是Borrowed变量,拥有一个共享的引用,到最后也变成了一个Owned变量。

我们来看一下实现过程:

#[stable(feature = "rust1", since = "1.0.0")]
pub fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned {match *self {Borrowed(borrowed) => {*self = Owned(borrowed.to_owned());match *self {Borrowed(..) => unreachable!(),Owned(ref mut owned) => owned,}}Owned(ref mut owned) => owned,}
}

这里 如果是Borrowed,则首先会to_owned得到U的一个新值,然后再将self重新赋值为Owned,然后再重新对self进行match操作,

此时已经是一个Owned,所以直接借出了ref mut,注意因为match操作会消耗值,所以这里的Owned(ref mut owned) => owned, 中加了ref 代表是一个引用,结合上例,我们就得到了一个&mut String。

相似的,Cow也实现了into_owned方法将引用转换为一个拥有的值。如果必须,则可以将值的所有权转移给调用者,在这个过程中Cow本身的值会被消耗掉。

Cow一个常见的用法是返回一个静态的字符串文字值常量或者一个动态的字符串。例如,假定你需要将一个枚举类型转换成一个消息,枚举的大多数变量都可用于固定的字符串,但是有一些变量或者一些额外的信息,因此你可以返回一个Cow<'static str>

use std::path::PathBuf;
use std::borrow::Cow;
fn describe(error: &Error) -> Cow<'static, str> {match *error {Error::OutOfMemory => "out of memory".into(),Error::StackOverflow => "stack overflow".into(),Error::MachineOnFire => "machine on fire".into(),Error::Unfathomable => "machine bewildered".into(),Error::FileNotFound(ref path) => {format!("file not found: {}", path.display()).into()}}
}

上面的代码使用了CowInto特型实现来构造值。这里其实是CowFrom实现,然后相对应的&str就有了Into实现。这个Match的绝大多数分支都返回一个静态分配的文字串文本用于Cow::Borrowed绑定,只有最后一个分支返回一个String用于Owned变量绑定。

describe函数的调用者不用管返回的到底Cow的哪个变量,它只用简单的将返回值看成是&str就行了,例如:

println!("Disaster has struck: {}", describe(&error));

如果你需要一个拥有的值,调用into_owned函数就可,例如 (describe(&error).into_owned() 就返回一个String

使用Cow可以让describle函数和它的调用者直到在需要时才会分配内存来保存新生成的字符串。

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



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

相关文章

Python常用命令提示符使用方法详解

《Python常用命令提示符使用方法详解》在学习python的过程中,我们需要用到命令提示符(CMD)进行环境的配置,:本文主要介绍Python常用命令提示符使用方法的相关资料,文中通过代码介绍的... 目录一、python环境基础命令【Windows】1、检查Python是否安装2、 查看Python的安

python判断文件是否存在常用的几种方式

《python判断文件是否存在常用的几种方式》在Python中我们在读写文件之前,首先要做的事情就是判断文件是否存在,否则很容易发生错误的情况,:本文主要介绍python判断文件是否存在常用的几种... 目录1. 使用 os.path.exists()2. 使用 os.path.isfile()3. 使用

Java实现本地缓存的常用方案介绍

《Java实现本地缓存的常用方案介绍》本地缓存的代表技术主要有HashMap,GuavaCache,Caffeine和Encahche,这篇文章主要来和大家聊聊java利用这些技术分别实现本地缓存的方... 目录本地缓存实现方式HashMapConcurrentHashMapGuava CacheCaffe

Python将字符串转换为小写字母的几种常用方法

《Python将字符串转换为小写字母的几种常用方法》:本文主要介绍Python中将字符串大写字母转小写的四种方法:lower()方法简洁高效,手动ASCII转换灵活可控,str.translate... 目录一、使用内置方法 lower()(最简单)二、手动遍历 + ASCII 码转换三、使用 str.tr

Spring Boot 常用注解整理(最全收藏版)

《SpringBoot常用注解整理(最全收藏版)》本文系统整理了常用的Spring/SpringBoot注解,按照功能分类进行介绍,每个注解都会涵盖其含义、提供来源、应用场景以及代码示例,帮助开发... 目录Spring & Spring Boot 常用注解整理一、Spring Boot 核心注解二、Spr

rust 中的 EBNF简介举例

《rust中的EBNF简介举例》:本文主要介绍rust中的EBNF简介举例,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 什么是 EBNF?2. 核心概念3. EBNF 语法符号详解4. 如何阅读 EBNF 规则5. 示例示例 1:简单的电子邮件地址

Java中的内部类和常用类用法解读

《Java中的内部类和常用类用法解读》:本文主要介绍Java中的内部类和常用类用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录内部类和常用类内部类成员内部类静态内部类局部内部类匿名内部类常用类Object类包装类String类StringBuffer和Stri

MySQL连接池(Pool)常用方法详解

《MySQL连接池(Pool)常用方法详解》本文详细介绍了MySQL连接池的常用方法,包括创建连接池、核心方法连接对象的方法、连接池管理方法以及事务处理,同时,还提供了最佳实践和性能提示,帮助开发者构... 目录mysql 连接池 (Pool) 常用方法详解1. 创建连接池2. 核心方法2.1 pool.q

Spring Boot 常用注解详解与使用最佳实践建议

《SpringBoot常用注解详解与使用最佳实践建议》:本文主要介绍SpringBoot常用注解详解与使用最佳实践建议,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录一、核心启动注解1. @SpringBootApplication2. @EnableAutoConfi

SQL常用操作精华之复制表、跨库查询、删除重复数据

《SQL常用操作精华之复制表、跨库查询、删除重复数据》:本文主要介绍SQL常用操作精华之复制表、跨库查询、删除重复数据,这些SQL操作涵盖了数据库开发中最常用的技术点,包括表操作、数据查询、数据管... 目录SQL常用操作精华总结表结构与数据操作高级查询技巧SQL常用操作精华总结表结构与数据操作复制表结