pointer-utils
pointer-utils copied to clipboard
`slice-dst`: Provide example for custom 'slices' using `SliceWithHeader`
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?
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.
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.
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.
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?
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.
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).
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 :sweat_smile: