Deref vs & in trait return types

Hello,

I wonder if there are any drawbacks in always using impl Deref<Target=MyType> over &MyType as return types when defining traits. To me, Deref although much more verbose seems to give implementors more flexibility as internally they can use references or smart pointers, but I am not sure if an abuse will be seen as idiomatic.

It is not idiomatic.

It doesn't make a difference if the implementer is just using smart pointers, as the implementer can get a &T into the smart pointer. It could help if the implementer is using RefCell/Mutex as they can now return a MutexGuard from the function, but don't do that kind of dance unless you really really need it.

Note also that it will make it less convenient for the user of the trait, because it is more restrictive lifetime-wise.

And the trait will not be object safe, which means it can't be used with dyn TraitName syntax.

2 Likes

Thank you for your answer. I didn't realize this pattern would make the trait non object safe. However, then, my question is how to effectively use smart pointers when the return type is a &T. Let me give a small example to better explain myself.

use std::cell::RefCell;
use std::ops::Deref;

pub trait Building {
    fn owner(&self) -> &String;
}

pub struct House {
    pub owner: RefCell<String>,
}

impl Building for House {
    fn owner(&self) -> &String{
        self.owner.borrow().deref()
    }
}

This code will refuse to compile because cannot return value referencing temporary value. The error is completely right, and that is why my first thought was changing the trait so owner returns a impl Deref<Target=String> and when implementing it for House I can directly return the Ref. This happens because House is using the smart pointer instead of a normal reference, but that is something not easy to change in the real code. Is there an idiomatic way to solve this situation?

RefCell is not a smart pointer.

The answer is that you're probably using too many ref cells. You probably shouldn't do that.

But if you must, needing to use the trait with RefCell types is a valid reason to use impl Deref.

You are describing the pattern Alice meant by

I personally like the callback approach for this, rather than returning the RAII guard from a method:

use std::cell::RefCell;

pub trait Building {
    fn use_owner<T>(&self, f: impl FnOnce(&str) -> T) -> T;
}

pub struct House {
    pub owner: RefCell<String>,
}

impl Building for House {
    fn use_owner<T>(&self, f: impl FnOnce(&str) -> T) -> T {
        f(&*self.owner.borrow())
    }
}

Playground.

1 Like

I've used that approach sometimes, to solve other situations, but it usually feels weird. It's something I could use in internal stuff, as a workaround, but I don't like it for traits meant to be used by anyone else. If you consider that more idiomatic than `Deref, I'll give it another go though. Thank you.

I think it depends on how it's used and your preference. Both can be idiomatic.