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() {
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();
}
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);
}