Skip to content

Scratchpad on PropertyMaps

Lukas Kalbertodt edited this page Jun 11, 2018 · 5 revisions

Ways to access elements

Access is in the form fn get($self, handle: Handle) -> $ret where $self is one of self, &self and &mut self and $ret one of P, &P and &mut P (where the references have the same lifetime as $self references!). This leaves us with 9 possibilities in theory:

Impossible

  • self -> &P
    • Not possible: since self then lives in the stack of the function, we cannot return references to self.
  • self -> &mut P
    • Not possible: see above.
  • &self -> &mut P
    • This is interior mutability. It's usually not implementable like this (because then the caller could make the mut ref live forever without the collection knowing about it). The only way I can imagine implementing this is by allowing only a "one-shot borrow". Where the first call succeeds and all of the following panic. Not very useful.

Not useful

  • self -> P
    • As we are talking about collections, consuming self doesn't really make a whole lot of sense. It's like Vec<T> -> T... useful in only a few rare situations.
  • &mut self -> &P
    • If &mut self is required to create a &P, this means that there is some non-semantic mutability at play. Like ... caching or something. In those cases, usually interior mutability should be used. So we can ignore this (as do many of the std traits, like Index).

Useful

  • &self -> P
    • Useful e.g. for mappings where from one value a new one is created. This new one doesn't live anywhere in the collection.
  • &self -> &P
    • Very useful, like Index
  • &mut self -> &mut P
    • Very useful, like IndexMut

Questionable

  • &mut self -> P

The Plan

Without GATs, it's not very useful to have a &self -> P trait, because such a trait would be unrelated to a &self -> &P trait. With GATs we would like to have something like this:

trait PropMap<H: Handle> {
    type Property<'a>;
    fn get(&self) -> Self::Property;
}

trait PropIndex<H: Handle>
    : Index<H> + for<'a> PropMap<H, Property = &'a <Self as Index<H>>::Output> 
{}

Meaning: PropMap is a super trait of PropIndex. This in turn means that many functions can bound their maps with PropMap (because they don't care if they get a reference or an owned value).

But again, without GATs, it's not really nice to implement this. It can be done something like this, but this spills lifetimes everywhere.

So for now, we will only use the two trait analogous to ops::Index and ops::IndexMut. This makes a few pattern impossible (like a nice Mapping which doesn't just use subreferences), but it's a sensible start for now and works for most cases.

Handle

Two possibilities:

  1. The handle kind becomes an input type for the trait. Then types could implement PropMap<EdgeHandle> and PropMap<FaceHandle>. Problem: using the trait as parameter might be more difficult: impl PropMap<impl FaceHandle> or something like that?
  2. The handle kind becomes an output type for the trait. That way, a specific type can only be indexed by one specific handle type. That would have more restrictions, but one could work around this like mesh.face_props().

The "handle kind" should probably be generic over the HandleIndex used. So something that can be indexed by FaceHandle<u32> should also be indexable by FaceHandle<u16> and FaceHandle<u64>. This looks like one could use the family pattern here. However, this requires GATs.

Don't use different handle indices until we have GAT?

This could make the API way easier when we can't use GATs. And using indices other than u32 is an edge case anyway.

Nice design

trait Props<H: Handle> {
    type Output;
}

Potential names

  • PropMap
  • Props
  • PropStore