如何在数值类型之间安全地、习惯地进行转换?

173
注:此问题来自 Rust 1.0 之前的版本,并引用了一些在 Rust 1.0 中不存在的项目。答案仍包含有价值的信息。
什么是将(例如)usize 转换为 u32 的惯用方式?
例如,使用“4294967295us as u32”进行强制转换可以工作,Rust 0.12参考文档中关于类型转换的内容如下:
数字值可以转换为任何数字类型。原始指针值可以转换为任何整数类型或原始指针类型。任何其他转换都不受支持并将无法编译。
但是,“4294967296us as u32”将会悄悄地溢出并给出0的结果。
我发现了ToPrimitiveFromPrimitive,它们提供了很好的函数,比如to_u32() -> Option<u32>,但是它们被标记为不稳定的:

#[unstable(feature = "core", reason = "trait is likely to be removed")]

转换数字(和指针)类型的惯用(且安全)方法是什么?

平台相关的 isize / usize 大小是我提出这个问题的原因之一 - 最初的场景是我想将 u32 转换为 usize,以便在 Vec<u32> 中表示树(例如,let t = Vec![0u32, 0u32, 1u32],然后获取节点2的祖父节点将是 t[t[2us] as usize]),并且我想知道如果 usize 小于32位会发生什么错误。


3
使用 isize / usize 时要小心 - 它们可以表示的数字范围取决于平台你正在编译的!你的示例可能更好地使用 u64 表达。 - Shepmaster
1个回答

191

转换数值

从一个完全适合另一个类型的类型中转换

在这里没有问题。使用From特性明确表示没有发生任何损失:

fn example(v: i8) -> i32 {
    i32::from(v) // or v.into()
}

你可以选择使用as,但是建议在不需要时避免使用它(见下文):

fn example(v: i8) -> i32 {
    v as i32
}

当一个类型无法完全适应另一个类型

并没有一种通用的方法 - 您正在询问如何将两个物体放入一个原本只能容纳一个的空间中。一个很好的初始尝试是使用 Option — 当值适合时使用 Some ,否则使用 None。然后,根据您的需求,可以使程序失败或替换为默认值。

自Rust 1.34以来,您可以使用TryFrom

use std::convert::TryFrom;

fn example(v: i32) -> Option<i8> {
    i8::try_from(v).ok()
}

在那之前,你得自己编写类似的代码:

fn example(v: i32) -> Option<i8> {
    if v > std::i8::MAX as i32 {
        None
    } else {
        Some(v as i8)
    }
}

可能完全适配另一个类型的范围

isize/usize 可以表示的数字范围基于编译平台而有所不同。无论当前使用的平台是什么,您都需要使用 TryFrom

另请参阅:

关于 as 的作用

但是,4294967296us as u32 会静默溢出,并得到结果为 0

当转换成较小类型时,as 只取数字的低位,忽略高位(包括符号):

fn main() {
    let a: u16 = 0x1234;
    let b: u8 = a as u8;
    println!("0x{:04x}, 0x{:02x}", a, b); // 0x1234, 0x34

    let a: i16 = -257;
    let b: u8 = a as u8;
    println!("0x{:02x}, 0x{:02x}", a, b); // 0xfeff, 0xff
}

另见:

关于 ToPrimitive / FromPrimitive

RFC 369,Num Reform,指出

理想情况下[...]应该删除 ToPrimitive [...] ,并采用一种更有原则的方式来处理 C 类型的枚举

与此同时,这些 trait 存在于 num crate中:


关于如何在一个空间中放入两个东西的问题:确切地说,因此返回 Option<u32> 的 API 看起来就是我想要的 - 难道没有替代 ToPrimitive 的方法吗?这是我期望的回答类型(尽管你的片段也很有用)。 - Caspar
2
据我所知,@HutchMoore的 as 转换不会引发 panic。只有数学运算(例如加法、减法)可能会导致 panic。虽然通常被认为是调试/发布,但其可以单独控制。请参见如何编译并运行启用溢出检查的 Rust 优化程序 - Shepmaster
使用as关键字可能会出现错误的示例... println!("{}", (-1i32) as u32) 将输出 4294967196,而不会报错。 - sam
我该如何将usize和isize转换为u64?From对我似乎不起作用,还是我太蠢了? - Vít Skalický
@VítSkalický 如何在惯用语中转换u32和usize?. From / Into不能工作,因为usize的大小取决于您正在构建的平台(为什么可以使用as将类型从u64转换为usize,但不能使用From)。 - Shepmaster
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接