Open dspyz-matician opened 10 months ago
Just checking what you're asking for by using a concrete example
Given
struct S {
a: u64,
b: bool
}
let s = Struct { a: 5, b: false};
let s_ref = &s;
You want to be able to (something like) this:
let s_ref_repr: HList![ &u64, &bool ] = s_ref.into_generic_ref();
?
Some follow up questions:
s_ref.into_generic_ref()
) or the same method as the existing one (e.g. s_ref.into_generic()
)Yes, that's exactly what I'm looking for
I have a large crate that was originally littered with structs like:
struct Fields {
foo: Field<Foo>,
bar: Field<Bar>,
baz: Field<Baz>,
}
struct Page<'a> {
foo: Accessor<'a, Foo>,
bar: Accessor<'a, Bar>,
baz: Accessor<'a, Baz>,
}
struct Mission {
foo: Layer<Foo>,
bar: Layer<Bar>,
baz: Layer<Baz>,
}
I think in total there were about 8 structs like this and each one has the "same" 21 fields. About half have associated lifetimes and are constructed from references to others. Every time anyone wants to add a new field, they have to go through and update all of them and then they have to go through and update every place where each of the fields is addressed in turn doing the exact same thing.
I noticed I could reduce boilerplate with GATs and marker types:
trait Domain {
type ElemType<T>;
}
struct General<D: Domain> {
foo: D::ElemType<Foo>,
bar: D::ElemType<Bar>,
baz: D::ElemType<Baz>,
}
struct FieldsDomain;
impl Domain for FieldsDomain {
type ElemType<T> = Field<T>;
}
type Fields = General<FieldsDomain>;
struct PageDomain<'a>;
impl Domain for PageDomain<'a> {
type ElemType<T> = Accessor<'a, T>;
}
type Page<'a> = General<PageDomain<'a>>;
struct MissionDomain;
impl Domain for MissionDomain {
type ElemType<T> = Layer<T>;
}
type Mission = General<MissionDomain>;
Then for the conversions between them that didn't involve references, I could use frunk (by deriving Generic
on my
General
type) and expressing conversions with HList::map
:
trait GenFunc<D: Domain, E: Domain> {
fn call<T>(t: D::ElemType<T>) -> E::ElemType<T>;
}
struct MyPoly<F>(F)
impl<D: Domain, E: Domain, F: GenFunc<D, E>, T> Func<D::ElemType<T>> for MyPoly<F> {
type Output = E::ElemType<T>;
// This is a bit of a simplification, I actually wrote `MyFunc` with a `call` that takes `&mut self` as well and
// re-implemented `MyHMappable` to use it so that I could smuggle in context from the caller. Also I'm
// leaving out a constraint on T.
fn call(input: D::ElemType<T>) -> E::ElemType<T> {
GenFunc::<F>::call(t)
}
}
impl<D: Domain> General<D> {
fn hmap<F: GenFunc<D, E>, E: Domain>(self, f: F) -> General<E>
where Self::Repr: HMappable<MyPoly<F>, Output=General<E>::Repr> // This constraint always holds, but the compiler doesn't know that until it sees the concreate instance
{
Generic::from(Generic::into(self).map(MyPoly(f)))
}
}
But I still ended up with three instances of expressing all the fields in turn rather than a single
source of truth. The first was the General
struct itself, and the other two were the manual implementations of
to_refs
and to_muts
. Auto-deriving this would allow me to reduce it to exactly one place since to_refs
and
to_muts
could be expressed in terms of these functions together with HList::map
.
(Preferrable):
trait RefToGeneric<'a> {
type Repr;
fn to_generic_refs(&'a self) -> Self::Repr;
}
impl RefToGeneric for Foo {
...
}
than if it's defined as:
(Less preferrable):
trait ToGenericRefs {
type Repr;
fn to_generic_refs(self) -> Self::Repr
}
impl<'a> ToGenericRefs for &'a Foo {
...
}
Thanks so much!
FYI, just opened https://github.com/davidspies/frunk_utils
Thanks @dspyz-matician I think what you want makes sense and would make a good addition to the lib.
Regarding introducing a new into_generic_ref
or re-using the existing into_generic
method; I was mostly curious whether from an API/DX perspective, it would be preferred to have an explicit ref-handling way of going generic (ignoring for a second whether the compiler would actually allow us to use it nicely (w/o requiring type ascriptions), and whether we can even re-use the backing traits!). My thinking there is that it could be nice to try to re-use the existing one just to avoid increasing the API surface of frunk :)
BTW, if you're willing to give this a go, by all means, please go ahead!
If I have an
&'a T
whereT
implementsGeneric
, it seems like I ought to be able to get a<T::Repr as ToRef<'a>>::Output
from it, but there isn't any safe way to do that generically. It would be nice to have a derive macro which does this