指向特征的指针
Pointer to trait
当我开始学习 Rust 时,我天真地假设 Rust 的指向特征的指针就像 C++ 指向基的指针一样实现 class,并编写了一些即使在该假设下也能正常工作的代码。具体来说,我编写的代码与需要读取和查找流的 FFI 库接口,它是这样的:
struct StreamParts {
reader: *mut Read,
seeker: *mut Seek,
}
fn new_ffi_object<T: Read + Seek + 'static>(stream: T) -> FFIObject {
let stream_ptr = Box::into_raw(Box::new(stream));
let stream_parts = Box::into_raw(Box::new(StreamParts {
reader: stream_ptr as *mut Read,
seeker: stream_ptr as *mut Seek,
}));
ffi_library::new_object(stream_parts, ffi_read, ffi_seek, ffi_close)
}
extern "C" fn ffi_read(stream_parts: *mut StreamParts, ...) -> c_ulong {
(*stream_parts.reader).read(...)
...
}
extern "C" fn ffi_seek(stream_parts: *mut StreamParts, ...) -> c_ulong {
(*stream_parts.seeker).seek(...)
...
}
extern "C" fn ffi_close(stream_parts: *mut StreamParts) {
mem::drop(Box::from_raw(stream_parts.reader));
mem::drop(Box::from_raw(stream_parts));
}
它奏效了。但是,关于它的工作原理,我不完全理解三件事:
- Rust 的特征对象很胖,包含两个指针。因此,与 C++ 不同,
*mut Read
是指向特征对象的指针,对吗?这个特征对象分配在哪里? Rust 文档没有涉及这个特定案例。
- 我假设
mem::drop(Box::from_raw(stream_parts.reader))
完全删除原始流是否正确?
- 为什么
new_ffi_object()
中需要 'static
?
指针和引用的行为完全相同,除了禁止你有悬空引用的借用检查器以及你需要将指针解除引用包装到 unsafe
块中这一事实。
- 所以是的,
sizeof::<*mut Read>() == sizeof::<*mut ()>() * 2
。 trait 对象没有分配到任何地方。它只不过是一个有两个字段的结构体。一个是指向您的数据的指针,一个是指向 vtable 的指针。 vtable分配在静态内存中。
- 正确。它访问
reader
的 vtable 指针并在 vtable 中查找 drop
impl。
- 如果您没有
'static
生命周期,您的 T
可能包含生命周期短于 'static
的引用。 lifetime bound 所说的只是 T
没有这样的引用,因此可以不受限制地复制到任何地方,即使是在堆上也是如此。
当我开始学习 Rust 时,我天真地假设 Rust 的指向特征的指针就像 C++ 指向基的指针一样实现 class,并编写了一些即使在该假设下也能正常工作的代码。具体来说,我编写的代码与需要读取和查找流的 FFI 库接口,它是这样的:
struct StreamParts {
reader: *mut Read,
seeker: *mut Seek,
}
fn new_ffi_object<T: Read + Seek + 'static>(stream: T) -> FFIObject {
let stream_ptr = Box::into_raw(Box::new(stream));
let stream_parts = Box::into_raw(Box::new(StreamParts {
reader: stream_ptr as *mut Read,
seeker: stream_ptr as *mut Seek,
}));
ffi_library::new_object(stream_parts, ffi_read, ffi_seek, ffi_close)
}
extern "C" fn ffi_read(stream_parts: *mut StreamParts, ...) -> c_ulong {
(*stream_parts.reader).read(...)
...
}
extern "C" fn ffi_seek(stream_parts: *mut StreamParts, ...) -> c_ulong {
(*stream_parts.seeker).seek(...)
...
}
extern "C" fn ffi_close(stream_parts: *mut StreamParts) {
mem::drop(Box::from_raw(stream_parts.reader));
mem::drop(Box::from_raw(stream_parts));
}
它奏效了。但是,关于它的工作原理,我不完全理解三件事:
- Rust 的特征对象很胖,包含两个指针。因此,与 C++ 不同,
*mut Read
是指向特征对象的指针,对吗?这个特征对象分配在哪里? Rust 文档没有涉及这个特定案例。 - 我假设
mem::drop(Box::from_raw(stream_parts.reader))
完全删除原始流是否正确? - 为什么
new_ffi_object()
中需要'static
?
指针和引用的行为完全相同,除了禁止你有悬空引用的借用检查器以及你需要将指针解除引用包装到 unsafe
块中这一事实。
- 所以是的,
sizeof::<*mut Read>() == sizeof::<*mut ()>() * 2
。 trait 对象没有分配到任何地方。它只不过是一个有两个字段的结构体。一个是指向您的数据的指针,一个是指向 vtable 的指针。 vtable分配在静态内存中。 - 正确。它访问
reader
的 vtable 指针并在 vtable 中查找drop
impl。 - 如果您没有
'static
生命周期,您的T
可能包含生命周期短于'static
的引用。 lifetime bound 所说的只是T
没有这样的引用,因此可以不受限制地复制到任何地方,即使是在堆上也是如此。