pretzelhammer / rust-blog

Educational blog posts for Rust beginners
Apache License 2.0
7.11k stars 380 forks source link

T only contains owned types - Question ? #7

Closed bamarb closed 4 years ago

bamarb commented 4 years ago

In Misconception-1: T only contains owned types. It is mentioned that:

trait Trait {}

impl<T> Trait for T {}

impl<T> Trait for &T {} // compile error

impl<T> Trait for &mut T {} // compile error

The above is True , but why does this work for concrete types:

#[derive(Copy, Clone)]
struct X {
     val: i32,
}

 trait T {}

impl T for X {}

impl T for &X {}

impl T for &mut X {}

This compiles fine .

So concrete types are special ?. Aren't generic types monomorphised to concrete types, i am confused.

pretzelhammer commented 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 because String implements the Deref trait such that it returns str. 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 the deref 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 after T.

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

  1. T's inherent methods (methods implemented directly on T).
  2. Any of the methods provided by a visible trait implemented by T. If T is a type parameter, methods provided by trait bounds on T 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 Structs 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

Let me know if that helps.

bamarb commented 4 years ago

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.