Proposal: integer conversion methods

A pain point I’ve always had with Rust is the lack of methods to convert between integer types. Yes there’s as, but as can fail when downcasting. Yes there’s TryFrom/TryInto, but try_from and try_into aren’t very descriptive since they don’t include the output type in the name and this makes code harder to read.

Can we just add the following inherent methods to the integer types?:

impl u8 {
    fn to_u16(self) -> u16;
    fn to_u32(self) -> u32;
    fn to_u64(self) -> u64;
    fn to_u128(self) -> u128;
    fn to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn to_i16(self) -> i16;
    fn to_i32(self) -> i32;
    fn to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn to_isize(self) -> isize;
}

impl u16 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn to_u32(self) -> u32;
    fn to_u64(self) -> u64;
    fn to_u128(self) -> u128;
    fn to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn to_i32(self) -> i32;
    fn to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn to_isize(self) -> isize;
}

impl u32 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn to_u64(self) -> u64;
    fn to_u128(self) -> u128;
    fn to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn checked_to_i32(self) -> Option<i32>;
    fn saturating_to_i32(self) -> i32;
    fn wrapping_to_i32(self) -> i32;
    fn to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn checked_to_isize(self) -> Option<isize>;
    fn saturating_to_isize(self) -> isize;
    fn wrapping_to_isize(self) -> isize;
}

impl u64 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn to_u128(self) -> u128;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn checked_to_i32(self) -> Option<i32>;
    fn saturating_to_i32(self) -> i32;
    fn wrapping_to_i32(self) -> i32;
    fn checked_to_i64(self) -> Option<i64>;
    fn saturating_to_i64(self) -> i64;
    fn wrapping_to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn checked_to_isize(self) -> Option<isize>;
    fn saturating_to_isize(self) -> isize;
    fn wrapping_to_isize(self) -> isize;
}

impl u128 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn checked_to_i32(self) -> Option<i32>;
    fn saturating_to_i32(self) -> i32;
    fn wrapping_to_i32(self) -> i32;
    fn checked_to_i64(self) -> Option<i64>;
    fn saturating_to_i64(self) -> i64;
    fn wrapping_to_i64(self) -> i64;
    fn checked_to_i128(self) -> Option<i128>;
    fn saturating_to_i128(self) -> i128;
    fn wrapping_to_i128(self) -> i128;
    fn checked_to_isize(self) -> Option<isize>;
    fn saturating_to_isize(self) -> isize;
    fn wrapping_to_isize(self) -> isize;
}

impl usize {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn to_u64(self) -> u64;
    fn to_u128(self) -> u128;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn checked_to_i32(self) -> Option<i32>;
    fn saturating_to_i32(self) -> i32;
    fn wrapping_to_i32(self) -> i32;
    fn checked_to_i64(self) -> Option<i64>;
    fn saturating_to_i64(self) -> i64;
    fn wrapping_to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn checked_to_isize(self) -> Option<isize>;
    fn saturating_to_isize(self) -> isize;
    fn wrapping_to_isize(self) -> isize;
}

impl i8 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
    fn checked_to_u128(self) -> Option<u128>;
    fn saturating_to_u128(self) -> u128;
    fn wrapping_to_u128(self) -> u128;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn to_i16(self) -> i16;
    fn to_i32(self) -> i32;
    fn to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn to_isize(self) -> isize
}

impl i16 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
    fn checked_to_u128(self) -> Option<u128>;
    fn saturating_to_u128(self) -> u128;
    fn wrapping_to_u128(self) -> u128;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn to_i32(self) -> i32;
    fn to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn to_isize(self) -> isize
}

impl i32 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
    fn checked_to_u128(self) -> Option<u128>;
    fn saturating_to_u128(self) -> u128;
    fn wrapping_to_u128(self) -> u128;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn to_i64(self) -> i64;
    fn to_i128(self) -> i128;
    fn to_isize(self) -> isize
}

