Expand description
Ever wish you could make a reference type wrapper generic?
struct Foo<W>(W<usize>);
type SharedFoo = Foo<Arc>; // would become `Foo(Arc<usize>)`Ever wish Rust had “mutability generics”?
struct Bar<'a, M>(&'a M usize);
type BarRef<'a> = Bar<'a, ref>; // would become `Bar<'a>(&'a usize)`
type BarMut<'a> = Bar<'a, mut>; // would become `Bar<'a>(&'a mut usize)`Well please keep wishing, because I want those too…
But in the meantime, this crate provides a hack to emulate this behavior in userland!
struct Foo<'a, W: Wrap>(W::Wrap<'a, usize>);
type SharedFoo = Foo<'static, WArc>; // actually becomes `Foo(Arc<usize>)`struct Bar<'a, W: WrapRef>(W::WrapRef<'a, usize>);
type BarRef<'a> = Bar<'a, WRef>; // actually becomes `Bar<'a>(&'a usize)`
type BarMut<'a> = Bar<'a, WMut>; // actually becomes `Bar<'a>(&'a mut usize)`This is a limited form of higher-kinded types, like you find in languages like Haskell.
§Example
Here’s an example of a cursor type like you might find in a simple text editor. It maintains its own state, but whether or not it’s valid is in relation to the string it’s an offset in.
Show code
// Use the weakest trait bound `Wrap` when defining the type to perform wrapping:
struct CursorView<'a, W: Wrap> {
text: W::Wrap<'a, String>,
byte_offset: usize,
}
type RawCursor = CursorView<'static, WPhantomData>;
type Cursor<'a> = CursorView<'a, WRef>;
type CursorEdit<'a> = CursorView<'a, WMut>;
// ...or when you don't need to touch the wrapped type:
impl<W: Wrap> CursorView<'_, W> {
fn reset(&mut self) {
self.byte_offset = 0;
}
}
// Use the stronger trait bound `WrapRef` if you need to read values:
impl<W: WrapRef> CursorView<'_, W> {
fn is_empty(&self) -> bool {
// We can call `String::is_empty` transparently because `WrapRef` implies `Deref`:
self.text.is_empty()
}
}
// Use the strongest trait bound `WrapMut` if you need to mutate values:
impl<W: WrapMut> CursorView<'_, W> {
fn insert(&mut self, text: &str) {
// We can call `String::insert_str` transparently because `WrapMut` implies `DerefMut`:
self.text.insert_str(self.byte_offset, text);
}
}There are certain operations you can perform:
- …without seeing the string (e.g.
RawCursorcould safely setbyte_offsetto 0). - …while holding an immutable reference (e.g. checking whether the string is empty).
- …while holding a mutable reference (e.g. inserting text into the string at the byte offset).
These are captured with the Wrap, WrapRef, and WrapMut traits respectively.
If you write generic impl blocks with trait bounds on the reference type wrapper (e.g.
impl<W: WrapMut> CursorView<'_, W>) rather than hardcoding it (e.g. impl CursorView<'_, WMut>),
you get a kind of subtyping/inheritance thing where the most powerful versions of your type (e.g.
CursorEdit) have all the methods available, but your weaker versions (e.g. Cursor) only get the
subset that apply to their wrapped type.
Enums§
- WArc
- Wrap with
Arc - WBox
- Wrap with
Box - WMut
- Wrap with
&mut - WMutex
Guard - Wrap with
MutexGuard - WPhantom
Data - Wrap with
PhantomData - WRc
- Wrap with
Rc - WRef
- Wrap with
& - WRef
Cell - Wrap with
Ref - WRef
Cell Mut - Wrap with
RefMut