Deno op2 宏深度解析:Rust 与 V8 之间的高性能桥梁(Fastcall、Async 与 CppGC 对象)
#[op2] 是 Deno 运行时中连接 Rust 侧扩展(extensions)与 V8 引擎的核心 proc-macro。本文基于仓库内 libs/ops/op2/README.md 的完整内容展开,并结合 libs/ops/op2/mod.rs、libs/ops/op2/config.rs 等源码实现,系统讲解 op2 的字符串拷贝语义、可失败 op、异步 op 的三种模式(eager / lazy / deferred)、V8 fastcall 的声明与校验规则、参数与返回值的类型转换体系,以及基于 V8 CppGC 的原生 JavaScript 类定义与继承机制。读完后你应当能够:正确声明并注册一个 op、为其选择合适的参数类型与 fastcall 策略、编写带错误处理的异步 op,以及定义支持 JS extends 的 CppGC 对象。
一、op2 的定位:从 #[op] 到标准接口层
libs/ops/op2/README.md 开篇即说明:#[op2] 是 #[op] 的替代实现(in-progress replacement for #[op])。在当前的仓库代码中,op2 事实上已经成为 Deno 扩展开发的标准 op 声明方式——它由 deno_ops proc-macro crate 定义并在 libs/ops/lib.rs 中导出:
/// A macro designed to provide an extremely fast V8->Rust interface layer.
#[doc = include_str!("op2/README.md")]
#[proc_macro_attribute]
pub fn op2(attr: TokenStream, item: TokenStream) -> TokenStream {
op2_macro(attr, item)
}
注意第 24 行的 #[doc = include_str!("op2/README.md")]:本文所依据的 README 实际上被直接编译进了 op2 宏的 rustdoc,也就是说这份文档同时是宏的官方 API 文档。随后 deno_core 将其再导出给所有扩展 crate 使用(见 libs/core/lib.rs):
pub use deno_ops::CppgcBase;
pub use deno_ops::CppgcInherits;
pub use deno_ops::FromV8;
pub use deno_ops::ToV8;
pub use deno_ops::WebIDL;
pub use deno_ops::op2;
一个最小的完整示例(来自 libs/ops/README.md 与 libs/ops/op2/test_cases/sync/add.rs):
use deno_core::{op2, extension};
// 声明一个 op。
#[op2(fast)]
pub fn op_add(a: i32, b: i32) -> i32 {
a + b
}
// 注册到 extension。
extension!(
math,
ops = [op_add]
)
从源码结构看,op2 宏的入口函数 op2() 会先把输入按 ItemFn(普通函数 op)解析;若解析失败则尝试按 syn::ItemImpl(impl 块)解析,后者交给 libs/ops/op2/object_wrap.rs 处理——这正是后文 CppGC 对象包装的入口。对于函数 op,宏会:
- 解析
#[op2(...)]属性中的配置项,生成MacroConfig(见 libs/ops/op2/config.rs); - 解析函数签名为 op 参数/返回值模型(
parse_signature/RetVal); - 同时生成慢路径(slow) 与快路径(fast) 两套 dispatch 代码,最终产出一个实现
deno_core::_ops::Optrait 的零大小结构体,其DECL: OpDecl常量携带name、is_async、arg_count、slow_fn、fast_fn等元数据(见 libs/ops/op2/mod.rs)。
MacroConfig 中的字段一一对应了 README 中出现的各种属性标记:fast / nofast / fast_alternative(fastcall 三态)、async_lazy / async_deferred / fake_async(异步模式)、reentrant、no_side_effects、stack_trace、required、rename、symbol、promise_id、constructable、constructor、getter / setter / static_member、method 等(libs/ops/op2/config.rs)。宏还强制这些 flag 按字母序书写,并拒绝非法组合,例如 fast 与 nofast 同时出现、no_side_effects 与 reentrant 同时出现都会直接编译报错(libs/ops/op2/config.rs)。
二、字符串:为什么 String 参数总有一次拷贝
README 的 "Strings" 一节给出了 op2 处理字符串的底层约束:
- Rust 中的
String永远是 UTF-8; - V8 中的字符串要么是两字节 UTF-16,要么是一字节 Latin-1;
- 一字节 Latin-1 与 UTF-8 不字节兼容:索引 128–255 的字符在 UTF-8 中需要两个字节编码。
因此 op 中的 String 参数至少需要一次拷贝,以避免把 Latin-1 数据误当作 UTF-8 传给下游方法。目前无法完全避免这次拷贝,但 op 代码会尽可能利用**栈缓冲区(stack buffer)**来避免堆分配——这一点在参数表的 #[string] &str 说明中也有体现("Will create an owned String copy of the String data if it doesn't fit on the stack. Will never allocate in a fastcall")。
基于这一约束,op2 提供了梯度不同的字符串参数类型(详见第四节参数表):#[string] String 总是产生一次堆分配的 UTF-8 拷贝;#[string] &str 与 #[string] Cow<str> 优先走栈缓冲、放不下才分配;而 #[string(onebyte)] Cow<[u8]> 是最快的字符串类型——它要求字符串必须是 Latin-1,否则抛出 TypeError,完全不产生转换拷贝。
三、可失败的 op(Fallible ops)
op 函数可以通过返回 Result 声明自己是可失败的:错误类型必须实现 deno_error::JsErrorClass。当函数返回 Err 时,op2 生成的 dispatch 代码会在 JS 侧抛出对应的异常(exception)。也就是说 Deno 中常见的 NotSupported、NotFound、PermissionDenied 等错误类别,在 op 层就是普通 Rust Result 的错误变体,由 JsErrorClass 决定 JS 侧捕获到的异常类名。
四、异步 op:两种声明形式与三种调度模式
4.1 声明形式:从签名完全推断
异步调用完全从函数定义中推断,支持两种等价形式:
async fn op_xyz(/* ... */) -> X {}
fn op_xyz(/* ... */) -> impl Future<Output = X> {}
4.2 语法糖的真相:隐藏的 promise_id 与 Option<X>
上述两种写法最终会被 desugar 成如下形态(README 原文示例):
fn op_xyz(promise_id: i32 /* ... */) -> Option<X> {}
即:函数被加上了一个隐藏的 promise_id 参数,返回值变为 Option<X>。Deno 会立即(eagerly)轮询这个 future:
- 如果 op 立刻 ready,函数直接返回
Some(X),结果同步送达 JS,延迟最低; - 如果 op 未 ready,函数返回
None,该 future 交由 Deno 的 pending op 系统(libs/ops/op2/dispatch_async.rs 生成的异步 dispatch)接管,待其完成后按promise_id唤醒 JS 侧的 promise。
宏在 libs/ops/op2/mod.rs 中根据 signature.ret_val.is_async() 或 fake_async 标志选择生成 generate_dispatch_async(异步)或 generate_dispatch_slow(同步)路径。
4.3 三种异步调度模式
| 模式 | 声明 | 行为 | 适用场景 |
|---|---|---|---|
| eager(默认) | 直接写 async fn |
调用时立即轮询 future;若首次轮询即 ready 则同步返回 | 绝大多数异步 op,延迟最低 |
async(lazy) |
#[op2(async(lazy))] |
允许 runtime 推迟到稍后才轮询该 op。提交(submit)开销可能更快,但对"首次轮询即 ready"的 op,其结果延迟会更高 | 可能用于提高吞吐,必须配合仔细的 benchmarking 使用;它可能走 fastcall,但结果解析仍在慢路径 |
async(deferred) |
#[op2(async(deferred))] |
runtime 立即轮询 op,但即使结果已经 ready,也会推迟到事件循环的下一轮才交付 | README 明确警告:"This is almost certainly not what you want to use and should only be used if you really know what you are doing." |
README 同时强调:lazy / deferred 的异步调用可能是 fastcall(提交阶段),但结果的解析(resolution)仍发生在慢路径。从 libs/ops/op2/config.rs 的 AsyncMode 枚举(Deferred / Fake / Lazy)可以看到这些模式在宏层面的落地,fake_async 则用于 Deno 内部的确定性/模拟时钟场景(is_fake_async 传入 GeneratorState,见 libs/ops/op2/mod.rs)。
五、Fastcall:op2 的性能核心
V8 fastcall 允许 JavaScript 调用不经过标准的 JS 参数对象转换,直接把标量/简单引用以 C ABI 风格传入 Rust,显著降低调用开销。op2 对 fastcall 的规则:
-
必须显式声明。op2 要求 fastcall 兼容的 op 标注
fast;若你不想为某个 op 生成 fastcall(这种情况很少),可以改用nofast。这不是可选建议,而是编译期强制:宏在 libs/ops/op2/mod.rs 中会先尝试generate_dispatch_fast——- 若函数 fastcall 兼容,但未标
fast(也未标nofast/fast(...)/getter/setter),直接报ShouldBeFast错误("This op is fast-compatible and should be marked as (fast)"); - 若函数不兼容却被标了
fast或nofast,则报ShouldNotBeFast。
- 若函数 fastcall 兼容,但未标
-
fastcall 备选函数
fast(op_XYZ)。可以为一个慢路径函数指定另一个 Rust 函数作为其 fastcall 实现:#[op2(fast(op_xyz_fast))] pub fn op_xyz(/* 接受任意 buffer 类型 */) -> ... { ... } #[op2(fast)] pub fn op_xyz_fast(/* 只接受高速类型化的 u8 buffer */) -> ... { ... }被指定的 fast 函数必须标注
#[op2(fast)],且不需要在 extension 中注册。当 V8 将慢函数优化为 fastcall、且参数类型兼容时,会切换到这个 fast 实现。README 给出的典型场景:慢路径接受任意 buffer 类型的函数,希望 fast 路径使用极快的类型化u8buffer。 -
属性解析层面,
fast(...)的参数是一个 Rust 类型(Type),见 libs/ops/op2/config.rs 中Flags::Fast(Option<String>)的解析逻辑。
仓库中大量真实扩展都使用了 #[op2(fast)],例如 ext/fs/ops.rs、ext/net/lib.rs、ext/canvas/lib.rs、ext/crypto/lib.rs 等(ext/* 目录下广泛出现 #[op2(fast)] 标注),可以作为实际用法参考。
六、参数与返回值的类型转换
6.1 参数转换:默认 FromV8Scopeless,可选 #[scoped]
非 fast op 的参数默认使用 deno_core::convert::FromV8Scopeless trait 进行转换。该 trait 的转换过程不需要 v8 scope,因此对很多类型更高效。如果需要转换期间访问 v8 scope,则对该参数添加 #[scoped] 属性,改用 FromV8 trait:
fn op_xyz(#[scoped] arg: MyFromV8Type) -> X {}
#[scoped] 参数不走 fastcall,且可能较慢(README 参数表中标注 "⚠️ May be slow")。
6.2 返回值转换:默认 ToV8
返回类型在非 fast op 中默认使用 ToV8 trait:任何实现了 deno_core::convert::ToV8 的类型都可以不带任何属性直接作为返回值。
6.3 参数类型支持表(Parameters)
以下表格完整继承自 libs/ops/op2/README.md 中由宏测试自动生成的参数表("Fastcall" 列的 ✅ 表示该类型可走 fastcall;"v8" 列为可接受的 JS 值类型):
| Rust | Fastcall | v8 | 说明 |
|---|---|---|---|
bool |
✅ | Bool | |
i8 |
✅ | Uint32, Int32, Number, BigInt | |
u8 |
✅ | Uint32, Int32, Number, BigInt | |
i16 |
✅ | Uint32, Int32, Number, BigInt | |
u16 |
✅ | Uint32, Int32, Number, BigInt | |
i32 |
✅ | Uint32, Int32, Number, BigInt | |
u32 |
✅ | Uint32, Int32, Number, BigInt | |
#[smi] ResourceId |
✅ | Uint32, Int32, Number, BigInt | SMI 内部以有符号整数表示,但无符号的 #[smi] 类型在 Rust 侧会按位转换为无符号值;JavaScript 侧仍看到有符号整数 |
#[bigint] i64 |
✅ | Uint32, Int32, Number, BigInt | |
#[bigint] u64 |
✅ | Uint32, Int32, Number, BigInt | |
#[bigint] isize |
✅ | Uint32, Int32, Number, BigInt | |
#[bigint] usize |
✅ | Uint32, Int32, Number, BigInt | |
f32 |
✅ | Uint32, Int32, Number, BigInt | |
f64 |
✅ | Uint32, Int32, Number, BigInt | |
#[string] String |
✅ | String | 仅当字符串为 Latin-1 时可走 fastcall;总是产生一次堆分配的 UTF-8 拷贝 |
#[string] &str |
✅ | String | 仅 Latin-1 可走 fastcall;放不进栈缓冲时产生 owned String 拷贝;fastcall 中绝不分配,但会做 Latin-1 → UTF-8 转换 |
#[string] Cow<str> |
✅ | String | 仅 Latin-1 可走 fastcall;放不进栈缓冲时产生 Cow::Owned;fastcall 中总是 Cow::Borrowed,但会做 Latin-1 → UTF-8 转换 |
#[string(onebyte)] Cow<[u8]> |
✅ | String | 最快的 String 类型;字符串非 Latin-1 时抛 TypeError |
&v8::Value |
✅ | any | |
&v8::String |
✅ | String | |
&v8::Object |
✅ | Object | |
&v8::Function |
✅ | Function | |
&v8::... |
✅ | ... | 任意其他 v8 类型 |
v8::Local<v8::Value> |
✅ | any | |
v8::Local<v8::String> |
✅ | String | |
v8::Local<v8::Object> |
✅ | Object | |
v8::Local<v8::Function> |
✅ | Function | |
v8::Local<v8::...> |
✅ | ... | |
FromV8Scopeless |
any | 任何实现 deno_core::convert::FromV8Scopeless 的类型 |
|
#[scoped] FromV8Type |
any | 任何实现 deno_core::convert::FromV8 的类型。⚠️ 可能较慢 |
|
#[scoped] (Tuple, Tuple) |
any | 任何实现 deno_core::convert::FromV8 的类型。⚠️ 可能较慢 |
|
#[serde] SerdeType |
any | ⚠️ 可能较慢。遗留方案,不推荐,请改用 FromV8 trait 与宏 |
|
#[serde] (Tuple, Tuple) |
any | ⚠️ 可能较慢。遗留方案,不推荐,请改用 FromV8 trait 与宏 |
|
#[arraybuffer] &mut [u8] |
✅ | ArrayBuffer (resizable=true,false) | ⚠️ 若 V8 被重入调用,JS 可能修改 slice 内容 |
#[arraybuffer] &[u8] |
✅ | ArrayBuffer (resizable=true,false) | ⚠️ 同上 |
#[arraybuffer] *mut u8 |
✅ | ArrayBuffer (resizable=true,false) | ⚠️ 同上;由于 V8 对空数组的处理,fastcall 中空数组总是以 null 传入 |
#[arraybuffer] *const u8 |
✅ | ArrayBuffer (resizable=true,false) | ⚠️ 同上;空数组同样传 null |
#[arraybuffer(copy)] Vec<u8> |
✅ | ArrayBuffer (resizable=true,false) | 安全,但强制拷贝 |
#[arraybuffer(copy)] Box<[u8]> |
✅ | ArrayBuffer (resizable=true,false) | 安全,但强制拷贝 |
#[arraybuffer(copy)] bytes::Bytes |
✅ | ArrayBuffer (resizable=true,false) | 安全,但强制拷贝 |
#[buffer(copy)] Vec<u8> |
✅ | UInt8Array (resizable=true,false) | 安全,但强制拷贝 |
#[buffer(copy)] Box<[u8]> |
✅ | UInt8Array (resizable=true,false) | 安全,但强制拷贝 |
#[buffer(copy)] bytes::Bytes |
✅ | UInt8Array (resizable=true,false) | 安全,但强制拷贝 |
#[buffer] &mut [u32] |
✅ | UInt32Array (resizable=true,false) | ⚠️ 重入时 JS 可能修改 slice |
#[buffer] &[u32] |
✅ | UInt32Array (resizable=true,false) | ⚠️ 重入时 JS 可能修改 slice |
#[buffer(copy)] Vec<u32>, #[buffer(copy)] Box<[u32]> |
✅ | UInt32Array (resizable=true,false) | 安全,但强制拷贝 |
#[buffer(detach)] JsBuffer |
ArrayBufferView (resizable=true,false) | 安全 | |
*const std::ffi::c_void |
✅ | External | |
*mut std::ffi::c_void |
✅ | External | |
&OpState |
✅ | ||
&mut OpState |
✅ | ||
Rc<RefCell<OpState>> |
✅ | ||
&JsRuntimeState |
✅ | 只能在 deno_core 内部使用 |
使用建议可直接从表中读出:优先选标量(i32/u32/f64/bool)以吃满 fastcall;字符串按性能需求在 #[string] &str 与 #[string(onebyte)] Cow<[u8]> 之间选择;buffer 场景若不希望 JS 侧重入修改数据,用 (copy) 变体或 detach 变体换取安全。
6.4 返回值类型支持表(Return Values)
同样继承自 libs/ops/op2/README.md 的返回类型表:
| Rust | Fastcall | v8 / 说明 |
|---|---|---|
bool, i8, u8, i16, u16, i32, u32 |
✅ | 直接映射为对应 JS 数值 |
#[smi] ResourceId |
✅ | SMI 说明同参数表(Rust 侧无符号位转换,JS 侧仍为有符号) |
#[bigint] i64, #[bigint] u64, #[bigint] isize, #[bigint] usize |
✅ | 映射为 BigInt |
#[number] i64, #[number] u64, #[number] isize, #[number] usize |
✅ | 结果必须落在 Number.MIN_SAFE_INTEGER 与 Number.MAX_SAFE_INTEGER 之间 |
f32, f64 |
✅ | 直接映射为 Number |
#[string] String, #[string] &str, #[string] Cow<str>, #[string(onebyte)] Cow<[u8]> |
映射为 JS String | |
#[arraybuffer] V8Slice<u8>, #[arraybuffer] Vec<u8>, #[arraybuffer] Box<[u8]>, #[arraybuffer] bytes::BytesMut |
映射为 ArrayBuffer | |
#[buffer] V8Slice<u8>, #[buffer] Vec<u8>, #[buffer] Box<[u8]>, #[buffer] bytes::BytesMut |
映射为 TypedArray | |
#[buffer] V8Slice<u32> |
映射为 UInt32Array | |
*const std::ffi::c_void, *mut std::ffi::c_void |
✅ | 映射为 External |
v8::Local<v8::Value>, v8::Local<v8::String>, v8::Local<v8::Object>, v8::Local<v8::Function>, v8::Local<v8::...> |
返回预构建好的 v8 句柄 | |
ToV8Type |
任何实现 deno_core::convert::ToV8 的类型 |
|
(ToV8Type, ToV8Type) |
二元组,两元素均实现 ToV8(映射为 JS 二元组) |
|
#[serde] SerdeType, #[serde] (SerdeType, SerdeType) |
⚠️ 遗留方案,不推荐,请改用 ToV8 trait 与宏 |
七、CppGC 对象:Rust 背板的原生 JavaScript 类
op2 支持基于 V8 CppGC(C++ 垃圾回收)定义由 Rust 类型背板的原生 JavaScript 类。这些对象存放在 V8 堆上,自动被 GC 回收——不需要像传统 Rc<RefCell<...>> 资源那样手动管理生命周期。
7.1 基本用法
定义一个实现 GarbageCollected 的结构体,然后在 impl 块上加 #[op2] 定义其 JS API(完整示例继承自 README):
use deno_core::GarbageCollected;
use deno_core::v8::cppgc::GcCell;
#[repr(C)]
pub struct MyObject {
value: GcCell<f64>,
}
unsafe impl GarbageCollected for MyObject {
fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
c"MyObject"
}
}
#[op2]
impl MyObject {
#[constructor]
#[cppgc]
fn new(value: f64) -> MyObject {
MyObject {
value: GcCell::new(value),
}
}
#[getter]
fn value(&self, isolate: &v8::Isolate) -> f64 {
*self.value.get(isolate)
}
#[setter]
fn value(&self, isolate: &mut v8::Isolate, value: f64) {
self.value.set(isolate, value);
}
#[fast]
fn double_value(&self, isolate: &v8::Isolate) -> f64 {
*self.value.get(isolate) * 2.0
}
#[static_method]
#[cppgc]
fn create(value: f64) -> MyObject {
MyObject {
value: GcCell::new(value),
}
}
}
在 extension 中注册对象:
deno_core::extension!(
my_ext,
objects = [MyObject],
// ...
);
注册后 JavaScript 侧即可使用:
import { MyObject } from "ext:core/ops";
const obj = new MyObject(42);
console.log(obj.value); // 42
console.log(obj.doubleValue()); // 84
obj.value = 10;
7.2 支持的对象成员类型
#[constructor]— JS 构造函数。必须返回该结构体类型(可以包在Result中);用#[cppgc]标记表示返回的是 CppGC 对象;#[getter]/#[setter]— 属性访问器。同一属性的 getter 与 setter 应使用相同的函数名;#[static_method]— 类上的静态方法(如MyObject.create());#[fast]— 普通实例方法。使用&self作为首参以接收原生对象。
在宏实现中,这些成员属性会改写函数名(setter 前缀 __set_、静态方法前缀 __static_,见 libs/ops/op2/mod.rs),并设置 MacroConfig 中对应的 constructor / getter / setter / static_member 标志,getter/setter 最终映射为 deno_core::AccessorType(libs/ops/op2/mod.rs)。impl 块的解析与成员分发逻辑位于 libs/ops/op2/object_wrap.rs。
7.3 继承:base 与 derived 类
CppGC 对象支持镜像 JavaScript 类继承的原型继承模型:可以在 Rust 中定义基类,派生类像 class Child extends Parent 一样继承其方法与属性。
定义基类:结构体标记 #[derive(CppgcBase)],impl 块标记 #[op2(base)]:
use deno_core::CppgcBase;
#[derive(CppgcBase)]
#[repr(C)]
pub struct Shape {
sides: GcCell<u32>,
}
unsafe impl GarbageCollected for Shape {
fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
c"Shape"
}
}
#[op2(base)]
impl Shape {
#[constructor]
#[cppgc]
fn new(sides: u32) -> Shape {
Shape {
sides: GcCell::new(sides),
}
}
#[getter]
fn sides(&self, isolate: &v8::Isolate) -> u32 {
*self.sides.get(isolate)
}
}
base 属性告诉 op2 在访问 &self 时使用多态 unwrap(polymorphic unwrap),使得 Shape 上的方法可以在任何继承自它的类型上被调用。在 libs/ops/op2/mod.rs 中,这对应 try_unwrap_cppgc 在 base 模式下选用 try_unwrap_cppgc_base_object,否则选用 try_unwrap_cppgc_object。
定义派生类:结构体标记 #[derive(CppgcInherits)],将基类型作为第一个字段,impl 块使用 #[op2(inherit = BaseType)]:
use deno_core::CppgcInherits;
#[derive(CppgcInherits)]
#[cppgc_inherits_from(Shape)]
#[repr(C)]
pub struct Rectangle {
base: Shape, // 必须是第一个字段
width: GcCell<f64>,
height: GcCell<f64>,
}
unsafe impl GarbageCollected for Rectangle {
fn trace(&self, _visitor: &mut v8::cppgc::Visitor) {}
fn get_name(&self) -> &'static std::ffi::CStr {
c"Rectangle"
}
}
#[op2(inherit = Shape)]
impl Rectangle {
#[constructor]
#[cppgc]
fn new(width: f64, height: f64) -> Rectangle {
Rectangle {
base: Shape {
sides: GcCell::new(4),
},
width: GcCell::new(width),
height: GcCell::new(height),
}
}
#[fast]
fn area(&self, isolate: &v8::Isolate) -> f64 {
*self.width.get(isolate) * *self.height.get(isolate)
}
}
JavaScript 侧 Rectangle 继承自 Shape:
const rect = new Rectangle(3, 4);
console.log(rect.sides); // 4(继承自 Shape)
console.log(rect.area()); // 12
console.log(rect instanceof Rectangle); // true
console.log(rect instanceof Shape); // true
JavaScript 类也可以继续扩展这些原生类:
class Square extends Rectangle {
constructor(size) {
super(size, size);
}
}
const sq = new Square(5);
console.log(sq.area()); // 25
console.log(sq.sides); // 4
多级继承:如果一个派生类本身还要被继承,它必须同时是基类——即同时 derive CppgcInherits 与 CppgcBase,并使用 #[op2(base, inherit = ParentType)]:
// Rectangle 既是 Shape 的子类,又是进一步派生的基类。
#[derive(CppgcInherits, CppgcBase)]
#[cppgc_inherits_from(Shape)]
#[repr(C)]
pub struct Rectangle {
base: Shape,
width: GcCell<f64>,
height: GcCell<f64>,
}
#[op2(base, inherit = Shape)]
impl Rectangle {
// ... methods ...
}
// Square 继承自 Rectangle
#[derive(CppgcInherits)]
#[cppgc_inherits_from(Rectangle)]
#[repr(C)]
pub struct Square {
base: Rectangle,
}
#[op2(inherit = Rectangle)]
impl Square {
// ... methods ...
}
硬性要求(README "Requirements" 一节):
- 继承链上的所有类型必须使用
#[repr(C)]; - 基类型必须是派生结构体的第一个字段,且位于 offset 0;
- 叶子类型(不再被继承)只需
#[derive(CppgcInherits)];根基类只需#[derive(CppgcBase)]; - 继承链中间的类型需要
#[derive(CppgcInherits, CppgcBase)]两者兼有; - 所有类型都必须注册进 extension 的
objects = [...]列表,且基类型必须列在派生类型之前。
CppgcBase / CppgcInherits 这两个 derive 宏由 deno_ops crate 提供(libs/ops/lib.rs),并经 libs/core/lib.rs 从 deno_core 再导出;GarbageCollected trait 与 GcCell 等实现位于 libs/core/cppgc.rs。
八、工程化保证:文档表与宏行为如何保持一致
值得了解的是,README 中的两张类型支持表不是手写的,而是由宏的测试用例自动生成并做一致性断言的。在 libs/ops/op2/mod.rs 中:
test_valid_args_md从源表 libs/ops/op2/valid_args.md 读取每一行类型声明,真正构造fn op_test(x: T)并调用generate_op2验证宏确实能生成该 op,再把生成结果与README.md中<!-- START ARGS -->...<!-- END ARGS -->之间的 HTML 表格逐字节比对;test_valid_retvals_md对 libs/ops/op2/valid_retvals.md 与<!-- START RV -->...<!-- END RV -->区段做同样的事,并对支持异步的类型额外验证async fn形式;- 修改类型支持后,需要设置环境变量
UPDATE_EXPECTED=1重跑测试来重新生成 README 中的表格。
此外,宏的所有展开行为都有 fixture 测试覆盖:libs/ops/op2/test_cases/sync/(40+ 个同步用例,如 add.rs、buffers.rs、fast_alternative.rs、smi.rs、string_onebyte.rs 等)与 libs/ops/op2/test_cases/async/(async_lazy.rs、async_deferred.rs、async_result_impl.rs 等),每个用例的展开结果以 .out 期望文件做快照比对,失败时同样可用 UPDATE_EXPECTED=1 刷新。这意味着本文所述行为(如 fast/nofast 强制校验、promise_id 注入、Option<X> 返回)均有可执行的回归测试背书。
九、速查总结
- 声明与注册:
#[op2(...)]标注函数(或impl块),通过extension!(name, ops = [...], objects = [...])注册;deno_core已再导出op2、CppgcBase、CppgcInherits、FromV8、ToV8、WebIDL; - 字符串:总有一次拷贝不可避(Latin-1/UTF-8 不兼容),用栈缓冲规避分配;最快路径是
#[string(onebyte)] Cow<[u8]>(非 Latin-1 抛 TypeError); - 错误:返回
Result,错误类型实现JsErrorClass,Err时向 JS 抛异常; - 异步:
async fn或-> impl Future,desugar 成promise_id + Option<X>,默认 eager 轮询;async(lazy)换取提交速度、async(deferred)推迟结果交付(慎用); - fastcall:兼容必须标
fast,否则编译报错;可用fast(op_XYZ)指定类型更严格的 fast 实现;#[scoped]/#[serde]参数不走 fastcall; - CppGC 对象:
GarbageCollected+#[op2] impl定义原生类,支持 constructor/getter/setter/static_method/fast 成员;继承链要求#[repr(C)]、基类为 offset 0 的首字段、按基类在前顺序注册。
本文所有结论均可在仓库中核验:文档主体为 libs/ops/op2/README.md,宏实现位于 libs/ops/op2/(入口 libs/ops/op2/mod.rs、属性解析 libs/ops/op2/config.rs、slow/fast/async 三套 dispatch 生成器 libs/ops/op2/dispatch_slow.rs / libs/ops/op2/dispatch_fast.rs / libs/ops/op2/dispatch_async.rs、对象包装 libs/ops/op2/object_wrap.rs)。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00