Rspack 的编译阶段会同时读取 compilation、修改 runtime requirements,并把新模块放进输出向量。理解所有权和借用,才能看懂这些函数为什么使用
&Compilation、&mut RuntimeGlobals、*chunk_ukey和.boxed()。本篇只讲所有权模型与共享/可变借用。
1. 所有权决定值由谁负责释放
Rust 中每个值有一个所有者。所有者离开作用域时,值被自动释放;把非 Copy 值赋给另一个变量会发生 move,原变量不能再使用:
fn main() {
let name = String::from("rspack");
let moved = name;
println!("{}", moved);
// println!("{}", name); // 编译器拒绝:value borrowed here after move
}
String 拥有堆内存,所以赋值时 Rust 不会偷偷复制字符串内容。若确实需要两份独立数据,显式调用 clone();这会产生实际的内存复制,不能当成免费操作。
Rspack 中 Box<dyn RuntimeModule> 也是拥有堆上对象的值。调用 .boxed() 后,模块所有权转移到 Box,再被放入 Vec<(ChunkUkey, Box<dyn RuntimeModule>)>。向量负责最终释放这些模块,hook 本身不需要手动管理生命周期。
2. &T 借用:只读访问,不转移所有权
源码中的 hook 使用:
async fn runtime_requirements_in_tree(
&self,
compilation: &Compilation,
chunk_ukey: &ChunkUkey,
runtime_requirements: &RuntimeGlobals,
runtime_requirements_mut: &mut RuntimeGlobals,
runtime_modules_to_add: &mut Vec<(ChunkUkey, Box<dyn RuntimeModule>)>,
) -> Result<Option<()>>
compilation 和 runtime_requirements 只是被读取,因此用共享借用 &T。函数返回后借用结束,调用方仍拥有原值。共享借用可以同时存在多个,但借用期间不能通过别的路径修改同一个值。
这正适合 RuntimePlugin:它可以从 compilation 查询 chunk graph 和 output 配置,同时不把 compilation 的所有权交给某个 runtime module。
3. &mut T 借用:一次只能有一个修改者
runtime_requirements_mut 和 runtime_modules_to_add 使用 &mut,因为 hook 要向集合写入隐含依赖或新模块:
fn add_requirement(requirements: &mut Vec<&'static str>) {
requirements.push("MODULE");
}
fn main() {
let mut requirements = Vec::new();
add_requirement(&mut requirements);
println!("{}", requirements[0]);
}
输出是 MODULE。调用 add_requirement 时,函数暂时获得唯一的可变借用;调用结束后,requirements 恢复由 main 使用。Rust 不允许在同一时间存在一个 &mut T 和另一个指向同一值的 &T,因为这会让读者观察到不一致状态,也会让容器扩容后的地址失效问题更难控制。
错误写法:
let mut values = vec![1, 2];
let first = &values[0];
values.push(3); // 可能扩容,编译器拒绝与 first 同时存在
println!("{}", first);
编译器拒绝它,是因为 push 可能重新分配 vector,first 可能变成悬垂引用。把 first 的使用放到 push 之前,或先复制出 let first = values[0];,就能消除这个借用冲突。
4. 为什么源码里经常出现 *chunk_ukey
chunk_ukey 的参数类型是 &ChunkUkey,但输出向量需要拥有一个 ChunkUkey:
runtime_modules_to_add.push((
*chunk_ukey,
EnsureChunkRuntimeModule::new(&compilation.runtime_template, has_async_chunks).boxed(),
));
这里的解引用 *chunk_ukey 能按值复制,是因为 ChunkUkey 实现了 Copy(Rspack 的 key 类型被设计为轻量、可复制的索引)。它不会移动 compilation 内部的 key,也不会创建深层数据副本。若一个类型没有实现 Copy,就不能直接这样做,需要 .clone() 或改变输出结构让它继续借用;但把借用放进跨阶段向量通常会引入生命周期约束,因此拥有一个轻量 key 往往更简单。
5. 最小可运行示例与验证
下面的程序演示共享借用和可变借用的顺序:
fn append(values: &mut Vec<String>, value: &str) {
values.push(value.to_owned());
}
fn main() {
let mut values = vec![String::from("compile")];
let count = values.len();
append(&mut values, "emit");
println!("{}:{}", count, values.len());
}
我用 rustc 编译并运行,输出为:
1:2
value: &str 只读借用调用者的字符串,values: &mut Vec<String> 允许函数修改向量;to_owned() 在向量中创建拥有所有权的 String,因此函数返回后,向量不会持有一个可能已经失效的外部引用。
6. 工程边界与常见误区
&T不代表值永远不变,只代表这段借用期间当前路径不能修改它。&mut T不等于线程安全;它只保证 Rust 借用范围内的唯一访问,跨线程共享仍需要Send、Sync和合适的同步容器。clone()能解决所有权问题,但可能复制大量数据。应优先判断能否缩短借用范围、传递轻量 key 或重构 API。- 把引用存进返回值或异步任务会引入生命周期约束;Rspack 在需要跨阶段保存运行时模块时通常转移
Box所有权,而不是保存 compilation 内部对象的引用。
简要说明
用 RuntimePlugin 的真实签名理解所有权、共享借用和可变借用,解释 Box、&mut 与可复制 chunk key 如何避免悬垂引用和无谓复制。