impl i64 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
    fn checked_to_u128(self) -> Option<u128>;
    fn saturating_to_u128(self) -> u128;
    fn wrapping_to_u128(self) -> u128;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn checked_to_i32(self) -> Option<i32>;
    fn saturating_to_i32(self) -> i32;
    fn wrapping_to_i32(self) -> i32;
    fn to_i128(self) -> i128;
    fn checked_to_isize(self) -> Option<isize>;
    fn saturating_to_isize(self) -> isize;
    fn wrapping_to_isize(self) -> isize;
}

impl i128 {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
    fn checked_to_u128(self) -> Option<u128>;
    fn saturating_to_u128(self) -> u128;
    fn wrapping_to_u128(self) -> u128;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn checked_to_i32(self) -> Option<i32>;
    fn saturating_to_i32(self) -> i32;
    fn wrapping_to_i32(self) -> i32;
    fn checked_to_i64(self) -> Option<i64>;
    fn saturating_to_i64(self) -> i64;
    fn wrapping_to_i64(self) -> i64;
    fn checked_to_isize(self) -> Option<isize>;
    fn saturating_to_isize(self) -> isize;
    fn wrapping_to_isize(self) -> isize;
}

impl isize {
    fn checked_to_u8(self) -> Option<u8>;
    fn saturating_to_u8(self) -> u8;
    fn wrapping_to_u8(self) -> u8;
    fn checked_to_u16(self) -> Option<u16>;
    fn saturating_to_u16(self) -> u16;
    fn wrapping_to_u16(self) -> u16;
    fn checked_to_u32(self) -> Option<u32>;
    fn saturating_to_u32(self) -> u32;
    fn wrapping_to_u32(self) -> u32;
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
    fn checked_to_u128(self) -> Option<u128>;
    fn saturating_to_u128(self) -> u128;
    fn wrapping_to_u128(self) -> u128;
    fn checked_to_usize(self) -> Option<usize>;
    fn saturating_to_usize(self) -> usize;
    fn wrapping_to_usize(self) -> usize;
    fn checked_to_i8(self) -> Option<i8>;
    fn saturating_to_i8(self) -> i8;
    fn wrapping_to_i8(self) -> i8;
    fn checked_to_i16(self) -> Option<i16>;
    fn saturating_to_i16(self) -> i16;
    fn wrapping_to_i16(self) -> i16;
    fn checked_to_i32(self) -> Option<i32>;
    fn saturating_to_i32(self) -> i32;
    fn wrapping_to_i32(self) -> i32;
    fn to_i64(self) -> i64;
    fn to_i128(self) -> i128;
}
3 Likes

The sheer number of methods there (for every pair of types) seems like a case study in why into and try_into work more concisely. You can always choose to explicitly write the output type if you feel it’ll make your code more self-documenting.

13 Likes

It’d be nice if we could more easily type hint .into (that’s generalized type ascription). If you’re worried about clarity, though, just use <uN>::from(<expr>).

1 Like

How is .into() significantly more concise than .to_u16()? .into::<u16>() is less concise, uglier, and more annoying to type than .to_u16().

I think it's bad form to use from/into when you know the concrete types. Methods should have descriptive names that say what the code is doing. Just about any method could be replaced with from/into if we wanted, eg. we could add an impl TryFrom<SocketAddr> for TcpStream then deprecate TcpStream::connect since it's unnecessary. We wouldn't do that since connect is so much clearer than from, however I've seen actual code that uses .into().into().into() due to lack of explicit methods and it's just painful to decipher. {Try,}{From,Into} should be restricted to when you don't know the concrete types and so you're forced to use some generic, general-purpose trait, otherwise you should use something less ambiguous.

Also TryFrom doesn't give you the checked/saturating/wrapping distinction that I proposed and which is sometimes useful.

I could, but methods would often be nicer.

3 Likes

The only downside I can see to adding these is that it adds to the documentation. But the int types already have lots of methods that someone exploring the documentation needs to scroll through.

How is .into() significantly more concise than .to_u16()? .into::<u16>() is less concise, uglier, and more annoying to type than .to_u16().

