Rust常用特型之From和Into特型

devtools/2025/2/22 21:43:39/

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

std::convert::Fromstd::convert::Into特型是一种用于转换的特型,它们会消耗某个类型的值,返回另一个类型的值。相对于AsRefAsMut特型用来从一个类型借出另一个类型的引用,FromInto特型会获取参数值的所有权并对它进行转换,最后返回生成的新类型的值(注意不是引用)。

他们两个特型的定义相当对称:

rust">trait Into<T>: Sized {
fn into(self) -> T;
}
trait From<T>: Sized {fn from(other: T) -> Self;
}

标准库自动实现了所有类型到它自己的转换,每个类型都实现了From<T>Into<T>。注意这里的类型必须是Sized类型,否则违反这两个特型的定义,注意,特型定义中,T是否Sized是显式定义,所以默认它就是Sized 类型。

虽然这两个特型提供的函数实现了相同的功能,但是它们却应用于不同的场景。

通常使用Into 类型让你的函数可接受的参数更加灵活,例如,你可以写如下 代码:

rust">use std::net::Ipv4Addr;
fn ping<A>(address: A) -> std::io::Result<bool>
where A: Into<Ipv4Addr>
{let ipv4_address = address.into();...
}

这里,ping函数不仅能接收IPV4Addr作为参数,它也能接受u32或者[u8;4]类型的值。因为这些类型都实现了Into<Ipv4Addr>。(从含义上来讲,把一个IPV4地址当成一个32位的值或者长度为4的u8数组也是有意义的)。

ping函数仅需要知道address参数实现了Into<Ipv4Addr>就行了,这里并不需要限定你实际调用时传递进去参数的类型。这里只有一种可能工作的情况,所以类型推断自动为你填充了相应内容。

正向我们先前在学习AsRef时提到的,Into特型可以让函数实现类似C++多态的效果。当我们使用上面的ping函数定义时,你可以做下面的任意调用:

rust">println!("{:?}", ping(Ipv4Addr::new(23, 21, 68, 141))); // pass an Ipv4Addr
println!("{:?}", ping([66, 146, 219, 98]));   // pass a [u8; 4]
println!("{:?}", ping(0xd076eb94_u32));  // pass a u32

然而,From特型,却扮演了一个不同的角色。from函数主要用来作为一个构造器,它从其它类型的值产生一个当前类型的实例。例如,IPV4Addr除了有两个函数from_arrayfrom_u32外,它还实现了From<[u8;4]>From<u32>,因此我们可以写出如下代码:

rust">let addr1 = Ipv4Addr::from([66, 146, 219, 98]);
let addr2 = Ipv4Addr::from(0xd076eb94_u32);

我们可以让类型推断来确定到底适配哪个实现。

给定一个恰当的From实现,Rust标准库会自动为你实现相对应的Into特型。当你定义自己的类型时,如果你的构造器只有单一参数,你应该采用实现From<T>方式来编写它。这时,你会免费得到相应的Into实现。

这里举个简单的例子吧,如A实现了From<B>,那么B就自动实现了Into<A>。过程很简单,直接在Into特型的into函数里调用A::from(B)即可。

因为fromto转换函数会获取传递参数的所有权,转换可以在新构造的值上重用初始值的资源。例如,假设有如下代码:

rust">let text = "Beautiful Soup".to_string();
let bytes: Vec<u8> = text.into();

StringInto<Vec<u8>>实现简单的获得了原字符串的堆缓冲区并且重用它,并没有改变它,而是直接作为向量元素的缓冲区返回。这个转换并不需要重新分配空间或者复制文本。这是move使得实现更高效的另一个场景。

这些转换同时也提供了一个优雅的方法来松绑一个值,它从一个约束型类型转换成一个更灵活的类型, 同时并不削弱约束类型的约束的保证。例如,String类型保证它的内容总是有效的UTF-8字符,它的可变方法会被仔细限制为确保不会引入无效的UTF-8字符。但是这个示例演示了一个字符串可以转化为普通的字节,然后你就可以在它上面做你想做的事了,例如压缩它,或者和其它不是UTF-8字符相链接。由于into函数会获取值的所有权,因此text变量在转换之后就变成未初始化的,意味着我们可以自由的访问前者的字符串缓冲区而不必打破字符串的任何约定。

