处理由 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
一起存储在结构中?有几个我想到的选项,但行不通:
- 更改C接口:我不控制C接口,所以无法更改。
- 让
B
存储一个 *mut c_A
而不是 &A
:如果 A
被删除,那么原始指针将变得无效,并且当 B
被丢弃。
- 让
B
存储拥有的 A
而不是引用 &A
:对于我的用例,我需要能够为每个创建多个 B
A
。如果B
拥有A
,那么每个A
只能用来创建一个B
。
- 让
A
拥有 B
的所有实例,并且在创建新的 B
时仅 return 引用 B
:这有一个问题B
s 会随着时间的推移而累积,直到 A
被删除,消耗了比必要更多的内存。不过,如果这确实是解决问题的最佳方式,我可以解决一些不便。
- 使用
rental
crate:我宁愿承受轻微的内存使用损失,也不愿将新宏的复杂性添加到我的代码中。 (也就是说,任何阅读我的代码的人都需要了解这个宏的工作原理)
我怀疑最好的解决方案以某种方式涉及在堆上至少存储 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();
}
我有一个 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
一起存储在结构中?有几个我想到的选项,但行不通:
- 更改C接口:我不控制C接口,所以无法更改。
- 让
B
存储一个*mut c_A
而不是&A
:如果A
被删除,那么原始指针将变得无效,并且当B
被丢弃。 - 让
B
存储拥有的A
而不是引用&A
:对于我的用例,我需要能够为每个创建多个B
A
。如果B
拥有A
,那么每个A
只能用来创建一个B
。 - 让
A
拥有B
的所有实例,并且在创建新的B
时仅 return 引用B
:这有一个问题B
s 会随着时间的推移而累积,直到A
被删除,消耗了比必要更多的内存。不过,如果这确实是解决问题的最佳方式,我可以解决一些不便。 - 使用
rental
crate:我宁愿承受轻微的内存使用损失,也不愿将新宏的复杂性添加到我的代码中。 (也就是说,任何阅读我的代码的人都需要了解这个宏的工作原理)
我怀疑最好的解决方案以某种方式涉及在堆上至少存储 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();
}