I actually don't know much about async.
However, when it comes to trait-bounds, typically, fewer trait-bound are better. In other words, only declare those trait-bounds that you really need.
In the case of a struct, as long as you don't need an associated type within your struct, you are mostly good without any bounds. This is pretty much to what @George Glavan wrote in his answer.
When you add methods to your struct, you are more likely to use traits and thus require trait-bounds. Sometimes it is useful to combine the trait-bound of multiple functions by declaring it on the impl-block itself. Tho, this has some restrictions. You should also consider whether each function really needs all these constraints.
For instance, consider the following code:
struct X;
struct Y;
struct MyStruct<F>(F);
impl<F> MyStruct<F> {
pub fn new(f: F) -> Self {
MyStruct(f)
}
pub fn invoke<'a, Fut>(&self) -> Fut
where
F: Fn(&'a X) -> Fut,
{
(self.0)(&X)
}
}
I added a new and a invoke function. The former doesn't require any traits, thus it doesn't have trait-bounds. The latter only calls the function, so it bounds F by Fn. And this is good enough, because in the end, the caller must already know what the return type is, i.e. whether it is some Future or not.
However, there are a few cases where one really needs additional trait-bounds, which involves additional generics such as for a function return type. In such a case, you can declare additional (phantom) generics on a struct, e.g.:
use std::future::Future;
use std::marker::PhantomData;
struct X;
struct Y;
struct MyStruct<F, Fut> {
func: F,
_fut: PhantomData<Fut>,
}
impl<'a, F, Fut> MyStruct<F, Fut>
where
F: Fn(&'a X) -> Fut,
Fut: Future<Output = Y> + Send + Sync + 'a,
{
pub fn new(f: F) -> Self {
MyStruct {
func: f,
_fut: PhantomData,
}
}
pub fn invoke(&self) {
(self.func)(&X);
}
}
Notice, that in this example the trait-bounds apply to both function new and invoke, and both are over-restricted. Still, you don't need to over-restrict the struct itself.