Closed xiBread closed 2 years ago
Hello, @xiBread! This is a good question.
A quick summary of Rust references:
&
) references at one time&mut
) reference, no other references can be held.These rules are critical to Rust's safety guarantees.
Neon only gives shared references (immutable, &
) to values in a JsBox
. Why is that? JavaScript and Rust have very different ownership rules. In JavaScript, it's possible to have lots of different aliased mutable references to the same data.
const data = {};
const a = data;
const b = data;
a.a = 1;
b.b = 2;
console.log(data);
// { a: 1, b: 2 }
Writing this code in Rust would be a compile error. Since there could be any number of handles (references) to a JsBox
, it would be unsound to give a mutable reference. Consider the following:
fn example(mut ctx: FunctionContext) -> JsResult<JsUndefined> {
let mut a = cx.argument::<JsBox<Set>>(0)?;
let mut b = cx.argument::<JsBox<Set>>(1)?;
Ok(cx.undefined())
}
const box = addon.new_box();
addon.example(box, box);
In this example, both a
and b
point to the same box! If we could take a mutable reference to each, it would be invalid.
How do we solve this? Rust has a concept called interior mutability. When using interior mutability, borrow checking is deferred to runtime instead of at compile time. The most common example of interior mutability is a Mutex
.
Instead of storing a JsBox<Set>
, store a JsBox<RefCell<Set>>
. A RefCell
performs dynamic borrow checking. It only works for single threaded code, but JavaScript is single threaded! It follows the same Rust borrow checking rules that happen at compile time, except at runtime. Unlimited shared references and only a single exclusive reference.
There is an example of how to use it in the Neon JsBox
docs.
Let me know if this clears things up!
This does clear things up, thank you for the detailed answer!
I should add since you have a wrapper, you could also push the RefCell
internally to keep it as a nice JsBox<Set>
.
struct Set {
set: RefCell<HashSet<String>>,
}
let this = ctx.this().downcast_or_throw::<JsBox<Set>, _>(&mut ctx)?;
this.set.borrow_mut().insert(String::from("Hello, World!"));
One way isn't strictly better than the other. It mostly depends how you would like to structure your code.
I'm still a beginner at Rust, so I'm trying to do something relatively simple and bind some of the
HashSet
methods. However, I'm getting stumped by an error when implementing anadd
function forinsert
.I realize this is most likely a mistake with my code and not the library, but I would appreciate any pointers as to how to fix this. Thanks! 🙂