# 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<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 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>; } ``` 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<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: * tuples * `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<Vec<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. ## 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<String>; type B: Into<i32>; fn example(&self) -> IndexMap<Self::A, Self::B>; } ``` The passed number tells dynamize how many generic type parameters to expect. * for 1 dynamize expects: `Type<A>: IntoIterator<Item=A> + FromIterator<A>` * for 2 dynamize expects: `Type<A,B>: IntoIterator<Item=(A,B)> + FromIterator<(A,B)>` * for 3 dynamize expects: `Type<A,B,C>: IntoIterator<Item=(A,B,C)> + FromIterator<(A,B,C)>` * etc ...