multithreadinggenericsrusttraitsparametric-polymorphism

How to send generic T to another thread?


How to send generic T?

I try to send a generic T to another thread but I'm getting:

error[E0308]: mismatched types
  --> src/main.rs:23:22
   |
23 |             t1.merge(Element(vec![3]));
   |                      ^^^^^^^^^^^^^^^^ expected associated type, found struct `Element`
   |
   = note: expected associated type `<T as Join>::Item`
                       found struct `Element`
   = help: consider constraining the associated type `<T as Join>::Item` to `Element`

Full code:

trait Join {
    type Item;
    fn merge(&mut self, other: Self::Item);
}

#[derive(Debug, Default)]
struct Element(Vec<u8>);

impl Join for Element {
    type Item = Element;
    fn merge(&mut self, mut other: Self::Item) {
        self.0.append(&mut other.0);
    }
}

fn work<T>()
where
    T: Default + Join + Send + Sync + 'static,
{
    let (sender, receiver) = std::sync::mpsc::channel::<(T)>();
    std::thread::spawn(move || {
        while let (mut t1) = receiver.recv().unwrap() {
            t1.merge(Element(vec![3]));
        }
    });

    loop {
        let mut t1 = T::default();
        sender.send(t1);
        std::thread::sleep(std::time::Duration::from_secs(5));
    }
}

fn main() {
    // works!
    let mut e = Element(vec![1]);
    e.merge(Element(vec![2]));

    // bad!
    work::<Element>();
}

Playground link


Solution

  • When you use generics you let the caller decide which types must be used by your generic function.

    This line in your example t1.merge(Element(vec![3])); is invalid because it assumes T = Element but the caller can chose from infinitely many possible types of T where T != Element which is why the compiler is complaining.

    To make your function fully generic you have to do something like add a Default bound to <T as Join>::Item in the function signature and then change the offending line to t1.merge(<T as Join>::Item::default());.

    Updated working commented example:

    use std::fmt::Debug;
    
    trait Join {
        type Item;
        fn merge(&mut self, other: Self::Item);
    }
    
    #[derive(Debug)]
    struct Element(Vec<u8>);
    
    // updated Default impl so we can observe merges
    impl Default for Element {
        fn default() -> Self {
            Element(vec![1])
        }
    }
    
    impl Join for Element {
        type Item = Element;
        fn merge(&mut self, mut other: Self::Item) {
            self.0.append(&mut other.0);
        }
    }
    
    fn work<T>() -> Result<(), Box<dyn std::error::Error>>
    where
        T: Default + Join + Send + Sync + Debug + 'static,
        <T as Join>::Item: Default, // added Default bound here
    {
        let (sender, receiver) = std::sync::mpsc::channel::<T>();
        std::thread::spawn(move || {
            while let Ok(mut t1) = receiver.recv() {
                // changed this to use Default impl
                t1.merge(<T as Join>::Item::default());
    
                // prints "Element([1, 1])" three times
                println!("{:?}", t1);
            }
        });
    
        let mut iterations = 3;
        loop {
            let t1 = T::default();
            sender.send(t1)?;
            std::thread::sleep(std::time::Duration::from_millis(100));
            iterations -= 1;
            if iterations == 0 {
                break;
            }
        }
    
        Ok(())
    }
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        // works!
        let mut e = Element(vec![1]);
        e.merge(Element(vec![2]));
    
        // now also works!
        work::<Element>()?;
    
        Ok(())
    }
    

    playground