在 Rust 中创建工厂函数

Creating a factory function in Rust

我刚开始学习 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 函数,它 return 是一个 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);
}

到目前为止,我了解代码应该如何工作以及为什么不能工作。编译器说 return 语句:returns a value referencing data owned by the current function。这对我来说很有意义,isolatescope 变量是在此函数中创建的。但是如果我想把这个函数作为一个工厂,换句话说,让这个函数构造一个ContextScope对象,isolatescope对象必须保持存活。我将如何实现这一目标?

Rust 的默认规则不允许你编写你想写的函数。

每当你看到一个有生命周期的类型,比如HandleScope<'s>,你应该理解这意味着这种类型的实例是临时的(生命周期的通常例外是“静态的,此处不适用”)并且通常存在于堆栈框架的范围内。为了return这样的类型,函数必须传递它借用的任何实例。

在这种情况下,库的意图是您应该遵循堆栈帧:HandleScope 的文档说“管理多个本地句柄的堆栈分配 class。”确切的措辞是无稽之谈——你可以自由地将 HandleScope 移动到 Box 中,从而使其成为堆分配的——但显然他们 期望 你使用它以面向堆栈的方式。

执行此操作的最简单方法是将函数修改为 accept 范围内的函数 运行:

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 crate 来管理它,它以内存安全的方式提供了必要的机制。这仍然强加了您必须从闭包中引用数据的约束,但是您可以重复而不是一次。


但是,您可能应该缩小工厂范围:坚持 returning a v8::Isolate,并将其余部分作为“eval”而不是“init”过程。这似乎更接近图书馆的预期用途。