这里的意思是你可以将一个约束更多的类型转换成一个更灵活的类型,然后你就可以做更多的处理。

然而,一个便宜的转换并不是Into或者From的一部分。相对的,AsRefAsMut 转换被期望为便宜的(因为他们只是借出引用),FromInto转换有可能分配空间,进行复制或者包含对值内容的其它处理。例如,String实现了From<&str>,它会将字符串切片复制到一个新分配的堆上的缓冲区。std::collections::BinaryHeap<T>实现了From<Vec<T>>,它会根据具体的算法来比较和重新排序元素。

注意,有时候会在FromInto上应用?操作符。它一般用于错误转换,将各种不同的错误转换成一个更广泛的统一的错误。使用?操作符可以让你少写很多代码。这个经常和thiserror::error搭配在一起使用,此时使用一个枚举定义一个错误。枚举的每个变量代表不同的错误。例如 枚举MyError作为函数返回值,而函数类的语句返回OtherError时,在其后面使用?操作符会调用相应的into方法,转换成MyError

例如,假定有一个需求,需要读取二进制数据,然后转成10进制数字,最后输出UTF-8文本。这就意味着使用std::str::from_utf8和在i32上使用FromStr实现,它们会返回不同的错误类型。假设我们使用第七章定义的GenericErrorGenericError类型,?操作符可以为你做这种自动转换。

rust">type GenericError = Box<dyn std::error::Error + Send + Sync +'static>;
type GenericResult<T> = Result<T, GenericError>;
fn parse_i32_bytes(b: &[u8]) -> GenericResult<i32> {Ok(std::str::from_utf8(b)?.parse::<i32>()?)
}

像绝大多数error类型一样,Utf8ErrorParseIntError都实现了Error特型,标准库增加了一个空From实现,它可以将任意实现了Error特型的对象转化为一个Box<dyn Error>,我们可以使用?自动来转换。

rust">impl<'a, E: Error + Send + Sync + 'a> From<E>
for Box<dyn Error + Send + Sync + 'a> {fn from(err: E) -> Box<dyn Error + Send + Sync + 'a> {Box::new(err)}
}

如果不使用?操作符,而是使用match匹配的话,你需要写很多代码。通过使用两次?操作符,你只需要一行代码。

其实这里的?操作符的作用是错误传递,如果一个Result结果返回的是Ok(v),则?操作符直接得到v的值,如果返回的是Err(e),则会终止函数运行并把Err(e)作为函数结果返回。由于函数的返回类型是一个更广泛的类型Result<T,U>,因此Err(e)会自动转化为Err(u)。

这里书中的示例和常用的thiserror示例基本是相同的,只不过一个是返回了自定义的枚举类型(实现了Error特型),一个是返回Error特型对象(当然特型对象更广泛了)。

虽然Result定义中的E并没有约定E:Error,但一般都用于E:Error。

FromInto特型被添加到Rust标准库之前,Rust代码充斥着临时热转换特型和构造方法,每个都定义了一个单独的类型。FromInto特型统一化了这种转换,只要你遵循它,你可以很容易的使用,你的用户对此也会很熟悉。语言本身和其它库也可以依赖这些特型来实现一个标准的,范例性的转换封装。

FromInto是不可失败特型,他们的API需要这种转换一定是成功的。不幸的是,许多转换复杂的多,例如,转换一个很大的i64整数到i32会丢失一些信息,转换后的结果可能并不是我们想要的。例如:

rust">let huge = 2_000_000_000_000i64;
let smaller = huge as i32;
println!("{}", smaller); // -1454759936

有好几种方式来处理这种情况,根据代码中的上下文环境,一个wrapping转换可能更合适。另一方面,数字信息和处理系统经常会使用saturating转换,这里超过最大值的数会限制为最大值。

