Skip to content

Support arbitrary invariant mappings #1945

Closed
@joshlf

Description

@joshlf

This design is in progress in #1969.

In #1896, we added support for "invariant mappings". For a given I (where I = trait Alignment or I = trait Validity), and for all X: I, a mapping maps X to some Y: I.

The idea behind these mappings is that some operations modify the invariants of a Ptr. Historically, we had to be conservative and return a new Ptr with a single, concrete invariant. For example, Ptr::cast_unsized casts to a Ptr of a different type, and so it must conservatively assume that the resulting validity is Unknown. With mappings, we can encode that, regardless of the types involved, an Initialized validity will always map to an Initialized validity (because a Ptr whose referent bytes are all initialized retains this invariant regardless of what type is has).

However, even invariant mapping is more limited than it could be. In full generality, some operations are valid over multiple possible invariant mappings.

Consider, for example, performing a Ptr transmute from T to MaybeUninit<T>. We can observe that any of the following mappings would be valid:

  • Unknown -> Unknown, AsInitialized, Valid
  • Initialized -> Unknown, Initialized, AsInitialized, Valid
  • AsInitialized -> Unknown, AsInitialized, Valid
  • Valid -> Unknown, AsInitialized, Valid

This suggests that we in fact want to encode a family of mappings. In this case, there are 3 * 4 * 3 * 3 = 108 valid mappings in this family!

Of course we don't actually want to encode 108 mappings. Not only would that be patently ridiculous, it would also be too specific. The real information we need is merely that a given pair of invariants constitutes a valid transition. For example, that it's valid to map Valid to AsInitialized.

Likely the most natural way to encode this is as a trait:

// Implemented for pairs of `(Type, Validity)`. This won't actually work because it doesn't
// support `Type: ?Sized`, but something similar would.
unsafe trait FromValidity<T: ?Sized, V: Validity> {}

unsafe impl<T> FromValidity<T, Valid> for (MaybeUninit<T>, AsInitialized) {}
// ...

This would permit us to support an API like:

impl<'a, T, I> Ptr<'a, T, I>
where
    T: 'a + ?Sized,
    I: Invariants,
{
    pub fn transmute<U, A, V>(
        self,
    ) -> Ptr<'a, U, (I::Aliasing, A, V)>
    where
        U: ?Sized + FromAlignment<T, I::Alignment> + FromValidity<T, I::Validity>
    { ... }

This API as written might be unsound! We also need to make sure to handle bidirectional validity if the aliasing invariant permits writing to the returned Ptr. But the basic idea remains the same, so I haven't bothered to work out the details here.

This is maximally powerful, but will also cause inference ambiguities in many cases, requiring the user to manually specify the desired invariants. It may be useful to retain the more conservative existing notion of a mapping in order to provide reasonable defaults.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions