Allow use as and try as for From and TryFrom traits

It's not obvious to me that the bt and sbb version is better assembly than the shift-based one. You can do shifts with sarx etc. without touching the carry flags which may be an advantage in some situations.

I don't really care for describing this as perjoratively as "tricky" or involving "hackery." It is idiomatic in its domain. Fortunately in this case num provides a trait for arithmetic shifts, though it would be better if it were in std. (And I personally would have preferred if it had been called "arithmetic" rather than "signed" shift.)

For narrowing or signedness conversions, if I can't write them with as, I still need a standard name for them.

Rust community was strongly against this proposal:

Strongly against your vision of this:

let foo = 1 as Foo + "two" as _ as$ Bar;

I just checked out your RFC - is pretty bad, really.

Only thing I try to propose it shortcut for From and TryFrom traits (last isn't even discussed in your document). So I still think that this idea is not bad, at least.

Really?!

(B) Alternatively allow also to use for non-general Result-Types like Result<SomeType,SomeErrorType> cast implementation of TryFrom Trait:

let foo = 5i32 as Result<u32,TryFromIntError>;

// which desugars into
let foo = u32::try_from(5i32);

Both conversions are lossy: i32 to u32 wraps around for negative values, and u32 to i32 wraps around for values greater than i32::MAX (231–1).

as casts for numbers are a footgun that many people don't fully understand, which is why I wrote RFC 3415 to deprecate as for lossy numeric casts (not as a breaking change, but as a warn-by-default lint).

Note that the existing as_conversions lint has some issues:

  1. it needlessly warns on lossless conversions, like u16 as u32
  2. it needlessly warns on coercions not related to numbers, like &T as &dyn Trait, or &[T; N] as &[T]
  3. it warns on lossy conversions where no alternative exists, like f32 as i32

Instead you can enable the cast_possible_truncation, cast_possible_wrap, cast_precision_loss, and cast_sign_loss lints, which at least don't have first two issues.


As for why I think extending as to the From and TryFrom traits is a bad idea: The as operator already has too many purposes, and adding another would make the language even more complicated and harder to learn. Here's the list of supported type casts and coercions.

1 Like

I think a different word here would be better. They're not lossy, because converting back again perfectly recovers the original, and thus nothing was "lost". u32 as u16 is lossy; BinaryHeap<T>: From<Vec<T>> is lossy; u32 as i32 is lossless. (Indeed, u32 as i32 is a nop at runtime. They're the same type in LLVM, so rustc emits literally nothing for it in codegen.)

I'm no fan of as myself, but it would help to have a better word for it. Ideally something that we can say 1_i16 -> f32 has, since it's still the same numeric conceptual value, even though it changes the bit pattern of the concrete value.

1 Like

I would say these are "value-preserving" conversions. The value after conversion is the same, in a wider type.

1 Like

Speculating heavily: perhaps a .truncate() method, for truncating a wider type to a narrower one. And a to_signed() on u types and to_unsigned() on i types.

I would recommend something that indicates the value will wrap, such as wrapping_to_unsigned. Alternatively, to_bits focuses on representations rather than values.

Or it could be a generic conversion trait: WrappingFrom::<T>::wrapping_from. This would be implemented for u32 to i32 as well as for u32 to u16. Both wrap. Truncating the representation is the same as wrapping the value.

to_unsigned has the same problem as as u32 has -- it silently wraps which may be surprising.

I would expect the nongeneric i32::to_unsigned(self) -> u32, so there's no significant surprise; the method would always just be a transmute. Similar to how *const T as *mut U is broken down into two steps, .cast_mut().cast().

wrapping_from isn't particularly an improvement over as if it includes changing signs, because all integer as casts have wrapping semantics.

The surprising thing is that based on the name alone, there is nothing indicating a transmutation aka wrapping. One might reasonably expect this to panic for out of bounds numbers (or at least in debug mode, just like "+" panics for out of bounds).

2 Likes

I'm not sure why the top line should be considered sugar for the bottom one while being longer, especially since it will be like in the general case too (T::try_from is shorter than as Result<T, ...>, whatever the ... are)

Maybe we should consider something like 5i13 as? u32? Though it would be nicer to have something with a better precedence than as.

4 Likes

Fair. It's possible another name would better convey that it's taking the same bits and interpreting them as the other signedness.

1 Like

I like this! :boom:

let foo = 5i32 as Result<u32,TryFromIntError>;
let bar = 77i32 as? u32;

// which desugars into
let foo = u32::try_from(5i32);
let bar = u32::try_from(77i32)?;
1 Like

17 posts were split to a new topic: Subscripts and sizes should be signed (redux)

It's actually safe because it doesn't do anything that could corrupt memory or anything else.

The returned value is completely usable and valid!

Of course, this increases the number of LOGIC bugs, and the subsequent operation may result in undefined behavior in unsafe code. But that's okay, converting via as is safe in itself, so just be careful with subsequent operations.

Thanks for this phrasing!

I stole it for some new docs that landed in nightly today: https://doc.rust-lang.org/nightly/std/convert/trait.From.html#when-to-implement-from.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.