No momento format_args!
usa, por exemplo, ArgumentV1::new(&runtime_data, Debug::fmt)
(para {:?}
), em tempo de execução, usando até dois ponteiros por argumento em tempo de execução em vez de apenas um ( &runtime_data
) .
Com allow_internal_unsafe
e # 44240, podemos colocar os (por exemplo, Debug::fmt
) fn
ponteiros em dados (promovidos por rvalue) 'static
, o obstáculo restante é como para inferir o tipo dos dados de tempo de execução.
Ou seja, Debug::fmt
é realmente <_ as Debug>::fmt
e que _
está agora inferido por causa da assinatura de ArgumentV1::new
digitando-os juntos. Se eles estiverem separados, precisamos de algo novo.
Proponho usar o padrão HList
( struct HCons<H, T>(H, T); struct HNil;
- então, para 3 elementos, dos tipos A
, B
e C
você teria HCons<A, HCons<B, HCons<C, HNil>>>
), com #[repr(C)]
, o que daria a ele um layout determinístico que corresponde ao de uma matriz, ou seja, estes dois:
&'static HCons<fn(&A), HCons<fn(&B), HCons<fn(&C), HNil>>>
&'static [unsafe fn(*const Opaque); 3]
têm a mesma representação e a última pode ser desdimensionada em uma fatia. Esta transformação de HList
para array (e então fatia) pode ser realizada em cima de um HCons
seguro promovido por rvalue, que é um requisito necessário para mover os ponteiros fn
em 'static
dados em tudo.
Para inferência, podemos simplesmente inserir algumas chamadas de função para combinar os tipos, por exemplo, para inferir B
, poderíamos fazer fmt::unify_fn_with_data((list.1).0, &b)
, o que tornaria B
em typeof b
.
Na verdade, pode ser mais simples ter uma interface de "construtor" completamente segura, que combina HList
de formatadores com HList
de referências de tempo de execução, unificando os tipos, mas estou um pouco preocupado com tempos de compilação devido a todo o despacho do traço - em qualquer caso, o impacto deve ser medido.
@rustbot reivindicação
Para inferência, podemos simplesmente inserir algumas chamadas de função para combinar os tipos, por exemplo, para inferir
B
, poderíamos fazerfmt::unify_fn_with_data((list.1).0, &b)
, o que tornariaB
emtypeof b
.
Não tenho certeza do que estava pensando lá, deveria ser muito mais fácil do que isso!
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]) },
}
}
}
ou seja, construímos dois HList
s "em paralelo", um com metadados inteiramente constantes e o outro com as referências aos dados de tempo de execução, e então todas as inferências de tipo podem vir de where
cláusula em fmt::Arguments::new
, com zero codegen cruft!
EDIT : @ m-ou-se teve que me lembrar por que eu usei o truque de inferência explícita em primeiro lugar: argumentos de acesso aleatório: decepcionado:
(talvez com o uso de ab generics const suficiente, poderíamos ter D: IndexHList<i, Output = T>
mas isso é um grande esforço)
Eu tenho uma nova implementação de fmt::Arguments
que tem apenas dois indicadores de tamanho, usando uma nova forma de 'metadados estáticos que contém as partes da string e quaisquer opções de formatação (se houver). (Portanto, agora ele se encaixa em um par de registradores, o que é muito bom.) Também requer apenas um ponteiro na pilha por argumento em vez de dois, como @eddyb aparentemente já sugeriu três anos atrás nesta edição. ^^ ( Perdi completamente este problema até que
O que ainda resta é atualizar format_args!()
para produzir este novo tipo em vez disso, o que resultará no problema de dividir o ponteiro do objeto e o ponteiro da função (que estão atualmente juntos em ArgumentV1
), como o ponteiro da função agora deve ir para os 'metadados estáticos. A sugestão nesta edição parece uma boa maneira de fazer isso. Tentarei implementar isso em breve. Ansioso pelo desempenho do desempenho :)
Comentários muito úteis
Não tenho certeza do que estava pensando lá, deveria ser muito mais fácil do que isso!
ou seja, construímos dois
HList
s "em paralelo", um com metadados inteiramente constantes e o outro com as referências aos dados de tempo de execução, e então todas as inferências de tipo podem vir dewhere
cláusula emfmt::Arguments::new
, com zero codegen cruft!EDIT : @ m-ou-se teve que me lembrar por que eu usei o truque de inferência explícita em primeiro lugar: argumentos de acesso aleatório: decepcionado:
(talvez com o uso de
abgenerics const suficiente, poderíamos terD: IndexHList<i, Output = T>
mas isso é um grande esforço)