google / google/zerocopy

[ptr] Store invariants in the referent type

Open
#2,354 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
2.6k
Forks
179
Avg merge
1d 19h
Merged PRs (30d)
29

Description

In #2334, inspired by https://github.com/google/zerocopy/issues/1866#issuecomment-2654625725, we moved the validity invariant of `Ptr`s into the type position. This move was inspired by the observation that the "set of possible valid referents" has a name in type theory — i.e., a *type*. However, technical limitations prevented us from wholly integrating validity as "real" type. In particular:
- `MaybeUninit` requires `T: Sized`
- `AsInitialized` is possibly not expressible because initialized bytes which are written to initialized-but-invalid positions in `MaybeUninit` are not necessarily preserved through a typed copy (https://github.com/rust-lang/unsafe-code-guidelines/issues/555)
- `Initialized` is not expressible, because initialized bytes written to padding locations in `MaybeUninit` are almost certainly not preserved through typed copy.
- We could perhaps work around this by requiring `KnownLayout` and `repr(C)` recursively on all fields, but this requirement seems onerous. We probably need language support for this case.

#2334 works around this by introducing 'virtual' wrapper types — `Uninit`, `Initialized`, `AsInitialized`, and `Valid` — that occupy the type position of `Ptr` but which are not, themselves, real, instantiated types. This trick lets us revise `Ptr`'s API in such a way that makes it less likely for us to independently and invalidly update `T` and the validity (see #1866).

Now, as we begin to extend `Ptr` to support owned `Box`, `Arc` and the ilk (#1183), we have discovered a modeling challenge between the aliasing mode and alignment. We are considering modeling `Box`, `Arc`, etc., as distinct aliasing modes, but it is unsound for such pointers to be unaligned or to change alignment. This leads to some modeling challenges:
- For example, while going from a `Ptr<_, [u8; 2], (Aligned, Exclusive)>` to `Ptr<_, u16, (Unaligned, Exclusive)>` is sound, going from a `Ptr<_, [u8; 2], (Aligned, Box)>` to `Ptr<_, u16, (Unaligned, Box)>` is not, or at least unusable — it is unsound to even drop the resulting `Ptr`.
- For example, while going from a `Ptr<_, u16, (Aligned, Exclusive)>` to `Ptr<_, [u8; 2], (Aligned, Exclusive)>` to `&mut [u8; 2]` is sound, going from a `Ptr<_, u16, (Aligned, Box)>` to `Ptr<_, [u8; 2], (Aligned, Box)>` to `Box<[u8; 2]>` is not — again, it would be unsound to drop the resulting `Box`.

Once again, we must tread carefully before transitioning between the referent type and an invariant (in this case, alignment). Whereas it was impossible to *really* move the validity into the type position in #2334, that is not the case for alignment: #1828 provides a prototype implementation of `UnalignUnsized`. Taking this approach *would* require us to require `KnownLayout` on `Ptr`'s referent, but that might have minimal impact on our public API — only affecting APIs which operate on unsized types that do not already have a `KnownLayout` bound. This is a small list, encompassing `FromZeros::zero` and possibly nothing else.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.