问题描述
我刚刚开始学习 Rust,遇到了一些障碍;
我正在尝试创建一个初始化 rusty_v8
库的函数。他们提供了以下代码进行设置:
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));
现在,我为自己设定了使其可重复使用的挑战。我希望能够调用某种类型的 init
函数,它返回一个 v8::Scope
对象,我可以用它来执行 v8::Script
对象。我设法创建了这个函数:
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);
}
到目前为止,我了解代码应该如何工作以及为什么不应该。编译器说返回语句:returns a value referencing data owned by the current function
。这对我来说很有意义,isolate
和 scope
变量是在此函数中创建的。但是,如果我想将此函数用作工厂,换句话说,要使此函数构造一个 ContextScope
对象,则必须使 isolate
和 scope
对象保持活动状态。我将如何实现这一目标?
解决方法
Rust 的默认规则不允许你写你想写的函数。
每当你看到一个有生命周期的类型,比如 HandleScope<'s>
,你应该理解这意味着这种类型的实例是临时的(生命周期的通常例外是“静态的,这里不适用)并且通常存在于堆栈帧的范围内。为了返回这样的类型,一个函数必须传递一个它借用的任何东西的实例。
在这种情况下,库的意图是您应该遵循堆栈帧:HandleScope
的文档说“一个管理多个本地句柄的堆栈分配类”。确切的措辞是无稽之谈——你可以自由地将 HandleScope
移动到 Box
中,从而使其被堆分配——但显然他们希望你在一个面向堆栈的时尚。
最简单的方法是修改你的函数以接受一个在作用域内运行的函数:
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)
}
(注意:这似乎不会编译,因为你有 scope
的重叠借用。我不熟悉 rusty_v8
所以我不知道为什么他们的例子看起来像那样,以及是否有错误。)
更复杂的方法是构造一个“自引用结构”,它可以包含所有需要的对象,同时它们相互引用。如果直接完成,这是不安全的(因为可以移动结构),但可以使用 ouroboros
板条箱进行管理,该板条箱以内存安全的方式提供必要的机制。这仍然强加了必须从闭包中引用数据的约束,但您可以重复执行此操作,而不仅仅是一次。
但是,您可能应该缩小工厂范围:坚持返回 v8::Isolate
,并将其余部分作为“eval”而不是“init”过程的一部分。这似乎更接近于图书馆的预期用途。