aboutsummaryrefslogtreecommitdiff
path: root/README.md
blob: 332de4f04e877d9f9f43b9470a6fe2136d674959 (plain)
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
# 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
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
#[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 an `Into<T>` 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
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?

Dynamize recognizes the `Result<T, E>` in the return type and knows that
associated types in `T` need to be mapped with `map()` whereas associated types
in `E` need to be mapped with `map_err()`. Dynamize also understands
`Option<T>`. Thanks to recursion Dynamize can deal with arbitrarily nested
options and results, so e.g. `Result<Option<Self::Item>, Self::Error>` also
just works.

## 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
#[dynamize::dynamize]
#[dyn_trait_attr(async_trait)]
#[blanket_impl_attr(async_trait)]
#[async_trait]
trait Client: Sync {
    type Error: Into<SuperError>;

    async fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}
```

The `#[dyn_trait_attr(...)]` attribute lets you attach macro attributes to the
generated dynamized trait.  The `#[blanket_impl_attr(...)]` attribute lets you
attach macro attributes to the generated blanket implementation. Note that it
is important that the dynamize attribute comes before the `async_trait`
attribute.

## Dynamize supports Fn, FnOnce & FnMut

The following also just works:

```rust
#[dynamize::dynamize]
trait TraitWithCallback {
    type A: Into<String>;

    fn fun_with_callback<F: Fn(Self::A)>(&self, a: F);
}
```

Note that since in order to be object-safe methods must not have generics,
dynamize simply moves the generic from the method to the trait definition.