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

Size Specialization #2149

Open
jswrenn opened this issue Dec 11, 2024 · 0 comments
Open

Size Specialization #2149

jswrenn opened this issue Dec 11, 2024 · 0 comments

Comments

@jswrenn
Copy link
Collaborator

jswrenn commented Dec 11, 2024

#1828 implements initial support for an UnalignUnsized<T> wrapper, that drop its value by moving it to an aligned place, then running its destructor. For derivations of KnownLayout on default repr types (which are always sized), this achieved by stack allocating the well-aligned place. For derivations of KnownLayout on repr(C) types (which may or may not be sized), this is achieved by heap allocation.

These conditions are less precise than we'd wish. Rather than depending on the syntax T, we'd like the drop behavior to depend on the actual sizedness of T. I.e., if T is sized, a place for it it can be stack allocated; otherwise, heap allocated.

Single Trait Approach

Unfortunately, it is impossible to straight-forwardly extend KnownLayout with this capability; e.g., this attempt induces solver cycles on implementations of KnownLayout for wrapper types (playground):

use std::cell::UnsafeCell;
use std::mem::MaybeUninit;

// Helps ensure that the correct metadata kind is used and
// can hold methods whose impl differs on `T`'s (un)sizedness.
trait Metadata<T: ?Sized> {}
impl<T> Metadata<T> for () {}
impl<T: ?Sized> Metadata<T> for usize {}

// A trait encoding layout facts about `Self`.
trait KnownLayout {
    // What metadata do pointers to `Self` have?
    type Metadata: Metadata<Self>;

    // A type with a layout like `Self`, but allowing uninit
    // bytes in all positions.
    type MaybeUninit: ?Sized + KnownLayout<Metadata = Self::Metadata>;
}

// A working-as-intended impl of `KnownLayout`:
impl<T> KnownLayout for [T] {
    type Metadata = usize;
    type MaybeUninit = [MaybeUninit<T>];
}

// Is there anything we can do to get this impl to compile?
impl<T: ?Sized + KnownLayout> KnownLayout for UnsafeCell<T> {
    type Metadata = <T as KnownLayout>::Metadata;

    type MaybeUninit = UnsafeCell<<T as KnownLayout>::MaybeUninit>;
}

Layered Approach

To break the cycles, we must introduce an additional trait layer. In the below approach, derive(KnownLayout) actually emits an impl for KnownLayoutInternal, while KnownLayout (now backed by a blanket impl bounded by MetadataCoherent):

use std::cell::UnsafeCell;
use std::mem::MaybeUninit;

pub trait Metadata {}
impl Metadata for () {}
impl Metadata for usize {}

// This trait is the one implemented by `derive(KnownLayout)`
#[doc(hidden)]
pub trait KnownLayoutInternal {
    // What metadata do pointers to `Self` have?
    type Metadata: Metadata;

    // A type with a layout like `Self`, but allowing uninit
    // bytes in all positions.
    type MaybeUninit: ?Sized + KnownLayoutInternal<Metadata = Self::Metadata>;
}

#[doc(hidden)]
pub trait MetadataCoherent<T: ?Sized>: Metadata {}

impl<T> MetadataCoherent<T> for usize
where
    T: ?Sized + KnownLayoutInternal<Metadata = Self>
{}

impl<T> MetadataCoherent<T> for ()
where
    T: KnownLayoutInternal<Metadata = Self>
{}

// This trait is the one that appears as a bound in public APIs.
pub trait KnownLayout: KnownLayoutInternal
where
    Self::Metadata: MetadataCoherent<Self>
{}

impl<T: ?Sized + KnownLayoutInternal> KnownLayout for T
where
    Self::Metadata: MetadataCoherent<Self>
{}

// A working-as-intended impl of `KnownLayoutInternal`:
impl<T> KnownLayoutInternal for [T] {
    type Metadata = usize;
    type MaybeUninit = [MaybeUninit<T>];
}

// And this now compiles, too!
impl<T: ?Sized + KnownLayoutInternal> KnownLayoutInternal for UnsafeCell<T>
{
    type Metadata = <T as KnownLayoutInternal>::Metadata;

    type MaybeUninit = UnsafeCell<<T as KnownLayoutInternal>::MaybeUninit>;
}

Unfortunately, the

where
    Self::Metadata: MetadataCoherent<Self>

on KnownLayout is not implied by KnownLayout!

If you define a function with an argument bounded by KnownLayout; e.g.:

fn foo<T: KnownLayout>() {}

...rustc produces this error:

error[E0277]: the trait bound `<T as KnownLayoutInternal>::Metadata: MetadataCoherent<T>` is not satisfied
  --> src/lib.rs:1:11
   |
1  | fn foo<T: KnownLayout>() {}
   |           ^^^^^^^^^^^ the trait `MetadataCoherent<T>` is not implemented for `<T as KnownLayoutInternal>::Metadata`
   |
note: required by a bound in `KnownLayout`
  --> src/lib.rs:37:21
   |
35 | pub trait KnownLayout: KnownLayoutInternal
   |           ----------- required by a bound in this trait
36 | where
37 |     Self::Metadata: MetadataCoherent<Self>
   |                     ^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `KnownLayout`
help: consider further restricting the associated type
   |
1  | fn foo<T: KnownLayout>() where <T as KnownLayoutInternal>::Metadata: MetadataCoherent<T> {}
   |                          +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
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