如何安全且惯用地在数字类型之间进行转换? [英] How do I convert between numeric types safely and idiomatically?
问题描述
编者注:此问题来自 Rust 1.0 之前的版本,并引用了 Rust 1.0 中不存在的一些项目.答案仍然包含有价值的信息.
从(比如)usize
转换为 u32
的惯用方法是什么?
例如,使用 4294967295us as u32
进行转换,并且 关于类型转换的 Rust 0.12 参考文档 说
数值可以转换为任何数值类型.可以将原始指针值转换为任何整数类型或原始指针类型或从任何整数类型或原始指针类型转换.任何其他类型的转换均不受支持且无法编译.
但是 4294967296us as u32
会悄悄溢出并给出 0 的结果.
我找到了 ToPrimitive
和 FromPrimitive
提供了很好的功能,比如 to_u32() ->选项
,但它们被标记为不稳定:
#[unstable(feature = "core", reason = "trait 可能会被移除")]
在数字(和指针)类型之间转换的惯用(和安全)方式是什么?
isize
/usize
的平台相关大小是我问这个问题的一个原因 - 最初的场景是我想从 u32 转换
到 usize
以便我可以在 Vec
中表示一棵树(例如 let t = Vec![0u32, 0u32, 1u32]
,然后获取节点 2 的祖父节点将是 t[t[2us] as usize]
),我想知道如果 usize
它将如何失败小于 32 位.
转换值
来自完全适合另一种类型的类型
这里没有问题.使用 From
trait 是明确表示没有损失发生:
fn 示例(v: i8) ->i32 {i32::from(v)//或 v.into()}
您可以选择使用 as
,但建议您在不需要时避免使用它(见下文):
fn 示例(v: i8) ->i32 {v 作为 i32}
来自一种不完全适合另一种的类型
没有一种方法具有普遍意义 - 您正在询问如何在一个空间中放置两件东西.一个很好的初始尝试是使用 Option
— Some
当值适合时使用 None
否则.然后,您可以根据需要使程序失败或替换默认值.
从 Rust 1.34 开始,您可以使用 TryFrom
:
使用 std::convert::TryFrom;fn 示例(v: i32) ->选项{i8::try_from(v).ok()}
在此之前,您必须自己编写类似的代码:
fn 示例(v: i32) ->选项{如果 v >std::i8::MAX 作为 i32 {没有任何} 别的 {一些(v as i8)}}
as
做什么
<块引用>但是 4294967296us as u32
会悄悄溢出并给出 0 的结果
当转换为更小的类型时,as
只取数字的低位,不考虑高位,包括符号:
fn main() {让一个:u16 = 0x1234;让 b: u8 = a as u8;println!("0x{:04x}, 0x{:02x}", a, b);//0x1234, 0x34让 a: i16 = -257;让 b: u8 = a as u8;println!("0x{:02x}, 0x{:02x}", a, b);//0xfeff, 0xff}
另见:
关于ToPrimitive
/FromPrimitive
<块引用>理想情况下 [...] ToPrimitive
[...] 都将被删除,以支持更原则的方式来处理类似 C 的枚举
与此同时,这些特征仍然存在于num crate中:
Editor's note: This question is from a version of Rust prior to 1.0 and references some items that are not present in Rust 1.0. The answers still contain valuable information.
What's the idiomatic way to convert from (say) a usize
to a u32
?
For example, casting using 4294967295us as u32
works and the Rust 0.12 reference docs on type casting say
A numeric value can be cast to any numeric type. A raw pointer value can be cast to or from any integral type or raw pointer type. Any other cast is unsupported and will fail to compile.
but 4294967296us as u32
will silently overflow and give a result of 0.
I found ToPrimitive
and FromPrimitive
which provide nice functions like to_u32() -> Option<u32>
, but they're marked as unstable:
#[unstable(feature = "core", reason = "trait is likely to be removed")]
What's the idiomatic (and safe) way to convert between numeric (and pointer) types?
The platform-dependent size of isize
/ usize
is one reason why I'm asking this question - the original scenario was I wanted to convert from u32
to usize
so I could represent a tree in a Vec<u32>
(e.g. let t = Vec![0u32, 0u32, 1u32]
, then to get the grand-parent of node 2 would be t[t[2us] as usize]
), and I wondered how it would fail if usize
was less than 32 bits.
Converting values
From a type that fits completely within another
There's no problem here. Use the From
trait to be explicit that there's no loss occurring:
fn example(v: i8) -> i32 {
i32::from(v) // or v.into()
}
You could choose to use as
, but it's recommended to avoid it when you don't need it (see below):
fn example(v: i8) -> i32 {
v as i32
}
From a type that doesn't fit completely in another
There isn't a single method that makes general sense - you are asking how to fit two things in a space meant for one. One good initial attempt is to use an Option
— Some
when the value fits and None
otherwise. You can then fail your program or substitute a default value, depending on your needs.
Since Rust 1.34, you can use TryFrom
:
use std::convert::TryFrom;
fn example(v: i32) -> Option<i8> {
i8::try_from(v).ok()
}
Before that, you'd have to write similar code yourself:
fn example(v: i32) -> Option<i8> {
if v > std::i8::MAX as i32 {
None
} else {
Some(v as i8)
}
}
What as
does
but
4294967296us as u32
will silently overflow and give a result of 0
When converting to a smaller type, as
just takes the lower bits of the number, disregarding the upper bits, including the sign:
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
}
See also:
About ToPrimitive
/ FromPrimitive
Ideally [...]
ToPrimitive
[...] would all be removed in favor of a more principled way of working with C-like enums
In the meantime, these traits live on in the num crate:
这篇关于如何安全且惯用地在数字类型之间进行转换?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!