Rust: Utilice # [repr (C)] HList para inferir punteros fmt fn borrados de tipo en los datos estáticos de format_args!

Creado en 5 sept. 2017  ·  3Comentarios  ·  Fuente: rust-lang/rust

En este momento format_args! usa, por ejemplo, ArgumentV1::new(&runtime_data, Debug::fmt) (para {:?} ), en tiempo de ejecución, usando hasta dos punteros por argumento en tiempo de ejecución en lugar de solo uno ( &runtime_data ) .

Con allow_internal_unsafe y # 44240, podemos colocar los (por ejemplo, Debug::fmt ) fn en los datos (rvalue-protected) 'static , el obstáculo restante es cómo para inferir el tipo de datos en tiempo de ejecución.
Es decir, Debug::fmt es realmente <_ as Debug>::fmt y ese _ ahora mismo se infiere debido a que la firma de ArgumentV1::new los escribe juntos. Si están separados, necesitamos algo nuevo.

Propongo usar el patrón HList ( struct HCons<H, T>(H, T); struct HNil; - así que para 3 elementos, de los tipos A , B y C tendrías HCons<A, HCons<B, HCons<C, HNil>>> ), con #[repr(C)] , lo que le daría un diseño determinista que coincide con el de una matriz, es decir, estos dos:

  • &'static HCons<fn(&A), HCons<fn(&B), HCons<fn(&C), HNil>>>
  • &'static [unsafe fn(*const Opaque); 3]

tienen la misma representación, y este último se puede dividir en un corte. Esta transformación de HList a una matriz (y luego un corte) se puede realizar sobre un HCons seguro, promovido por rvalue, que es un requisito necesario para mover los punteros fn en 'static datos en absoluto.

Para la inferencia, podemos simplemente insertar algunas llamadas a funciones para hacer coincidir los tipos, por ejemplo, para inferir B podríamos hacer fmt::unify_fn_with_data((list.1).0, &b) , lo que convertiría B en typeof b .

De hecho, podría ser más simple tener una interfaz de "constructor" completamente segura, que combine los HList de formateadores con HList de referencias en tiempo de ejecución, unificando los tipos, pero estoy un poco preocupado por tiempos de compilación debido a todo el envío de rasgos; en cualquier caso, se debe medir el impacto.


A-fmt C-enhancement I-heavy T-libs

Comentario más útil

Para la inferencia, podemos simplemente insertar algunas llamadas a funciones para hacer coincidir los tipos, por ejemplo, para inferir B podríamos hacer fmt::unify_fn_with_data((list.1).0, &b) , lo que convertiría B en typeof b .

No estoy seguro de lo que estaba pensando allí, ¡debería ser mucho más fácil que eso!

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]) },
        }
    }
}

es decir, construimos dos HList s "en paralelo", uno con metadatos completamente constantes y el otro con las referencias a los datos en tiempo de ejecución, y luego toda la inferencia de tipo puede provenir del where cláusula en fmt::Arguments::new , ¡con cero cruft de codegen!

EDITAR : @ m-ou-se tuvo que recordarme por qué fui con el truco de inferencia explícita en primer lugar: argumentos de acceso aleatorio: decepcionado:
(tal vez con suficiente uso de ab const genéricos podríamos tener D: IndexHList<i, Output = T> pero eso es mucho esfuerzo)

Todos 3 comentarios

Reclamación de @rustbot

Para la inferencia, podemos simplemente insertar algunas llamadas a funciones para hacer coincidir los tipos, por ejemplo, para inferir B podríamos hacer fmt::unify_fn_with_data((list.1).0, &b) , lo que convertiría B en typeof b .

No estoy seguro de lo que estaba pensando allí, ¡debería ser mucho más fácil que eso!

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]) },
        }
    }
}

es decir, construimos dos HList s "en paralelo", uno con metadatos completamente constantes y el otro con las referencias a los datos en tiempo de ejecución, y luego toda la inferencia de tipo puede provenir del where cláusula en fmt::Arguments::new , ¡con cero cruft de codegen!

EDITAR : @ m-ou-se tuvo que recordarme por qué fui con el truco de inferencia explícita en primer lugar: argumentos de acceso aleatorio: decepcionado:
(tal vez con suficiente uso de ab const genéricos podríamos tener D: IndexHList<i, Output = T> pero eso es mucho esfuerzo)

Tengo una nueva implementación de fmt::Arguments que tiene solo dos punteros de tamaño mediante el uso de una nueva forma de 'metadatos estáticos que contiene tanto las piezas de cadena como las opciones de formato (si las hubiera). (Así que ahora encaja en un par de registros, lo cual es realmente bueno). También solo requiere un puntero en la pila por argumento en lugar de dos, como @eddyb aparentemente ya sugirió hace tres años en este número. ^^ ( Me perdí por completo este problema hasta que

Lo que aún queda es actualizar format_args!() para producir este nuevo tipo en su lugar, lo que se encontrará con el problema de dividir el puntero de objeto y el puntero de función (que actualmente están juntos en ArgumentV1 ), como el puntero de función ahora debería ir en los 'metadatos estáticos. La sugerencia de este número parece una buena forma de hacerlo. Intentaré implementar eso pronto. Esperando la ejecución de perf :)

¿Fue útil esta página
0 / 5 - 0 calificaciones