phil
phil

Reputation: 1437

What's the best way to pass an output `io::Write` to a function that uses threads?

I try to write a function that takes an io::Write as an argument in order to output binary data. The tricky part is now, that the function internally uses a thread to produce that data (I know in the following code the usage of a thread does not make sense - it's there for demonstration).

Currently, I've different approaches:

1

fn process<W>(mut w: W) where W: io::Write + Send + Sync + 'static {
    thread::spawn(move || {
        write!(w, "hello world").unwrap();
    }).join().unwrap();
}

fn main() {
    let output = Vec::new();
    process(output);
}

The problem here is that output cannot be used after the call of process since it moved in there.

2

fn process<W>(w: &mut W) where W: io::Write + Send + Sync + 'static {
    thread::spawn(move || {
        write!(w, "hello world").unwrap();
    }).join().unwrap();
}

fn main() {
    let output = Vec::new();
    process(&mut output);
}

This is my preferred signature of process, but does not compile because the lifetime of w seem to be shorter (for the compiler) than the lifetime of the thread.

3

fn process(w: Arc<Mutex<io::Write + Send>>) {
    thread::spawn(move || {
        let mut w = w.lock().unwrap();
        write!(w, "hello world").unwrap();
    }).join().unwrap();
}

fn main() {
    let buffer = Arc::new(Mutex::new(Vec::new() as Vec<u8>));
    process(buffer.clone());
}

This works, but the signature of process exposes the internal implementation detail that a thread is used. The kind of design I would like to avoid, since in later versions the thread might disappear.

Does someone has a better solution? Many thanks in advance.

Upvotes: 2

Views: 105

Answers (2)

A.B.
A.B.

Reputation: 16670

You can pass ownership of W to the function, then to the thread, then back to the calling scope.

fn process<W>(mut w: W) -> W where W: io::Write + Send + 'static {
    thread::spawn(move || {
        write!(w, "hello world").unwrap();
        w
    }).join().unwrap()
}

fn main() {
    let output = Vec::new();
    let output = process(output);
    let output = process(output);
}

The Sync constraint is unnecessary so I removed it.

Upvotes: 3

Vladimir Matveev
Vladimir Matveev

Reputation: 128151

Depending on the architecture of your actual program, you may want to use crossbeam:

extern crate crossbeam;

use std::io;

fn process<W>(w: &mut W) where W: io::Write + Send {
    crossbeam::scope(|scope| {
        scope.spawn(|| {
            write!(w, "hello world").unwrap();
        });
    });
}

fn main() {
    let mut buffer = Vec::new();
    process(&mut buffer);
}

With crossbeam, it is possible to spawn threads which may hold to non-static references. It is guaranteed that these threads will outilve the captured references because all threads are implicitly joined when crossbeam::scope() function exits. Therefore, with crossbeam it is possible both to omit 'static and Sync bounds on W and to use &mut W reference in the thread closure.

Upvotes: 2

Related Questions