[Solved] Channel in a loop in a thread: borrowed value does not live long enough

I would like to ask for help with another one of these errors.

It seems I have to avoid a reference being borrowed, and I have seen a number of good ideas but I am out of tricks.

Below this is a minimal example with the same structure as the project code, and produces the same error.

I found out that match &sender.send in the thread has to take a reference, because if sender is owned, the next iteration of the loop will not have access to it.

And the closure in thread::spawn(move || loop { ... }) has to be move, otherwise closure may outlive the current function, but it borrows server_sender_arc, which is owned by the current function.

I asked this on Stackoverflow as well, where I received some comments:

The whole point of Rc and Arc is that you can pass around clones of these wrappers by value without having to clone the data underneath. They perform a similar level of indirection that a reference performs. So why are you trying to pass around a reference to an Arc ? - turbulencetoo

@turbulencetoo I added the surrounding closure webview_handle.dispatch(move |webview| { ... } , which complains that the server_sender_arc doesn't implement Copy , which I could only get around by passing it as a reference. โ€“ Gambhiro

For context, the basic steps (in the project) are:

  • thread: start a HTTP server which does things
  • build a webview window with channels between it and the server
  • thread: start a loop to receive messages from the webview and send them to the server
  • run the webview window (blocking on main thread until exits)
// main.rs
extern crate web_view;

use web_view::Content;

use std::sync::{Arc, Mutex, mpsc};
use std::thread::{self, sleep};
use std::time::Duration;
use std::error::Error;

fn main() {

    // Channel to pass messages from the server to the webview window.
    let (_webview_sender, webview_receiver) = mpsc::channel();

    // Channel to pass messages from the webview to the server.
    let (server_sender, _server_receiver) = mpsc::channel();

    // wrap the channel so that it can be sent across threads
    let server_sender_arc = Arc::new(Mutex::new(server_sender));
    let c = server_sender_arc.clone();

    start_webview(webview_receiver, c).unwrap();

}

fn start_webview(webview_receiver: mpsc::Receiver<String>,
                 server_sender_arc: Arc<Mutex<mpsc::Sender<String>>>)
-> Result<(), Box<Error>>
{

    // Build a webview window and keep a handle before running
    struct UserData {
        webview_receiver: mpsc::Receiver<String>,
    };

    {

        let webview = web_view::builder()
            .title("title")
            .content(Content::Url("http://localhost:8080/".to_owned()))
            .user_data(UserData {
                webview_receiver: webview_receiver,
            })
        .invoke_handler(|_webview, _arg| Ok(()))
            .build().unwrap();

        // https://docs.rs/web-view/0.4.0/web_view/struct.Handle.html
        let webview_handle = webview.handle();

        // Start the thread to receive messages from the webview window, and pass
        // them back to a server process.

        thread::spawn(move || loop {
            {
                let res = webview_handle.dispatch(move |webview| {

/*
[rustc] use of moved value: `server_sender_arc`

value moved into closure here, in previous iteration of loop

note: move occurs because `server_sender_arc` ... does not implement the `Copy` trait
*/

                    let sender = server_sender_arc.lock().expect("Can't lock server sender.");

                    let UserData {
                        webview_receiver,
                        //server_sender,
                    } = webview.user_data();

                    match webview_receiver.try_recv() {
                        Ok(text) => {
                            match text.as_ref() {
                                "FileOpen" => {
                                    match &sender.send("tell me about it...".to_owned()) {
                                        Ok(_) => {},
                                        Err(e) => println!("๐Ÿ”ฅ Can't send on user_data.server_sender: {:?}", e),
                                    };
                                },

                                _ => {},
                            };
                        }
                        Err(_) => {},
                    }

                    Ok(())
                });

                match res {
                    Ok(_) => {},
                    Err(e) => println!("๐Ÿ”ฅ webview_handle.dispatch() {:?}", e),
                }
            }

            sleep(Duration::from_millis(1000));
        });

        // This will block until the window is closed.
        webview.run()?;
    }

    Ok(())
}
# Cargo.toml

[dependencies]
web-view = "0.4"

When you put something in Arc for cheap, Send clones, you send the clones by value. Don't pass references across threads, you need to pass owned values.

Clone the sender, and send the clone.

...
   start_webview(webview_receiver, c).unwrap();
}

