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

slice-dst: Provide example for custom 'slices' using SliceWithHeader #69

Open
MattesWhite opened this issue Apr 7, 2021 · 7 comments
Open
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers

Comments

@MattesWhite
Copy link

Thank you for this awesome crate. I always respect when people dive into the unsafe world of Rust.

I'd like to use the slice-dst crate to build something similar to String and str. Let's assume:

struct MyString(Box<SliceWithHeader<Info, u8>>);

To mimicry str I want to implement Borrow<MyStr> for MyString and ToOwned for MyStr (type Owned = MyString). However, I have a problem defining MyStr. I could use a type alias:

type MyStr = SliceWithHeader<Info, u8>;

But this is inconvenient and won't allow some custom impl MyStr. What I would like is to use is a new-type-pattern. But this makes it difficult implementing Borrow:

struct MyStr(SliceWithHeader<Info, u8>);

impl Borrow<MyStr> for MyString {
    fn borrow(&self) -> &MyStr {
        &*self.0 // <-- requires some cast/convertion/idk
    }
}

Would it be safe to simple cast the reference from the Box within MyString to a &MyStr or would this be undefined behavior?

@CAD97
Copy link
Owner

CAD97 commented Apr 8, 2021

If you don't mind third parties being able to perform the cast safely, the ref-cast crate automates the required bits to cast from &SliceWithHeader<Info, u8> to &MyStr. If you need to make the cast private, for now you have to do it yourself with a #[repr(transparent)] and an unsafe pointer reinterpreting (&*(self.0 as *const _ as *const _)).

It's still on my todo list to make a #[derive(SliceDst)] that'd allow you to just write something like

#[derive(SliceDst)]
#[repr(C)]
#[slice_dst(new(Info, &[u8]))]
struct MyStr(Info, [u8]);

and get a custom type like SliceWithHeader, but I've just not found the time to do it yet.

@CAD97 CAD97 added the documentation Improvements or additions to documentation label Apr 8, 2021
@CAD97
Copy link
Owner

CAD97 commented Apr 8, 2021

Is there something specific I can call out in the documentation to help direct people towards the correct practice for this? It's pretty much standard requirement for wrappers around unsized types to know how/when you can go from &T to &U, so (as someone who has that as baseline knowledge) I don't know where best to put that callout.

@MattesWhite
Copy link
Author

Thank you. That helped a lot. Pulling in a third-party crate to ensure soundness seems feasible to me.
Personally, I hoped to find a well documented example in the project's repository.

@MattesWhite
Copy link
Author

To go a little bit further and to stay with the MyString example, if I want to have a mutable buffer like type like String I have to manage things like capacity and reallocation of heap by myself, right?

@CAD97
Copy link
Owner

CAD97 commented Apr 8, 2021

You would need to track the initialized prefix and handle reallocation. Actually, implementing our own Slice/SliceBuf pair would be an interesting case study for the docs... 🤔💭

Unfortunately I don't really have the time (or the clear freedom) to write up such a walkthrough / book-format material right now, but I'd be happy to mentor/guide/edit/review someone else doing so.

@CAD97 CAD97 added the good first issue Good for newcomers label Apr 8, 2021
@CAD97
Copy link
Owner

CAD97 commented Apr 8, 2021

The basic formula:

Store Box<WithHeader< Header, [MaybeUninit<T>] >>. In Header, track which elements are initialized. When pushing a new element that requires reallocation, make a new Box<...> by copying over the content from the existing one (but with some more MaybeUninit<_> capacity), then swap in the new box and do the regular push.

Capacity is the length of your [MaybeUninit<T>]. Length is stored in your header (or in your stack part, if you want to be like Vec). If you want a thin pointer, store length alongside the slice as well (SliceWithHeader does this) and use Thin (from erasable).

Probably the best format to show off the tools I'm providing would be to write a ThinVec<_> where size_of::<ThinVec<_>>() == size_of::<*mut u8>() but that otherwise functions like a Vec (with a bare minimum of surface API to avoid clogging the example).

@MattesWhite
Copy link
Author

Okay, this looks manageable to me. I think I'll give it a try when I find the time... after I looked into my own university contract 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

2 participants