|
1 |
| -Answer: 3311 |
| 1 | +Answer: 11 |
2 | 2 | Difficulty: 1
|
3 | 3 |
|
4 | 4 | # Hint
|
5 | 5 |
|
6 |
| -All traits have an independent set of methods. |
7 |
| - |
| 6 | +`Base::method` and `Derived::method` happen to have the same name but are |
| 7 | +otherwise unrelated methods. One does not override the other. |
8 | 8 |
|
9 | 9 | # Explanation
|
10 | 10 |
|
11 |
| -The trait `Base` has a default method `method`. Its impl for `OnlyBase` overrides that default method. Default methods are basically sugar for "copy this method into each trait impl that doesn't explicitly define this method". Once you override the default method there is no way to call the original default method for that given type. So both static and dynamic dispatch on `OnlyBase` produce the number `3`, from calling the `method` from `impl Base for OnlyBase`. |
| 11 | +The two traits `Base` and `Derived` each define a trait method called `method`. |
| 12 | +These methods happen to have the same name but are otherwise unrelated methods |
| 13 | +as explained below. |
| 14 | + |
| 15 | +Both traits provide a default implementation of their trait method. Default |
| 16 | +implementations are conceptually copied into each trait impl that does not |
| 17 | +explicitly define the same method. In this case for example `impl Base for |
| 18 | +BothTraits` does not provide its own implementation of `Base::method`, which |
| 19 | +means the implementation of `Base` for `BothTraits` will use the default |
| 20 | +behavior defined by the trait i.e. `print!("1")`. |
| 21 | + |
| 22 | +Additionally, `Derived` has `Base` as a _supertrait_ which means that every type |
| 23 | +that implements `Derived` is also required to implement `Base`. The two trait |
| 24 | +methods are unrelated despite having the same name -- thus any type that |
| 25 | +implements `Derived` will have an implementation of `Derived::method` as well as |
| 26 | +an implementation of `Base::method` and the two are free to have different |
| 27 | +behavior. Supertraits are not inheritance! Supertraits are a constraint that if |
| 28 | +some trait is implemented, some other trait must also be implemented. |
| 29 | + |
| 30 | +Let's consider what happens in each of the two methods called from `main`. |
| 31 | + |
| 32 | +- `dynamic_dispatch(&BothTraits)` |
| 33 | + |
| 34 | + The argument `x` is a reference to the trait object type `dyn Base`. A |
| 35 | + _trait object_ is a little shim generated by the compiler that implements |
| 36 | + the trait with the same name by forwarding all trait method calls to trait |
| 37 | + methods of whatever type the trait object was created from. The forwarding |
| 38 | + is done by reading from a table of function pointers contained within the |
| 39 | + trait object. |
| 40 | + |
| 41 | + ```rust |
| 42 | + // Generated by the compiler. |
| 43 | + // |
| 44 | + // This is an implementation of the trait `Base` for the |
| 45 | + // trait object type `dyn Base`, which you can think of as |
| 46 | + // a struct containing function pointers. |
| 47 | + impl Base for (dyn Base) { |
| 48 | + fn method(&self) { |
| 49 | + /* |
| 50 | + Some automatically generated implementation detail |
| 51 | + that ends up calling the right type's impl of the |
| 52 | + trait method Base::method. |
| 53 | + */ |
| 54 | + } |
| 55 | + } |
| 56 | + ``` |
| 57 | + |
| 58 | + In the quiz code, `x.method()` is a call to this automatically generated |
| 59 | + method whose fully qualified name is `<dyn Base as Base>::method`. Since `x` |
| 60 | + was obtained by converting a `BothTraits` to `dyn Base`, the automatically |
| 61 | + generated implementation detail will wind up forwarding to `<BothTraits as |
| 62 | + Base>::method` which prints `1`. |
| 63 | + |
| 64 | + Hopefully it's clear from all of this that nothing here has anything to do |
| 65 | + with the unrelated trait method `Derived::method` defined by `BothTraits`. |
| 66 | + Especially notice that `x.method()` cannot be a call to `Derived::method` |
| 67 | + because `x` is of type `dyn Base` and there is no implementation of |
| 68 | + `Derived` for `dyn Base`. |
| 69 | + |
| 70 | +- `static_dispatch(&BothTraits)` |
| 71 | + |
| 72 | + At compile time we know that `x.method()` is a call to `<T as |
| 73 | + Base>::method`. Type inference within generic functions in Rust happens |
| 74 | + independently of any concrete instantiation of the generic function i.e. |
| 75 | + before we know what `T` may be, other than the fact that it implements |
| 76 | + `Base`. Thus no inherent method on the concrete type `T` or any other trait |
| 77 | + method may affect what method `x.method()` is calling. By the time that `T` |
| 78 | + is decided, it has already been determined that `x.method()` is calling `<T |
| 79 | + as Base>::method`. |
| 80 | + |
| 81 | + The generic function is instantiated with `T` equal to `BothTraits` so this |
| 82 | + is going to call `<BothTraits as Base>::method` which prints `1`. |
| 83 | + |
| 84 | +If you are familiar with C++, the behavior of this code in Rust is _different_ |
| 85 | +from the behavior of superficially analogous C++ code. In C++ the output would |
| 86 | +be `22` as seen in the following implementation. This highlights the difference |
| 87 | +between Rust's traits and supertraits vs C++'s inheritance. |
| 88 | + |
| 89 | +```cpp |
| 90 | +#include <iostream> |
| 91 | + |
| 92 | +struct Base { |
| 93 | + virtual void method() const { |
| 94 | + std::cout << "1"; |
| 95 | + } |
| 96 | +}; |
| 97 | + |
| 98 | +struct Derived: Base { |
| 99 | + void method() const { |
| 100 | + std::cout << "2"; |
| 101 | + } |
| 102 | +}; |
| 103 | + |
| 104 | +void dynamic_dispatch(const Base &x) { |
| 105 | + x.method(); |
| 106 | +} |
| 107 | + |
| 108 | +template <typename T> |
| 109 | +void static_dispatch(const T x) { |
| 110 | + x.method(); |
| 111 | +} |
12 | 112 |
|
13 |
| -While subtraits _can_ define methods conflicting with the base trait, these are _independent_ methods, and do not override the original. Trait inheritance does not override methods, trait inheritance is a way of saying "all implementors of this trait _must_ implement the parent trait". Both `stat()` and `dynamic()` refer to the trait `Base`, so we look for a `method()` from `impl Base for OnlyBase`, which turns out to be the default method, so both these calls produce `1`. |
| 113 | +int main() { |
| 114 | + dynamic_dispatch(Derived{}); |
| 115 | + static_dispatch(Derived{}); |
| 116 | +} |
| 117 | +``` |
0 commit comments