Why would I want to implement a trait on &T or &mut T?

I recently made a post here asking for help dealing with a trait from petgraph (petgraph::visit::IntoEdgeReferences) implemented on a reference of a generic type. That issue is solved, but I'm confused as to why one would ever need to implement a trait on a reference. I've been using rust for quite a while now and I've never had such a need. So I have the following questions:

  1. Why would I ever need (or want) to implement a trait on a referenced (or mutably referenced) type?
  2. (If not answered above) How, and why would implementing a trait on a reference (or mutable reference) differ from the code below.
  3. (If not answered above) Why does petgraph do this?
trait MyTrait {
    fn take_ref(&self) {} // Takes a ref
    fn take_mut_ref(&mut self) {} // Takes a mut ref
}

impl<T> MyTrait for T {} // Why would I want &T or &mut T here?

If the method already takes the type of reference you want, there's not much reason to implement it on references. However, if a method takes self by value, you can then implement the trait for &T to avoid having to give ownership. You can't go the other way and make &self into self.

Petgraph does it because they need to use the reference's lifetime in the return type of the method. This isn't as necessary anymore since generic associated types were added, but that was pretty recent, requires the trait to be written with GATs, and there might still be some cases that GATs don't cover.

A less common thing is when a trait takes &mut self but you don't actually need mutability/exclusivity, so you implement the trait for &T. Then the methods will take &mut &T. This is what impl Read for &File is for: when doing concurrent reads, you only need to open one File.

When writing a trait, you should have methods take self by value since that's more flexible, unless:

  • A method needs to be called multiple times on the same Self value
  • You need a mix of immutable, mutable, or owned self arguments among your functions
  • You want to be able to turn the trait into a trait object
4 Likes

To give an example to what @drewtato said, look at the Add trait

pub trait Add<Rhs = Self> {
    type Output;

    // Required method
    fn add(self, rhs: Rhs) -> Self::Output;
}

It has to take self because you want to be able to use it for Copy types and I can imagine you want it to be able to consume self for some applications. But if you now want to be able to add for example large, non-copy Matrices you need to implement Add for &Matrix like this

// ignoring any generics for a possible Matrix type
impl Add for &Matrix {
    type Output = Matrix;

    fn add(self, rhs: Rhs) -> Self::Output { ... }
}

fn main() {
    let a = Matrix::<f32,100,100>::identity();
    let b = Matrix::<f32,100,100>::identity();
    let c = &a + &b;
}
4 Likes

GATs make your trait non-object-safe. (Not relevant to the motivation of IntoEdgeReferences, but is a case GATs don't cover.)


Check out the implementors of IntoIterator for some more examples of implementing traits on references.

Another use case is to make an implementation apply through any amount of &&&... nesting. Note that when you pass a &str to some_func<P: AsRef<Path>>(path: P), it works because of that blanket implementation (in combination with impl AsRef<Path> for <str>); you usually don't want or need to give up ownership when calling a function with an AsRef bound.

Another motivation is to avoid giving up ownership, not because a trait method takes self, but because some code consumes the value (e.g. by taking T: Trait by value). For a concrete example, consider the implementation of Iterator for &mut I. This allows you to do things like

let mut iter = todo!();
for x in &mut iter {
    if condition(x) { break; }
}
let _use_iter_some_more = iter.next();

even through for consumes the iterator. (Also consider that adapters wrap iterators by value, but don't always provide a method to get the inner iterator(s) back out.)

4 Likes

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.