diff --git a/src/dot-operator.md b/src/dot-operator.md index a1fc33b..6ebff27 100644 --- a/src/dot-operator.md +++ b/src/dot-operator.md @@ -4,3 +4,62 @@ The dot operator will perform a lot of magic to convert types. It will perform auto-referencing, auto-dereferencing, and coercion until types match. TODO: steal information from http://stackoverflow.com/questions/28519997/what-are-rusts-exact-auto-dereferencing-rules/28552082#28552082 + +Consider the following example of the dot operator at work. +```rust.ignore +fn do_stuff(value: &T) { + let cloned = value.clone(); +} +``` +What type is `cloned`? First, the compiler checks if we can call by value. +The type of `value` is `&T`, and so the `clone` function has signature +`fn clone(&T) -> T`. We know that `T: Clone`, so the compiler finds that +`cloned: T`. + +What would happen if the `T: Clone` restriction was removed? We 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 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 behaviour is used to create some subtle +effects. +```rust.ignore +use std::sync::Arc; + +#[derive(Clone)] +struct Container(Arc); + +fn clone_containers(foo: &Container, bar: &Container) { + let foo_cloned = foo.clone(); + let bar_cloned = bar.clone(); +} +``` +What types are `foo_cloned` and `bar_cloned`? We know that `Container: Clone`, +so the compiler calls `clone` by value to give `foo_cloned: Container`. +However, `bar_cloned` actually has type `&Container`. 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 Clone for Container where T: Clone { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } +} +``` +The derived `Clone` implementation is +[only defined where `T: Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html#derivable), +so there is no implementation for `Container: Clone` for a generic `T`. The +compiler then looks to see if `&Container` implements `Clone`, which it does. +So it deduces that `clone` is called by autoref, and so `bar_cloned` has type +`&Container`. + +We can fix this by implementing `Clone` manually without requiring `T: Clone`. +```rust.ignore +impl Clone for Container { + fn clone(&self) -> Self { + Self(Arc::clone(&self.0)) + } +} +``` +Now, the type checker deduces that `bar_cloned: Container`.