Heads up: I renamed your Cell
to WhatHaveYou
because Cell
is a special thing in Rust.
First, a problem. If I rewrite your bounds closer to how I might think of them, I get
fn upcast<'a, D, U, F>(mut self, map: F) -> Self
where
D: for<'any> Downcast<'any>,
U: for<'any> Upcast<'any>,
// for <'any> <U as Upcast<'any>>::Output: SomeBound???,
F: FnOnce(D) -> U,
{
let from: D = <D as Downcast<'_>>::Downcast::downcast(&mut self);
let mapped: U = map(from);
let to: <U as Upcast<'_>>::Output = <U as Upcast<'_>>::upcast(mapped);
It's still not clear how you can get from the generic U::Output
to Self
, but let's ignore that for now.
The problem is that type parameters like D
can only represent a single type, but given your trait signatures, the type of D
is probably supposed to vary per input lifetime of Downcast::downcast
. That would be a type constructor, not a single type.
Upcast
is probably in a similar boat.
Rust doesn't support generic type constructor parameters, but you can sometimes use GATs to work around this (GATs were originally called ACTs -- associated type constructors).
(...goes back to playground...)
I GATified the traits and gave this a shot...
trait Downcast {
type Realized<'a>: Sized;
fn downcast(cell: &mut WhatHaveYou) -> Self::Realized<'_>;
}
trait Upcast {
type Output<'a>: Sized;
type Realized<'a>: Sized;
fn upcast(this: Self::Realized<'_>) -> Self::Output<'_>;
}
impl WhatHaveYou {
fn upcast<D, U, F>(mut self, map: F) -> Self
where
D: Downcast,
U: Upcast,
F: for<'any> FnOnce(D::Realized<'any>) -> U::Realized<'any>,
// for<'any> <U as Upcast>::Output<'any>: SomeBound ???,
But because the lifetime is in a GAT, it's unconstrained.
Ugh, that means we probably have to go through the dance of uplifting a FnOnce
from single lifetime to any lifetime...
trait MapOne<'a, D: Downcast, U: Upcast>: Sized {
fn map_one(self, _: <D as Downcast>::Realized<'a>) -> <U as Upcast>::Realized<'a>;
}
trait Map<D: Downcast, U: Upcast>: for<'any> MapOne<'any, D, U> {
fn map(self, d: <D as Downcast>::Realized<'_>) -> <U as Upcast>::Realized<'_> {
self.map_one(d)
}
}
impl<F, D, U> Map<D, U> for F
where
F: for<'any> MapOne<'any, D, U>,
D: Downcast,
U: Upcast,
{}
impl<'a, F, D, U> MapOne<'a, D, U> for F
where
F: FnOnce(<D as Downcast>::Realized<'a>) -> <U as Upcast>::Realized<'a>,
D: Downcast,
U: Upcast,
{
fn map_one(self, d: <D as Downcast>::Realized<'a>) -> <U as Upcast>::Realized<'a> {
self(d)
}
}
fn upcast<D, U, F>(mut self, map: F) -> Self
where
D: Downcast,
U: Upcast,
F: Map<D, U>,
And this works... but all the indirection kills type inference. I had to add some annotations to upcast
and if you uncomment the rest of use_cases
, you'll get a bunch of inference errors.
You can still use it, with painful ergonomics. Or mostly, I stopped spending time on the owned -> borrowed
case.
More generally, this is what abstracting over owned and borrowed tends to look like. You might be able to ease some of the ergonomics as per that other post (but I'm out of time for this now).