Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I often use the newtype pattern, but I am tired of writing my_type.0.call_to_whatever(...). I am tempted to implement the Deref trait because it permits writing simpler code since I can use my newtype as if it were the underlying type in some situations, e.g.:

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
}

Is this a good or bad practice? Why? What can be the downsides?

like image 698
Boiethios Avatar asked Jul 13 '17 16:07

Boiethios


2 Answers

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


I think it's a bad practice.

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

That's the problem — it can be implicitly used as the underlying type whenever a reference is. If you implement DerefMut, then it also applies when a mutable reference is needed.

You don't have any control over what is and what is not available from the underlying type; everything is. In your example, do you want to allow people to call as_ptr? What about sort? I sure hope you do, because they can!

About all you can do is attempt to overwrite methods, but they still have to exist:

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

Even then, they can still be called explicitly (<[i32]>::as_ptr(&*my_array);).

I consider it bad practice for the same reason I believe that using inheritance for code reuse is bad practice. In your example, you are essentially inheriting from an array. I'd never write something like the following Ruby:

class MyArray < Array
  # ...
end

This comes back to the is-a and has-a concepts from object-oriented modeling. Is MyArray an array? Should it be able to be used anywhere an array can? Does it have preconditions that the object should uphold that a consumer shouldn't be able to break?

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

Like in other languages, I believe the correct solution is composition over inheritance. If you need to forward a call, create a method on the newtype:

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

The main thing that makes this painful in Rust is the lack of delegation. A hypothetical delegation syntax could be something like

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

While waiting for first-class delegation, we can use crates like delegate or ambassador to help fill in some of the gaps.

So when should you use Deref / DerefMut? I'd advocate that the only time it makes sense is when you are implementing a smart pointer.


Speaking practically, I do use Deref / DerefMut for newtypes that are not exposed publicly on projects where I am the sole or majority contributor. This is because I trust myself and have good knowledge of what I mean. If delegation syntax existed, I wouldn't.

like image 76
Shepmaster Avatar answered Sep 26 '22 03:09

Shepmaster


Contrary to the accepted answer, I found out that some popular crates implement Deref for types which are newtypes and aren't smart pointers:

  1. actix_web::web::Json<T> is a tuple struct of (T,) and it implements Deref<Target=T>.

  2. bstr::BString has one field typed Vec<u8> and it implements Deref<Target=Vec<u8>>.

So, maybe it's fine as long as it's not abused, e.g. to simulate multi-level inheritance hierarchies. I also noticed that the two examples above have either zero public methods or only one into_inner method which returns the inner value. It seems then a good idea to keep the number of methods of a wrapper type minimal.

like image 32
Daniel Avatar answered Sep 26 '22 03:09

Daniel