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

Support private impls of zerocopy traits #1855

Open
joshlf opened this issue Oct 8, 2024 · 2 comments
Open

Support private impls of zerocopy traits #1855

joshlf opened this issue Oct 8, 2024 · 2 comments

Comments

@joshlf
Copy link
Member

joshlf commented Oct 8, 2024

Many users ask for the ability to derive a zerocopy trait on their public type without allowing its methods to be called by users outside of the defining module - in other words, for the impl itself to be "private".

Rust doesn't support this natively, but we might be able to emulate it.


Some notes from a conversation with @kupiakos and @jswrenn discussion how to accomplish this.

#[derive(FromBytes)]
#[zerocopy(private)]
struct Foo { ... }
pub unsafe trait FromBytes<Scope = ()> {
    
}

// my crate:

struct Private;

#[derive(FromBytes)]
#[zerocopy(FromBytes(scope = Private))]

// can still access in another crate with a private scope, breaking
// this entirely:
fn transmute<Scope, T: FromBytes<Scope>>(x: &[u8]) -> &T {
    ...
}
#[derive(FromBytes)]
#[derive(...)]
struct FooInner {
    x: i32,
    y: i32,
}

#[repr(transparent)]
struct Foo(FooInner);

transmute!(ref_to_foo_inner) -> &Foo

// assert size and align of Foo equals FooInner?

transmute!(ref_to_foo_inner) -> &Bar

&Foo: TransmuteFrom<&FooInner, Assume::SAFETY>

impl Foo {
    fn from_inner(inner: &FooInner) -> &Foo {
        // SAFETY: We're only assuming safety, and we're the authors
        // of this abstraction, and we know that...
       unsafe { TransmuteFrom::<_, Assume::SAFETY>::transmute(inner) }
    }
}
// Input
#[derive(FromBytes)]
#[zerocopy(private)]
pub struct Foo(...);

// Converted into:
#[derive(FromBytes)]
struct FooInner(...);

#[repr(transparent)]
pub struct Foo(FooInner);

impl Foo {
    
}
// Input
#[derive(FromBytes)]
#[zerocopy(private)]
pub struct Foo(...);

// Converted into:
struct Foo(...);

// impls FromBytes on FooTransmute
#[repr(transparent)]
struct FooTransmute(Foo);

impl FooTransmute {
    fn inner_ref(&self) -> &Foo {
        
    }
}

#[repr(transparent)]
pub struct Foo(FooInner);

impl Foo {
    fn from_bytes(x: &[u8]) -> &Foo {
        &FooTransmute::ref_from(x).0
    }
}
@kupiakos
Copy link
Contributor

kupiakos commented Oct 9, 2024

This can also supplant #1330, since the wrapper type can serve as the utility base type for constructing a safe fn parse(buf: &mut &'a [u8]) -> Result<&'a Self> on a custom DST by first reading as the wrapper type with a zero tail length and performing any needed validation/length calculation from there.

Then TryFromBytes can remain focused solely on safety invariants.

@kupiakos
Copy link
Contributor

kupiakos commented Oct 10, 2024

The approach I suggested to create an external wrapper type could also be done as a derive macro, which could be less confusing to users as derive macros are incapable of modifying the input item, while also giving more control:

Input:

#[derive(Debug, zerocopy::Immutable, zerocopy::IntoBytes, zerocopy::Wrapper)]
#[zerocopy_wrapper(
    // Name defaults to e.g. `${struct}ZerocopyWrapper`
    name = "FooWrapper",

    // No scope lookup needed; these are known derive names.
    // Support for non-zerocopy derives may be unsupported.
    derive(FromBytes, KnownLayout, IntoBytes, Immutable),

    // Default visibility of pub(self)
    vis = pub(crate),

    // Default visibility of pub(self)
    inner_vis = pub
)]
#[repr(C)]
pub struct Foo {
    x: i32,
    num_elems: u32,
    values: [u32],
}

Generates:

#[repr(C)]
pub struct Foo {
    x: i32,
    num_elems: u32,
    values: [u32],
}

// `Debug` impl generated for `Foo`
// `Immutable` impl generated for `Foo`.
// `IntoBytes` impl generated for `Foo`.

#[repr(transparent)]
pub(crate) struct FooWrapper(pub Foo);

// `FromBytes` impl generated for `FooWrapper` but checking against the fields of `Foo`.
// `FromZeros` impl generated for `FooWrapper` but checking against the fields of `Foo`.
// `Immutable` impl generated for `FooWrapper` but checking against the fields of `Foo`.
// `IntoBytes` impl generated for `FooWrapper` but checking against the fields of `Foo`.
// `KnownLayout` impl generated for `FooWrapper` but checking against the fields of `Foo`.

Then, using this wrapper, I can write a custom validator for a DST, and there I can write any custom logic I'd like, such as extracting a length and validating it matches the length provided, or extracting a custom length based on the header:

impl<'a> TryFrom<&'a [u32]> for &'a Foo {
    type Error = Error;
    fn try_from(data: &'a [u32]) -> Result<&'a Foo, Error> {
        let out = &FooWrapper::ref_from_bytes(data.as_bytes())?.0;
        if out.num_elems != out.values.len() {
            return Err(Error::BadHeaderLen);
        }
        Ok(out)
    }
}

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

2 participants