.into() is more concise, though, and works well if you're in a context where you already know you need a u16.

I think it's bad form to use from/into when you know the concrete types.

I don't.

I think this is a great idea. I read into as “turn A into B, I don’t care how”… but when I’m casting integers, I do care how. The wrapping, checked, and saturated variants are things I really want to state explicitly in my code. There is no single obvious way to cast between different number types (I regularly have to go look up Rust’s casting behavior, because I can never remember what it is).

Moreover, into is obnoxious for inference reasons: arithmetic is one place where I don’t trust inference to figure out the correct type.

4 Likes

n.to_u16() is redundant when we already have u16::from(n). Both convey the same information. The difference is using From leans on a trait, which to me is more idiomatic, and permits things like using generics or associated types with bounds for convertible types, something I just used the other day as it were for this specific case.

Furthermore:

u16::from(n)

...already has impls for: bool, u8, NonZeroU16

u16::try_from(n)

...already has impls for: i8, i16, i32, i64, i128, isize, u32, u64, u128, usize

What you are proposing is redundant, less idiomatic, and as @josh noted results in an explosion of complexity.

This is a slippery slope argument. The usage of From / TryFrom mentioned here is specifically for the purpose of type conversions, which is exactly what you were originally asking about, exactly as these traits are intended to use, and many of these type conversions are already impl'd for these types.

No one is proposing they be used to things which have side effects or to create network packets. Yes, that would be bad, but it's also irrelevant for this use case.

1 Like

I would've expected this to be covered by From/Into impls between Wrapped<u8> and u16 and all the other combinations, but apparently there are no such impls.

Is there any reason we couldn't add those impls, as an alternative to adding all of OP's inherent methods?

Just a note on potentially relevant prior art: the conv crate (Disclaimer: I wrote it) tried to address this by defining two extra conversion trait pairs: Value{From,Into} (for value-preserving conversions, which is mostly subsumed by current Try{From,Into}), and Approx{From,Into}. The Approx* traits also required the user specify an approximation scheme as a generic parameter (DefaultApprox, Wrapping, plus RoundTo* for float → integer).

All together, that gave you lossy casts (with as), checked casts (with Value*), wrapping casts (with Approx* + Wrapping scheme), and saturating casts (with Value + saturate/unwrap_or_saturate extension methods on the result).

Also as a note, the OP’s impls for usize and isize are not forward-looking, and thus IMO are incomplete, because they do not account for usize == u128, which the RISC-V architecture supports as the logical endpoint of continuing growth in storage size. The missing impls are:

impl usize {
    fn checked_to_u64(self) -> Option<u64>;
    fn saturating_to_u64(self) -> u64;
    fn wrapping_to_u64(self) -> u64;
 }

impl isize {
    fn checked_to_i64(self) -> Option<i64>;
    fn saturating_to_i64(self) -> i64;
    fn wrapping_to_i64(self) -> i64;
 }

and the following potentially-silently-truncating impls, which presume that Rust will never support usize larger than u64, should be removed:

impl usize {
    fn to_u64(self) -> u64;
 }

impl isize {
    fn to_i64(self) -> i64;
}
1 Like

For those who think that this is too many functions, they could all be replaced by two additional traits:

  1. From/Into (existing, lossless conversion)
  2. TryFrom/TryInto (existing, checking conversion)
  3. TruncateFrom/TruncateInto (new, truncating/wrapping conversion)
  4. SaturateFrom/SaturateInto (new, saturating conversion)

Bikeshedding aside, this separates the debate about adding lots of inherent methods from that about having a way to do truncating/saturating conversions without as being a foot gun.

Note: An amusing application for TruncateFrom would be converting from a [u8] into a [u8; N].

3 Likes

It could also be done with three new types, (bikeshed names)

Saturating<T>(pub T);
Wrapping<T>(pub T);
Truncating<T>(pub T);

Then have have impls

From<Saturating<{integer}>> for {integer} { ... }
From<Wrapping<{integer}>> for {integer} { ... }
From<Truncating<{integer}>> for {integer} { ... }

for all pairs of integers, this way we can use the existing From machinery that we have, and have it documented separately.


With this we could also have the normal arithmetic operators setup to safely do the operations with the given protocol.

3 Likes

Open RFC in this area:

I’ve been meaning to try and propose a subset of that to avoid the floating point questions, so just

trait IntegerFrom<T> {
    fn wrapping_from(_: T) -> Self;
    fn saturating_from(_: T) -> Self;
    fn checked_from(_: T) -> Option<Self>;
}
trait IntegerInto<T> {
    ... I bet you know ...
}

Aping the naming of all the inherent methods. (And yes, checked_from is just try_from().ok(), but I’d still have it there to follow the pattern.)

I really want this so that, like we have with from, we can propose clearer-intent method calls instead of as – especially as _.

2 Likes

I don’t mind using my own traits until consensus stabilizes, but I really don’t like using fn names that will differ from those of the eventual standardized trait. Can we get past the bikeshed naming phase and decide on the various fn names now, particular for those fns usable with method syntax?

I’d really like to see more focus on reducing the amount of conversions necessary at all. Most of the time I know that the conversion I’m doing should never fail, but I still have to pick from among a whole bunch of different options. Typically I’m just dealing with small positive integers but still have to go through the motions of converting them between u32, u64, usize and sometimes even i64 all because different functions/libraries have different requirements.

One example where this is really all pointless is when indexing a vector. There’s no reason why I shouldn’t be able to use any unsigned integer type: there already has to be a bounds check, why should I need to convert to usize first?

1 Like

I don’t think we need even more traits. I almost always know the exact integer types I’m dealing with and don’t need the genericness of a trait, I just need a way to convert from one integer type to the other concisely and descriptively.

TryFromLossy in particular just looks waay too vague: “convert something into something else in a way that might lose information and could also fail”. The entire point of traits is to act as bounds on generic types. Why would I ever write <T: TryFromLossy<Foo>> without knowing anything about the nature of the conversion, why it could fail, or the implications of the lossyness? If you’re only ever calling your trait methods with concrete, non-generic types, then there’s no reason whatsoever to be using a trait at all. Your code would be clearer and more flexbile if you turned all the trait impls into inherent impls. In the case of TryFromLossy that clarity and flexibility means having method names that describe the kind of conversion being performed, and being able to have multiple different methods for different kinds of lossy-and-possibly-failing conversions.

Why are people averse to adding more inherent methods?

@bascule

I’m aware I can already perform most these conversions with from and friends. My gripe is that I’m often writing code with lots of fiddly little integer conversions, and compared to .to_u16():

  • u16::from(x) can’t be written as a method call
  • .into() doesn’t say what it’s converting into
  • .into::<u16>() is longer and uglier

I’m aware that most these inherent methods would be technically redundant, but I don’t think that matters and I certainly don’t think it’s more idiomatic to use a trait method where there are no generic types involved.

Me too. Specifically I do cryptography, where I often want different semantics (truncation/overflow/wrapping/checked) for different circumstances.

...but into() can...

...so add an explicit type annotation if that makes you feel better?

...so use a type annotation instead of the turbofish?

As someone who cares deeply about language aesthetics, these seem like extremely minor syntactic nits which do not justify adding 310(!!!) new methods to the language which both duplicate and are incompatible with existing functionality, and invent a separate, parallel, bespoke language of method names instead of modeling the same concepts with types and traits.

Adding a small number of traits and/or trait impls which can interoperate with the existing types like Wrapping would make an awful lot more sense to me.

9 Likes

Note that x.into::<u16>() is not a valid spelling (fn into() itself doesn't have generic parameters so you can't turbofish it). You need to write one of

let y: u16 = x.into();
// or
let y = Into::<u16>::into(x);

If RFC 2522 (type ascription) is accepted this would be written as

let y = x.into(): u16;
// vs.
let y = x.to_u16();
1 Like