There's no direct way. There are two problems:
- The
T
in a &mut T
must be invariant; if it could shrink, you could do unsound things
- E.g. push a
&local_borrow
into a &mut Vec<&'static T>
- A type parameter
T
must resolve to a single type
- ...and
&mut NumericalTypes<'long>
is not the same type as &mut NumericalTypes<'short>
So if you want to keep fn(&mut T)
around and still get the flexibility desired, you're going to need some way to construct multiple types that differ in lifetime from a single resolved T
. This is possible with indirection and a GAT or a higher-ranked trait bound (instead of a higher-ranked type like fn(&mut NumericalTypes<'_>)
, but it can be pretty unergonomic.
Still, let's give it a shot. The heart of the idea is that you need to have some single concrete type X
that can represent a NumericalTypes<'y>
for any lifetime 'y
. We do this on stable by having a GAT-like helper trait:
pub trait Mutably<'mb> {
type Borrowed: 'mb;
}
pub struct NumericalRepresentative;
impl<'mb> Mutably<'mb> for NumericalRepresentative {
type Borrowed = NumericalTypes<'mb>;
}
Now we have a single type (NumericalRepresentative
) that can construct NumericalTypes<'_>
for any lifetime, given the appropriate trait bound U: for<'any> Mutably<'any>
. Unfortunately I don't know of any way to avoid this boiler-plate in the general case (like a lifetime generic struct such as NumericalTypes
).
Usually I've see this helper-GAT in coordination with a generic callback, at which point trait bounds on the Fn
-like traits starts coming into play. Your example is different in that you're wanting to work with some concrete callable types (e.g. fn(&mut NumericalTypes<'_>)
) instead. This might not be the best approach, but what I came up with on the fly is to capture both the higher-ranked bound mentioned above and the concrete types of your function pointers in another trait:
pub trait Callback: for<'any> Mutably<'any> {
type Simple: for<'any> Fn(&mut <Self as Mutably<'any>>::Borrowed);
type Complex: for<'any> Fn(&mut <Self as Mutably<'any>>::Borrowed, usize);
}
And then implement it:
impl Callback for NumericalRepresentative {
type Simple = fn(&mut NumericalTypes<'_>);
type Complex = fn(&mut NumericalTypes<'_>, usize);
}
This is more boiler-plate as I couldn't think of a way to blanket-implement the trait (there's no way I'm aware of to go from the helper-GAT trait to the NumericalTypes<'_>
helper). This might be cleaner with built-in GAT support.
Anyway, moving on, we can now use this scaffolding like so:
pub enum NumericalCallback<T: Callback> {
SimpleCallback(<T as Callback>::Simple),
ComplexCallback(<T as Callback>::Complex),
}
After that I had to adjust the construction, as coercion didn't apply through the trait projection for whatever reason:
let mut container = Container {
- callback: NumericalCallback::SimpleCallback(add_one),
+ callback: NumericalCallback::SimpleCallback(add_one as _),
value: 3.0
};
And then it worked.
A macro could help with the boilerplate:
macro_rules! represent_callbacks {
($repr:ident, $gentype:ident) => {
pub struct $repr;
impl<'mb> Mutably<'mb> for $repr {
type Borrowed = $gentype<'mb>;
}
impl Callback for $repr {
type Simple = fn(&mut $gentype<'_>);
type Complex = fn(&mut $gentype<'_>, usize);
}
}
}
represent_callbacks!(NumericalRepresentative, NumericalTypes);
Playground.