Is it possible to send closures via channels?

后端 未结 2 1165
耶瑟儿~
耶瑟儿~ 2021-02-19 23:37

I would like to send a closure via channels:

use std::thread;
use std::sync::mpsc;

#[derive(Debug)]
struct Test {
    s1: String,
    s2: String,
}

fn main() {         


        
相关标签:
2条回答
  • 2021-02-19 23:59

    The accepted answer doesn't go into detail, but you can send closures to threads via channels, even on stable, if you don't use FnOnce:

    use std::thread;
    use std::sync::mpsc;
    
    struct RawFunc {
        data: Box<Fn() + Send + 'static>,
    }
    
    impl RawFunc {
        fn new<T>(data: T) -> RawFunc
        where
            T: Fn() + Send + 'static,
        {
            return RawFunc {
                data: Box::new(data),
            };
        }
    
        fn invoke(self) {
            (self.data)()
        }
    }
    
    fn main() {
        // Local
        let x = RawFunc::new(move || {
            println!("Hello world");
        });
        x.invoke();
    
        // Via channel
        let (sx, rx) = mpsc::channel::<RawFunc>();
        sx.send(RawFunc::new(move || {
            println!("Hello world 2");
        })).unwrap();
        let output = rx.recv().unwrap();
        output.invoke();
    
        // In a thread
        let guard = thread::spawn(move || {
            let output = rx.recv().unwrap();
            output.invoke();
        });
    
        sx.send(RawFunc::new(move || {
            println!("Hello world 3!");
        })).unwrap();
    
        guard.join().unwrap();
    
        // Passing arbitrary data to a thread
        let (sx, rx) = mpsc::channel::<RawFunc>();
        let guard = thread::spawn(move || {
            let output = rx.recv().unwrap();
            output.invoke();
        });
    
        let foo = String::from("Hello World 4");
        sx.send(RawFunc::new(move || {
            println!("Some moved data: {:?}", foo);
        })).unwrap();
    
        guard.join().unwrap();
    }
    
    0 讨论(0)
  • 2021-02-20 00:05

    Yes. There are a few problems with your code.

    First of all, FnOnce is a trait, so you can't use it directly. Traits have to be either a constraint on a concrete type, or behind an indirection of some kind. Since you're sending the closure to somewhere else, you want something like Box<FnOnce(...)>.

    Secondly, you can't use Box<FnOnce(...)> because, due to object safety rules, you can't actually call a FnOnce through an indirection.

    (As an aside, you also don't want to use FnOnce<...> syntax, which is technically unstable; use FnOnce(...) instead.)

    To solve this, you can either switch to Fn or FnMut or use the not-yet-stable FnBox trait. I've gone down this path on the basis that it probably has the semantics you want, and is likely to be stabilised in the near future. If you're uncomfortable with this, you will need to modify your closure appropriately.

    The following is a joint effort between myself and Manishearth (who pointed out I'd missed the + Send constraint):

    // NOTE: Requires a nightly compiler, as of Rust 1.0.
    
    #![feature(core)]
    use std::boxed::FnBox;
    use std::thread;
    use std::sync::mpsc;
    
    #[derive(Debug)]
    struct Test {
        s1: String,
        s2: String,
    }
    
    type ClosureType = Box<FnBox(&mut Test) + Send>;
    
    fn main() {
        let t = Test { s1: "Hello".to_string(), s2: "Hello".to_string() };
        let (tx, rx) = mpsc::channel::<ClosureType>();
    
        thread::spawn(move || {
            let mut test = t;
            let f = rx.recv().unwrap();
            f.call_box((&mut test,));
            println!("{:?}", test);
        });
    
        tx.send(Box::new(move |t: &mut Test| {
            let s = "test".to_string();
            t.s1 = s;
        })).unwrap();
    
        // To give the output time to show up:
        thread::sleep_ms(100);
    }
    
    0 讨论(0)
提交回复
热议问题