Is this function `call_with_ref` and struct `WeakRef` sound?

Code in playground: Rust Playground

Context: I want to integrate Rhai scripts to my application and modify host data from Rhai scripts. But there is a problem with happens due to combination of 2 factors:

  1. Rhai runtime accepts only owned values that implement Clone and 'static (so it can store it in Scope context that can live arbitrarily long). It doesn't accept non-static references.
  2. My data is stored in special storage and I don't want to move it from it (because it can be expensive).

I created call_with_ref function using inspiration from std::thread::scope and WeakRef struct that serves as runtime mutable reference that tracks lifetime of borrowed reference in runtime so can implement 'static and be given to Rhai runtime.

Do you think this all is sound?

No, this is unsound because you can smuggle the WeakRef and a reference obtained from it outside the closure and then accessed after the closure ends, resulting in overlapping borrows. The is_alive mechanism does not prevent this because the borrow from the WeakRef can be obtained before the closure returns, where is_alive is still true. Rust Playground

To fix this you will have to abort the process if the WeakRef has not been dropped after the closure returned. A panic won't be enough since they can be caught and variables are still dropped. As an alternative, you could provide callback based APIs to WeakRef instead of methods that return references.

1 Like

@SkiFire13 Thank you! I totally missed it.

How about this newer version? Rust Playground

impl<T: 'static> WeakRef<T> {
    pub fn visit_alive(&self, f: impl FnOnce(&T)) {
        unsafe {
            self.is_alive.get().then(|| self.data.as_ref()).map(f);
        }
    }

    pub fn visit_mut_alive(&mut self, f: impl FnOnce(&mut T)) {
        unsafe {
            self.is_alive.get().then(|| self.data.as_mut()).map(f);
        }
    }
}

It seems to work.

error[E0521]: borrowed data escapes outside of closure
  --> src/main.rs:80:34
   |
75 |     let mut data_ref = None;
   |         ------------ `data_ref` declared here, outside of the closure body
...
80 |             .visit_mut_alive(|x| data_ref = Some(x));
   |                               -  ^^^^^^^^^^^^^^^^^^ `x` escapes the closure body here
   |                               |
   |                               `x` is a reference that is only valid in the closure body

For more information about this error, try `rustc --explain E0521`.

I think this should fix it, at least for single-threaded uses.

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.