Is implicit dereferencing safe to use?

Have a look at this short program:

use std::ops::Deref;

fn main() {
    let a = ThingOne { content: ThingTwo {} };

    a.do_something();
}


struct ThingOne {
    content: ThingTwo
}

impl Deref for ThingOne {
    type Target = ThingTwo;

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

struct ThingTwo;

impl ThingTwo {
    pub fn do_something(&self) -> () {
        println!("ThingTwo did something");
    }
}

The result is:

ThingTwo did something

Now, what if ThingOne comes from some external library, and one day, the author decides to extend this type as follows:

impl ThingOne {
    pub fn do_something(&self) -> () {
        println!("ThingOne did something");
    }
}

It's a non-breaking change, so the version number could see a minor semver bump. However, now the result of my code's execution is:

ThingOne did something

Is it safe to rely on implicit dereferencing? Are there any best practices around that?

1 Like

Define safe. There is no unsafe here, so undefined behavior cannot directly result, for example.

Changes like this can also happen where autoderef isn't involved. Inherent methods take precedence over trait methods, for example.

For this reason, in the standard library, generic smart pointer types like Arc and Rc typically don’t have methods of their own. So you have to write Arc::make_mut(&mut arc) instead of arc.make_mut(), for example.

2 Likes

It's a possibly-breaking change. It's up to the authors of the crate to decide whether to consider that a breaking change and bump the major version or consider small breakage acceptable and do only a minor semver bump. Note that the authors of the crate must know about the impl Deref for ThingOne, so they know it will be possibly-breaking.

1 Like