为新类型实施 Deref 是否被认为是一种不好的做法?

Is it considered a bad practice to implement Deref for newtypes?

我经常用newtype模式,但是写腻了my_type.0.call_to_whatever(...)。我很想实现 Deref 特性,因为它允许编写更简单的代码,因为在某些情况下我可以使用我的新类型,就好像它是基础类型一样,例如:

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
}

这是好事还是坏事?为什么?缺点是什么?

the rules regarding Deref and DerefMut were designed specifically to accommodate smart pointers. Because of this, Deref should only be implemented for smart pointers to avoid confusion.

std::ops::Deref


我认为这是一个不好的做法

since I can use my newtype as if it were the underlying type in some situations

这就是问题所在 — 它可以隐式用作基础类型 只要 引用是。如果您实现 DerefMut,那么它也适用于需要可变引用的情况。

您无法控制基础类型中可用的内容和不可用的内容;一切都是。在你的例子中,你想让人们打电话给 as_ptr? What about 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-ahas-a 概念。 MyArray 是数组吗?它是否可以 数组可以 的任何地方使用?它是否具有对象应支持消费者不应该破坏的先决条件?

but I am tired of writing my_type.0.call_to_whatever(...)

与其他语言一样,我认为正确的解决方案是组合而不是继承。如果您需要转接电话,请在新类型上创建一个方法:

impl MyArray {
    fn call_to_whatever(&self) { self.0.call_to_whatever() } 
}

在 Rust 中造成这种痛苦的主要原因是缺少 委托假设的 委托语法可能类似于

impl MyArray {
    delegate call_to_whatever -> self.0; 
}

在等待第一个 class 委托时,我们可以使用像 delegate or ambassador 这样的箱子来帮助填补一些空白。

所以什么时候应该使用Deref/DerefMut?我提倡只有在您实施 智能指针 .

时才有意义

实际上,我确实使用Deref/DerefMut用于公开暴露的新类型我是唯一或主要贡献者的项目。这是因为我相信自己并且非常了解我的意思。如果存在委托语法,我不会。

与接受的答案相反,我发现一些流行的 crates 为新类型而不是智能指针的类型实现 Deref

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

  2. bstr::BString 有一个字段键入 Vec<u8> 并且 implements Deref<Target=Vec<u8>>.

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