Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Design for unsafe fields polyfill #1931

Open
joshlf opened this issue Oct 17, 2024 · 0 comments
Open

Design for unsafe fields polyfill #1931

joshlf opened this issue Oct 17, 2024 · 0 comments

Comments

@joshlf
Copy link
Member

joshlf commented Oct 17, 2024

This design is prototyped in #1929.

Sometimes, struct (or enum or union) fields have safety invariants:

struct EvenUsize {
    // INVARIANT: `n` is even.
    n: usize,
}

However, Rust has no mechanism to ensure that reads from or writes to fields with invariants must happen inside an unsafe block as is required for unsafe functions or to implement unsafe traits. I propose that we can support this behavior outside the language, allowing a user to write:

struct EvenUsize {
    // INVARIANT: `n` is even.
    #[unsafe]
    n: usize,
}

The #[unsafe] attribute (implemented as a proc macro attribute) modifies the type of n to something like Unsafe<usize>, although as we'll see in a moment, it needs to be a tad more complex than that.

Design take 1: Unsafe<T>

Let's start with the basic design, though:

#[repr(transparent)]
pub struct Unsafe<T>(T);

An Unsafe<T> is a type whose constructors and accessors are all unsafe to call. We can't prevent code from moving an Unsafe<T> around, but we can prevent code from doing anything with it. Thus, for example, we might imagine the following constructor for EvenUsize, calling the unsafe Unsafe::new constructor:

impl EvenUsize {
    /// Constructs a new `EvenUsize`.
    ///
    /// Returns `None` if `n` is odd.
    pub fn new(n: usize) -> Option<EvenUsize> {
        if n % 2 != 0 {
            return None;
        }
        // SAFETY: We just confirmed that `n` is even.
        let n = unsafe { Unsafe::new(n) };
        Some(EvenUsize { n })
    }
}

While this gets us a step in the right direction, it has a soundness hole: There is nothing to stop Unsafes from two different types being swapped:

struct OddUsize {
    // INVARIANT: `n` is odd.
    #[unsafe]
    n: usize,
}

Code operating on an EvenUsize and an OddUsize could swap the inner Unsafe<usize>s safely, which is obviously bad!

Design take 2: Unsafe<T, F>

To prevent this from happening, we can instead design Unsafe to also take a parameter which is the type of the field's outer type:

#[repr(transparent)]
pub struct Unsafe<T, F>(PhantomData<T>, F);

The #[unsafe] attribute would then modify the code to something like:

struct OddUsize {
    n: Unsafe<OddUsize, usize>,
}

This prevents swapping fields between types, but it doesn't prevent swapping between fields:

struct Pair {
    // INVARIANT: `m` is even.
    #[unsafe]
    m: usize,

    // INVARIANT: `n` is odd.
    #[unsafe]
    n: usize,
}

m and n have the same type, and so can be swapped without issue.

Design take 3: Unsafe<T, F, NAME>

To prevent this from happening, we can instead design Unsafe to also include the name of the field in its type:

#[repr(transparent)]
pub struct Unsafe<T, F, const NAME: &'static str>(PhantomData<T>, F);

Unfortunately, &str is not supported in const parameters right now, so we'll instead have to use a u64 which is the hash of the name:

#[repr(transparent)]
pub struct Unsafe<T, F, const NAME_HASH: u64>(PhantomData<T>, F);

This is ugly, but the user will never see this code, as it will be generated by the proc macro attribute. The example above would expand to:

struct Pair {
    m: Unsafe<Pair, usize, {hash("m")}>,
    n: Unsafe<Pair, usize, {hash("n")}>,
}

This gets us most of the way there. It's still possible to swap between instances of the same type (e.g., given p: Pair and q: Pair, to swap p.m and q.m). I'm not yet sure how to prevent this from happening, but it's a pretty small hole.

joshlf added a commit that referenced this issue Oct 21, 2024
TODO:
- Use in zerocopy (maybe in a follow-up commit?)
- Fill out `Cargo.toml` with more fields
- Include `build.rs` to enable version detection so we can support
  `as_mut` as a `const fn` on recent toolchains
- Confirm that it's sound to derive `Copy` and `Clone`

Closes #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
TODO:
- Use in zerocopy (maybe in a follow-up commit?)
- Fill out `Cargo.toml` with more fields
- Include `build.rs` to enable version detection so we can support
  `as_mut` as a `const fn` on recent toolchains
- Confirm that it's sound to derive `Copy` and `Clone`

Closes #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
TODO:
- Use in zerocopy (maybe in a follow-up commit?)
- Fill out `Cargo.toml` with more fields
- Include `build.rs` to enable version detection so we can support
  `as_mut` as a `const fn` on recent toolchains
- Confirm that it's sound to derive `Copy` and `Clone`

Closes #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
TODO: Test in CI

Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
github-merge-queue bot pushed a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: If0e198c377137dd941ebd5dc68787766a593e1eb
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: Ib2708e8f233f624bcd1f2ec80b5dae91c7e1db46
github-merge-queue bot pushed a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: Ib2708e8f233f624bcd1f2ec80b5dae91c7e1db46
joshlf added a commit that referenced this issue Oct 21, 2024
Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
github-merge-queue bot pushed a commit that referenced this issue Oct 21, 2024
Release 0.2.0.

Makes progress on #1931

gherrit-pr-id: I7ce0c981ed1f1bc1f4ff85dffef2a74114c6e76d
joshlf added a commit that referenced this issue Oct 22, 2024
Makes progress on #1931

gherrit-pr-id: Icc9b6841e66c961989862ff6fb3b4f5140c54513
joshlf added a commit that referenced this issue Oct 22, 2024
Release 0.2.1.

Makes progress on #1931

gherrit-pr-id: Icc9b6841e66c961989862ff6fb3b4f5140c54513
joshlf added a commit that referenced this issue Oct 22, 2024
Release 0.2.1.

Makes progress on #1931

gherrit-pr-id: Icc9b6841e66c961989862ff6fb3b4f5140c54513
joshlf added a commit that referenced this issue Oct 22, 2024
Release 0.2.1.

Makes progress on #1931

gherrit-pr-id: Icc9b6841e66c961989862ff6fb3b4f5140c54513
github-merge-queue bot pushed a commit that referenced this issue Oct 22, 2024
Release 0.2.1.

Makes progress on #1931

gherrit-pr-id: Icc9b6841e66c961989862ff6fb3b4f5140c54513
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant