mirror of https://github.com/rust-lang/nomicon
				
				
				
			Add cloning example for dot operator behaviour (#292)
	
		
	
				
					
				
			Co-authored-by: Yuki Okushi <yuki.okushi@huawei.com>pull/294/head
							parent
							
								
									7a13537f96
								
							
						
					
					
						commit
						f51734eb55
					
				@ -1,6 +1,131 @@
 | 
				
			||||
# The Dot Operator
 | 
				
			||||
 | 
				
			||||
The dot operator will perform a lot of magic to convert types. It will perform
 | 
				
			||||
auto-referencing, auto-dereferencing, and coercion until types match.
 | 
				
			||||
The dot operator will perform a lot of magic to convert types.
 | 
				
			||||
It will perform auto-referencing, auto-dereferencing, and coercion until types
 | 
				
			||||
match.
 | 
				
			||||
The detailed mechanics of method lookup are defined [here][method_lookup],
 | 
				
			||||
but here is a brief overview that outlines the main steps.
 | 
				
			||||
 | 
				
			||||
TODO: steal information from http://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules/28552082#28552082
 | 
				
			||||
Suppose we have a function `foo` that has a receiver (a `self`, `&self` or
 | 
				
			||||
`&mut self` parameter).
 | 
				
			||||
If we call `value.foo()`, the compiler needs to determine what type `Self` is before
 | 
				
			||||
it can call the correct implementation of the function.
 | 
				
			||||
For this example, we will say that `value` has type `T`.
 | 
				
			||||
 | 
				
			||||
We will use [fully-qualified syntax][fqs] to be more clear about exactly which
 | 
				
			||||
type we are calling a function on.
 | 
				
			||||
 | 
				
			||||
- First, the compiler checks if it can call `T::foo(value)` directly.
 | 
				
			||||
This is called a "by value" method call.
 | 
				
			||||
- If it can't call this function (for example, if the function has the wrong type
 | 
				
			||||
or a trait isn't implemented for `Self`), then the compiler tries to add in an
 | 
				
			||||
automatic reference.
 | 
				
			||||
This means that the compiler tries `<&T>::foo(value)` and `<&mut T>::foo(value)`.
 | 
				
			||||
This is called an "autoref" method call.
 | 
				
			||||
- If none of these candidates worked, it dereferences `T` and tries again.
 | 
				
			||||
This uses the `Deref` trait - if `T: Deref<Target = U>` then it tries again with
 | 
				
			||||
type `U` instead of `T`.
 | 
				
			||||
If it can't dereference `T`, it can also try _unsizing_ `T`.
 | 
				
			||||
This just means that if `T` has a size parameter known at compile time, it "forgets"
 | 
				
			||||
it for the purpose of resolving methods.
 | 
				
			||||
For instance, this unsizing step can convert `[i32; 2]` into `[i32]` by "forgetting"
 | 
				
			||||
the size of the array.
 | 
				
			||||
 | 
				
			||||
Here is an example of the method lookup algorithm:
 | 
				
			||||
 | 
				
			||||
```rust,ignore
 | 
				
			||||
let array: Rc<Box<[T; 3]>> = ...;
 | 
				
			||||
let first_entry = array[0];
 | 
				
			||||
```
 | 
				
			||||
 | 
				
			||||
How does the compiler actually compute `array[0]` when the array is behind so
 | 
				
			||||
many indirections?
 | 
				
			||||
First, `array[0]` is really just syntax sugar for the [`Index`][index] trait -
 | 
				
			||||
the compiler will convert `array[0]` into `array.index(0)`.
 | 
				
			||||
Now, the compiler checks to see if `array` implements `Index`, so that it can call
 | 
				
			||||
the function.
 | 
				
			||||
 | 
				
			||||
Then, the compiler checks if `Rc<Box<[T; 3]>>` implements `Index`, but it
 | 
				
			||||
does not, and neither do `&Rc<Box<[T; 3]>>` or `&mut Rc<Box<[T; 3]>>`.
 | 
				
			||||
Since none of these worked, the compiler dereferences the `Rc<Box<[T; 3]>>` into
 | 
				
			||||
`Box<[T; 3]>` and tries again.
 | 
				
			||||
`Box<[T; 3]>`, `&Box<[T; 3]>`, and `&mut Box<[T; 3]>` do not implement `Index`,
 | 
				
			||||
so it dereferences again.
 | 
				
			||||
`[T; 3]` and its autorefs also do not implement `Index`.
 | 
				
			||||
It can't dereference `[T; 3]`, so the compiler unsizes it, giving `[T]`.
 | 
				
			||||
Finally, `[T]` implements `Index`, so it can now call the actual `index` function.
 | 
				
			||||
 | 
				
			||||
Consider the following more complicated example of the dot operator at work:
 | 
				
			||||
 | 
				
			||||
```rust
 | 
				
			||||
fn do_stuff<T: Clone>(value: &T) {
 | 
				
			||||
    let cloned = value.clone();
 | 
				
			||||
}
 | 
				
			||||
```
 | 
				
			||||
 | 
				
			||||
What type is `cloned`?
 | 
				
			||||
First, the compiler checks if it can call by value.
 | 
				
			||||
The type of `value` is `&T`, and so the `clone` function has signature
 | 
				
			||||
`fn clone(&T) -> T`.
 | 
				
			||||
It knows that `T: Clone`, so the compiler finds that `cloned: T`.
 | 
				
			||||
 | 
				
			||||
What would happen if the `T: Clone` restriction was removed? It would not be able
 | 
				
			||||
to call by value, since there is no implementation of `Clone` for `T`.
 | 
				
			||||
So the compiler tries to call by autoref.
 | 
				
			||||
In this case, the function has the signature `fn clone(&&T) -> &T` since
 | 
				
			||||
`Self = &T`.
 | 
				
			||||
The compiler sees that `&T: Clone`, and then deduces that `cloned: &T`.
 | 
				
			||||
 | 
				
			||||
Here is another example where the autoref behavior is used to create some subtle
 | 
				
			||||
effects:
 | 
				
			||||
 | 
				
			||||
```rust
 | 
				
			||||
# use std::sync::Arc;
 | 
				
			||||
#
 | 
				
			||||
#[derive(Clone)]
 | 
				
			||||
struct Container<T>(Arc<T>);
 | 
				
			||||
 | 
				
			||||
fn clone_containers<T>(foo: &Container<i32>, bar: &Container<T>) {
 | 
				
			||||
    let foo_cloned = foo.clone();
 | 
				
			||||
    let bar_cloned = bar.clone();
 | 
				
			||||
}
 | 
				
			||||
```
 | 
				
			||||
 | 
				
			||||
What types are `foo_cloned` and `bar_cloned`?
 | 
				
			||||
We know that `Container<i32>: Clone`, so the compiler calls `clone` by value to give
 | 
				
			||||
`foo_cloned: Container<i32>`.
 | 
				
			||||
However, `bar_cloned` actually has type `&Container<T>`.
 | 
				
			||||
Surely this doesn't make sense - we added `#[derive(Clone)]` to `Container`, so it
 | 
				
			||||
must implement `Clone`!
 | 
				
			||||
Looking closer, the code generated by the `derive` macro is (roughly):
 | 
				
			||||
 | 
				
			||||
```rust,ignore
 | 
				
			||||
impl<T> Clone for Container<T> where T: Clone {
 | 
				
			||||
    fn clone(&self) -> Self {
 | 
				
			||||
        Self(Arc::clone(&self.0))
 | 
				
			||||
    }
 | 
				
			||||
}
 | 
				
			||||
```
 | 
				
			||||
 | 
				
			||||
The derived `Clone` implementation is [only defined where `T: Clone`][clone],
 | 
				
			||||
so there is no implementation for `Container<T>: Clone` for a generic `T`.
 | 
				
			||||
The compiler then looks to see if `&Container<T>` implements `Clone`, which it does.
 | 
				
			||||
So it deduces that `clone` is called by autoref, and so `bar_cloned` has type
 | 
				
			||||
`&Container<T>`.
 | 
				
			||||
 | 
				
			||||
We can fix this by implementing `Clone` manually without requiring `T: Clone`:
 | 
				
			||||
 | 
				
			||||
```rust,ignore
 | 
				
			||||
impl<T> Clone for Container<T> {
 | 
				
			||||
    fn clone(&self) -> Self {
 | 
				
			||||
        Self(Arc::clone(&self.0))
 | 
				
			||||
    }
 | 
				
			||||
}
 | 
				
			||||
```
 | 
				
			||||
 | 
				
			||||
Now, the type checker deduces that `bar_cloned: Container<T>`.
 | 
				
			||||
 | 
				
			||||
[fqs]: ../book/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation-calling-methods-with-the-same-name
 | 
				
			||||
[method_lookup]: https://rustc-dev-guide.rust-lang.org/method-lookup.html
 | 
				
			||||
[index]: ../std/ops/trait.Index.html
 | 
				
			||||
[clone]: ../std/clone/trait.Clone.html#derivable
 | 
				
			||||
 | 
				
			||||
					Loading…
					
					
				
		Reference in new issue