Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to send closures via channels?

Tags:

rust

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() {
    let t = Test {
        s1: "Hello".to_string(),
        s2: "Hello".to_string(),
    };
    let (tx, rx) = mpsc::channel::<FnOnce(&mut Test)>();
    thread::spawn(move || {
        let mut test = t;
        let f = rx.recv().unwrap();
        f(&mut test);
        println!("{:?}", test);
    });
    tx.send(move |t: &mut Test| {
        let s = "test".to_string();
        t.s1 = s;
    });
}

(playground)

I get a bunch of errors:

error[E0277]: the trait bound `for<'r> std::ops::FnOnce(&'r mut Test): std::marker::Sized` is not satisfied
  --> src/main.rs:15:20
   |
15 |     let (tx, rx) = mpsc::channel::<FnOnce(&mut Test)>();
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `for<'r> std::ops::FnOnce(&'r mut Test)` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `for<'r> std::ops::FnOnce(&'r mut Test)`
   = note: required by `std::sync::mpsc::channel`

error[E0277]: the trait bound `for<'r> std::ops::FnOnce(&'r mut Test): std::marker::Sized` is not satisfied
  --> src/main.rs:15:20
   |
15 |     let (tx, rx) = mpsc::channel::<FnOnce(&mut Test)>();
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `for<'r> std::ops::FnOnce(&'r mut Test)` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `for<'r> std::ops::FnOnce(&'r mut Test)`
   = note: required by `std::sync::mpsc::Sender`

error[E0599]: no method named `recv` found for type `std::sync::mpsc::Receiver<for<'r> std::ops::FnOnce(&'r mut Test)>` in the current scope
  --> src/main.rs:18:20
   |
18 |         let f = rx.recv().unwrap();
   |                    ^^^^
   |
   = note: the method `recv` exists but the following trait bounds were not satisfied:
           `for<'r> std::ops::FnOnce(&'r mut Test) : std::marker::Sized`

error[E0599]: no method named `send` found for type `std::sync::mpsc::Sender<for<'r> std::ops::FnOnce(&'r mut Test)>` in the current scope
  --> src/main.rs:22:8
   |
22 |     tx.send(move |t: &mut Test| {
   |        ^^^^
   |
   = note: the method `send` exists but the following trait bounds were not satisfied:
           `for<'r> std::ops::FnOnce(&'r mut Test) : std::marker::Sized`

It seems that FnOnce is not sendable but I don't understand why.

like image 869
Maik Klein Avatar asked May 31 '15 11:05

Maik Klein


2 Answers

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);
}
like image 84
DK. Avatar answered Sep 28 '22 00:09

DK.


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();
}
like image 37
Doug Avatar answered Sep 28 '22 01:09

Doug