# Dynamize In order to turn a trait into a [trait object] the trait must be [object-safe] and the values of all [associated types] 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 a procedural macro to achieve that. [trait object]: https://doc.rust-lang.org/book/ch17-02-trait-objects.html [object-safe]: https://doc.rust-lang.org/reference/items/traits.html#object-safety [associated types]: https://doc.rust-lang.org/book/ch19-03-advanced-traits.html The following code illustrates a scenario where dynamize can help you: ```rust ignore trait Client { type Error; fn get(&self, url: String) -> Result, 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 To use dynamize you only have to make some small changes: ```rust ignore #[dynamize::dynamize] trait Client { type Error: Into; fn get(&self, url: String) -> Result, Self::Error>; } ``` 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`: ```rust ignore let client: HttpClient = ...; let object = &client as &dyn DynClient; ``` The new "dynamized" trait can then be used without having to specify the associated type value. ## How does this work? For the above example dynamize generates the following code: ```rust ignore trait DynClient { fn get(&self, url: String) -> Result, SuperError>; } impl<__to_be_dynamized: Client> DynClient for __to_be_dynamized { fn get(&self, url: String) -> Result, 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` the destination type is `T` * otherwise the destination type is the boxed trait object of all trait bounds e.g. `Error + Send` becomes `Box` (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(&self, f: F)` Dynamize also understands if you wrap associated types in the following types: * tuples * `Option<_>` * `Result<_, _>` * `some::module::Result<_>` (type alias with fixed error type) * `&mut dyn Iterator` * `Vec<_>`, `VecDeque<_>`, `LinkedList<_>`, `HashSet`, `BinaryHeap`, `BTreeSet`, `HashMap`, `BTreeMap` (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, 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(&self, a: A) -> A; } ``` becomes ```rust trait DynGen { 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, 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. ## Using dynamize with other collections Dynamize automatically recognizes collections from the standard library like `Vec<_>` and `HashMap<_, _>`. Dynamize can also work with other collection types as long as they implement `IntoIterator` and `FromIterator`, for example dynamize can be used with [indexmap](https://crates.io/crates/indexmap) as follows: ```rust ignore #[dynamize::dynamize] #[collection(IndexMap, 2)] trait Trait { type A: Into; type B: Into; fn example(&self) -> IndexMap; } ``` The passed number tells dynamize how many generic type parameters to expect. * for 1 dynamize expects: `Type: IntoIterator + FromIterator` * for 2 dynamize expects: `Type: IntoIterator + FromIterator<(A,B)>` * for 3 dynamize expects: `Type: IntoIterator + FromIterator<(A,B,C)>` * etc ...