需保持地址稳定的结构体的API设计方案有哪些?
强制结构体地址稳定性的API设计方案
假设有一个因某些原因需要地址稳定性的结构体,例如它包含后台线程,线程会共享其内部状态的引用。从API设计角度,我们有哪些方案可以强制实现这种地址稳定性?是否存在无需强制用户进行额外内存分配或使用MaybeUninit的方案?
现有方案的问题说明
方案一:工厂函数返回
Pin<Box<T>>
让工厂函数直接返回固定指针,强制结构体仅通过固定指针被访问,但需要使用拥有所有权的指针:pub struct Foo { _pin: std::marker::PhantomPinned, } impl Foo { // 工厂函数强制固定(优点),但替调用者选择了Box作为所有权载体(缺点)。 pub fn new() -> Pin<Box<Foo>> { todo!() } // 用户实际使用结构体时必须使用固定引用,符合预期。 pub fn do_something(self: Pin<&Foo>) { todo!(); } }使用
Box会强制Foo在堆上单独分配内存。如果用户希望将其嵌入到另一个固定结构体中,这种额外分配就显得多余。方案二:在已固定内存位置构造结构体
仅允许在已固定的内存位置中构造结构体:pub struct Bar { _pin: std::marker::PhantomPinned, } impl Bar { // 工厂函数接受构造Bar对象的目标位置,从一开始就强制固定(优点),避免了强制使用Box进行额外分配(优点),但同时强制调用者使用`MaybeUninit`(缺点)。 pub fn emplace(_target: Pin<&mut MaybeUninit<Bar>>) { todo!() } // 用户实际使用结构体时必须使用固定引用,符合预期。 pub fn do_something(self: Pin<&Bar>) { todo!(); } }这个API可以安全使用,但容易出错。更重要的是它强制调用者必须使用
MaybeUninit,且组合性不佳——无法对MaybeUninit字段进行投影,如果父结构体也采用此策略(同样需要固定),就会出现多层MaybeUninit字段。
你也可以用Pin<&mut Option<Bar>>字段来更安全地实现,但缺点相同。
此外,某些API还会设置单独的“start”方法,但这对用户来说并不方便,且会让结构体存在可见的“未就绪”状态。我怀疑如果要避免Box分配,就无法完全避免某种形式的“未就绪”状态,但想知道是否有更便捷的方案。
内容的提问来源于stack exchange,提问作者jacobsa
相关产品推荐
相关产品推荐

