aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
authorMartin Fischer <martin@push-f.com>2021-11-15 10:29:52 +0100
committerMartin Fischer <martin@push-f.com>2021-11-18 23:36:01 +0100
commit2a8a0601afcb82d90d0766db5a954b70b10f856d (patch)
tree0271062335d450e151598d4ad9aa327ffa0dfaea /README.md
publishv0.1.0
Diffstat (limited to 'README.md')
-rw-r--r--README.md119
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.