Is there a difference between Deref and inheritance in OOP languages?

The Deref trait allows user to access fields and methods of its Target. Is it just basically the same as class inheritance in object oriented languages?

Yes, there is a difference. Deref is just a method that returns one of the fields of the struct. It's just that in some cases, it is called automatically for you.

For example, with inheritance, you can override methods.

2 Likes

An important difference is that implementing Deref (or even DerefMut) does not create a subtyping relationship, so for example U: Deref<Target = T> does not let you assign a value of type U to a variable of type T, or pass an owned U to a function that's expecting an argument of type T. By contrast, in a language with inheritance, if Y is a class that inherits from X then you can generally use an instance of Y anywhere an instance of X is expected (Liskov substitution principle).

8 Likes

Additionally, they have a conceptually different meaning. While X inheriting Y means that "X is a type of Y", X dereferencing to Y means that "X is a wrapper around Y" . For example you would never say "String is a type of str" - String is just a specific way of storing a str.

Also, I'm not sure whether its possible to inherit a generic type in OO languages, but its certainly possible to dereference to a generic type (e.g. Box, Rc, Arc, ManuallyDrop).

2 Likes

I know of one example where you can inherit from a generic: C++. And I can tell you, it's a mess. It leads to abomimations like the Curiously Recurring Template Pattern, when you tie generics to inheritance then back to generics, creating a loop in spacetime out of infinite-mass types, where good taste is sucked in and gets lost forever.

8 Likes

It does get a bit more complex than that though depending on how a given function you might be calling is written. The following compiles and just prints 24, for example:

use std::ops::Deref;

struct DerefExample<T> {
    value: T,
}

impl<T> Deref for DerefExample<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.value
    }
}

fn bloop(i: &i32) {
    println!("{}", i);
}

fn main() {
    let y = DerefExample { value: 24 };
    bloop(&y);
}

Sure, that's why I said "a function that's expecting an argument of type T"; when the function expects &T or &mut T it's a different story.

Yeah, that's fair enough.

Tell us what you really think. Don’t hold back :))

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.