error[E0521]: borrowed data escapes outside of closure when pass RefCell struct instance into closure

I want to implement function that will let me to send websocket request and return string result. This is my code:

use std::cell::RefCell;
use std::sync::{Arc, Mutex};
use ws::{CloseCode, connect, Message};

pub struct WSQuery;

impl WSQuery {
    pub fn query(&mut self, data: & str) -> &str {
        let result = RefCell::new(WSQueryResult::new());

        connect("ws://127.0.0.1:1234", |out| {
            let result = RefCell::clone(&result);

            if out.send(Message::from(data)).is_err() {
                println!("Websocket couldn't queue an initial message.")
            }

            move |msg: Message| {
                result.borrow_mut().data = msg.as_text().ok();
                out.close(CloseCode::Normal)
            }
        }).unwrap();

        result.borrow().data.unwrap()
    }
}

#[derive(Clone)]
pub struct WSQueryResult<'a> {
    pub data: Option<&'a str>,
}

impl<'a> WSQueryResult<'a> {
    pub fn new() -> Self {
        Self {
            data: None,
        }
    }
}

The issue here is that I am not sure how to pass WSQueryResult reference into closure to modify it internally and then return modified result from outside. I got this error:

16 |             let result = RefCell::clone(&result);
   |                 ------ `result` declared here, outside of the closure body
...
23 |                 result.borrow_mut().data = msg.as_text().ok();
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^-------------^^^^^
   |                 |                          |
   |                 |                          borrow is only valid in the closure body
   |                 reference to `msg` escapes the closure body here

Could somebody help to solve the issue ?

The error is because you're trying to store a borrow of msg into result, but msg will be destroyed when the closure returns. It looks like you might be able to just store the whole Message in your RefCell which should solve that error.

I see another couple potential issues in your code too. This code

let result = RefCell::clone(&result);

is creating a new copy of the RefCell and the data in it. You probably want to wrap your RefCell in an Rc.

You're returning a reference to a string slice from query, but the data is owned by the function, that's not going to work either since the data will be dropped when the function returns.

Are you sure connect is blocking the thread? The fact that you're passing closures makes me suspect that it isn't. If the closure is called asynchronously you're likely going to have other problems. Even if you're able to get the code to compile.

2 Likes

Are you sure connect is blocking the thread? The fact that you're passing closures makes me suspect that it isn't. If the closure is called asynchronously you're likely going to have other problems. Even if you're able to get the code to compile.

according to the docs it should be blocking. I hope I understand it correctly:

This function blocks until the event loop finishes running. Avoid calling this method within another WebSocket handler. If you need to establish a connection from inside of a handler, use the connect method on the Sender.

could you clarify, you mean this ?

let result = Rc::new(RefCell::clone(&result));

probably there another ways exists of how can I modify variable from outside of closure and return it then?

Ah okay knowing what library you're using helps a lot, I missed that it was included in the use statements in your original post. Sorry about that!

I see why it's taking a closure now, connect doesn't return until the websocket connection is closed.

I've made some changes to your code and added comments explaining them.

use std::cell::RefCell;
use std::rc::Rc;
use ws::{connect, CloseCode, Message};

pub struct WSQuery;

impl WSQuery {
    pub fn query(&mut self, data: &str) -> String {
        // Wrap the RefCell in an Rc so it has a stable location in memory.
        let result = Rc::new(RefCell::new(WSQueryResult::new()));

        // Note: `connect` will only return when the connection is completely closed.
        connect("ws://127.0.0.1:1234", |out| {
            // Now we can get a new Rc that still points to the same location and move it into the inner closure.
            let result = result.clone();

            if out.send(Message::from(data)).is_err() {
                println!("Websocket couldn't queue an initial message.")
            }

            move |msg: Message| {
                // Create a new String from the data instead of trying to borrow from the message.
                // We can't borrow from message for longer than the call to 
                // this closure lasts, because message will be dropped when the call returns. 
                // You can either pass the whole message back out via `result`, 
                // or you can copy the data you need out of the message.

                result.borrow_mut().data = Some(msg.into_text().unwrap());
                out.close(CloseCode::Normal)
            }
        })
        .unwrap();

        // Now we can take the string value out of the result, and replace it with None.
        let output = result.borrow_mut().data.take();
        output.unwrap()
    }
}

fn main() {
    println!("{}", WSQuery.query("Hello!"));
}

#[derive(Clone)]
pub struct WSQueryResult {
    pub data: Option<String>,
}

impl WSQueryResult {
    pub fn new() -> Self {
        Self { data: None }
    }
}

1 Like

Seems like result.borrow_mut().data is not correct, for me it works with result.get_mut().data, but I got another 2 errors:

   |
22 |                 result.get_mut().data = Some(msg.into_text().unwrap());
   |                 ^^^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<RefCell<WSQueryResult>>`

and

   |
27 |         let output = result.get_mut().data.take();
   |                      ^^^^^^^^^^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Rc<RefCell<WSQueryResult>>`

This is new code:

use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::rc::Rc;
use ws::{CloseCode, connect, Message};

pub mod query;

pub struct WSQuery;

impl WSQuery {
    pub fn query(&mut self, data: &str) -> String {
        let mut result = Rc::new(RefCell::new(WSQueryResult::new()));

        connect("ws://127.0.0.1:1234", |out| {
            let mut result = Rc::clone(&result);

            if out.send(Message::from(data)).is_err() {
                println!("Websocket couldn't queue an initial message.")
            }

            move |msg: Message| {
                result.get_mut().data = Some(msg.into_text().unwrap());
                out.close(CloseCode::Normal)
            }
        }).unwrap();

        let output = result.get_mut().data.take();
        output.unwrap()
    }
}

#[derive(Clone)]
pub struct WSQueryResult {
    pub data: Option<String>,
}

impl WSQueryResult {
    pub fn new() -> Self {
        Self {
            data: None,
        }
    }
}

Fixed ! Need to use additionally .as_ref():

result.as_ref().borrow_mut().data = Some(msg.into_text().unwrap());

Thanks @semicoleon very much for help !