Skip to content

Support private casts #9

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

Closed
197g opened this issue Jan 8, 2020 · 10 comments · Fixed by #35
Closed

Support private casts #9

197g opened this issue Jan 8, 2020 · 10 comments · Fixed by #35

Comments

@197g
Copy link

197g commented Jan 8, 2020

This is related but not the same as #3. For many applications the reference cast is wanted in a safe interface but want to assert some invariants on an inner type in the process. It would be very useful to expose a converter that can only be used by the type itself, instead of always generating a trait which by definition can be used by all users.

struct ethernet_frame([u8]);

impl ethernet_frame {
    pub fn new(bytes: &[u8]) -> Option<&Self> {
        if Self::valid(bytes) { // E.g. validate length, encoding, ...
            Some(ethernet_frame::__private_derive_conversion(bytes))
        } else {
            None
        }
    }
}
@Hawk777
Copy link

Hawk777 commented Apr 12, 2021

It looks like rust-lang/rfcs#2529 is postponed. In light of that, would it make sense to implement this feature by generating an inherent impl with a private function in it, rather than a trait implementation? I don’t know anything about the implementation of ref-cast to tell whether that would be practical, but from a user’s perspective, that seems like a very reasonable way to get a function that “outside” code can’t call.

My specific use case for this is making a newtype wrapper around str. For some such types I might want to allow the user of my crate to convert from &str, but with validation, so returning Option<&MyType> or Result<&MyType, Something> rather than just &MyType; my own hand-written conversion function would internally call ref_cast to do the actual magic, but by making ref_cast private, I could rely on every instance of MyType passing the validation requirements.

@197g
Copy link
Author

197g commented Apr 12, 2021

I've since found a way for a private cast by using one extra indirection (and totally forgot about notifying this issue about it).

#[derive(RefCast)]
#[repr(transparent)]
pub struct ethernet_frame(Private);

#[derive(RefCast)]
#[repr(transparent)]
struct Private([u8]);

impl ethernet_frame {
    pub fn new(bytes: &[u8]) -> Option<&Self> {
        if Self::valid(bytes) { // E.g. validate length, encoding, ...
            Some(ethernet_frame::ref_cast(Private::ref_cast(bytes)))
        } else {
            None
        }
    }
}

Since the type Private is not accessible its trait impls aren't either. This effectively hides the [u8] -> ethernet_frame conversion since neither part of the transitive chain [u8] -> Private, Private -> ethernet_frame is accessible. While the second portion is visible in the documentation it is not possible to construct the value &Private so it can not be called outside the module.

@Hawk777
Copy link

Hawk777 commented Apr 12, 2021

That was my first idea too, but it doesn’t work for me:

error[E0446]: private type `Private` in public interface
 --> src/main.rs:3:10
  |
3 | #[derive(RefCast)]
  |          ^^^^^^^ can't leak private type
...
9 | struct Private([u8]);
  | --------------------- `Private` declared as private
  |
  = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info)

How did you make it work?

@197g
Copy link
Author

197g commented Apr 12, 2021

Ah, yes, it needs to be pseudo-private, which is a bit of a hack. The type itself is qualified pub but it's defined in a private module.

use ref_cast::RefCast;

#[derive(RefCast)]
#[repr(transparent)]
pub struct ethernet_frame(inner::Private);

mod inner {
    use ref_cast::RefCast;

    #[derive(RefCast)]
    #[repr(transparent)]
    pub struct Private([u8]);
}

impl ethernet_frame {
    pub fn new(bytes: &[u8]) -> Option<&Self> {
        if Self::valid(bytes) { // E.g. validate length, encoding, ...
            Some(ethernet_frame::ref_cast(inner::Private::ref_cast(bytes)))
        } else {
            None
        }
    }

    pub fn valid(bytes: &[u8]) -> bool { todo!() }
}

@Hawk777
Copy link

Hawk777 commented Apr 12, 2021

Oh that’s perfect, thank you! Weird that this technique allows working around the private-leakage error, but I’ll take it.

@197g 197g closed this as completed Apr 12, 2021
@CAD97
Copy link

CAD97 commented Apr 16, 2021

FWIW, there's a second (opt-in) warning, unreachable_pub, which warns for any unqualified-pub type which is not exposed (transitively) to downstream crates. (It's still not perfect and not everyone agrees with using pub(crate) everywhere that isn't downstram-visible, so it's likely going to be opt-in for a good long while yet, if not forever.) However, this pattern of unnamable types is fairly common for advanced safety-enforcement tricks (pseudo-sealed traits, for one) is fairly common.

@WaffleLapkin
Copy link

Since the type Private is not accessible its trait impls aren't either.

@HeroicKatora please note that it's not true. You can access type impls without being able to name type (you only need compiler to infer it):

ethernet_frame::ref_cast(<_>::ref_cast(&[] as _));
ethernet_frame::ref_cast(RefCast::ref_cast(&[] as _));

playground

@197g
Copy link
Author

197g commented Apr 16, 2021

@WaffleLapkin Ohhh, that's right. Hm, I guess you could create a fake second impl such that these aren't uniquely deducable?

@WaffleLapkin
Copy link

@HeroicKatora I don't think that's possible with ref_cast since it uses associated type, so you can't impl RefCast twice with different From types

@197g 197g reopened this Apr 16, 2021
@GREsau
Copy link

GREsau commented Aug 7, 2022

would it make sense to implement this feature by generating an inherent impl with a private function in it, rather than a trait implementation?

I think that would be perfectly do-able by introducing a new attribute macro, which could be implemented in the same crate as the existing derive macro (and would mostly share the implementation). I imagine it would look like:

[ref_cast]
[repr(transparent)]
struct EthernetFrame([u8]);

which could expand to something like:

[repr(transparent)]
struct EthernetFrame([u8]);

impl EthernetFrame{
  fn ref_cast(from: &[u8]) -> &Self {
    // same implementation as RefCast::ref_cast derived trait implementation
  }
  fn ref_cast_mut(from: &mut [u8]) -> &mut Self {
    // same implementation as RefCast::ref_cast_mut derived trait implementation
  }
}

It could allow overriding the visibility of the generated functions, e.g. [ref_cast(visibility = pub(crate))] to produce pub(crate) fn ref_cast(...), but maybe that would be too much unneeded complexity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
5 participants