能否通过生命周期在编译期追踪值归属者并限制跨归属传递?
如何追踪值的「归属者」,跨归属者传递时触发编译错误?
我想实现一种机制,让某个值(比如示例中的Foo)只能被创建它的「归属者」(比如Owner)接收,当把它传递给其他归属者时触发编译错误。我研究了不变生命周期和generativity crate,但能力还不足以实现。以下代码最能说明我的需求:
use core::marker::PhantomData; // 生命周期`'a`仅对创建`Foo`的`Owner`有效 #[derive(Default, Copy, Clone)] struct Foo<'a> { phantom: PhantomData<&'a Owner>, } #[derive(Default)] struct Owner {} impl Owner { fn get<'a, 'b: 'a>(&'a self) -> Foo<'b> { Foo::<'b>::default(); } // 要求`self`的唯一生命周期同时适用于`Foo` fn set<'a>(&'a mut self, _foo: Foo<'a>) { // 任意代码 } } fn main() { let mut owner = Owner::default(); let mut owner2 = Owner::default(); let anon_foo = Foo::default(); let foo2 = owner2.get(); owner.set(foo2); // 不应编译 owner.set(anon_foo); // 也不应编译 owner2.set(foo2); // 可以编译! }
这是一个理论性问题,我并非要解决特定问题,只是想了解Rust中是否存在这类零开销抽象结构。
编辑补充:不要求在复杂场景下工作,只需做到安全失败(若编译器无法证明Owner是实际归属者,则绝不允许调用set)。此外,Owner结构体可通过宏创建,无需支持运行时构造。
可行的零开销实现方案
Rust的类型系统和生命周期系统完全可以实现这种需求,以下是两种符合要求的方案:
方案1:用generativity crate实现实例级归属追踪
generativity crate可以为每个Owner实例生成唯一、无法伪造的生命周期,让Foo绑定到该生命周期,从而确保只有创建它的Owner能接收它。该方案是实例级的,所有Owner是同一类型的不同实例,且完全零开销(PhantomData和Guard都是零大小类型)。
use generativity::make_guard; use core::marker::PhantomData; // Foo绑定到唯一的生成性生命周期'_id #[derive(Default, Copy, Clone)] struct Foo<'id> { _phantom: PhantomData<&'id ()>, } struct Owner<'id> { _guard: generativity::Guard<'id>, } impl<'id> Owner<'id> { // 创建Owner时生成唯一生命周期 fn new() -> Self { make_guard!(guard); Owner { _guard: guard } } // 返回绑定当前Owner生命周期的Foo fn get(&self) -> Foo<'id> { Foo::default() } // 仅接受与当前Owner同生命周期的Foo fn set(&mut self, _foo: Foo<'id>) { // 业务逻辑 } } fn main() { let mut owner = Owner::new(); let mut owner2 = Owner::new(); // 无法直接创建匿名Foo,因为没有对应的唯一生命周期 // let anon_foo = Foo::default(); // 编译错误 let foo2 = owner2.get(); owner.set(foo2); // 编译错误:生命周期不匹配 // owner.set(anon_foo); // 编译错误:无法创建 owner2.set(foo2); // 编译通过! }
方案2:用宏生成类型级归属标记
如果不想依赖外部crate,可以用宏为每个Owner生成唯一的标记类型,让Foo与该标记绑定。该方案是类型级的,不同的Owner是不同类型,但同样零开销,且符合你允许宏创建Owner的要求。
use core::marker::PhantomData; // 宏生成带唯一标记的Owner和对应Foo macro_rules! make_owner { ($owner_name:ident, $foo_name:ident) => { #[derive(Default)] pub struct $owner_name; #[derive(Default, Copy, Clone)] pub struct $foo_name { _phantom: PhantomData<&'static $owner_name>, } impl $owner_name { pub fn get(&self) -> $foo_name { $foo_name::default() } pub fn set(&mut self, _foo: $foo_name) { // 业务逻辑 } } }; } // 生成两个独立的Owner-Foo对 make_owner!(Owner1, Foo1); make_owner!(Owner2, Foo2); fn main() { let mut owner = Owner1::default(); let mut owner2 = Owner2::default(); let foo2 = owner2.get(); owner.set(foo2); // 编译错误:类型不匹配(Foo2不能传给Owner1的set) // let anon_foo = Foo1::default(); // 可创建,但只能给Owner1使用 owner2.set(foo2); // 编译通过! }
总结
两种方案都满足你的需求:
- 零开销:编译后无额外运行时成本
- 安全失败:编译器严格验证归属关系,不符合条件的调用直接编译报错
- 适配宏创建Owner的要求
其中方案1是实例级追踪,方案2是类型级追踪,可根据实际偏好选择。
内容的提问来源于stack exchange,提问作者Kryštof Vosyka
相关产品推荐
相关产品推荐

