Rust 中的类型转换
1. as
运算符
as
运算符有点像 C 中的强制类型转换,区别在于,它只能用于原始类型(i32
、i64
、f32
、
f64
、 u8
、 u32
、 char
等类型),并且它是安全的。
例
在 Rust 中,不同的数值类型是不能进行隐式转换的,比如:
let b: i64 = 1i32;
会出现编译错误,提示无法进行类型转换。
error[E0308]: mismatched types
--> src\main.rs:2:18
|
2 | let b: i64 = 1i32;
| ^^^^ expected i64, found i32
help: change the type of the numeric literal from `i32` to `i64`
这时可以使用as 进行转换。
let b: i64 = 1i32 as i64;
为什么它是安全的?
尝试以下代码:
let b = 1i32 as char;
编译器错误:
error[E0604]: only `u8` can be cast as `char`, not `i32`
--> src\main.rs:2:13
|
2 | let b = 1i32 as char;
| ^^^^^^^^^^^^
可见在不相关的类型之间,Rust 会拒绝转换,这也避免了运行时错误。
2. Trait From<T>
和 Into<T>
上文说到,as
运算符之能在原始类型之间进行转换,那么对于 Struct 和 Enum 这样的类型该如何进行转换呢? 这就是我们这节的内容 From<T>
和 Into<T>
。
先来看一看这两个 Trait 的结构。
pub trait From<T> {
fn from(T) -> Self;
}
pub trait Into<T> {
fn into(self) -> T;
}
很简单,From<T>
有一个 from
方法,Into<T>
有一个 into
方法。
一般来说,我们应该尽量优先选择实现 From<T>
而不是 Into<T>
,因为当你为 U
实现 From<T>
,这意味着你同时也为 T
隐式实现了 Into<U>
。
来看个例子
fn main() {
println!("Hello, world!");
let b: Complex = 1.into();
println!("{:?}", b);
}
#[derive(Debug)]
struct Complex {
re: i32,
im: i32
}
impl From<i32> for Complex{
fn from(re: i32) -> Self {
Complex{
re,
im:0
}
}
}
当我为 Complex
实现 From<i32>
后,我也可以在 i32
上使用 into
方法,转换到 Complex
。
原始类型实现了与 as
转换相对应的 From<T>
与 Into<T>
。
当你为 U
实现 From<T>
之后,你要确保这个转换一定能成功,如若有失败的可能,你应该选择为 U
实现 TryFrom<T>
。
什么时候该使用
Into<T>
Into<T>
被设计出来,总有该用到的地方。那什么时候该使用呢?先复习一下 Rust 中的
孤儿原则
在声明trait和impl trait的时候,Rust规定了一个Orphan Rule(孤儿规则):impl块要么与trait的声明在同一个的crate中,要么与类型的声明在同一个crate中。
也就是说,不能在一个crate中,针对一个外部的类型,实现一个外部的trait。
因为在其它的crate中,一个类型没有实现一个trait,很可能是有意的设计。
如果我们在使用其它的crate的时候,强行把它们“拉郎配”,是会制造出bug的。
比如说,我们写了一个程序,引用了外部库lib1和lib2,lib1中声明了一个trait T,lib2中声明了一个struct S ,我们不能在自己的程序中针对S实现T。
这也意味着,上游开发者在给别人写库的时候,尤其要注意。
一些比较常见的标准库中的 trait,比如 Display Debug ToString Default 等,应该尽可能地提供好。
否则,使用这个库的下游开发者,是没办法帮我们把这些 trait 实现的。
同理,如果是匿名impl,那么这个impl块必须与类型本身存在于同一个模块中。
显然,
From<T>
不属于当前 crate ,当你要实现当前 crate 中的类型T
转换到其他 crate 中的类型U
时,如果选择为U
实现From<T>
,由于孤儿原则,编译器会阻止你这么做。这时我们就可以选择为T
实现Into<U>
。注意,和
From<T>
不同,实现Into<U>
之后并不会隐式实现From<T>
,这点需特别注意。From<T>
的妙用回忆一下 Rust 的
?
操作符,它被用于 返回值为Result<T,E>
或者Option<T>
的函数。回想一下,它是如何处理Err(E)
的。fn apply() -> Result<i32,i32> {
Err(1)
}
fn main() -> Result<(),i64> {
let a = apply()?;
Ok(())
}
上面的例子是可以通过编译的,既然 Rust 中的数值类型是不能隐式转换的,那么,当返回
Err(i32)
时是如何转换到Err(i64)
的呢?这其实是一个 Rust 的语法糖。展开后的代码类似于下面:fn apply() -> Result<i32,i32> {
Err(1)
}
fn main() -> Result<(),i64> {
let a = match apply() {
Ok(v) => v,
Err(e) => return Err(i64::from(e)),
};
Ok(())
}
也就是说,Rust 会自动调用目标类
from
方法进行转换。
3. 解引用强制多态
这次先看一个例子:
fn print(message: &str) {
println!("{}",message);
}
fn main() {
let message: String = "message".to_string();
print(&message);
}
print
的形参是 &str
类型,然而在 main
中,我传递却是一个 &String
类型的实参。明显,这两个类型不相同!!Rust 为什么会通过这样的代码呢?
没错,这就是 Rust 的 解引用强制多态。
首先,需要了解一个 Deref
Trait 。
#[lang = "deref"]
pub trait Deref {
type Target: ?Sized;
#[must_use]
fn deref(&self) -> &Self::Target;
}
deref
方法返回一个 &Target
类型的引用。
回忆一下 Rust 中的解引用语法,当 ref
是一个引用或智能指针时,我们可以使用 *ref
的方式解引用。这是类似一个语法糖,对于 *ref
这种写法,写全应该时 *(ref.deref())
。
回想 Box<T>
的使用,Box<T>
实现了 Deref
,它的 deref
方法返回 &T
的引用,然后使用解引用运算符 *
,我们顺利拿到一个 T
类型的数据。也就是,你可以通过实现 Deref
以重载解引用运算符。
Deref
和这节的内容有什么关系呢?
当 T
实现了 Deref<Target=U>
时,对于需要 &U
的地方,你可以提供一个 &T
类型的数据,Rust会为你自动调用 deref
方法,而这个过程可以重复多次。
比如,我自定义类型 P
实现了 Deref<Target=String>
,那么可以把 &P
类型变量传递给一个 &str
类型变量。&P -> &String -> &str
,伪代码: &P.deref().deref()
。
回到这节开头的例子,print(&message)
相当于 print((&message).deref())
,正好是一个 &str
类型。
Rust 中的类型转换的更多相关文章
- JavaScript中数据类型转换总结
JavaScript中数据类型转换总结 在js中,数据类型转换分为显式数据类型转换和隐式数据类型转换. 1, 显式数据类型转换 a:转数字: 1)Number转换: 代码: var a = " ...
- java中强制类型转换
在Java中强制类型转换分为基本数据类型和引用数据类型两种,这里我们讨论的后者,也就是引用数据类型的强制类型转换. 在Java中由于继承和向上转型,子类可以非常自然地转换成父类,但是父类转换成子类则需 ...
- JS中String类型转换Date类型 并 计算时间差
JS中String类型转换Date类型 1.比较常用的方法,但繁琐,参考如下:主要使用Date的构造方法:Date(int year , int month , int day)<script& ...
- SQL中的类型转换
SQL中的类型转换一直是以块心病,因为用得比较少,所以每次想用的时候都要想半天,恰好这段时间比较空,整理整理.今天写个标题先.
- Rust 中的继承与代码复用
在学习Rust过程中突然想到怎么实现继承,特别是用于代码复用的继承,于是在网上查了查,发现不是那么简单的. C++的继承 首先看看c++中是如何做的. 例如要做一个场景结点的Node类和一个Sprit ...
- Struts2中的类型转换
1. Struts2中的类型转换 我们知道通过HTTP提交到后台的数据,都是字符串的形式,而我们需要的数据类型当然不只字符串类型一种.所以,我们需要类型转换! 在Struts2中,类型转换的概 ...
- java中的类型转换
java中的类型转换分为两种 自动类型转换 要实现数据的自动类型转换必须同时满足下面两个条件 两种数据类型彼此兼容 目标类型的取值范围大于原类型范围 强制类型转换 当两种数据类型彼此不兼容,或者说目标 ...
- HQL语句中数据类型转换,及hibernate中createQuery执行hql报错
一.HQL语句中数据类型转换: 我们需要从数据库中取出序号最大的记录,想到的方法就是使用order by子句进行排序(desc倒序),然后取出第一个对象,可是当初设计数据库时(我们是在原来的数据库的基 ...
- Java中数据类型转换&基本类型变量和对象型变量
1.Java的数据类型分为三大类 布尔型,字符型和数值型 其中数值型又分为整型和浮点型 2.Java的变量类型 布尔型 boolean 字符型 char 整型 byte,short,int,lo ...
随机推荐
- MongoDB入门系列之科普篇
目录 背景 对比 MongoDB的数据存储格式 背景 最近公司扩展了很多国外客户,那么一个很严重的问题就是翻译,对于国外客户来说,肯定看不懂中文,那就要项目中提供切换各自国家语言的功能. 由于每个 ...
- .Net Core 商城微服务项目系列(七):使用消息队列(RabbitMQ)实现服务异步通信
RabbitMQ是什么,怎么使用我就不介绍了,大家可以到园子里搜一下教程.本篇的重点在于实现服务与服务之间的异步通信. 首先说一下为什么要使用消息队列来实现服务通信:1.提高接口并发能力. 2.保证 ...
- ouc_software第一次作业:OUC二手物品交易
一.前言 1.项目名称:ouc二手物品交易 2.项目简介 (1)创办一个网上校内二手物品交易平台,供校内师生进行交易二手物品. (2)经过身份认证的用户,可将自己想要交易的二手物品发布至平台,供其他用 ...
- Java 学习笔记之 实例变量与线程安全
实例变量与线程安全: 不共享数据: public class NoSharedThread extends Thread { private int count = 5; public NoShare ...
- Kafka 学习笔记之 High Level Consumer相关参数
High Level Consumer相关参数 自动管理offset auto.commit.enable = true auto.commit.interval.ms = 60*1000 手动管理o ...
- 快学Scala 第五课 (构造映射,获取映射值,更新映射值,迭代映射,与Java互操作)
构造映射: val score = Map[String, Int]() val score1 = HashMap[String, Int]() val value1 = Map[String, In ...
- 死磕 java线程系列之自己动手写一个线程池(续)
(手机横屏看源码更方便) 问题 (1)自己动手写的线程池如何支持带返回值的任务呢? (2)如果任务执行的过程中抛出异常了该怎么处理呢? 简介 上一章我们自己动手写了一个线程池,但是它是不支持带返回值的 ...
- mysql 生成数据字典sql语句
SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLUMN_COMMENT FROM information_schema. C ...
- 在Mac上搭建带ssl协议和域名指向的Apache服务器
顾名思义,就是要在苹果电脑上搭建 Apache 服务器,并且支持 https 协议,能用指定域名访问(有些开发调试需要注册域名,比如调试微信JS-SDK),当然最好能在手机端进行调试.首先,Mac 系 ...
- 记一次共享内存/dev/shm 小于memory_target 引发的客户DB 宕机问题
1> 记一次共享内存/dev/shm 小于memory_target 引发的客户DB 宕机问题(处理心得)