Reputation: 1618
I'm just starting out learning Rust, and I've hit a bit of a roadblock;
I'm trying to create a function that initialises the rusty_v8
library. They've given the following code get set up with:
use rusty_v8 as v8;
let platform = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
let isolate = &mut v8::Isolate::new(Default::default());
let scope = &mut v8::HandleScope::new(isolate);
let context = v8::Context::new(scope);
let scope = &mut v8::ContextScope::new(scope, context);
let code = v8::String::new(scope, "'Hello' + ' World!'").unwrap();
println!("javascript code: {}", code.to_rust_string_lossy(scope));
let script = v8::Script::compile(scope, code, None).unwrap();
let result = script.run(scope).unwrap();
let result = result.to_string(scope).unwrap();
println!("result: {}", result.to_rust_string_lossy(scope));
Now, I've set myself the challenge of making this reusable. I want to be able to call an init
function of some sort, which returns a v8::Scope
object, that I can use to execute v8::Script
objects. I've managed to create this function:
pub(crate) fn init<'a>() -> v8::ContextScope<'a, v8::HandleScope<'a, v8::Context>> {
let platform = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
let mut isolate = v8::Isolate::new(Default::default());
let mut scope = v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(&mut scope);
return v8::ContextScope::new(&mut scope, context);
}
So far, I understand how the code should work and why it doesn't. The compiler says for return statement: returns a value referencing data owned by the current function
. This makes sense to me, the isolate
and scope
variables are created within this function. But if I want to use this function as a factory, in other words, to make this function construct a ContextScope
object, the isolate
and scope
objects have to be kept alive. How would I achieve this?
Upvotes: 0
Views: 461
Reputation: 43773
The default rules of Rust do not allow you to write the function you want to write.
Whenever you see a type which has a lifetime in it, like HandleScope<'s>
, you should understand this to mean that instances of this type are temporary (with the usual exception for the lifetime being 'static, which does not apply here) and will usually exist within the scope of a stack frame. In order to return such a type, a function must be passed an instance of whatever it borrows.
The intention of the library in this case is that you should follow stack frames: HandleScope
's documentation says "A stack-allocated class that governs a number of local handles." The exact wording is nonsense — you're free to move a HandleScope
into a Box
and thereby make it heap-allocated — but evidently they expect you to use it in a stack-oriented fashion.
The simplest way to do this is to modify your function to accept a function to run within the scope:
pub(crate) fn init<F, R>(f: F) -> R
where
for<'a> F: FnOnce(v8::ContextScope<'a, v8::HandleScope<'a, v8::Context>>) -> R,
{
let platform = v8::new_default_platform().unwrap();
v8::V8::initialize_platform(platform);
v8::V8::initialize();
let mut isolate = v8::Isolate::new(Default::default());
let mut scope = v8::HandleScope::new(&mut isolate);
let context = v8::Context::new(&mut scope);
let cscope = v8::ContextScope::new(&mut scope, context);
f(cscope)
}
(Note: This seems like it will not compile because you have overlapping borrows of scope
. I'm not familiar with rusty_v8
so I don't know why their example looks like that and whether there's an error or not.)
The more complex approach is to construct a “self-referential struct” which can contain all of the objects needed, while they refer to each other. This is unsafe if done directly (since the struct could be moved) but it can be managed using the ouroboros
crate which provides the necessary mechanism in a memory-safe fashion. This still imposes the constraint that you have to refer to the data from within a closure, but you can do it repeatedly rather than only once.
However, it is likely that you should make your factory narrower: stick to returning a v8::Isolate
, and make the rest part of your "eval" rather than "init" procedures. That seems likely to be closer to the intended use of the library.
Upvotes: 1