这里的说的意思是由于FromInto不可能失败,有时直接进行转换并不合适,例如上面的例子,直接转换得到了负数。因此要根据实际使用情况选择合适的方法,而不是简单的使用FromInto直接进行转换。

总结

本章重点讲了如下几点:

  1. From和Into将一种类型转换成另一种类型,他们是对称的,实现其中一种,Rust会自动帮你实现另外一种。
  2. From和Into会消耗转换的值,同时有可能重新分配空间和进行复制,因此不是便宜的转换。但某些场景下,Move方式更高效。
  3. From和Into可以通过?操作符自动转换,一般这种场景用于错误传递。
  4. From和Into不可失败,并且也不是万能的(有些场景不适用直接转换)。
  5. From和Into可以将一种约束类型转换为更广泛的类型而不打破这种约束。
  6. Into特型可以让你的函数参数更灵活,而From特型主要用于构造器。

http://www.ppmy.cn/devtools/11607.html

相关文章

C++_特殊类的设计和单例模式

文章目录 学习目标&#xff1a;1.请设计一个类&#xff0c;不能被拷贝2. 请设计一个类&#xff0c;只能在堆上创建对象3. 请设计一个类&#xff0c;只能在栈上创建对象4. 请设计一个类&#xff0c;不能被继承5. 请设计一个类&#xff0c;只能创建一个对象(单例模式) 特殊类的设…

计算机网络----由概述到ICMP

麻烦先把五个层次刻进DNA里面 应用层 传输层 网络层 数据链路层 物理层 网络层和传输层的区别 网络层:设备到设备 传输层:端口到端口&#xff0c;进程到进程 物理层: 考虑的是怎样才能在连接计算机的传输媒体上传输比特流&#xff0c; 主要考虑的是屏蔽掉不同传输媒体和通信手段…

flutter 点击按钮限流方案

文章目录 前言一、理解限流的思想二、flutter实现代码如下&#xff1a;总结 前言 最近写flutter项目&#xff0c;遇到提交表单重复点击问题&#xff0c;下面是解决方案&#xff0c;希望帮助到大家。 一、理解限流的思想 1、限流思想 限流&#xff08;Throttle&#xff09; 限…

springboot中thymeleaf模板引用页面总结

1、先创建一个需要被引用的html页面&#xff0c;在页面里面不需要放公共的css、js&#xff0c;只需要写一些html片段即可&#xff0c;但是需要在外层html添加上th:fragment属性&#xff0c;如下所示&#xff1a; <div th:fragment"common_top"><ul><…

智能合约:概念与特点(了解什么是智能合约以及它的特点,如自动执行、不可篡改和可信任)

1.自动执行 智能合约通过预定义的代码和规则&#xff0c;在满足特定条件时自动执行。无需第三方介入&#xff0c;合约中的操作将在预定条件满足时自动触发。 2.不可篡改 智能合约的代码一旦部署在区块链上&#xff0c;便不可更改。这意味着合约一旦被创建&#xff0c;其中的规则…

Day13-Java进阶-IO字节流及其练习题

1. IO流介绍 2. IO 流体系结构 字节流读取纯文本文件会出现乱码问题 2.1 FileOutputStream 字节输出流 package com.itheima.stream.output;import java.io.FileOutputStream; import java.io.IOException;public class FileOutputStreamDemo3 {/*IO流的异常处理方式: jdk7版本…

SRS服务接入华为云CDN

CDN简介: CDN的全称是Content Delivery Network&#xff0c;即内容分发网络。其基本思路是尽可能避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节&#xff0c;使内容传输得更快、更稳定。通过在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网…

科研基础与工具(论文写作)

免责申明&#xff1a; 本文内容只是学习笔记&#xff0c;不代表个人观点&#xff0c;希望各位看官自行甄别 参考文献 科研基础与工具&#xff08;YouTube&#xff09; 学术写作句型 Academic Phrase bank 曼彻斯特大学维护的一个网站 写论文的时候&#xff0c;不不知道怎么…