|
1 | 1 | # The Dot Operator
|
2 | 2 |
|
3 |
| -The dot operator will perform a lot of magic to convert types. It will perform |
4 |
| -auto-referencing, auto-dereferencing, and coercion until types match. |
| 3 | +The dot operator will perform a lot of magic to convert types. |
| 4 | +It will perform auto-referencing, auto-dereferencing, and coercion until types |
| 5 | +match. |
| 6 | +The detailed mechanics of method lookup are defined [here][method_lookup], |
| 7 | +but here is a brief overview that outlines the main steps. |
5 | 8 |
|
6 |
| -TODO: steal information from http://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules/28552082#28552082 |
| 9 | +Suppose we have a function `foo` that has a receiver (a `self`, `&self` or |
| 10 | +`&mut self` parameter). |
| 11 | +If we call `value.foo()`, the compiler needs to determine what type `Self` is before |
| 12 | +it can call the correct implementation of the function. |
| 13 | +For this example, we will say that `value` has type `T`. |
| 14 | + |
| 15 | +We will use [fully-qualified syntax][fqs] to be more clear about exactly which |
| 16 | +type we are calling a function on. |
| 17 | + |
| 18 | +- First, the compiler checks if it can call `T::foo(value)` directly. |
| 19 | +This is called a "by value" method call. |
| 20 | +- If it can't call this function (for example, if the function has the wrong type |
| 21 | +or a trait isn't implemented for `Self`), then the compiler tries to add in an |
| 22 | +automatic reference. |
| 23 | +This means that the compiler tries `<&T>::foo(value)` and `<&mut T>::foo(value)`. |
| 24 | +This is called an "autoref" method call. |
| 25 | +- If none of these candidates worked, it dereferences `T` and tries again. |
| 26 | +This uses the `Deref` trait - if `T: Deref<Target = U>` then it tries again with |
| 27 | +type `U` instead of `T`. |
| 28 | +If it can't dereference `T`, it can also try _unsizing_ `T`. |
| 29 | +This just means that if `T` has a size parameter known at compile time, it "forgets" |
| 30 | +it for the purpose of resolving methods. |
| 31 | +For instance, this unsizing step can convert `[i32; 2]` into `[i32]` by "forgetting" |
| 32 | +the size of the array. |
| 33 | + |
| 34 | +Here is an example of the method lookup algorithm: |
| 35 | + |
| 36 | +```rust,ignore |
| 37 | +let array: Rc<Box<[T; 3]>> = ...; |
| 38 | +let first_entry = array[0]; |
| 39 | +``` |
| 40 | + |
| 41 | +How does the compiler actually compute `array[0]` when the array is behind so |
| 42 | +many indirections? |
| 43 | +First, `array[0]` is really just syntax sugar for the [`Index`][index] trait - |
| 44 | +the compiler will convert `array[0]` into `array.index(0)`. |
| 45 | +Now, the compiler checks to see if `array` implements `Index`, so that it can call |
| 46 | +the function. |
| 47 | + |
| 48 | +Then, the compiler checks if `Rc<Box<[T; 3]>>` implements `Index`, but it |
| 49 | +does not, and neither do `&Rc<Box<[T; 3]>>` or `&mut Rc<Box<[T; 3]>>`. |
| 50 | +Since none of these worked, the compiler dereferences the `Rc<Box<[T; 3]>>` into |
| 51 | +`Box<[T; 3]>` and tries again. |
| 52 | +`Box<[T; 3]>`, `&Box<[T; 3]>`, and `&mut Box<[T; 3]>` do not implement `Index`, |
| 53 | +so it dereferences again. |
| 54 | +`[T; 3]` and its autorefs also do not implement `Index`. |
| 55 | +It can't dereference `[T; 3]`, so the compiler unsizes it, giving `[T]`. |
| 56 | +Finally, `[T]` implements `Index`, so it can now call the actual `index` function. |
| 57 | + |
| 58 | +Consider the following more complicated example of the dot operator at work: |
| 59 | + |
| 60 | +```rust |
| 61 | +fn do_stuff<T: Clone>(value: &T) { |
| 62 | + let cloned = value.clone(); |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +What type is `cloned`? |
| 67 | +First, the compiler checks if it can call by value. |
| 68 | +The type of `value` is `&T`, and so the `clone` function has signature |
| 69 | +`fn clone(&T) -> T`. |
| 70 | +It knows that `T: Clone`, so the compiler finds that `cloned: T`. |
| 71 | + |
| 72 | +What would happen if the `T: Clone` restriction was removed? It would not be able |
| 73 | +to call by value, since there is no implementation of `Clone` for `T`. |
| 74 | +So the compiler tries to call by autoref. |
| 75 | +In this case, the function has the signature `fn clone(&&T) -> &T` since |
| 76 | +`Self = &T`. |
| 77 | +The compiler sees that `&T: Clone`, and then deduces that `cloned: &T`. |
| 78 | + |
| 79 | +Here is another example where the autoref behavior is used to create some subtle |
| 80 | +effects: |
| 81 | + |
| 82 | +```rust |
| 83 | +# use std::sync::Arc; |
| 84 | +# |
| 85 | +#[derive(Clone)] |
| 86 | +struct Container<T>(Arc<T>); |
| 87 | + |
| 88 | +fn clone_containers<T>(foo: &Container<i32>, bar: &Container<T>) { |
| 89 | + let foo_cloned = foo.clone(); |
| 90 | + let bar_cloned = bar.clone(); |
| 91 | +} |
| 92 | +``` |
| 93 | + |
| 94 | +What types are `foo_cloned` and `bar_cloned`? |
| 95 | +We know that `Container<i32>: Clone`, so the compiler calls `clone` by value to give |
| 96 | +`foo_cloned: Container<i32>`. |
| 97 | +However, `bar_cloned` actually has type `&Container<T>`. |
| 98 | +Surely this doesn't make sense - we added `#[derive(Clone)]` to `Container`, so it |
| 99 | +must implement `Clone`! |
| 100 | +Looking closer, the code generated by the `derive` macro is (roughly): |
| 101 | + |
| 102 | +```rust,ignore |
| 103 | +impl<T> Clone for Container<T> where T: Clone { |
| 104 | + fn clone(&self) -> Self { |
| 105 | + Self(Arc::clone(&self.0)) |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +The derived `Clone` implementation is [only defined where `T: Clone`][clone], |
| 111 | +so there is no implementation for `Container<T>: Clone` for a generic `T`. |
| 112 | +The compiler then looks to see if `&Container<T>` implements `Clone`, which it does. |
| 113 | +So it deduces that `clone` is called by autoref, and so `bar_cloned` has type |
| 114 | +`&Container<T>`. |
| 115 | + |
| 116 | +We can fix this by implementing `Clone` manually without requiring `T: Clone`: |
| 117 | + |
| 118 | +```rust,ignore |
| 119 | +impl<T> Clone for Container<T> { |
| 120 | + fn clone(&self) -> Self { |
| 121 | + Self(Arc::clone(&self.0)) |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +Now, the type checker deduces that `bar_cloned: Container<T>`. |
| 127 | + |
| 128 | +[fqs]: ../book/ch19-03-advanced-traits.html#fully-qualified-syntax-for-disambiguation-calling-methods-with-the-same-name |
| 129 | +[method_lookup]: https://rustc-dev-guide.rust-lang.org/method-lookup.html |
| 130 | +[index]: ../std/ops/trait.Index.html |
| 131 | +[clone]: ../std/clone/trait.Clone.html#derivable |
0 commit comments