为newtypes实现Deref被认为是一种不好的做法吗?

时间:2017-07-13 16:41:23

标签: rust dereference

我经常使用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
}

这是一种好的还是坏的做法?为什么?可能是什么缺点?

2 个答案:

答案 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

  1. actix_web::web::Json<T>(T,) 的元组结构,它是 implements Deref<Target=T>

  2. bstr::BString 有一个字段类型为 Vec<u8>,它是 implements Deref<Target=Vec<u8>>

所以,也许只要不被滥用就可以了,例如模拟多级继承层次结构。我还注意到上面的两个例子要么有零个公共方法,要么只有一个返回内部值的 into_inner 方法。保持包装器类型的方法数量最少似乎是个好主意。