Lossy conversion trait (`as`)

Could we have a trait over what the as cast does, maybe?

I want to write fn foo<FP: From<f64>+..>(...) -> FP with f32 and f64 implementations, however f32 does not implement From<f64>. I don’t care if there is some loss of precision (actually, this is expected), and want to be able to write f64 constants and use other functions with only f64 implementations.

There are two obvious ways Rust could support this:

  1. impl From<f64> for f32 — which goes against the trend of not allowing lossy conversions, but I think usually this is not a problem since lower precision is expected with f32 and the range is still huge with explicit +/-inf.
  2. A new trait for lossy conversions

Of course I can roll my own trait if this doesn’t deserve a common solution, but may be worth discussing (especially the first suggestion).

num-traits has AsPrimitive for this.

4 Likes

You can also roll your own type that wraps around f32

1 Like

I'm strongly against one that's just "whatever as does", since I find it an unfortunate mess of conversions right now -- some coercions, some extensions, some truncations, ...

I would like a more principled lossy trait, maybe WrappingFrom, but that has traditionally been the domain of num-traits, not core.

5 Likes

To bring this up again from a slightly different perspective, the documentation on the From trait specifies:

Simple and safe type conversions in to Self

and

Note: this trait must not fail. If the conversion can fail, use TryFrom or a dedicated method which returns an Option or a Result<T, E>.

but does not mention anything about the accuracy of the returned result (put another way: i64 → f64 is "safe" but inexact).

Obviously for some uses inexact conversions are not safe, but one does not usually use floating-point types to represent pointers or expect them to always be exact in any case, so this point may not be relevant (at least for target types regarded as "lossy").

So should From support inexact conversions?

I'm not particularly biased either way (though eventually getting some kind of lossy conversion trait would be nice), but we need to decide because currently SIMD types do implement lossy conversions via From, e.g. From<u64x4> for f32x4. (Then again, there also appear to be fallible conversions like From<f32x4> for i8x4, which are definitely not safe.)

1 Like

Perhaps the best answer is that From should not allow lossy conversions, but we should add LossyFrom (which also allows things like LossyFrom<f64> for f32 which may overflow to f32::INFINITY).

(WrappingFrom isn't quite right for loss of precision conversions, as u64 → f64.)

Note that cases like f64 → u64 are still not satisfied, but that's what TryFrom is for.

I completely agree, it is more reminiscent of C conventions than Rust. It would be nice to make as redundant and eventually remove it from the language.

3 Likes

RFC: https://github.com/rust-lang/rfcs/pull/2484

I like the idea of being more explicit about classes of conversion. This RFC enumerates five traits: From, TryFrom, FromLossy, TryFromLossy, and TruncateFrom. The RFC proposes to add FromLossy and TryFromLossy traits to the existing From and the long-awaited but not-yet-stabilized TryFrom traits.

The RFC does not address the fifth listed trait, TruncateFrom, which is the class of conversions I use the most, often to convert from a usize iterator index to a (usually smaller) known-size storage representation (e.g., u8, u32). I've attempted to implement this trait for the various uN -> uM conversions, but haven't yet found a way to do so efficiently other than through the current as conversion.

To me the RFC will feel incomplete until it also addresses this very common lossy conversion of unsigned integers. Thus I'd like to request that those with more understanding propose a way to include this fifth trait in the RFC.

Note: There are other uncommon lossy conversions – iN -> iM truncation and iN sign transmutation – listed in the RFC's Related Problems section. I see no need to address either of those at this time.

@Tom-Phinney are you talking about checked or unchecked conversions to/from usize? https://github.com/rust-lang/rust/issues/49415 addresses checked conversion to and from usize/isize. TruncateFrom could support unchecked conversions to/from usize I suppose even though many of the conversions (e.g. usizeu64) are not actually truncations (on current platforms).

I have to ask, what is the use-case? E.g. if it is serialisation of program data involving usize counters then probably checked conversions are more appropriate.

I was not talking about conversions from usize, either checked or unchecked; truncation from u64 to u32 is probably my most frequent use case.

However, it’s easy for nominally-truncating conversions from usize to occur when processing modest-length slices in a loop, e.g., where the loop’s usize slice index of an element of interest has statically-known high-order zero bits truncated so that the index can be stored as a u8, u16, or u32 in a field of a different replicated data structure.

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