Closed bamarb closed 4 years ago
Concrete types aren't special, which is why your example compiles. No concrete type overlaps with any other concrete type. X
, &X
, and &mut X
in your example are all unique separate distinct types.
However, I think I understand where your confusion is coming from, because the program below compiles:
#[derive(Copy, Clone)]
struct Struct;
trait Printable {
fn print(self);
}
impl Printable for Struct {
fn print(self) {
println!("struct");
}
}
fn main() {
let s = Struct;
s.print(); // prints "struct"
let s = Struct;
let s_ref = &s;
s_ref.print(); // prints "struct"
let mut s = Struct;
let s_mut_ref = &mut s;
s_mut_ref.print(); // prints "struct"
}
Despite only implementing Printable
for Struct
it looks like we got implementations for &Struct
and &mut Struct
for free... or did we? Lets test our hypothesis by attempting to desugar the above example:
#[derive(Copy, Clone)]
struct Struct;
trait Printable {
fn print(self);
}
impl Printable for Struct {
fn print(self) {
println!("struct");
}
}
fn main() {
let s = Struct;
Struct::print(s); // prints "struct"
let s = Struct;
let s_ref = &s;
<&Struct>::print(s_ref); // compile error
let mut s = Struct;
let s_mut_ref = &mut s;
<&mut Struct>::print(s_mut_ref); // compile error
}
Throws these errors:
error[E0599]: no function or associated item named `print` found for reference `&Struct` in the current scope
--> src/main.rs:20:16
|
20 | <&Struct>::print(s_ref);
| ^^^^^ function or associated item not found in `&Struct`
|
= help: items from traits can only be used if the trait is implemented and in scope
note: `Printable` defines an item `print`, perhaps you need to implement it
--> src/main.rs:4:1
|
4 | trait Printable {
| ^^^^^^^^^^^^^^^
error[E0599]: no function or associated item named `print` found for mutable reference `&mut Struct` in the current scope
--> src/main.rs:24:20
|
24 | <&mut Struct>::print(s_mut_ref);
| ^^^^^ function or associated item not found in `&mut Struct`
|
= help: items from traits can only be used if the trait is implemented and in scope
note: `Printable` defines an item `print`, perhaps you need to implement it
--> src/main.rs:4:1
|
4 | trait Printable {
| ^^^^^^^^^^^^^^^
Rust is complaining that there's no implementations of Printable
for &Struct
and &mut Struct
but then how did our previous program work!? It works because of a special Rust feature called deref coercion. To quote some authoritative sources:
from Implicit Deref Coercions with Functions and Methods in The Rust Book
Deref coercion is a convenience that Rust performs on arguments to functions and methods. Deref coercion works only on types that implement the
Deref
trait. Deref coercion converts such a type into a reference to another type. For example, deref coercion can convert&String
to&str
becauseString
implements theDeref
trait such that it returnsstr
. Deref coercion happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition. A sequence of calls to thederef
method converts the type we provided into the type the parameter needs.Deref coercion was added to Rust so that programmers writing function and method calls don’t need to add as many explicit references and dereferences with
&
and*
. The deref coercion feature also lets us write more code that can work for either references or smart pointers.
from Method-call expressions in The Rust Reference
When looking up a method call, the receiver may be automatically dereferenced or borrowed in order to call a method. This requires a more complex lookup process than for other functions, since there may be a number of possible methods to call. The following procedure is used:
The first step is to build a list of candidate receiver types. Obtain these by repeatedly dereferencing the receiver expression's type, adding each type encountered to the list, then finally attempting an unsized coercion at the end, and adding the result type if that is successful. Then, for each candidate
T
, add&T
and&mut T
to the list immediately afterT
.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 onT
).- Any of the methods provided by a visible trait implemented by
T
. IfT
is a type parameter, methods provided by trait bounds onT
are looked up first. Then all remaining methods in scope are looked up.If this results in multiple possible candidates, then it is an error, and the receiver must be converted to an appropriate receiver type to make the method call.
For our example we're only interested in deref coercions that happen at method calls, but if you're interested in knowing the full exhaustive list of potential coercion sites you should read Type coercions in The Rust Reference. Anyway, armed with our new knowledge of defer coercions lets attempt to desugar our example program again:
#[derive(Copy, Clone)]
struct Struct;
trait Printable {
fn print(self);
}
impl Printable for Struct {
fn print(self) {
println!("struct");
}
}
fn main() {
let s = Struct;
Struct::print(s); // prints "struct"
let s = Struct;
let s_ref = &s;
Struct::print(*s_ref); // prints "struct"
let mut s = Struct;
let s_mut_ref = &mut s;
Struct::print(*s_mut_ref); // prints "struct"
}
As we can now see, only the implementation of Printable
for Struct
is used for all the method calls, since when we call the print
method on instances of &Struct
and &mut Struct
Rust is coercing those types into Struct
s at the point of the method call via a deref operation.
The key thing to understand here is that providing an implementation of Printable
for Struct
does not also provide an implementation for &Struct
or &mut Struct
so there's no conflict if we want to add separate distinct implementations for those types like so:
#[derive(Copy, Clone)]
struct Struct;
trait Printable {
fn print(self);
}
impl Printable for Struct {
fn print(self) {
println!("struct");
}
}
impl Printable for &Struct {
fn print(self) {
println!("borrowed struct");
}
}
impl Printable for &mut Struct {
fn print(self) {
println!("mut borrowed struct");
}
}
fn main() {
let s = Struct;
s.print(); // prints "struct"
let s = Struct;
let s_ref = &s;
s_ref.print(); // prints "borrowed struct"
let mut s = Struct;
let s_mut_ref = &mut s;
s_mut_ref.print(); // prints "mut borrowed struct"
}
Above program but desugared:
#[derive(Copy, Clone)]
struct Struct;
trait Printable {
fn print(self);
}
impl Printable for Struct {
fn print(self) {
println!("struct");
}
}
impl Printable for &Struct {
fn print(self) {
println!("borrowed struct");
}
}
impl Printable for &mut Struct {
fn print(self) {
println!("mut borrowed struct");
}
}
fn main() {
let s = Struct;
Struct::print(s); // prints "struct"
let s = Struct;
let s_ref = &s;
<&Struct>::print(s_ref); // prints "borrowed struct"
let mut s = Struct;
let s_mut_ref = &mut s;
<&mut Struct>::print(s_mut_ref); // prints "mut borrowed struct"
}
Key Takeaways
Struct
does not provide implementations for &Struct
and &mut Struct
but you can still call trait methods on instances of &Struct
and &mut Struct
because Rust will coerce them into Struct
s at the point of the method call using a deref operation and then the Struct
implementation of the trait methods will be usedStruct
, &Struct
, and &mut Struct
are unique separate distinct concrete types you can implement the same trait for all of them without any overlapping implementation conflictsT
, &T
, and &mut T
since as explained in Common Rust Lifetime Misconceptions T
overlaps both &T
and &mut T
Let me know if that helps.
Thanks a lot for taking time and explaining. The de-sugaring cleared it up for me. Thanks for the deref coercion and method lookup process pointers.
In Misconception-1: T only contains owned types. It is mentioned that:
The above is True , but why does this work for concrete types:
This compiles fine .
So concrete types are special ?. Aren't generic types monomorphised to concrete types, i am confused.