How to handle logic between type parameters of two types?

I have this horrible struct and I would like to reduce the number of type parameters.

pub struct Edge<SU, ST, SL, SV, RU, RT, RL, RV> {
    pub stem: Arc<RwLock<Node<SU, ST, SL, SV>>>,
    pub root: Option<Weak<RwLock<Node<RU, RT, RL, RV>>>>,
    pub meta: Meta,
}

// Here is Node, but I think the focus should be on Edge
pub struct Node<U, T, L, V> {
    pub unit: U,
    pub work: HashMap<T, L>,
    _variance: PhantomData<V>,
}
impl<U, T, L, V> Node<U, T, L, V> 
where ... {
    pub fn react(&mut self, variance: V) {
        self.unit.react(variance);
    }
...

I would love to do something like this:

pub struct Edge<Stem, Root> {
    pub stem: Arc<RwLock<Stem>>,
    pub root: Option<Weak<RwLock<Root>>>,
    pub meta: Meta,
}

However, I think I want methods on Edge that know of type parameters of Stem and Root. Take note of the closure that takes SU from the Stem and it returns an RV to be given to the Root.

impl<SU, ST, SL, SV, RU, RT, RL, RV> Edge<SU, ST, SL, SV, RU, RT, RL, RV>
where
    ...
{
pub fn write<F: FnOnce(&mut SU) -> RV>(&self, write: F) {
      let mut stem = self.stem.write().expect(NO_POISON);
      let variance = write(&mut stem.unit);
      if let Some(weak) = &self.root {
          let arc = weak.upgrade().expect(ROOT);
          let mut root = arc.write().expect(NO_POISON);
          root.react(variance);
      }
  }
...

I imagine a way to do something like this. Take note of the made up syntax to specify a type in Stem and a type in Root (Stem::SU and Root::RV).

impl<Stem, Root> Edge<Stem, Root>
where
    ...
{
pub fn write<F: FnOnce(&mut Stem::SU) -> Root::RV>(&self, write: F) {
      let mut stem = self.stem.write().expect(NO_POISON);
      let variance = write(&mut stem.unit);
      if let Some(weak) = &self.root {
          let arc = weak.upgrade().expect(ROOT);
          let mut root = arc.write().expect(NO_POISON);
          root.react(variance);
      }
  }

Is there a way to specify type parameters of type parameters like in the above snippet? Is there a better way that would avoid the need for the first question? My goal is to organize type parameters better. Please let me know how I can clarify this question further. Any suggestions are appreciated.

More context: I am trying to learn ways to build graphs. I would like to make an Edge type where I can write to some generic content in the stem/child and the root/parent has generic content that can react the write event.

This is real syntax! You just need to make an appropriate trait with associated types.

trait Nodelike {
    type U;
    type T;
    type L;
    type V;
}

impl<S, R> Edge<S, R>
where
    S: Nodelike,
    R: Nodelike,
{
    pub fn write<F: FnOnce(&mut S::U) -> R::V>(&self, write: F) {
        let mut stem = self.stem.write().expect(NO_POISON);
        let variance = write(&mut stem.unit);
        if let Some(weak) = &self.root {
            let arc = weak.upgrade().expect(ROOT);
            let mut root = arc.write().expect(NO_POISON);
            root.react(variance);
        }
    }
}

You might want to move some functions from Node to the Nodelike trait.

Edit: actually one trait seems closer to what you want.

3 Likes

Awesome! Thank you so much! I tried the syntax but was missing the trait with "type" keywords like that. I've seen them but never understand exactly how they work.

1 Like