A helper macro to create dyn compatible variants for traits.
`#[dynify]` accepts a trait or function as input and returns it as is, along
with a *dynified* variant. This involves converting each input function into a
function constructor. Consider the following example:
```rust
# use dynify::dynify;
#[dynify]
trait Client {
async fn request(&self, uri: &str) -> String;
}
```
It expands to something like this:
```rust
# use core::future::Future;
# use dynify::{Fn, from_fn};
trait Client {
async fn request(&self, uri: &str) -> String;
}
// A dynified trait is generated and implemented for any type that implements
// `Client`. Although this can be done without this macro, it helps stay
// synchronized with the original trait, eliminating the need to handle elided
// lifetimes, which can be quite annoying for signatures with many lifetimes.
trait DynClient {
fn request<'this, 'uri, 'dynify>(
&'this self,
uri: &'uri str,
) -> Fn!(&'this Self, &'uri str => dyn 'dynify + Future<Output = String>)
where
'this: 'dynify,
'uri: 'dynify;
}
impl<ClientImplementor: Client> DynClient for ClientImplementor {
fn request<'this, 'uri, 'dynify>(
&'this self,
uri: &'uri str,
) -> Fn!(&'this Self, &'uri str => dyn 'dynify + Future<Output = String>)
where
'this: 'dynify,
'uri: 'dynify,
{
from_fn!(ClientImplementor::request, self, uri)
}
}
```
## Customizing the generated traits
You can specify an identifier as the name of the generated trait:
```rust
# use dynify::dynify;
#[dynify(MyDynClient)]
trait Client {
async fn request(&self, uri: &str) -> String;
}
async fn run(client: &dyn MyDynClient) {
// ...
}
```
The identifier must be supplied as the first argument.
## Lifetime conventions
The core feature of `#[dynify]` is the expansion of
[elided lifetimes](https://doc.rust-lang.org/nomicon/lifetime-elision.html).
`#[dynify]` employs a deterministic method for this:
1. The name of each elided lifetime is based on the name of the argument that
contains it. Therefore, the binding pattern of each argument must be an
identifier.
2. If a lifetime is the only one, it is named exactly the same as the argument.
3. Otherwise, an index is appended to each lifetime. The index is zero-based and
corresponds to the order in which a lifetime occurs. Both elided and named
lifetimes are taken into account when counting indices.
4. For a method receiver, the expanded lifetime is always named based on `this`
instead of `self`. Rule *(3)* applies in this case as well.
5. If the return type is a dyn object, it always has a bound named `'dynify`.
The following demonstrates how these rules work:
```rust
# use dynify::Fn;
# use core::future::Future;
//#[dynify::dynify]
trait Trait {
async fn method(&self, foo: &str, bar: (&str, &str)) -> String;
}
trait DynTrait {
fn method<'this, 'foo, 'bar0, 'bar1, 'dynify>(
&'this self,
foo: &'foo str,
bar: (&'bar0 str, &'bar1 str),
) -> Fn!(
&'this Self,
&'foo str,
(&'bar0 str, &'bar1 str)
=> dyn 'dynify + Future<Output = String>)
where
'this: 'dynify,
'foo : 'dynify,
'bar0: 'dynify,
'bar1: 'dynify,
// Add extra bounds here
;
}
```
In common cases, you can rely on the lifetimes generated by `#[dynify]`, adding
extra bounds as needed.
## Making generated traits [`Send`]able
Unlike `async-trait`, this macro does not provide support for adding `Send`
bounds to returned `Future`s (or any other `impl Trait`s). However, as
illustrated below, you can combine `#[dynify]` with
[trait-variant](https://crates.io/crates/trait-variant) to achieve this:
```rust
# use dynify::{Dynify, dynify};
# use std::mem::MaybeUninit;
// You can also use `#(make(SendClient: Send))`. However, in this case, you can
// no longer specify a name in `#[dynify]` because `#[trait_variant::make]`
// will generate two traits, which leads to conflicting trait definitions.
#[trait_variant::make(Send)]
#[dynify] // must be put within the scope of `#[trait_variant::make]`
trait Client {
async fn request(&self, uri: &str) -> String;
}
fn run_client(
client: &(dyn DynClient + Sync),
) -> impl '_ + std::future::Future<Output = ()> + Send {
let mut stack = [MaybeUninit::<u8>::uninit(); 16];
let mut heap = Vec::<MaybeUninit<u8>>::new();
async move {
client.request("http://magic/request").init2(&mut stack, &mut heap).await;
}
}
```
As an alternative, you can also [bitte](https://crates.io/crates/bitte) for this
purpose:
```rust,ignore
# use dynify::{PinDynify, dynify};
#[bitte::bitte(Send)]
#[dynify] // must be put within the scope of `#[bitte]`
trait Client {
async fn request(&self, uri: &str) -> String;
}
```
## Working with remote items
Suppose you're going to add a variant for a remote trait, for example:
```rust
// external_crate::
pub trait Read {
async fn read_to_string(&mut self) -> String;
}
```
You need to copy the trait definition and specify the path to that trait using
`#[dynify(remote = "path::to::trait")]`:
```rust
# use dynify::dynify;
# mod external_crate {
# pub trait Read { async fn read_to_string(&mut self) -> String; }
# }
#[dynify(remote = "external_crate::Read")]
// It's not necessary to include all the items; you can filter out unused ones.
pub(crate) trait DynRead {
async fn read_to_string(&mut self) -> String;
}
```
This also works for remote functions:
```rust
# use dynify::dynify;
# mod external_crate {
# pub async fn read_to_string(path: &str) -> String { todo!() }
# }
#[dynify(remote = "external_crate::read_to_string")]
pub(crate) async fn dyn_read_to_string(path: &str) -> String {
/* the body of this local function doesn't matter */
}
```