Rather than put constraints on the struct, the simplest and best approach is to put the constraints on the implementation of all methods that will need to use the function:
struct Foo<F, T> {
data: T,
f: F,
}
impl<F, T> Foo<F, T> {
fn call_f<P>(&self, arg: P)
where
T: Copy,
F: Fn(T, P)
{
(self.f)(self.data, arg);
}
}
First, once the "implied trait bounds" RFC is implemented, this allows me to omit the duplicate trait bounds from all the impl blocks.
So it sounds like your main concern is about removing duplicate bounds. If that's the problem, you can try to group all the methods with the same bounds into a common impl
, so you're still only ever write them once:
impl<F, T, P> Foo<F, T>
where
T: Copy,
F: Fn(T, P),
{
fn call_f(&self, arg: P) {
(self.f)(self.data, arg);
}
}
There's a little problem here, similar to the one you found yourself: unconstrained type parameter: P
. However, now that we've got to here, you can solve it very simply by introducing a trait (you can name it better for your specific use case):
trait FIsAFunction<F, T, P> {
fn call_f(&self, arg: P);
}
impl<F, T, P> FIsAFunction<F, T, P> for Foo<F, T>
where
T: Copy,
F: Fn(T, P),
{
fn call_f(&self, arg: P){
(self.f)(self.data, arg);
}
}
And users don't have to do anything weird[1]:
fn main() {
fn callback(x: u32, y: &str) {
println!("I was given {:?} and {:?}", x, y)
}
let foo = Foo { data: 1u32, f: callback };
foo.call_f("hello!");
}
[1] They may have to use
the trait. Which isn't so weird: you already have to do that with a lot of std
stuff, like std::io::Read
etc.