How do I convert a usize to a u32 using TryFrom?

2020-02-07 01:29发布

问题:

I want to convert a usize typed variable into a u32 typed variable in Rust. I am aware that the usize variable might contain a value larger than 2^32, and in that case the conversion should fail. I am trying to use the TryFrom trait to perform the conversion.

This is a simple example (Nightly Rust, Playground):

#![feature(try_from)]
use std::convert::TryFrom;

fn main() {
    let a: usize = 0x100;
    let res = u32::try_from(a);
    println!("res = {:?}", res);
}

The code doesn't compile, with the following compilation error:

error[E0277]: the trait bound `u32: std::convert::From<usize>` is not satisfied
 --> src/main.rs:6:15
  |
6 |     let res = u32::try_from(a);
  |               ^^^^^^^^^^^^^ the trait `std::convert::From<usize>` is not implemented for `u32`
  |
  = help: the following implementations were found:
            <u32 as std::convert::From<std::net::Ipv4Addr>>
            <u32 as std::convert::From<u8>>
            <u32 as std::convert::From<char>>
            <u32 as std::convert::From<u16>>
  = note: required because of the requirements on the impl of `std::convert::TryFrom<usize>` for `u32`

I deduce from the compilation error that having TryFrom<usize> for u32 is dependent on having From<usize> for u32, which seems somewhat strange to me.

Is there any other way I could utilize TryFrom to convert from usize to u32? If not, is there any other idiomatic way to perform this conversion?

I know that I can use the as keyword, but it doesn't notify me if something went wrong with the conversion. In addition, I think that I can write my own function that does the conversion, but I would be surprised if Rust doesn't have some idiomatic way to do this conversion. usize and u32 are two basic types, after all.

回答1:

Since this answer was created, it was decided to have the implementation of TryFrom<usize> always allow for the possibility of failure, regardless of the current platform. The original code now compiles successfully in Rust 1.34.

Original answer

having TryFrom<usize> for u32 is dependent on having From<usize> for u32, which seems somewhat strange to me

This is because there's a blanket implementation of TryFrom for anything that implements From:

impl<T, U> TryFrom<U> for T
where
    T: From<U>,
{
    type Error = !;
}

As you mentioned, since Rust supports platforms where the native integer length is 16, 32, or 64 bits, having such an implementation of From / Into would not be lossless on some of these platforms.

This error occurs because there's no direct implementation of TryFrom / TryInto for these types. This is because users of these traits prefer that the implementations be infallible when platform-appropriate (The type Error = !).

There is a separate tracking issue 49415 specifically for deciding this issue.

I think that I can write my own function that does the conversion

Yes, that is what you should do. Something like this untested piece of code:

use std::u32;

struct SomeError;

// usize is a u16 or u32, which always fits in a u32
#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))]
fn my_thing(a: usize) -> Result<u32, SomeError> {
    Ok(a as u32)
}

// usize is a u64, which might be too big
#[cfg(target_pointer_width = "64")]
fn my_thing(a: usize) -> Result<u32, SomeError> {
    if a > u32::MAX as usize {
        Err(SomeError)
    } else {
        Ok(a as u32)
    }
}

I would be surprised if Rust doesn't have some idiomatic way to do this conversion. usize and u32 are two basic types, after all.

The problem is that usize isn't really a "basic" type because it changes size depending on the target platform. Getting this correct, performant and ergonomic is not easy.



标签: rust