-
Notifications
You must be signed in to change notification settings - Fork 290
Add cloning example for dot operator behaviour #292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
50132d9
Add cloning example for dot operator behaviour
zeramorphic d829c51
Outline method lookup semantics
zeramorphic f4653e1
Move links to bottom
zeramorphic d0a45b7
Apply suggestions from code review
zeramorphic f9a9c9f
Apply suggestions from code review
zeramorphic ad27976
One sentence per line; "we" => "compiler/it"
zeramorphic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>`. | ||
zeramorphic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[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 |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.