以下是您可能遇到的一些更常见的示例。我确信还有其他内容我忘记了,但这些希望对您将来阅读 Rust 代码时有所帮助。
创建结构来保存特征的不同实现
您可能会发现自己处于这样一种情况:能够使用自定义处理程序会很好,但又希望避免在每个结构中存储函数的开销。一个简单的替代方案是为其创建一个特征,并定义类型,其唯一目的是在其上实现不同版本的特征。
通过这种方式,您可以像编译时类型修饰符一样使用它们,从而可以在以后轻松交换或重新定义核心功能,而无需任何额外的开销或需要将额外的信息存储在结构中。
trait Smoothing {
fn smooth(a: i32, b: i32) -> i32;
}
struct LinearStrategy;
impl Smoothing for LinearStrategy {
fn smooth(a: i32, b: i32) -> i32 {
(a + b) / 2
}
}
struct GeometricStrategy;
impl Smoothing for GeometricStrategy {
fn smooth(a: i32, b: i32) -> i32 {
i32::sqrt(a * a + b * b)
}
}
struct ComplexStruct<T> {
/* etc. */
_phantom: PhantomData<T>,
}
// Change how ComplexStruct operates at compile time
impl<T: Smoothing> ComplexStruct<T> {
pub fn sample(&self, x: i32) -> i32 {
T::smooth(self.raw_sample(x - 1), self.raw_sample(x + 1))
}
}
我能找到的最好的例子可能是Vec
。乍一看可能不是这样,但是Vec
有 2 个类型参数。在Vec<T, A = Global>
, the A
是使用的分配器Vec
。默认情况下,它设置为全局分配器,但在某些情况下,能够轻松地将其与其他东西切换,并且仍然可以访问所有正常功能,这真的很方便。Vec
.
FFI 的占位符
当为 C/其他库创建 rust api 时,添加类型来镜像 C api 而不包含相同的数据可能是有意义的。通常情况下,最终看起来像这样,其中指针被安全的 rust 替代方案和占位符生命周期包装(因为 rust 不拥有此数据)。
pub struct Foo<'a> {
ptr: *mut sys::Foo,
_phantom: PhantomData<&'a ()>,
}
但是,在需要强制为资源调用构造函数/析构函数的情况下,使用结构体也可能有意义。由于 Rust 调用结构体drop
当函数超出范围然后从内存中删除时,执行这些规则非常容易。在这种情况下,通过拥有一个结构体,我们可以强制满足一些先决条件,以获得对结构体中函数的访问。
pub struct FooApi;
impl FooApi {
pub fn new() -> Self {
unsafe { sys::init_thread_foo(); }
FooApi
}
/// Some call that is only safe if sys::init_thread_foo() has been called
pub fn do_something(&self) { /* ... */}
}
/// Call FFI function to dispose of this resource once this FooApi is no longer needed
impl Drop for FooApi {
fn drop(&mut self) {
unsafe {
sys::dispose_thread_foo();
}
}
}
冗长
有时结构可能会被模块替换。然而,根据开发人员的不同,在考虑对对象进行操作更具有概念意义的情况下,他们可能更喜欢使用结构体。通常,这些情况相当罕见,并且通常表明某个类型之前或将来将被分成特征并泛化。或者,它也可以用于有几个等效但不相同的替代方案可以在之间交换的情况(例如:CPU 类型)。
到目前为止,这些是我想到的几个原因,但我可能会回来补充更多。