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

Fre*_*ios 24 dereference rust

我经常使用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
}
Run Code Online (Sandbox Code Playgroud)

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

She*_*ter 20

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

因为在某些情况下我可以使用我的newtype,就好像它是底层类型一样

这就是问题 - 只要引用它就可以隐式地用作底层类型.如果实现DerefMut,则在需要可变引用时也适用.

您无法控制基础类型的内容和内容; 一切都是.在你的例子中,你想让人们打电话as_ptr吗?怎么样sort?我当然希望你这样做,因为他们可以!

关于你所能做的就是尝试覆盖方法,但它们仍然必须存在:

impl MyArray {
    fn as_ptr(&self) -> *const i32 {
        panic!("No, you don't!")
    }
}
Run Code Online (Sandbox Code Playgroud)

即使这样,它们仍然可以被明确地调用(<[i32]>::as_ptr(&*my_array);).

我认为这是不好的做法,因为我认为使用继承代码重用是不好的做法.在您的示例中,您基本上是从数组继承.我永远不会写类似下面的Ruby:

class MyArray < Array
  # ...
end
Run Code Online (Sandbox Code Playgroud)

这回到了is-a并且具有面向对象建模的概念.是MyArray 阵列?它应该可以在阵列可以使用的任何地方使用吗?是否有先决条件,对象应该坚持消费者不应该破坏?

但我厌倦了写作 my_type.0.call_to_whatever(...)

与其他语言一样,我认为正确的解决方案是继承的组合.如果您需要转发呼叫,请在newtype上创建一个方法:

impl MyArray {
    fn call_to_whatever(&self) { self.0.call_to_whatever() } 
}
Run Code Online (Sandbox Code Playgroud)

使Rust痛苦的主要原因是缺乏授权.一个假想的代表团语法是像

impl MyArray {
    delegate call_to_whatever -> self.0; 
}
Run Code Online (Sandbox Code Playgroud)

那么什么时候应该使用Deref/ DerefMut?我主张唯一有意义的是你实现一个智能指针.


实际上,我确实使用Deref/ DerefMut用于在我作为唯一或多数贡献者的项目中不公开暴露的新类型.这是因为我相信自己,并且对我的意思有很好的了解.如果存在委托语法,我就不会.

  • 我不得不反对,至少在'Deref`方面 - 我的大部分新类型仅仅作为花哨的构造函数存在,因此我可以通过静态保证传递数据,使其满足某些不变量.即,一旦构造了对象,我就不再真正关心newtype,_only_底层数据; 必须模式匹配/".0"到处都只是噪音,委托我可能关心的每一种方法也是如此.我认为有一个类型工具`Deref`而不是'DerefMut`可能会令人惊讶,但毕竟它们是一个独立的特征...... (11认同)
  • @ildjarn *具有满足某些不变量的静态保证* - 如果您实现`DerefMut`,则不能再静态地保证这些不变量,因为任何人都可以微不足道地更改它们,而不管 newtype 字段的可见性如何。如果你只实现了 `Deref`,你仍然允许人们查看你的数据。这不应该造成任何实质性损害,但通常会提供比您需要公开的更广泛的 API。 (4认同)
  • "*这不应该造成任何物质伤害,但通常会提供比你需要暴露更广泛的API.*"不比`std :: str` IMO更多; 例如,在协议工作中,你经常处理原始类型的序列,其中隐藏(/试图抽象)这个事实是毫无意义的,_但是有严格的不变量来维护(参见UTF-8).我对此并不感到强烈; 我只是觉得"糟糕的做法"是相当强烈的.: - ](编辑:如果一个人可以使`deref_mut`不安全,那么我可能会感到强烈,因为没有'Deref` sans`DerefMut`难题.) (3认同)
  • @ildjarn 我认为这是一个有趣的例子。`std::str` 确实*不*实现 `Deref&lt;Target = [u8]&gt;`,但内部仅此而已。`str` *不是* 字节片,但它*有* * 字节片。听起来,我没有像你做过那么多底层工作,但我发现强迫自己添加委托会让我评估每个委托,反过来又会让我围绕细节开发更高级别的 API (位技巧 -&gt; `read_le_u8` -&gt; `read_msg`)。 (2认同)
  • 我认为此链接非常适合您的答案:https://rust-lang-nursery.github.io/api-guidelines/predictability.html#only-smart-pointers-implement-deref-and-derefmut-c-解除引用 (2认同)
  • “这又回到了面向对象建模中的 is-a 和 has-a 概念。MyArray 是数组吗?它应该能够在数组可以使用的任何地方使用吗?它是否有一个先决条件,即该对象应该坚持消费者不应该能够破坏?`可能有点晚了,但新类型实际上是针对“is-a”情况......你只有在这样做时才使用它想要一个充当旧类型的新类型。如果公开包装类型的所有功能是不安全的(不是 rust 类型的不安全),则应使用通用组合,而不是 newtype 模式。你的担忧是正确的,但理由是错误的。 (2认同)

Dan*_*iel 14

与公认的答案相反,我发现一些流行的板条箱实现了Deref新类型而不是智能指针的类型:

  1. actix_web::web::Json<T>是一个元组结构(T,)并且它实现了Deref<Target=T>.

  2. bstr::BString输入一个字段Vec<u8>并实现Deref<Target=Vec<u8>>.

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

  • 虽然在流行的板条箱中使用不一定是“最佳实践”的一个很好的论据,但我同意 actix 的“Json”*应该*是“Deref”,它只是作为框架其余部分的标记,并且应该是对用户的代码尽可能透明。 (4认同)