rust类型转换

news/2024/11/17 0:38:35/

类型转换

Rust 是类型安全的语言,因此在 Rust 中做类型转换不是一件简单的事。

as转换

Rust 不提供原生类型之间的隐式类型转换(coercion),但可以使用 as 关键字进行显式类型转换(casting)。例如:

fn main() {cast();
}// as 进行的显示类型强制转换
fn cast() {let n: u8 = 123;let m: i32 = n as i32;      // 将u8强制转换为i32类型println!("u8({})转i32({})", n, m);let a = 12345;         // 整型字面值常量是i32类型let b: i8 = a as i8;        // 能容纳更大数值的类型i32转容纳范围较小的i8,存在数据溢出的风险。println!("i32({})转i8({})", a, b);let c = '我';          // char类型let d = c as u32;println!("char({})转u32({})", c, d);       let f = 100u8;      let h = f as char;      // 只有u8才能转char(相当于只支持ASCII码的值和字符转换)println!("u8({})转char({})", f, h);let f = 123.123;    let q = f as i32;println!("f64({})转i32({})", f, q);let mut num = [1, 2, 3];let mut y = num.as_mut_ptr();       // 可变的指针类型let mut p = y as usize;                // 把指针转为usize类型p += 4;                                       // 指针步进一步(i32类型占4字节,因此加4即可)y = p as *mut i32;                            // 将 usize转为指针unsafe {                                        println!("{}", *y);                       // 在unsafe模块中操作指针}
}

转换不具有传递性 就算 e as U1 as U2 是合法的,也不能说明 e as U2 是合法的(e 不能直接转换成 U2)。as转换基本上只用于数值类型之间的转换。而且需要注意,当你从可以容纳范围更大的数据类型向可以容纳范围较小的数据类型转换的时候会发生溢出,因此你要人为保证数据转换是正确的。

into和from

From 和 Into 两个 trait 是内部相关联的,实际上这是它们实现的一部分。如果我们能够从类型 B 得到类型 A,那么很容易相信我们也能够把类型 B 转换为类型 A。

From

From trait 允许一种类型定义 “怎么根据另一种类型生成自己”,因此它提供了一种类型转换的简单机制。在标准库中有无数 From 的实现,规定原生类型及其他常见类型的转换功能。

比如,可以很容易地把 str 转换成 String:

let s = String::from("qwert");
println!("s={s}");

也可以为我们自己的类型定义转换机制:

#[derive(Debug)]
#[allow(unused)]
struct Number {value: i32,
}impl From<i32> for Number {fn from(item: i32) -> Self {Number { value: item }}
}let num = Number::from(30);
println!("My number is {:?}", num);

Into

Into trait 就是把 From trait 倒过来而已。也就是说,如果你为你的类型实现了 From,那么同时你也就免费获得了 Into。

使用 Into trait 通常要求指明要转换到的类型,因为编译器大多数时候不能推断它。不过考虑到我们免费获得了 Into,这点代价不值一提。

// 需要指明转换到的类型是Number
let a: Number = 1.into();
println!("My number is {:?}", a);

TryInto和TryFrom

类似于 From 和 Into,TryFrom 和 TryInto 是类型转换的通用 trait。不同于 From/Into 的是,TryFrom 和 TryInto trait 用于易出错的转换,也正因如此,其返回值是 Result 型。

pub fn catsing(){let b = 123;let a: u8 = b.try_into().unwrap();          // try_intoprintln!("{a}");let b:i32 = 12345;                // 有一点非常奇怪,那就是必须显示声明b的类型,否则编译器无法推断e的类型,导致错误。let _a: u8 = match b.try_into() {           // try_intoOk(v) => v,Err(e) => {println!("{:?}", e.to_string());0}};
}

如果我们需要自己实现try_from和try_into方法,那么需要实现TryFrom trait即可。例如:

#[derive(Debug, PartialEq)]
struct EvenNumber(i32);impl TryFrom<i32> for EvenNumber {type Error = ();fn try_from(value: i32) -> Result<Self, Self::Error> {if value % 2 == 0 {Ok(EvenNumber(value))} else {Err(())}}
}// TryFrom
assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));
assert_eq!(EvenNumber::try_from(5), Err(()));// TryInto
let result: Result<EvenNumber, ()> = 8i32.try_into();
assert_eq!(result, Ok(EvenNumber(8)));
let result: Result<EvenNumber, ()> = 5i32.try_into();
assert_eq!(result, Err(()));

ToString 和 FromStr

上面的这些转换适大多数时候不适合字符串。它更需要ToString

Display

要把任何类型转换成 String,只需要实现那个类型的 ToString trait。然而不要直接这么做,您应该实现fmt::Display trait,它会自动提供 ToString,并且还可以用来打印类型。

pub fn format_string() {use std::fmt;struct Circle {radius: i32}impl fmt::Display for Circle {      // 为 Circle 实现 Display traitfn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {write!(f, "Circle of radius {}", self.radius)}}let circle = Circle { radius: 6 };println!("{}", circle.to_string());     // to_string是由Display trait实现的。
}

当然了,也可以实现ToString trait。例如:

pub fn to_stirng() {struct Circle {radius: i32}impl ToString for Circle {fn to_string(&self) -> String {format!("Circle of radius {:?}", self.radius)}}let circle = Circle { radius: 6 };println!("{}", circle.to_string());
}

字符串转数字

只要对目标类型实现了 FromStr trait,就可以用 parse 把字符串转换成目标类型。 标准库中已经给无数种类型实现了 FromStr。如果要转换到用户定义类型,只要手动实现 FromStr 就行。
我们得提供要转换到的类型,这可以通过显示声明类型,或者用 “涡轮鱼” 语法(turbo fish,<>)实现。例如:

pub fn string_to_number(){let num = "12345";let num = num.parse::<i32>().unwrap();      // turbo fish写法println!("{}", num);let num = "12345";let num: u64 = num.parse().unwrap();            // 显示声明类型写法println!("{}", num);
}

点操作符

方法调用的点操作符看起来简单,实际上非常不简单,它在调用时,会发生很多魔法般的类型转换,例如:自动引用、自动解引用,强制类型转换直到类型能匹配等。

假设有一个方法 foo,它有一个接收器(接收器就是 self、&self、&mut self 参数)。如果调用 value.foo(),编译器在调用 foo 之前,需要决定到底使用哪个 Self 类型来调用。现在假设 value 拥有类型 T。再进一步,我们使用完全限定语法来进行准确的函数调用:

  1. 首先,编译器检查它是否可以直接调用 T::foo(value),称之为值方法调用
  2. 如果上一步调用无法完成(例如方法类型错误或者特征没有针对 Self 进行实现,上文提到过特征不能进行强制转换),那么编译器会尝试增加自动引用,例如会尝试以下调用: <&T>::foo(value)<&mut T>::foo(value),称之为引用方法调用
  3. 若上面两个方法依然不工作,编译器会试着解引用 T ,然后再进行尝试。这里使用了 Deref 特征 —— 若 T: Deref<Target = U> (T 可以被解引用为 U),那么编译器会使用 U 类型进行尝试,称之为解引用方法调用
  4. 若 T 不能被解引用,且 T 是一个定长类型(在编译器类型长度是已知的),那么编译器也会尝试将 T 从定长类型转为不定长类型,例如将 [i32; 2] 转为 [i32]
  5. 若还是不行,那么调用失败

因此点操作符的背后是按照 值方法调用->引用方法调用->解引用方法调用->其它 的顺序来进行调用的。下面是一个例子:

fn do_stuff<T: Clone>(value: &T) {let cloned = value.clone();
}

上面例子中 cloned 的类型是什么?首先编译器检查能不能进行值方法调用, value 的类型是 &T,同时 clone 方法的签名也是 &T : fn clone(&T) -> T,因此可以进行值方法调用,再加上编译器知道了 T 实现了 Clone,因此 cloned 的类型是 T。

如果 T: Clone 的特征约束被移除呢?

fn do_stuff<T>(value: &T) {let cloned = value.clone();
}

首先,从直觉上来说,该方法会报错,因为 T 没有实现 Clone 特征,但是真实情况是什么呢?

我们先来推导一番。 首先通过值方法调用就不再可行,因为 T 没有实现 Clone 特征,也就无法调用 T 的 clone 方法。接着编译器尝试引用方法调用,此时 T 变成 &T,在这种情况下, clone 方法的签名如下: fn clone(&&T) -> &T,接着我们现在对 value 进行了引用。 编译器发现 &T 实现了 Clone 类型(所有的引用类型都可以被复制,因为其实就是复制一份地址),因此可以推出 cloned 也是 &T 类型。

最终,我们复制出一份引用指针,这很合理,因为值类型 T 没有实现 Clone,只能去复制一个指针了。

下面是一个更复杂的例子:

#[derive(Clone)]
struct Container<T>(Arc<T>);fn clone_containers<T>(foo: &Container<i32>, bar: &Container<T>) {let foo_cloned = foo.clone();let bar_cloned = bar.clone();
}

上面代码中,Container<i32> 实现了 Clone 特征,因此编译器可以直接进行值方法调用,此时相当于直接调用 foo.clone,其中 clone 的函数签名是 fn clone(&T) -> T,由此可以看出 foo_cloned 的类型是 Container<i32>

然而,bar_cloned 的类型却是 &Container<T>。这是因为derive 宏最终生成的代码大概如下所示:

impl<T> Clone for Container<T> where T: Clone {fn clone(&self) -> Self {Self(Arc::clone(&self.0))}
}

从上面代码可以看出,派生 Clone 能实现的根本是 T 实现了Clone特征:where T: Clone, 因此 Container<T> 就没有实现 Clone 特征。

编译器接着会去尝试引用方法调用,此时 &Container<T> 引用实现了 Clone,最终可以得出 bar_cloned 的类型是 &Container<T>

当然,也可以为 Container<T> 手动实现 Clone 特征:

impl<T> Clone for Container<T> {fn clone(&self) -> Self {Self(Arc::clone(&self.0))}
}

此时,编译器首次尝试值方法调用即可通过,因此 bar_cloned 的类型变成 Container<T>

参考资料

  1. 通过例子学 Rust
  2. rust语言圣经

http://www.ppmy.cn/news/247709.html

相关文章

(一)CSharp-Net框架

.NET框架由三部分组成&#xff1a; 1.编程工具。 2.基类库(BCL). 3.公共语言运行库(CLR) CLR 在运行时管理程序的执行&#xff0c;包括以下内容&#xff1a; 内存管理和垃圾收集。代码安全验证。代码执行、线程管理及异常处理。 NET 框架的特点以及其带来的好处&#xff1a…

js字节面试题 读代码题 精讲

这是在牛客上看到的今天一个中国地质大学面试的上海字节的前端题 读代码题 下面这段便是从那里搬过来的 ******** 看代码说答案&#xff0c;关于this指向的&#xff0c;我说错了&#xff0c;甚至我还不知道为什么 看来还需要补补 const length 10 const fn function () …

第四章:运算符

第四章&#xff1a;运算符 4.1&#xff1a;算术运算符 ​ 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达值&#xff0c;对数值或表达式进行加()、减(-)、乘(*)、除(/)、取模(%)运算。 运算符名称作用示例加法运算符计算两个值或表达式的和SE…

神州信息祝贺国盾量子科创板上市!

神州信息祝贺国盾量子科创板上市&#xff01;

[BUUOJ] [RE] [ACTF新生赛2020] rome1

IDA 好久没写博客了&#xff0c;最近在刷re&#xff0c;这道题是我觉得十分有意义的一道题。故AC后总结分享给大家。不足之处请指正。 分析 直接导入IDA shift F12 双击后按 ctrl x跳转到被调用的函数中&#xff0c;按F5反编译&#xff0c;源代码如下 int func() {int r…

神奇九转公式 适用于东方财富证券客户端

找了很久才找到这段代码&#xff0c;相对有效降低金融交易风险。 HH:(OHC)*(1.02-1)/3H; AB:C>REF(C,4); X9:EVERY(AB,9); X8:EVERY(AB,8); X7:EVERY(AB,7); X6:EVERY(AB,6); X5:EVERY(AB,5); X4:EVERY(AB,4); X3:EVERY(AB,3); X2:EVERY(AB,2); X1:EVERY(AB,1); Y9:REF(X9,…

通达OA2019版本全功能

链接来源&#xff1a; https://item.taobao.com/item.htm?spma2oq0.12575281.0.0.45e51debBlhtoz&ftt&id594395052494 通达OA&#xff08;Office Anywhere网络智能办公系统&#xff09;是由北京通达信科科技有限公司自主研发的协同办公自动化软件&#xff0c;是与中…