Per this question, if you implement Deref<Target = T>
on a type U
, you can call T
's inherent methods (the methods that are defined in impl T { ... }
) on expressions of type U
, but you cannot call methods on traits defined for T
.
pub struct Thing(String);
impl Thing {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
}
However, when I try to square this with the Rust Reference section on method call resolution, I get confused. It says the steps to find available methods are:
Obtain [a list of candidate receiver types] by repeatedly dereferencing the receiver expression’s type, ... then, for each candidate type T, search for a visible method with a receiver of that type in the following places:
- T’s inherent methods (methods implemented directly on T).
- Any of the methods provided by a visible trait implemented by T.
Part 2 sounds like it should disagree with the answer of the question I linked.
When I try this out (playground:
use std::ops::Deref;
struct Thing(String);
impl Thing {
pub fn new(s: String) -> Self {
Self(s)
}
}
impl Deref for Thing {
type Target = String;
fn deref(&self) -> &String {
&self.0
}
}
fn main() {
let thing = Thing::new(" jup ".to_string());
// I can call trim on a Thing, because of deref-coercion.
println!("{}", thing.trim());
// I cannot call fmt, because that is a trait implemented on the inner type.
// println!("{}", thing);
// But, I can call clone...?
println!("{}", thing.clone());
}
I do indeed find that my outer type doesn't act like it implements Display, even though the inner one does, but I do find that it acts like it implements Clone
.
What am I not understanding here? The Rust Reference section I quoted seems like it should find String
as a candidate type when I call methods on Thing
objects, and I believe both Clone
and Display
are "visible traits implemented by" String
. Yet the question I linked seems to suggest neither should be available (although it doesn't say exactly the same thing - it just says that Thing
doesn't implement Clone
or Display
, maybe that's different from being able to call those traits' methods on expressions of type Thing
).
When you invoke a method using dot-syntax, deref coercion applies, and so you can invoke methods of the type you deref to. This can even be applied multiple times, which is why you can call trim
, which is on str
, not String
, because Thing
derefs to String
, and String
derefs to str
.
Under the hood, println!()
is doing something like Display::fmt(&thing, _)
and deref coercion doesn't work out the way you would think it should here. It might also use a function that bounds on Display
, and bounds do not trigger deref coercion:
fn assert_display(_: &impl Display) {}
fn example(thing: &Thing) {
assert_display(thing); // compile error
}
This would fail even for e.g. impl Clone
; Display
is not special here.
If you bring Display
into scope (use std::fmt::Display;
) you will find that you can invoke fmt
on it!
fn example(thing: &Thing, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use std::fmt::Display;
thing.fmt(f)
}
Deref coercion generally applies whenever you take a reference to a T
and try to bind it to a reference to U
, when there is a chain of Deref
implementations from T
to U
. However, this doesn't work in the case where you are directly invoking a trait method using associated function syntax. I believe this is because when you say Display::fmt(&thing, _)
, the compiler infers that the Display
implementation being called is that of the type of thing
. Note that <String as Display>::fmt(&thing, _)
does work:
fn example(thing: &Thing, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
<String as std::fmt::Display>::fmt(thing, f)
}
This is because we told the compiler exactly which Display
implementation to use, and so it can auto-deref from &Thing
to &String
.
tl;dr: For one out of several possible reasons, deref coercion doesn't work with println!()
.