So I've tried a few permutations of the generic bounds on the Signal struct below but the only way I can get things to be happy is using T as a member of the struct. I know about "associated types" on traits (such as Item on Iterator) but I don't think I can use that on a struct. What is the idiomatic way to do what I am trying to do below? Or maybe it's just not meant to be and there is just another way?
As it's written now it's saying that T is not used in the struct. T is only used in the bound of F, but if I remove T from the generics list then the F bound breaks.
It wouldn't be a good idea to make F a type parameter of Signal. Each closure in a program has its own automatically-generated type, so the user could only put in the exact same closure over and over again. Instead, you can store your subscriber list as a vector of boxed trait objects, of type dyn FnMut(&T). Then, since T is used, you can write a definition of Signal<T>:
This way, any closure can be added as a handler, so long as it takes an &T parameter. But note the 'static bound on the subscribe() method; it means that the handler cannot borrow any data, so we can hold onto it for however long we want.
Thank you both for the replies. @chrefr, I had seen a hint for PhantomData in the compiler message, I should have looked further into it. In my naivete, since I had not come across it before, I assumed it was for a more advanced and specialized use case. It is now more clear what this can do.
@LegionMammal978, I had forgotten that each closure gets a unique type definition at compile time, I had read that in Blandy's book. That is not true of function definitions is it? Even still I think I agree that is probably makes more sense to only be generic over T in this case. There isn't much that is "generic" about the callable itself, rather what the callable takes as it's argument.
Thanks for the responses, this forum and community are an incredible resource!
Technically, each function definition also has its own type. But all functions can be cast to a function pointer of type fn(Args...) -> Ret. Closures can also be cast to a function pointer (RFC 1558), but only if they capture no data from their environment, by reference or by value. Since it is often useful for closures to refer to outside data, APIs generally keep them as Fn* objects.