-
Notifications
You must be signed in to change notification settings - Fork 13k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use #[repr(C)] HList's to infer type-erased fmt fn pointers in format_args!'s static data. #44343
Comments
@rustbot claim |
Not sure what I was thinking there, it should be much easier than that! struct ArgMetadata<T: ?Sized> {
// Only `unsafe` because of the later cast we do from `T` to `Opaque`.
fmt: unsafe fn(&T, &mut Formatter<'_>) -> Result,
// ... flags, constant string fragments, etc.
}
// TODO: maybe name this something else to emphasize repr(C)?
#[repr(C)]
struct HCons<T, Rest>(T, Rest);
// This would have to be in a "sealed module" to make it impossible to implement on more types.
trait MetadataFor<D> {
const LEN: usize;
}
impl MetadataFor<()> for () {
const LEN: usize = 0;
}
impl<'a, T: ?Sized, D, M> MetadataFor<HCons<&'a T, D>> for HCons<ArgMetadata<T>, M>
where M: MetadataFor<D>
{
const LEN: usize = M::LEN;
}
impl<'a> Arguments<'a> {
fn new<M, D>(meta: &'a M, data: &'a D) -> Self
where M: MetadataFor<D>
{
Self {
meta: unsafe { &*(meta as *const _ as *const [ArgMetadata<Opaque>; M::LEN]) },
data: unsafe { &*(data as *const _ as *const [&Opaque; M::LEN]) },
}
}
} i.e. we build two EDIT: @m-ou-se had to remind me why I went with the explicit inference trick in the first place: random access arguments 😞 |
I have a new implementation of What's still left is updating |
@lcnr shared a fun hack idea: using That'd allow this approach: &(
loop { break Display::fmt; break fmt_signature(&arg0); },
loop { break Display::fmt; break fmt_signature(&arg1); },
loop { break Display::fmt; break fmt_signature(&arg2); },
loop { break Display::fmt; break fmt_signature(&arg3); },
) Where fn fmt_signature<T>(&T) -> fn(&T, &mut Formatter) -> fmt::Result {
loop {}
} |
I proposed the exact same solution as above because I was to lazy to read the issue before posting... |
Right now
format_args!
uses, e.g.ArgumentV1::new(&runtime_data, Debug::fmt)
(for{:?}
), at runtime, using up two pointers per argument at runtime instead of just one (&runtime_data
).With
allow_internal_unsafe
and #44240, we can place the (e.g.Debug::fmt
)fn
pointers in (rvalue-promoted)'static
data, the remaining hurdle is how to infer the type of the runtime data.That is,
Debug::fmt
is really<_ as Debug>::fmt
and that_
is right now inferred because ofArgumentV1::new
's signature typing them together. If they're separate, we need something new.I propose using the
HList
pattern (struct HCons<H, T>(H, T); struct HNil;
- so for 3 elements, of typesA
,B
andC
you'd haveHCons<A, HCons<B, HCons<C, HNil>>>
), with#[repr(C)]
, which would give it a deterministic layout which matches that of an array, that is, these two:&'static HCons<fn(&A), HCons<fn(&B), HCons<fn(&C), HNil>>>
&'static [unsafe fn(*const Opaque); 3]
have the same representation, and the latter can be unsized into a slice. This transformation from
HList
to array (and then slice) can be performed on top of a safe, rvalue-promotedHCons
, which is a necessary requirement for moving thefn
pointers into'static
data at all.For inference, we can simply insert some function calls to match up the types, e.g. to infer
B
we could dofmt::unify_fn_with_data((list.1).0, &b)
, which would makeB
intotypeof b
.It might actually be simpler to have a completely safe "builder" interface, which combines the
HList
of formatters with aHList
of runtime references, unifying the types, but I'm a bit worried about compile-times due to all the trait dispatch - in any case, the impact should be measured.The text was updated successfully, but these errors were encountered: