Rust 中那些让人眼花缭乱的类型转换

172 阅读5分钟

本文将介绍类型转换,因为rust是一门类型安全的语言,所以在rust中进行类型转换不是一件简单事轻松的事情。rust中类型转换大体分为as转换TryInto 转换,通用类型转换等三大类。

IMG_0152.jpeg

as转换

我们先看一个简单的小类型转大类型的例子:

fn main(){
   let x:i32 = 12;
   let y:u16 = 10;
   if a < b {
      println!("ab小!!");
   }
}

首先我们定义两个变量xyx的类型是i32类型,y的类型是u16类型。i32明显表示的范围要比u16大,那么一个大范围类型的数据和一个小范围的数据类型做比较明显的不合时宜,打个比方我们大人和小孩子做比较,你能把大人变成小孩子吗,明显的不可能,同理可得在编程语言中也是如此,编程只不过对我们人类世界的模仿,那么模仿就要遵循人类社会的一些规则,这就造就了编程语言的语法和规则。因此我们对于大小不同类型的数据类型做比较要有一定的手段和方法,我们都之都我们不可能回到过去但是我们可以畅想未来,假设此时未来在于当下是小范围的,而过去则是大范围的。我们只需要把未来无限叠加就会到跟过去一样的时间维度。那么类型转换也是一样,我们把小数据类型需要转成大数据类型。

使用类型转换需要小心,因为如果执行以下操作 300_i32 as i8,你将获得 44 这个值,而不是 300,因为 i8 类型能表达的的最大值为 2^7 - 1,使用以下代码可以查看 i8 的最大值:

let a = i8::MAX;
println!("{}",a);

下面我们给出常用的类型转换:

fn main() {
   let a = 3.1 as i8;
   let b = 10_i8 as i32;
   let c = 'b' as u8// 将字符'a'转换为整数,98
   println!("{},{},{}", a, b, c);
}

as类型转换中还有一个比较特殊的存在就是内存地址转化为指针,下面我们给出一个例子来说明这种转化类型的具体用法:

fn   mem_address_change_point() {
    let mut values: [i322] = [13];
    let p1: *mut i32 = values.as_mut_ptr();
    let first_address = p1 as usize// 将p1内存地址转换为一个整数
    let second_address = first_address + 4// 4 == std::mem::size_of::<i32>(),i32类型占用4个字节,因此将内存地址 + 4
    let p2 = second_address as *mut i32// 访问该地址指向的下一个整数p2
    unsafe {
        *p2 += 1;
    }
    assert_eq!(values[1], 3);
}

需要注意的是类型转换不具备传递性:就算 e as U1 as U2 是合法的,也不能说明 e as U2 是合法的(e 不能直接转换成 U2)。

TryInto 转换

我们根据上面的as转换得出as转换会导致好精度丢失的问题,可能转换的数据和原来的差距比较的大例如:300_i32 as i8的结果是44是不是和原来的300相差很大啊,那么rust提供了另外一种解决思路就是我们现在讲到的TryInto 转换。先看一个例子:

let valueu32 = 10;
let resultResult<i32, TryFromIntError> = value.try_into();
match result {
    Ok(num) => println!("转换成功: {}", num),
    Err(e) => println!("转换失败: {}", e),
}
/// 打印出:转换成功: 10

TryInto被实现为泛型trait,这意味着它可以用来转换任何实现了TryInto的类型。当你对一个值使用try_into()方法时,它会尝试将该值转换成目标类型。如果转换成功,它返回Ok包含转换后的值;如果失败,则返回Err包含错误信息。

通用类型转换

struct Foo {
    x: u32,
    y: u16,
}

struct Bar {
    a: u32,
    b: u16,
}

fn reinterpret(foo: Foo) -> Bar {
    let Foo { x, y } = foo;
    Bar { a: x, b: y }
}

这段Rust代码定义了两个结构体FooBar,以及一个函数reinterpret,该函数将Foo类型的实例转换为Bar类型的实例。下面是对代码的逐行解释:

  1. 定义了一个名为Foo的结构体,它包含两个字段:

    • x:一个无符号的32位整数(u32)。
    • y:一个无符号的16位整数(u16)。
  2. 定义了一个名为Bar的结构体,它也包含两个字段:

    • a:一个无符号的32位整数(u32)。
    • b:一个无符号的16位整数(u16)。
  3. 定义了一个名为reinterpret的函数,它接受一个Foo类型的参数并返回一个Bar类型的实例:

    • 函数参数foo是一个Foo实例。
    • 使用let Foo { x, y } = foo;解构foo,将fooxy字段分别赋值给局部变量xy
    • 创建一个新的Bar实例,将x赋值给Bara字段,将y赋值给Barb字段。
    • 返回这个新创建的Bar实例。

这个函数的目的是将Foo结构体的字段直接“重新解释”为Bar结构体的字段。这里的“重新解释”实际上只是简单的字段赋值,因为两个结构体的字段类型是相同的。在这种情况下,并没有进行任何底层的位级操作或内存重新解释,仅仅是字段值的复制。

这种转换是安全的,因为FooBar的字段类型完全匹配,且没有涉及到任何可能会导致数据丢失或错误的类型转换。这个函数可以用于不同结构体之间的数据共享,或者在需要将一种类型的数据结构转换为另一种类型但保持数据不变的情况下使用。

扫码_搜索联合传播样式-标准色版.png