Closed kjvalencik closed 4 years ago
This RFC has entered the final comment period.
While exploring the Persistent
RFC, an alternate design that requires manual drops was proposed. Manual drops are very simple for single use Persistent
, but can become complicated when Persistent
are cloned.
JsBox
could support an optional finalizer
that is called on the main JavaScript thread prior to the Rust data being dropped. Since in most cases multi-use Persistent
will be owned by a JsBox
, it provides an opportunity to perform a manual drop.
pub trait Finalize {
fn finalize(self, cx: FinalizeContext);
}
impl<T: Send + 'static> JsBox<T> {
pub fn with_finalizer<'a, C>(cx: &mut C, value: T) -> Handle<'a, JsBox<T>>
where
C: Context<'a>,
T: Finalize;
}
An additional JsBox
constructor is added that accepts a T: Finalize
and calls the finalize
method prior to dropping.
Following idiomatic patterns establish elsewhere, Persistent
would implement a Drop
trait that panics to prevent user error.
impl<T> Drop for Persistent<T> {
fn drop(&mut self) {
panic!("Persistent must be manually dropped.");
}
}
An alternative design is to provide the finalize
method as an argument to the constructor instead of as a trait.
impl<T: Send + 'static> JsBox<T> {
pub fn with_finalizer<'a, C>(
cx: &mut C,
value: T,
finalizer: fn(FinalizerContext, T),
) -> Handle<'a, JsBox<T>>
where
C: Context<'a>,
}
The advantage of the trait approach is that it co-locates the finalizer with the data. The disadvantage is that Neon would not prevent a user from defining a trait, but failing to use the with_finalizer
constructor.
In my opinion the trait
approach is preferred and the ergonomics could be improved in and when the Rust specialization RFC is implemented.
struct MyServer {
server: Server,
// Wrapping the `Persistent` in a `ManuallyDrop` provides two advantages:
// 1. Immediately signals that something in this struct needs special attention
// 2. If a manual `drop` is missed, it causes a leak instead of a `panic`
callback: ManuallyDrop<Persistent<JsFunction>>,
}
impl Finalize for MyServer {
fn finalize(self, cx: FinalizeContext) {
self.callback.drop(&mut cx);
}
}
fn create_server(cx: FunctionContext) -> JsResult<JsBox<MyServer>> {
let callback = cx.argument::<JsFunction>(0)?.persistent(&mut cx);
let server = MyServer {
server: Server::new(),
callback: ManuallyDrop::new(callback),
};
Ok(JsBox::with_finalizer(server))
}
JsBox
is a smart pointer to data created in Rust and managed by the V8 garbage collector.JsBox
are a basic building block for higher level APIs like neon classes.Rendered RFC
Work in progress implementation