У меня есть библиотека C с интерфейсом, похожим на этот: (я представлял C API в Rust, так что весь код в этом вопросе можно объединить в один файл .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:
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
по единственной причине, что a_ptr
необходим при вызове функции destroy_b
для очистки памяти. Эта ссылка мне не нужна ни для одного из моих кодов 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> {
| ^^^^^^^^^^^^^^
Я понимаю, почему это не получается: при возврате структуры C
из C::new()
она перемещает C
. Это означает, что A
, содержащийся внутри, перемещен, что делает все ссылки на него недействительными. Поэтому я не могу создать эту C
структуру. ( Более подробно объяснено здесь )
Как я могу реорганизовать этот код таким образом, чтобы я мог хранить B
в структуре вместе с ее "родительским" A
? У меня есть несколько вариантов, которые не будут работать:
- Изменение интерфейса C: я не контролирую интерфейс C, поэтому не могу его изменить.
- Пусть
B
хранит *mut c_A
вместо &A
: если A
отброшен, то этот необработанный указатель становится недействительным и приведет к неопределенному поведению при отбрасывании B
.
- Пусть
B
хранит принадлежащий A
, а не ссылку &A
: для моего случая использования я должен иметь возможность создавать несколько B
с для каждого A
. Если B
принадлежит A
, то каждый A
может использоваться только для создания одного B
.
- Имеет
A
все экземпляры B
и возвращает только ссылки на B
при создании нового B
: проблема в том, что B
s будет накапливаться со временем до тех пор, пока A
не станет упал, занимая больше памяти, чем необходимо. Однако, если это действительно лучший способ, я могу справиться с небольшим неудобством.
- Используйте ящик
rental
: я предпочел бы получить небольшое использование памяти, чем добавить сложность нового макроса в мой код. (То есть сложность любого, кто читает мой код, должен узнать, как работает этот макрос)
Я подозреваю, что лучшее решение каким-то образом предполагает хранение по крайней мере A
в куче, так что ему не нужно перемещаться, но я не могу понять, как заставить это работать. Кроме того, мне интересно, есть ли что-нибудь умное, что я могу сделать, используя необработанные указатели.