Skip to content

Commit cbeec91

Browse files
committed
Focus on how BothTraits case differs from C++
1 parent 7c1ace3 commit cbeec91

File tree

3 files changed

+125
-29
lines changed

3 files changed

+125
-29
lines changed

docs/questions.js

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

questions/027-subtrait-dispatch.md

Lines changed: 109 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,117 @@
1-
Answer: 3311
1+
Answer: 11
22
Difficulty: 1
33

44
# Hint
55

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.
88

99
# Explanation
1010

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+
}
12112

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+
```

questions/027-subtrait-dispatch.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
1-
21
trait Base {
3-
fn method(&self) {print!("1")}
2+
fn method(&self) {
3+
print!("1");
4+
}
45
}
56

67
trait Derived: Base {
7-
fn method(&self) {print!("2")}
8-
}
9-
10-
struct OnlyBase;
11-
12-
impl Base for OnlyBase {
13-
fn method(&self) {print!("3")}
8+
fn method(&self) {
9+
print!("2");
10+
}
1411
}
1512

16-
1713
struct BothTraits;
1814
impl Base for BothTraits {}
1915
impl Derived for BothTraits {}
2016

21-
// dynamic dispatch
22-
fn dynamic(x: &dyn Base) {
23-
x.method()
17+
fn dynamic_dispatch(x: &dyn Base) {
18+
x.method();
2419
}
2520

26-
// static dispatch
27-
fn stat<T: Base>(x: &T) {
21+
fn static_dispatch<T: Base>(x: T) {
2822
x.method();
2923
}
3024

3125
fn main() {
32-
dynamic(&OnlyBase);
33-
stat(&OnlyBase);
34-
dynamic(&BothTraits);
35-
stat(&BothTraits);
36-
}
26+
dynamic_dispatch(&BothTraits);
27+
static_dispatch(BothTraits);
28+
}

0 commit comments

Comments
 (0)