Skip to main content

Crate indigo_wrap

Crate indigo_wrap 

Source
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. RawCursor could safely set byte_offset to 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
WMutexGuard
Wrap with MutexGuard
WPhantomData
Wrap with PhantomData
WRc
Wrap with Rc
WRef
Wrap with &
WRefCell
Wrap with Ref
WRefCellMut
Wrap with RefMut

Traits§

Wrap
Generic reference type wrappers
WrapMut
Generic exclusive/mutable reference type wrappers
WrapRef
Generic immutable/shared reference type wrappers