Pre-RFC: Safe coercions

This is a pre-RFC for safe coercions.

Safe coercions are implemented via a magic trait Coercible<A, B>. Instances of Coercible<A, B> exist as follows:

  1. Between any two types A and B such that casting via as would be legal AND the two types have the same size, but NOT if one is a floating-point type and the other is not.
  2. Between any two struct, enum, tuple, or slice types A and B, such that:
    • a. Instances of Coercible<C, D> exist for every corresponding member of A and B.
    • b. All fields that have different types in A and B are visible at the location where the coercion is made.
  3. Optionally, other instances may exist, such as allowing an array of integers to be coerced to an array of bytes of the same size.

Coercible<A,B> looks as follows:

use std::mem;
unsafe trait Coercible<A, B> {
    #[inline(always)]
    fn coerce(a: A) -> B {
        unsafe { mem::transmute(a) }
    }
}

Creating instances of Coercible manually is nonsensical and a compile-time error. Alternatively, it may be possible for users to opt-in manually by implementing it. This doesn’t break Rust’s safety guarantees, since Coercible is an unsafe trait.

Because of rule 2b, it is not possible to use Coercible to circumvent privacy restrictions.

The inspiration for this comes entirely from GHC’s Coercible typeclass and matching function coerce, which serve the same purpose as here.

1 Like

That’s an interesting idea. It’d useful for situations where you want to switch out some phantom type deep inside some container (or nested containers).

But I’m not sure the idea of “visibilty-dependent trait implementations” would work without some additional machinery in the language. Trait implementations are global in Rust, much like in Haskell, so it would be odd to have one whose availability is dependent on which module you’re in.

Another concern is that the layout of structs may change after optimization. Currently there is no guarantee that the layout of two structs are the same even if they have identical underlying types and identical ordering.

Maybe it’s best to make Coercible a strictly opt-in trait. The user would opt-in by writing a blank impl along with the appropriate Coercible trait bounds for its interior types. The compiler would then check if the bounds are sane (i.e. do not violate memory safety or existing Coercible constraints), and if it passes the impl will be upheld by the compiler. The impl acts as an optimization constraint: these two types must have the same internal memory representation even after optimization and possible reshuffling.

GHC handles this (visibility-dependent trait implementations) just fine, although instead of visibility (which Haskell doesn’t have) it uses whether or not the corresponding type constructor is in scope.

Can you see a reason why the layout of two structs with identical underlying types and ordering would change after optimization? I can’t. In fact, I would not be surprised if this would break existing code, at least in the case of mem::transmute-ing a phantom type parameter (this trick can be used to implement a form of subtyping I believe).

Not saying it does. It’s just something that has never been actually specified.

Does this include (i32, f32)?

What is the self type here?

Sorry about the bad code – I was thinking in Haskell terms, and Haskell doesn’t have any concept of self. So the current idea is a free function, rather than a method.

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