diff options
author | Martin Fischer <martin@push-f.com> | 2021-11-15 10:29:52 +0100 |
---|---|---|
committer | Martin Fischer <martin@push-f.com> | 2021-11-18 23:36:01 +0100 |
commit | 2a8a0601afcb82d90d0766db5a954b70b10f856d (patch) | |
tree | 0271062335d450e151598d4ad9aa327ffa0dfaea /README.md |
publishv0.1.0
Diffstat (limited to 'README.md')
-rw-r--r-- | README.md | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..332de4f --- /dev/null +++ b/README.md @@ -0,0 +1,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. |