fn start_webview(webview_receiver: mpsc::Receiver<String>,
                 server_sender_arc: Arc<Mutex<mpsc::Sender<String>>>)
-> Result<(), Box<Error>>
{

You might find this recent article helpul: Rust &TheMachine. Machine-Centric Guide to the Why ofโ€ฆ | by Prolific K | Medium
In particular, it has some good concrete advice about passing references across threads and why it's not a good idea.

Thanks! In that case I'm back to another problem, namely how to avoid the ownership problem in the loop.

I updated the sample, and the situation is that now in the part below, the Arc...Mutex...Channel thing doesn't implement Copy.

Do you have any tips for this?

use of moved value: `server_sender_arc`

value moved into closure here, in previous iteration of loop
        thread::spawn(move || loop {
            {
                let res = webview_handle.dispatch(move |webview| {

                    let sender = server_sender_arc.lock().expect("Can't lock server sender.");

Does your closure inside the loop need to be move too? If so you might need to clone sender outside it, each time around the loop (so that each registered callback has a reference-counted sender handle). I'm guessing there are several of them potentially queued up at once, and this is why you have the mutex as well? (I'm pretty sure that's not needed)

Yes, if it is not move, i.e.

let res = webview_handle.dispatch(|webview| { ... })

Then

[rustc] closure may outlive the current function, but it borrows `server_sender_arc`, which is owned by the current function

may outlive borrowed value `server_sender_arc`

help: to force the closure to take ownership of `server_sender_arc` (and any other referenced variables), use the `move` keyword: `move |webview|`

You need to

let server_sender_arc = server_sender_arc.clone();

before the move closure:

        thread::spawn(move || loop {
            {
                let server_sender_arc = server_sender_arc.clone();
                let res = webview_handle.dispatch(move |webview| {

                    let sender = server_sender_arc.lock().expect("Can't lock server sender.");

@Yandros Fantastic! Compiles successfully.

Many thanks for taking a look.

Do you have any tips for reading material so that I can understand this better?

Somehow visualizing the mental picture of the whole closure - arc - borrow - thread thing was unclear to me.

It added [solved] to the topic title, is that the right thing to do?

Hmm, your example was more difficult than the standard one since you had to use two levels of closures.

Since both are given to other threads, there are lifetime issues involved (see this post of mine explaining why), so you need a runtime mechanism to guarantee that the referee only gets dropped when there are no (owning) references left, which is what Arc does.

Whenever some x: T is "mentioned" in a closure that uses it by shared reference (e.g., printing it, or in your example, .lock()ing it), the closure will use by default &x: &T whenever you "mention" it, which requires that the closure do not outlive the local frame of x (Rust will raise an error when that can happen, such as with threads).

The solution, to avoid feeding &x: &T to the closure, is to feed it "a magic reference that artificially keeps the (heap-allocated) referee alive" a Arc<T>.

For that to happen, you need to use a move closure to force take ownership of the captures (i.e., prevent having &_ in the captures). Note that up until this point the key is about using a move closure, Arc is not needed yet.

Where Arc<T> shines is to solve the issue you had: after giving a variable to a move closure, you cannot use it again (unless it is Copy).

The solution, which Arc was designed for, is to (cheaply) .clone() it so that what the closure captures is a "clone" of your local.

  • The cloning is guaranteed to be "cheap" since all that Arc-cloning does is incrementing a counter, which is the way to "artifically keep the (heap-allocated) referee alive".

Back to your example and the two levels of closures, since you did not use server_sender_arc outside the first move closure, you did not need to .clone() it. But then, within that outer closure, you did need to use it multiple times (c.f. the loop). Hence the cloning.

It shadows the local var for ergonomics, but only withing the scope of the loop, so all is fine: it may have been more beginner friendly if the clone had been declared with a different name, and if I had used that name in the inner closure.

You could mark my previous answer as the solving one (symbol below any post).

Thanks for walking through the process.

Quite the mental exercise, some people do Sudoku, others solve borrow puzzles!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.