处理由 C FFI 强制执行的有问题的父子关系

Dealing with problematic parent-child relationships enforced by C FFI

我有一个 C 库,其接口与此类似:(我在 Rust 中表示了 C API,所以这个问题中的所有代码都可以连接在一个 .rs 文件并易于测试)

// Opaque handles to C structs
struct c_A {}
struct c_B {}

// These 2 `create` functions allocate some heap memory and other 
// resources, so I have represented this using Boxes.
extern "C" fn create_a() -> *mut c_A {
    let a = Box::new(c_A {});
    Box::into_raw(a)
}

// This C FFI function frees some memory and other resources, 
// so I have emulated that here.
extern "C" fn destroy_a(a: *mut c_A) {
    let _a: Box<c_A> = unsafe { Box::from_raw(a) };
}

extern "C" fn create_b(_a: *mut c_A) -> *mut c_B {
    let b = Box::new(c_B {});
    Box::into_raw(b)
}

// Note: While unused here, the argument `_a` is actually used in 
// the C library, so I cannot remove it. (Also, I don't control 
// the C interface)
extern "C" fn destroy_b(_a: *mut c_A, b: *mut c_B) {
    let _b = unsafe { Box::from_raw(b) };
}

我对 C 函数创建了以下 Rusty 抽象:

struct A {
    a_ptr: *mut c_A,
}

impl A {
    fn new() -> A {
        A { a_ptr: create_a() }
    }
}

impl Drop for A {
    fn drop(&mut self) {
        destroy_a(self.a_ptr);
    }
}

struct B<'a> {
    b_ptr: *mut c_B,
    a: &'a A,
}

impl<'a> B<'a> {
    fn new(a: &'a A) -> B {
        B {
            b_ptr: create_b(a.a_ptr),
            a,
        }
    }
}

impl<'a> Drop for B<'a> {
    fn drop(&mut self) {
        destroy_b(self.a.a_ptr, self.b_ptr);
    }
}

B 结构包含对 A 的引用,其唯一原因是调用 destroy_b 函数进行内存清理时需要 a_ptr。我的任何 Rust 代码都不需要这个参考。

我现在想创建以下同时引用 A 和 B 的结构:

struct C<'b> {
    a: A,
    b: B<'b>,
}

impl<'b> C<'b> {
    fn new() -> C<'b> {
        let a = A::new();
        let b = B::new(&a);
        C { a, b }
    }
}

// Main function just so it compiles
fn main() {
    let c = C::new();
}

然而,这不会编译:

error[E0597]: `a` does not live long enough
  --> src/main.rs:76:25
   |
76 |         let b = B::new(&a);
   |                         ^ borrowed value does not live long enough
77 |         C { a, b }
78 |     }
   |     - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'b as defined on the impl at 73:1...
  --> src/main.rs:73:1
   |
73 | impl<'b> C<'b> {
   | ^^^^^^^^^^^^^^

我明白为什么会失败:当 return 从 C::new() 调用 C 结构时,它会移动 C。这意味着包含在其中的 A 被移动,这使得对它的所有引用都无效。因此,我无法创建 C 结构。 ()

如何重构此代码,以便将 B 与其 "parent" A 一起存储在结构中?有几个我想到的选项,但行不通:

我怀疑最好的解决方案以某种方式涉及在堆上至少存储 A,这样它就不需要四处移动,但我无法弄清楚如何进行这项工作。另外,我想知道我是否可以使用原始指针做一些聪明的事情。

这听起来像是引用计数的理想情况。使用Rc or Arc,取决于你的多线程需求:

use std::rc::Rc;

struct B {
    b_ptr: *mut c_B,
    a: Rc<A>,
}

impl B {
    fn new(a: Rc<A>) -> B {
        B {
            b_ptr: create_b(a.a_ptr),
            a,
        }
    }
}

impl Drop for B {
    fn drop(&mut self) {
        destroy_b(self.a.a_ptr, self.b_ptr);
    }
}

fn main() {
    let a = Rc::new(A::new());
    let x = B::new(a.clone());
    let y = B::new(a);
}
  • 不更改 C 接口。
  • A 无法在仍有 B 引用它时删除。
  • 可以为每个 A 创建多个 B
  • A的内存占用永远不会增加
  • 创建单个堆分配以存储 A 及其引用计数。
  • Rc 在标准库中,没有新的 crate 可以学习。

将来,您将能够使用任意自我类型来更好地编写此代码:

#![feature(arbitrary_self_types)]

use std::rc::Rc;

struct A {
    a_ptr: *mut c_A,
}

impl A {
    fn new() -> A {
        A { a_ptr: create_a() }
    }

    fn make_b(self: &Rc<Self>) -> B {
        B {
            b_ptr: create_b(self.a_ptr),
            a: self.clone(),
        }
    }
}

impl Drop for A {
    fn drop(&mut self) {
        destroy_a(self.a_ptr);
    }
}

struct B {
    b_ptr: *mut c_B,
    a: Rc<A>,
}

impl Drop for B {
    fn drop(&mut self) {
        destroy_b(self.a.a_ptr, self.b_ptr);
    }
}

fn main() {
    let a = Rc::new(A::new());
    let x = a.make_b();
    let y = a.make_b();
}