我经常使用newtype模式,但我厌倦了编写my_type.0.call_to_whatever(...)
。我很想实现Deref
特性,因为它允许编写更简单的代码,因为我可以使用我的newtype,好像它是某些情况下的基础类型,例如。:
use std::ops::Deref;
type Underlying = [i32; 256];
struct MyArray(Underlying);
impl Deref for MyArray {
type Target = Underlying;
fn deref(&self) -> &Self::Target {
&self.0
}
}
fn main() {
let my_array = MyArray([0; 256]);
println!("{}", my_array[0]); // I can use my_array just like a regular array
}
这是一种好的还是坏的做法?为什么?可能是什么缺点?
答案 0 :(得分:16)
我认为这是不良做法。
因为我可以使用我的newtype,好像它在某些情况下是基础类型
这就是问题 - 只要引用,它就可以隐含地用作基础类型。如果您实现DerefMut
,那么当需要可变引用时它也适用。
您无法控制基础类型的内容和内容;一切都是。在您的示例中,您是否希望允许其他人拨打as_ptr
?那么sort
呢?我当然希望你这样做,因为他们可以!
您可以做的就是尝试覆盖方法,但它们仍然必须存在:
impl MyArray {
fn as_ptr(&self) -> *const i32 {
panic!("No, you don't!")
}
}
即便如此,仍然可以明确地调用它们(<[i32]>::as_ptr(&*my_array);
)。
我认为这是不好的做法,因为我认为使用继承代码重用是不好的做法。在您的示例中,您基本上是从数组继承。我永远不会写类似下面的Ruby:
class MyArray < Array
# ...
end
这回到了面向对象建模的 is-a 和 has-a 概念。 MyArray
是一个数组吗?它应该可以在阵列的任何地方使用吗?是否有先决条件,该对象应该支持消费者不应该破产?
但我厌倦了写
my_type.0.call_to_whatever(...)
与其他语言一样,我认为正确的解决方案是构图而不是继承。如果您需要转发呼叫,请在newtype上创建一个方法:
impl MyArray {
fn call_to_whatever(&self) { self.0.call_to_whatever() }
}
使Rust痛苦的主要原因是缺少委托。 假设委派语法可能类似于
impl MyArray {
delegate call_to_whatever -> self.0;
}
所以当应时,您使用Deref
/ DerefMut
?我提倡只有当你实现智能指针时才有意义。
实际上,我 使用Deref
/ DerefMut
用于不的新类型,这些新类型在我是唯一或多数的项目中公开公开贡献者。这是因为我相信自己,并且对我的意思有很好的了解。如果存在委托语法,我就不会。
答案 1 :(得分:1)
与接受的答案相反,我发现一些流行的 crate 为新类型而不是智能指针的类型实现了 Deref
:
actix_web::web::Json<T>
是 (T,)
的元组结构,它是 implements Deref<Target=T>
。
bstr::BString
有一个字段类型为 Vec<u8>
,它是 implements Deref<Target=Vec<u8>>
。
所以,也许只要不被滥用就可以了,例如模拟多级继承层次结构。我还注意到上面的两个例子要么有零个公共方法,要么只有一个返回内部值的 into_inner
方法。保持包装器类型的方法数量最少似乎是个好主意。