Generics, Fns and threads as a newbie, am I doing this right?

Hello! I've been playing around with toy code while following the book. I'm a little obsessed with the actor model, so one of the first things I try to do when learning a new language is implement it in some capacity (see for example this monstrosity). Attempting to fly before I can even crawl basically hahah.

I think I might be getting the hang of lifetimes (it's my worst obstacle right now)

Is this idiomatic? Any obvious caveats I might be missing? Furthermore, what would it take to actually get a result out of a message sent to a Worker?

use std::sync::mpsc;
use std::{time, thread};

struct Worker<'a, T> {
    name: &'a str,
    tx: mpsc::Sender<T>
}

impl <'a, T: 'static + Send> Worker<'a, T> {
    fn spawn<U: 'static + Send>(name: &'a str, f: impl Fn(T) -> U + Send + Copy + 'static) -> Worker<T> {
        let (tx, rx) = mpsc::channel();

        thread::spawn(move || {
            loop {
                let msg = rx.recv();
                match msg {
                    // spawn a new thread for the task
                    // do we have to copy f every time we receive a new message?
                    Ok(m) => { thread::spawn(move | | { f(m); }); },
                    _     => break
                }
            }
        });

        Worker { name: name, tx: tx }
    }

    fn send_async(&self, x: T) -> Result<(), mpsc::SendError<T>> {
        self.tx.send(x)
    }
}

fn main() {
    let a1 = Worker::spawn("#worker.1", |x| {
        println!("{}", x);
    });

    // just to get rid of the unused field warning
    println!("worker name is {}", a1.name);

    let _ = a1.send_async("a message!");
    let _ = a1.send_async("another message!");

    let a2 = Worker::spawn("#worker.2", |x: String| {
        println!("{}", x);
        let a3 = Worker::spawn("#worker.2b", |x: String| {
            println!("{}! Holy molly!", x);
        });
        let _ = a3.send_async(x);
    });
    let m = String::from("an owned message?");
    let _ = a2.send_async(m);

    thread::sleep(time::Duration::from_millis(500));
}

Thanks in advance for any feedback!

You don't want any lifetimes in your actors at all. Instead of &str, use owned types like String. To properly share f between all the threads, put it inside an Arc and clone the Arc to share it.

I have a blog post on actors in Rust here.

1 Like

That post answered a lot of my questions, some of which I didn't even know I had, tysm!

When you say I don't want lifetimes in my actors at all, does that include the 'static for T and U or am I misunderstanding? Aren't those necessary for moving stuff in and out of threads?

[...]

impl <T: 'static + Send> Worker<T> {
    fn spawn<U: 'static + Send>(f: impl Fn(T) -> U + Send + Sync + 'static) -> Self {
        let (tx, rx) = mpsc::channel();
        thread::spawn(move || {
            let f_arc = Arc::new(f);
            loop {
                let msg = rx.recv();
                match msg {
                    Ok(m) => {
                        let f_arc = Arc::clone(&f_arc);
                        thread::spawn(move | | { f_arc(m); });
                    },
                    _     => break
                }
            }
        });

        Worker { tx: tx }
    }

    [...]
}

[...]

Ah, no, good point. The T: 'static part is ok. You can think of it as a way to say "T does not involve any lifetimes", so it really is in line with what I said, in a round-about way.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.