1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
# Dynamize
In order for a trait to be usable as a trait object it needs to fulfill
[several requirements](https://doc.rust-lang.org/reference/items/traits.html#object-safety).
For example:
```rust ignore
trait Client {
type Error;
fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}
impl Client for HttpClient { type Error = HttpError; ...}
impl Client for FtpClient { type Error = FtpError; ...}
let client: HttpClient = ...;
let object = &client as &dyn Client;
```
The last line of the above code fails to compile with:
> error\[E0191\]: the value of the associated type `Error` (from trait `Client`)
> must be specified
Sometimes you however want a trait object to be able to encompass trait
implementations with different associated type values. This crate provides an
attribute macro to achieve that. To use dynamize you only have to make some
small changes:
```rust ignore
#[dynamize::dynamize]
trait Client {
type Error: Into<SuperError>;
fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}
let client: HttpClient = ...;
let object = &client as &dyn DynClient;
```
1. You add the `#[dynamize::dynamize]` attribute to your trait.
2. You specify a trait bound for each associated type.
Dynamize defines a new trait for you, named after your trait but with the `Dyn`
prefix, so e.g. `Client` becomes `DynClient` in our example. The new
"dynamized" trait can then be used without having to specify the associated
type.
## How does this work?
For the above example dynamize generates the following code:
```rust ignore
trait DynClient {
fn get(&self, url: String) -> Result<Vec<u8>, SuperError>;
}
impl<__to_be_dynamized: Client> DynClient for __to_be_dynamized {
fn get(&self, url: String) -> Result<Vec<u8>, SuperError> {
Client::get(self, url).map_err(|x| x.into())
}
}
```
As you can see in the dynamized trait the associated type was replaced with the
destination type of the `Into` bound. The magic however happens afterwards:
dynamize generates a blanket implementation: each type implementing `Client`
automatically also implements `DynClient`!
## How does this actually work?
The destination type of an associated type is
determined by looking at its trait bounds:
* if the first trait bound is `Into<T>` the destination type is `T`
* otherwise the destination type is the boxed trait object of all trait bounds
e.g. `Error + Send` becomes `Box<dyn Error + Send>`
(for this the first trait bound needs to be object-safe)
Dynamize can convert associated types in:
* return types, e.g. `fn example(&self) -> Self::A`
* callback parameters, e.g. `fn example<F: Fn(Self::A)>(&self, f: F)`
Dynamize also understands if you wrap associated types in the following types:
* `Option<_>`
* `Result<_, _>`
* `some::module::Result<_>` (type alias with fixed error type)
* `&mut dyn Iterator<Item = _>`
* `Vec<_>`, `VecDeque<_>`, `LinkedList<_>`, `HashSet<K>`, `BinaryHeap<K>`,
`BTreeSet<K>`, `HashMap<K, _>`, `BTreeMap<K, _>`
(for `K` only `Into`-bounded associated types work because they require `Eq`)
Note that since these are resolved recursively you can actually nest these
arbitrarily so e.g. the following also just works:
```rust ignore
fn example(&self) -> Result<Option<Self::Item>, Self::Error>;
```
## How does dynamize deal with method generics?
In order to be object-safe methods must not have generics, so dynamize simply
moves them to the trait definition:
```rust
trait Gen {
fn foobar<A>(&self, a: A) -> A;
}
```
becomes
```rust
trait DynGen<A> {
fn foobar(&self, a: A) -> A;
}
```
If two method type parameters have the same name, dynamize enforces that they
also have the same bounds and only adds the parameter once to the trait.
## Dynamize supports async
Dynamize supports async out of the box. Since Rust however does not yet support
async functions in traits, you'll have to additionally use another library like
[async-trait](https://crates.io/crates/async-trait), for example:
```rust ignore
#[dynamize::dynamize]
#[dyn_trait_attr(async_trait)]
#[blanket_impl_attr(async_trait)]
#[async_trait]
trait Client: Sync {
type Error: std::error::Error + Send;
async fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}
```
* `#[dyn_trait_attr(foo)]` attaches `#[foo]` to the dynamized trait
* `#[blanket_impl_attr(foo)]` attaches `#[foo]` to the blanket implementation
Note that it is important that the `#[dynamize]` attribute comes before the
`#[async_trait]` attribute, since dynamize must run before async_trait.
|