Problema de seguimiento para rust-lang / rfcs # 1681.
cc @alexcrichton
Pruebas de tornasol:
caracteristicas:
proc_macro
proc-macro
#[proc_macro_derive(Foo)]
proc-macro
cajas con -L
y #[macro_use]
para cargarlasproc-macro = true
Errores conocidos:
mod foo
fail - # 36691proc_macro
- # 38749[x] - Los atributos generados por el compilador dificultan la vida de los autores derivados personalizados - https://github.com/rust-lang/rust/issues/35900#issuecomment -245978831
[x] - Crea una caja de rustc_macro
librustc_macro
enlace a libsyntax
. Depende de librustc_macro
en librustc_driver
rustc_macro
como inestable con nuestro encabezado estándar .rustc_macro
con #![crate_type = "rlib"]
, no genere un dylib.rustc_macro
usando libsyntax
's TokenStream
internamenterustc_macro
con un elemento TokenStream
lang para que el compilador lo sepa.rustc_macro_derive
foo(bar)
, sin otros argumentos / formatosTokenStream
lang agregado arribarustc_macro_derive
en metadatos junto con el modo de derivación para el que se utilizan.rustc-macro
para otras cajasrustc-macro
no se puedan vincular como dylibs#[rustc_macro_derive]
cfg(rustc_macro)
como directiva cfg
inestable, configúrelo para el tipo de caja rustc-macro
rustc-macro
vinculen dinámicamente a libsytnax#[macro_use]
soporte para rustc-macro
cajasrustc-macro
cajas por separado de dylib / rlib rastreadas hoy al cargar cajasrustc-macro
para obtener información sobre las combinaciones de modo símbolo / derivacióndlopen
el dylibrustc-macro
similar a plugin = true
--crate-type=rustc-macro
cuando depende de ello#[derive]
Actualicé la descripción del problema con una lista de verificación de lo que se debe hacer. Es probable que no sea exhaustivo, pero debería llevarnos el 90% del camino, con suerte
Ok, voy a investigar esto hoy y ver qué tan lejos llego.
cc https://github.com/rust-lang/rust/pull/35957 , una implementación
De # 35957: deberíamos quitar el nombre de la caja librustc_macro un poco más. En particular, esta está destinada a ser una caja de larga duración que tendrá elementos esenciales para todos los autores de macro, por lo que limitarse a rustc_macro (que en mi opinión, al menos) es casi la idea 1.1 parece malo. Anteriormente quería libmacro para esto, pero como macro
es una palabra reservada (y podríamos quererla como palabra clave en el futuro) eso es imposible. @cgswords y yo hemos estado usando libproc_macro, y creo que no es un mal nombre, aunque no estoy 100% satisfecho con él.
@nrc : Bien, mis pensamientos inmediatos sobre los nombres:
extern crate macros;
- breve y dulce, pero se puede leer como que contiene macros, en lugar del código de soporte para escribirlasextern crate macro_runtime;
- exactamente lo que dice en la lataextern crate metarust;
- escribe Rust, sobre Rust, para operar en Rustextern crate bikeshed;
- ¡con macros de procedimiento, puede tener el color de óxido que desee!extern crate macrame;
- suena como "macro make [r]"; posiblemente sea mejor dejarlo para una futura API "agradable" sobre la biblioteca en bruto.@nrc Parece que un aspecto importante de esta pregunta es el nombre de nuestros diversos estilos de macro en general - en particular, si vamos con libproc_macro
, estamos haciendo un fuerte compromiso con la "macro de procedimiento" terminología. No tengo una opinión fuerte aquí, pero no estoy seguro de si hemos explorado abiertamente el espacio de la terminología.
Para ser claros, ¿estás pensando que macro_rules
serían simplemente "macros", es decir, lo que queremos decir por defecto con macros, mientras que tienes que calificar "macros de procedimiento"? Me parece un plan bastante razonable. Y en ese mundo, diría que libproc_macro
es mejor que libmacros
.
Mi pensamiento aquí es que todas las macros son "macros", y cuando necesitamos hacer una distinción basada en la implementación (el uso debe ser exactamente el mismo para todos los tipos) usamos "macros de procedimiento" vs "macros por ejemplo". Me gustaría eliminar la "extensión de sintaxis" y el "complemento del compilador" por completo (y algún día reutilizar este último para complementos reales).
Pero, sí, quiero firmemente apoyarme en la terminología de "macro de procedimiento".
@nrc ¡ Tiene sentido para mí! Si bien "macros por ejemplo" es un poco difícil de manejar, también es un término muy evocador / intuitivo. Mi única preocupación acerca de la "macro de procedimiento" es que el "procedimiento" no es una cosa en Rust. Me pregunto si hay alguna forma de hacer la conexión más directa. La "macro de funciones" no es del todo correcta, pero ¿tal vez te dé una idea de lo que quiero decir?
Sí, no es del todo perfecto, pero dado que es un término muy conocido / utilizado fuera de Rust, creo que es mejor que acuñar nuestro propio término. La "macro programática" es posible, pero yo prefiero "procedimental".
El término de Perl para el equivalente más cercano que tiene de "macros de procedimiento" es "filtros de origen", que (especialmente con el cambio de AST a tokens) es una descripción bastante adecuada.
¿Quizás 'transformadores de sintaxis' o 'macros programáticas' funcionarían bien como nombres? Sin embargo, no tengo ningún problema con las macros de procedimiento.
"Procesal" ya es lo que la gente llama a esto, y creo que se entiende claramente lo que significa, especialmente en contraste con "macros por ejemplo". No me preocuparía por intentar encontrar un nombre diferente.
Me gusta el término "macro de procedimiento" para uso regular (o tal vez "macro personalizada"). Particularmente me gusta usar la palabra _macro_ para que quede claro que pueden (eventualmente ...) usarse de la misma manera que las "macros normales". Por esta razón, no me gustan los "filtros de origen" (también espero que un filtro simplemente elimine datos, no los transforme, aunque sé que el término se usa para ambos).
Estoy bien con libproc_macro
o libmacros
. Prefiero este último solo porque no me encanta tener _
en los nombres de las cajas cuando se puede evitar fácilmente. =)
Una pregunta: ¿alguna vez esperamos tener "rutinas de soporte" que puedan usarse desde macros no procedimentales? No conozco tales planes, pero si los tuviéramos y los quisiéramos en la misma caja, libmacros
sería un nombre mejor. =)
(Estoy pensando un poco en, por ejemplo, el comentario de
@nikomatsakis : Una pregunta relacionada, pero sutilmente diferente, es un caso de uso que https://github.com/rust-lang/rfcs/pull/1561#discussion_r60459479 : ¿queremos que las funciones de implementación de macros de procedimiento puedan llamar otras funciones de implementación de macros de procedimiento?
Puedo ver fácilmente el deseo de permitir que una derivación personalizada llame a otra, y eso esencialmente haría que las propias definiciones de macros de procedimiento pudieran usarse como tales "rutinas de soporte"
Pero sí, creo que el ejemplo gensym
@dherman es bastante convincente. Por supuesto, si la respuesta a mi pregunta anterior es "sí", gensym es tanto una macro (que podría ser usada por macros por ejemplo) como una función de soporte (que podría ser usada por macros de procedimiento).
Tengo un PR de carga https://github.com/rust-lang/cargo/pull/3064 que debería marcar todas las casillas de "integración de carga" en la lista de verificación.
Dejé un comentario sobre el RP de carga, pero creo que queremos un tipo diferente de _dependencia_, no solo un tipo diferente de _paquete_. En primer lugar, creo que esto es mejor estética y ergonómicamente, pero esa es solo mi opinión. Pero también tengo dos razones concretas.
Como esto muestra, la misma caja podría ser un depósito regular de otra caja de macros de procedimiento, o una macrodep de otra caja.
¿Serde funciona?
Sí https://github.com/serde-rs/serde/releases/tag/v0.8.6
(excepto para los atributos de contenedor en algunos casos # 36211)
¡Genial, gracias por las actualizaciones @dtolnay!
¿Hay documentos en estas macros? Supongo que el único ejemplo de cómo usarlos es en serde, ¿verdad?
Ha aterrizado bien la carga. Está bien, pero sería bueno volver a visitar https://github.com/rust-lang/rust/issues/35900#issuecomment -243976887 en algún momento antes de la estabilización. [Por lo que vale, quise mencionar esto en el RFC original, pero lo olvidé].
Creo que "macros por ejemplo" y "macros de procedimiento" podrían catalogarse mejor como "macros declarativas" y "macros imperativas" respectivamente. Esto proporciona un paralelismo informativo con la categorización declarativa / imperativa más conocida de los lenguajes de programación. Como imperativo se trata como un superconjunto o sinónimo de procedimental, debería estar lo suficientemente cerca para que la gente acostumbrada a la terminología de "macro procedimental" dé el salto. También debe evitar cualquier confusión con los conceptos de procedimiento / función / método en el propio rust.
Este esquema de nomenclatura nos da una caja y un módulo macro_imp
en paralelo macro_rules
. macro_rules
eventualmente podría convertirse en un módulo de una caja macro_dec
más general.
@nrc , cuando te refieres a "complementos reales", ¿estás incluyendo cosas como metacollect y clippy , cosas como rustw , rustfmt y Rust Language Server o alguna otra categoría de programa?
Por lo que vale, me disgusta levemente el nombre "por ejemplo" (ya que los patrones $foo
no son "ejemplos" en mi mente). Declarativo vs imperativo me suena mejor.
Jugando con esto, he notado un problema. Parece que las derivadas proporcionadas por Rust a veces agregan atributos que el compilador sabe que debe ignorar. Este contexto se pierde cuando pasa por una derivación personalizada. Esperaría que la función de identificación dada como una derivación personalizada no sea operativa, pero causará errores alrededor del atributo #[structural_match]
que se agrega.
Guión de reproducción
(En una caja llamada demo_plugin
)
#![feature(rustc_macro, rustc_macro_lib)]
extern crate rustc_macro;
use rustc_macro::TokenStream;
#[rustc_macro_derive(Foo)]
pub fn derive_foo(input: TokenStream) -> TokenStream {
input
}
(en otra caja)
#![feature(rustc_macro)]
#[macro_use] extern crate demo_plugin;
#[derive(PartialEq, Eq, Foo)]
struct Bar {
a: i32,
b: i32,
}
Eliminar #[derive(Eq)]
hace que todo funcione bien.
@sgrif ah sí, gracias por recordármelo!
Entonces, hay algunas cosas que suceden aquí:
#[derive(PartialEq, Eq)]
, el compilador está agregando silenciosamente #[structural_match]
porque entiende estáticamente que la derivación PartialEq
sí cumple ese contrato.#[derive(Copy, Clone)]
y se agrega #[rustc_copy_clone_marker]
.Entonces, algunas soluciones que podríamos hacer:
#[derive(Foo, Eq, PartialEq)]
Yo estaría a favor de no emitir estos atributos o de utilizar un mecanismo diferente. Sin embargo, esto es realmente complicado, porque #[derive(Copy, Foo, Clone)]
también necesita funcionar (donde se ejecuta un código personalizado en el medio que podría cambiar las definiciones).
Mi curso de acción preferido sería simplemente no emitir estos atributos si detectamos derivaciones personalizadas. Incluso eso, sin embargo, puede no ser trivial. Por ahora una convención de "personalizado primero y estándar último" debería ser suficiente, pero creo que deberíamos arreglar esto antes de estabilizar.
Descargo de responsabilidad: solo tengo una vista externa del compilador, por lo que hay muchas cosas que no sé. ^^
Pero por lo que entiendo, el enfoque actual de la derivación personalizada de macros 1.1 es más como una solución temporal. La solución temporal podría traducirse en "bastante tiempo". Pero a largo plazo (= macros 2.0) ya no haremos el viaje de ida y vuelta de análisis de cadenas, lo que actualmente conduce a la pérdida de información de intervalo. Me pregunto si estos atributos ocultos en el AST fueron algo tan malo en un mundo de macros 2.0. Quizás alguien con más conocimiento interno sobre el compilador pueda decirlo. Si estos atributos ocultos realmente tienen sentido en ese mundo futuro, yo diría que optar por la solución al poner convencionalmente derivaciones personalizadas al principio. De todos modos, todo el asunto ya es una solución alternativa, ¿no?
@ colin-kiegel Creo que tiene razón en el sentido de que lo que estamos haciendo en este momento _no_ está preparado para el futuro. Digamos que tiene, por ejemplo:
#[derive(Eq, Foo, PartialEq)]
Hoy agregaríamos la implementación Eq
, luego ejecutaríamos el código personalizado para Foo
, y luego agregaríamos una implementación de PartialEq
. La estructura podría _cambiar_ entre Eq
y PartialEq
, por lo que el #[structural_match]
agregado por Eq
puede no ser correcto después de ejecutar Foo
.
En ese sentido, estoy de acuerdo en que, en cualquier caso, no son necesariamente a prueba de futuro.
Mi sensación es que las derivaciones personalizadas que se basan en cambios estructurales generalmente no se componen muy bien, independientemente de esos atributos ocultos. ¿Un derivado personalizado v2.0 podrá cambiar la estructura del artículo, o se limitará de alguna manera solo a la decoración?
Sí, creo que la capacidad siempre estará ahí (inherente a la interfaz TokenStream -> TokenStream) aunque sospecho que cualquier implementación razonable de #[derive]
conservaría la estructura de la estructura original.
Supongo que no podríamos requerir que el flujo de tokens de salida no contenga la estructura en sí, para asegurarnos de que la estructura no cambie. ¿La estructura de entrada TokenStream sería un prefijo inmutable? El gran problema sería asegurarse de ignorar los atributos no reconocidos que utilizan los complementos después de que se completa la compilación. Quizás cada # [derive ()] pueda tener un prefijo (digamos # [derive (Foo)] tiene el prefijo Foo_) con el cual los atributos que entienden deben comenzar, y después de procesar cada derivado personalizado, quitamos esos atributos.
@mystor sí, el problema con ese enfoque son los atributos no reconocidos, por lo que tenemos la estructura completa como entrada. Eso es generalmente más flexible que confiar en un prefijo / sufijo / registro / etc. en particular.
Si un derivado personalizado v2.0 pudiera marcar atributos personalizados como _usados_, podría limitarse al acceso de _solo lectura_ al resto del flujo de tokens de elementos. De esta manera, IMO podría garantizar una mejor componibilidad de los derivados personalizados. Si una macro v2.0 necesita cambiar la estructura de un elemento, tendría que usar otra API, pero no una derivación personalizada. De esta forma, el problema con #[structural_mach]
y el orden de las derivadas (personalizadas) solo estaría presente en las macros 1.1. ¿Eso tendría sentido?
Otro problema. Si una estructura tiene dos derivaciones personalizadas diferentes y la segunda entra en pánico, el intervalo de error apuntará a la primera, no a la que entró en pánico.
Guión de reproducción
En una caja llamada demo_plugin
#![feature(rustc_macro, rustc_macro_lib)]
extern crate rustc_macro;
use rustc_macro::TokenStream;
#[rustc_macro_derive(Foo)]
pub fn derive_foo(input: TokenStream) -> TokenStream {
input
}
#[rustc_macro_derive(Bar)]
pub fn derive_bar(input: TokenStream) -> TokenStream {
panic!("lolnope");
}
En otra caja
#![feature(rustc_macro)]
#[macro_use] extern crate demo_plugin;
#[derive(Foo, Bar)]
struct Baz {
a: i32,
b: i32,
}
El error resaltará Foo
aunque Bar
entró en pánico.
¡Gracias por el informe @sgrif! He actualizado la descripción de este problema y espero poder realizar un seguimiento de todos los problemas pendientes relacionados con las macros 1.1 allí también.
Hmm, la interacción de la derivación personalizada con #[structural_eq]
es algo en lo que no había pensado antes. ¡Me molesta bastante!
Me parece que tener una forma de "agregar" texto a una secuencia de tokens podría ser una mejor interfaz al final del día ... preservaría la información del intervalo y evitaría este problema, ¿no?
Una ventaja de la interfaz más general es que permite que los paquetes tengan atributos en los campos, que se pueden eliminar cuando se ejecuta la macro. Este es el único caso de uso real que conozco para permitir que las macros de derivación personalizadas alteren el elemento original.
Creo que la cuestión de la interfaz se reduce a si queremos que la derivación personalizada sea 'solo otra macro', en cuyo caso parece importante tener la misma interfaz que otras macros de procedimiento (donde queremos modificar el elemento original). O si debería ser algo propio con una interfaz especial, en cuyo caso la interfaz más restrictiva (añadir) tiene sentido.
Notaré que las extensiones de sintaxis tenían durante mucho tiempo una distinción entre modificadores y decoradores, y creo que todos los involucrados realmente odiaban esa distinción. Por lo tanto, soy un poco reacio a seguir el camino de que la derivación personalizada sea un poco especial (una alternativa que se ha discutido es algún tipo de derivación personalizada muy especial, posiblemente incluso un formato declarativo).
@nrc bueno, todavía puede ser tokenstream -> tokenstream, donde simplemente no exponemos un método new
que comienza desde cero, ¿verdad?
Estoy de acuerdo en que debe ser posible tener atributos personalizados en los campos para que una derivación personalizada realmente tenga sentido. Pero creo que puede haber muchas formas de hacer esto con el estilo "añadir"; Definitivamente preferiría el estilo de adición, porque prefiero un mundo donde las macros de derivación no cambian el elemento y son componibles, además de que resuelve el problema #[structural_eq]
. Esta es la cantidad justa de libertad en mi opinión.
Si a la gente no le gustó ese destino, me gustaría preguntar por qué, porque obviamente había razones suficientes para hacer esta distinción antes, ¿no es así?
(Supongo que lo que sugerí es solo un truco temporal, en realidad no aborda el problema de componibilidad a largo plazo).
Mis diversas bibliotecas tienen actualmente macros que se parecen a foo!(Bar, parameters...)
, que generan una estructura Bar
partir de los parámetros.
Durante una discusión sobre IRC, tuvimos la idea de escribir #[derive(Foo)] #[params] struct Bar;
lugar, y reemplazar foo!
con #[derive(Foo)]
que generaría el cuerpo de la estructura.
Obviamente, no es un argumento sólido, pero realmente me gustó la idea, ya que es más claro para el usuario que se está construyendo una estructura.
Me pregunto si podríamos reelaborar el #[structural_match]
para colocarlo en el impl generado.
(Realmente no resuelve el problema)
Vale la pena señalar que tanto Serde como Diesel hacen mucho uso de atributos personalizados en los campos, por lo que existe una necesidad definida de derivación personalizada para permitir el reemplazo.
Entonces, en realidad, tal vez no esté pensando con claridad sobre el problema #[structural_match]
. Después de todo, ¿qué puede hacer una derivación personalizada?
#[structural_match]
falsamente, debería fallar la comprobación de estabilidad. Si no, ¡eso parece un error en sí mismo! (Sin embargo, dada la forma compleja y extraña en que funciona este código, eso no me sorprendería).Perdón por escribir toneladas de pequeños comentarios y pensar en voz alta, pero hay otra preocupación. Aunque es posible que una derivación personalizada no pueda "falsificar" una anotación #[structural_match]
(porque terminaría sin el "intervalo mágico"), probablemente acabaría arruinando el intervalo de cualquier anotación existente, a menos que el las derivadas se aplican en el orden correcto, lo cual es lamentable. Básicamente una instancia de la no componibilidad de la que ha estado hablando @ colin-kiegel, pero sin ningún intento de modificar la estructura en vuelo.
(En otras palabras, dado que confiamos en el intervalo para juzgar si se pueden usar elementos estables, la pérdida de información del intervalo puede causar algunos problemas complicados).
EDITAR: OK, leyendo de nuevo, veo que acabo de rederivar lo que @sgrif ya informó. Lo siento de nuevo. ;)
También es un poco asqueroso, porque significa que estamos exponiendo detalles de implementación inestables a código estable. El código idealmente estable nunca sabría que existe la anotación #[structural_match]
.
@nikomatsakis bueno, de una forma u otra, necesitamos hacer cumplir diferentes restricciones dependiendo de si la macro está destinada a ser una derivación personalizada o de algún otro tipo. Eso significa algún tratamiento separado (y semántica diferente), cualquiera que sea la firma de la función.
@ colin-kiegel
Definitivamente prefiero el estilo de adición, porque prefiero un mundo en el que las macros de derivación no cambien el elemento y sean componibles, además de que resuelve el problema # [estructural_eq]. Esta es la cantidad justa de libertad en mi opinión.
Creo que las macros mutantes se pueden componer, aunque, por supuesto, los términos de componibilidad son diferentes. Es evidente que debe haber un conjunto de condiciones previas y posteriores al funcionamiento de las derivaciones, ya sea impuestas por el compilador o por convención. La no mutación parece ser un extremo en el espectro de invariantes que podríamos elegir aquí, y tenga en cuenta que ya estamos discutiendo formas en las que se puede suavizar (por ejemplo, marcar los atributos utilizados). Creo que, en general, preferiríamos las condiciones más simples y que el compilador las hiciera cumplir. Sin embargo, esto queda subsumido en cierta medida por la cuestión de cómo deben tratarse las derivaciones especiales.
Si a la gente no le gustó ese destino, me gustaría preguntar por qué, porque obviamente había razones suficientes para hacer esta distinción antes, ¿no es así?
No creo que hubiera una motivación fuerte en ese momento. Creo que fue fácil de implementar y "me pareció una buena idea". No ha sido del agrado ya que porque hace la implementación más compleja, agrega una distinción para los autores de macros que generalmente es irrelevante, y hace que las macros sean menos flexibles al tener que elegir si modificar o decorar.
Me gustaría mucho considerar el diseño a largo plazo de la derivación personalizada y asegurarme de que vamos en la dirección correcta. Me parece que las limitaciones de la solución 1.1 y el deseo de hacer todo lo posible lo antes posible están enturbiando las aguas aquí y estamos perdiendo de vista la visión más amplia.
Estoy de acuerdo con @jimmycuadra en que parece que admitir atributos personalizados de una forma u otra es un requisito estricto. @nikomatsakis también tiene razón en que el tratamiento actual de #[derive(PartialEq, Eq)]
es insatisfactorio y no deberíamos estabilizarlo. Finalmente, @mystor tiene un muy buen punto de que los modos de derivación personalizados ni siquiera deberían conocer este atributo mágico. Estamos obligados a querer agregar más en el futuro y no quiero que las macros 1.1 nos impidan hacer eso.
También haciendo eco del sentimiento de @nrc sobre el diseño a largo plazo de la #[derive]
. Si admitimos atributos arbitrarios, creo que #[derive]
es bastante especial cuando una derivación personalizada no define un nuevo atributo, sino que simplemente agrega uno existente.
En este momento, la implementación tiene una expansión estricta de izquierda a derecha de los modos #[derive]
. Todos los modos de derivación internos se expanden en un bucle y cada vez que se activa un modo de derivación personalizado, reserializamos, expandimos y luego volvemos a la etapa 1. Esto a su vez significa que el compilador puede ejecutar el atributo #[derive]
_múltiples veces para uno tipo definición_. Eso conduce a un montón de vellosidad.
Una propuesta que podría tener es modificar el orden de expansión de #[derive]
:
#[derive(Clone, Foo)]
, primero derivaríamos Foo
donde la estructura tenía una anotación #[derive(Clone)]
. Si ese #[derive]
persistiera, obtendríamos el rasgo incorporado personalizado Clone
.#[derive_Bar]
. Este es solo un truco de compatibilidad con versiones anteriores que deberíamos eliminar en algún momento, y es la forma en que los atributos se expandieron.#[derive]
conocidos e integradosEsto tiene el efecto sorprendente de que no se está expandiendo de izquierda a derecha, pero recuerde nuevamente que esto es solo #[derive]
. Esto le da al compilador el máximo conocimiento sobre la definición de la estructura y cuando se expanden los rasgos incorporados sabemos que la estructura del tipo nunca cambiará.
¿Como suena eso? Creo que resuelve todas las limitaciones aquí.
@nikomatsakis No estoy seguro de que la estrategia de colocar el atributo en impl
funcione porque otros modos de derivación personalizados podrían, en teoría, cambiar el diseño de la estructura, incluso los tipos de campos. Creo que esto violaría las suposiciones del compilador cuando se expandió por primera vez.
¿Alguna vez se ha declarado oficialmente que el orden en el que se procesan los derivados es de izquierda a derecha, a través de la referencia de Rust o algo así? De manera más general, ¿importa alguna vez el orden de los atributos? Parece que es incidental que se implementó de esa manera, y los autores de macros no deberían haber dependido de un orden de izquierda a derecha. La propuesta de Alex de procesar derivaciones personalizadas primero para que nunca vean atributos mágicos agregados por el compilador tiene mucho sentido.
Solo me gustaría agregar que no me gusta la idea de que las derivaciones personalizadas puedan cambiar el diseño de la estructura. Me gustaría poder usar esto para algo que sea sensible a la seguridad. Como ejemplo, considere la implementación #[derive(Trace)]
utilizada por rust-gc
.
#[derive(Trace)]
struct Foo {
a: Gc<i32>,
}
Ampliando a:
struct Foo {
a: Gc<i32>,
}
unsafe impl Trace { // NOTE: Strawman impl
unsafe fn trace(&self) { Trace::trace(&self.a) }
}
Sin embargo, si permitimos cambiar los campos en la estructura, podemos definir una derivación personalizada Evil
:
#[derive(Evil)]
struct Foo {
a: Gc<i32>,
}
Ampliando a:
struct Foo {
a: Gc<i32>,
b: Gc<i32>,
}
Que, si los combinamos:
#[derive(Trace, Evil)]
struct Foo {
a: Gc<i32>,
}
Ampliando a:
struct Foo {
a: Gc<i32>,
b: Gc<i32>,
}
unsafe impl Trace {
unsafe fn trace(&self) { Trace::trace(&self.a) }
}
Que es una implementación poco sólida de Trace
. Cuando se usa con rust-gc
, esto permite que b
sea una referencia pendiente, lo cual es terriblemente inseguro e inseguro. Esto significa que Trace
ya no es algo seguro para #[derive]
en un tipo, lo cual es muy desafortunado.
Personalmente, creo que cualquier #[derive]
se comporte bien no modificará el diseño / composición de una estructura, y si lo hace, entonces no tendrá suerte. La capacidad de un derivado personalizado para eliminar atributos es fundamental, y renunciar a eso no es un comienzo. Además, otras implementaciones que implican algún tipo de inclusión en listas blancas o cualquier otra cosa difieren mucho de la interfaz simple que tenemos hoy.
Dicho de otra manera, no creo que la "pureza" de que #[derive]
nunca modifiquen la estructura valga la pena.
Me pregunto si habrá una forma de eliminar atributos sin permitirle agregar o eliminar campos (por ejemplo, verificar que los campos sean los mismos en la estructura analizada nuevamente que la estructura original y cometer errores si no lo son) t, pero sin quejarse si se cambian otras cosas).
Tengo un mal presentimiento sobre permitir que Derive cambie la estructura. El ejemplo de @mystor es lo que tenía en mente cuando hablaba de componibilidad ... en un nivel alto (puede que no sea el término correcto).
Creo que la gente aprovechará esto, si está disponible. Y esto obligará a los consumidores a razonar sobre los detalles de las implementaciones de derivaciones personalizadas y su orden de ejecución.
Preferiría que pudiera decir 'oye, no sé qué hace esta derivación, pero entiendo la otra' sin interdependencia. De lo contrario, será un dolor en el dedo del pie y creo que sucederá.
¿Es una macro de procedimiento que hace algo malicioso realmente diferente de cualquier caja que use para hacer algo malicioso? Cualquier caja puede tener un código inseguro que hace algo que no debería. Parece que este caso simplemente se relaciona con la forma en que determina la confiabilidad de cualquier código que no haya escrito usted mismo, por ejemplo, reputación de la comunidad, inspeccionando la fuente usted mismo, etc.
No creo que las cajas vayan a intentar hacer algo malicioso, más bien espero que sean "inteligentes" y hagan trucos ingeniosos para hacer su implementación más eficiente o porque pueden, y para que rompa otras implementaciones de derivaciones personalizadas. No me sorprendería mucho si algunas derivadas personalizadas comenzaran a agregar campos a estructuras que solo se usan en sus implementaciones porque pueden, y luego rompen algo como Trace.
@mystor Eso suena relevante en teoría, pero recuerde que en realidad necesita proporcionar todos los campos de una estructura en Rust, por lo que es mucho menos probable que funcione silenciosamente así, que en C ++, por ejemplo.
@alexcrichton
Una propuesta que podría tener es modificar el orden de expansión de
#[derive]
Me gusta esta idea. Quizás lo que hay que decir en términos de documentación es simplemente que el orden de expansión es "indefinido". Sucede que expandimos PartialEq / Eq últimamente estos días, pero no hay una razón estricta para hacerlo.
En cuanto a las derivaciones que modifican la definición de la estructura, estoy de acuerdo en que suena algo sutil, pero no me molesta demasiado. También creo que los campos de "inserción automática" podrían ser bastante útiles, pero preferiría que dichos modificadores sean atributos distintos en lugar de incluirlos en la lista de derivaciones, principalmente porque no queremos que las personas confíen en el pedido. de expansión en este momento.
PD. Consideraría seriamente usar un RNG (determinista) sembrado por el hash de la caja o algo así para reordenar la expansión de las derivaciones personalizadas del usuario, de modo que la gente no pueda confiar en el orden de expansión. Siempre quise hacer esto en algún contexto para evitar dependencias implícitas, pero nunca tuve la oportunidad. ;)
EDITAR: he cambiado de opinión y no veo ninguna razón para no permitir más la mutación de la estructura, pero aquí está mi comentario original para el contexto
Entonces, según tengo entendido, estos son los argumentos para permitir que el
#[derive]
mute la estructura:
- Podría ser útil en algunos casos (no he visto ningún ejemplo, pero creo que existen)
- Queremos poder eliminar atributos una vez que se hayan utilizado
- Dar más poder a los autores derivados personalizados
Si bien los argumentos para agregar limitaciones a las implementaciones personalizadas
#[derive]
(como exigir que el nombre de la estructura y los campos / nombres de los campos en la estructura permanezcan iguales) son:
- Permite que el código generado por un
#[derive]
dependa de la estructura del tipo del que se deriva para su solidez (por ejemplo,#[derive(Trace)]
de rust-gc, que _debe_ ver el tipo de respaldo real, o no es sólido, potencialmente de una manera sutil de uso después de la liberación)- Reduce la probabilidad de dependencias implícitas en macro expansiones, ya que hay menos información transmitida entre ellas a través de la estructura
En mi opinión, la capacidad de escribir implementaciones de derivación personalizadas de sonido que generan
unsafe trait
impls o código que depende de un código inseguro es extremadamente importante, y creo que hay formas de lograr la mayoría de las habilidades en la primera sección ( con la excepción de la capacidad de agregar campos de estructura) de forma segura. Si no tenemos algún tipo de restricción, no creo que las cajas como rust-gc sean posibles de implementar de manera segura. Tengo dos ideas:Idea 1
Antes de ejecutar una pasada de derivación personalizada, lea el nombre de la estructura y los nombres de cada uno de los campos. Cuando se complete el pase y se vuelva a analizar la estructura, verifique si el nombre de la estructura es el mismo y si los nombres de cada uno de los campos (y el recuento de los campos) son los mismos. si no es así, genere un error y finalice la compilación.
Esto aseguraría que las propiedades estructurales básicas de las que esperamos que dependan los complementos de derivación personalizados no se rompan, y significa que tenemos más complementos de derivación personalizados y sólidos. Esto también tiene la ventaja de ser retrocompatible con el enfoque actual, por lo que si decidimos que nos gusta más en el futuro, podemos simplemente cambiar y romper el código de nadie. También maneja el caso de atributos no utilizados, como hoy.
Idea 2
Dé a cada complemento derivado personalizado la misma entrada TokenStream (el texto original escrito en el programa). Cuando se vuelve a analizar el resultado, registre qué atributos todavía están presentes en la estructura de salida. Si un atributo está presente en cada flujo de tokens de salida, entonces quejese sobre un atributo no utilizado.
Esto significa que es imposible tener dependencias de ordenamiento (ya que conceptualmente, cada complemento derivado personalizado funciona con el mismo objeto original), y también hace que sea imposible estropear la estructura del complemento. Me gusta esta idea, ya que garantiza que cada derivado personalizado actúe de una manera mayormente sana, solo generando nuevos elementos basados en la estructura existente. Esto probablemente también sería fácil de transformar en cualquier solución en la que pudiéramos transformar la solución actual.
TL; DR
En resumen, me gustaría entender lo que la ventaja particular es de permitir estructuras mutantes, y por qué pesa más que los problemas de seguridad de hacer segura
#[derive(Trace)]
y siempre correcto-#[derive(Serialize)]
etc posible. Estoy seguro de que si terminamos siguiendo la ruta de las estructuras mutantes, habrá una buena razón, pero me entristecerá mucho cambiar el nombre de mi derivación personalizada de Trace a#[derive(unsafe_Trace)]
.
Encuentro que la solución de @alexcrichton es una buena compensación. Definitivamente esperaría que cualquier cambio que realicen algunas derivaciones personalizadas, se apliquen las predeterminadas.
Aunque @mystor tiene un buen punto de que puede llevar a sorpresas desagradables, tener la posibilidad de cambiar struct
parece obligatorio. Por otro lado, las cajas que combinan los casos de uso de macros de procedimiento, código inseguro _y_ preocupaciones de seguridad parecen bastante poco comunes.
Fuera del tema: ¿esta implementación proporcionará una forma para que la macro informe errores con elegancia?
Me gusta la idea de @nikomatsakis para aleatorizar el orden de expansión de las
@mystor, una tercera opción sería realizar estas comprobaciones de seguridad solo una vez después de aplicar todas las derivadas. Eso no sería 100% correcto (dos derivadas podrían agregar y eliminar el mismo campo), pero en términos de una contramedida heurística, debería ser suficiente para evitar cualquier intento de cambiar la definición de estructura en una derivada personalizada en primer lugar.
Realmente no veo la preocupación por la modificación de estructuras. Un campo no se puede agregar de forma invisible, habrá que cuidar algo durante la inicialización. Si está _realmente_ preocupado por que eso suceda, puede escribir su derivado para generar código que no se compila si no ve la estructura completa con bastante facilidad.
@sgrif probablemente sea cierto en la mayoría de los casos, pero no tanto si también deriva y usa el rasgo predeterminado, o algo equivalente.
@sgrif PD: Es cierto que la mayoría de los autores de rust probablemente entiendan lo que está sucediendo en su propio código y, por lo tanto, es posible que no se sorprendan por las alteraciones de la estructura si eligen usar tal macro a propósito.
El caso de uso general para aplicar macros en estructuras ciertamente es una combinación de alteraciones de estructuras + decoraciones. Pero espero que la proporción general sea "muchas decoraciones" con "solo algunas modificaciones". Es bueno tener una separación clara aquí, porque eso mejora la legibilidad y la flexibilidad.
Entonces, aunque encuentro convincente esta parte del argumento de
Permite que el código generado por un # [derivar] personalizado dependa de la estructura del tipo del que se deriva para su solidez (por ejemplo, # [derivar (Trazar)] de rust-gc que debe ver el tipo de respaldo real, o no es sólido, potencialmente de una manera sutil de uso después de la liberación)
Creo que tratar de hacer cumplir esta derivación puede ser la forma incorrecta de hacer las cosas. En concreto, es probable que sí queremos la capacidad de modificar las definiciones de estructura en el caso general (sí, no va a ser transparente, pero ¿y qué). Lo que significa que la idea de tener un "sonido" Trace
que pueda estar seguro de que la estructura no cambia después, podría necesitar ser resuelta de alguna otra manera. Considere si los decoradores se aplican de abajo hacia arriba:
#[mangle] // <-- custom decorator that does bad things to struct definition
#[derive(Trace)]
struct Foo {
x: T, y: U
}
Una idea podría ser que la Trace
impl puede escribirse de tal manera que _it_ no se compile si cambia la definición de struct
. Por ejemplo:
unsafe impl Trace for Foo {
fn trace(&self) {
let &Foo { ref x, ref y } = self;
<T as Trace>::trace(x);
<U as Trace>::trace(y);
}
}
Sin embargo, tenga en cuenta que #[mangle]
también puede arruinar su impl si es realmente diabólico. =) Hay mucho que podemos hacer aquí.
Como observador de estas conversaciones, me complacería tener la regla formal o informal de que #[derive]
solo puede agregar bloques impl
e introducir una anotación de hermanos ( #[mangle(Foo, Bar)]
bueno para mí 😸) que se dedica a _cambiar_ la estructura de un tipo. #[mangle]
podría tener un orden de evaluación definido.
Probablemente deba haber un orden de evaluación definido entre las anotaciones si aún no lo hay.
Creo que mis opiniones sobre esto se han relajado. @nikomatsakis hace un buen punto de que incluso si saldríamos con la nuestra de poder hacer suposiciones sobre los diseños de estructuras en el código de todos modos. El truco let Foo{ ... }
parece que funcionará para asegurarse de que el diseño sea correcto en casos cuerdos. Sin embargo, probablemente necesitemos documentarlo en algún lugar para que todos no tengan que descubrirlo de forma independiente.
@nikomatsakis
Pero también me alegra ver que hay otra forma de fortalecer las derivaciones personalizadas _individualmente_ frente a cambios posteriores. :-)
Con respecto a los casos que necesitan modificar el interior del elemento al que se aplica el atributo, me encontré con el hilo "Comentarios previos a la implementación para Qt con Rust" en u.r-l.o
, e hice esta publicación:
https://users.rust-lang.org/t/pre-implementation-feedback-for-qt-with-rust/7300/19
Una faceta notable es que aquí, sugiero que se aplique #[derive]
(o similar) a un _trait_, en lugar de a una estructura, y el elemento agregado dentro sería un método const
trait .
Similar a los problemas de carga que mencioné anteriormente, no estoy seguro
#[macro_use]
extern crate double;
importar macros de procedimiento desde una caja llamada double
. Dado que estamos usando código de tiempo de ejecución (como en Ruest real) en tiempo de compilación, debería haber una declaración de importación de incremento de fase análoga a (require (for-syntax ...))
Racket. [El documento de Racket es https://docs.racket-lang.org/reference/require.html#% 28form ._% 28% 28lib._racket% 2Fprivate% 2Fbase..rkt% 29._for-meta% 29% 29, desafortunadamente, no puedo encontrar la manera de vincular la sección correcta.]
La publicación del blog http://blog.ezyang.com/2016/07/what-template-haskell-gets-wrong-and-racket-gets-right/ señala los errores de fase cometidos en Template Haskell y puede ser de interés- - No quiero cometer los mismos errores en Rust.
@ Ericson2314 La diferencia en Rust es que double
ya está compilado _ para una fase específica_.
Es decir, la caja solo exporta la interfaz de macro / modificador, como si estuvieran definidos con, por ejemplo, macro_rules
.
Sería interesante poder crear cajas que exporten tanto las macros de procedimiento como los elementos de Rust subyacentes (que forman la macro de procedimiento), pero hasta ahora no parece que se proponga de ninguna manera.
Puede tener sentido permitir que la caja que se está construyendo elija mucho sobre qué y cómo exportar, pero simplemente tomar un sistema al por mayor de un LISP con un modelo de compilación diferente parece contraproducente.
Sí, @eddyb , soy escéptico de esta metodología de "crear sabe cómo se utilizará en el futuro". En todo caso, las fases son más importantes para nosotros que Racket debido a nuestro modelo de compilación (ni siquiera estoy seguro de si Racket puede realizar una compilación cruzada), así que no entiendo tu último argumento.
Nominado para la discusión del equipo lang sobre: un plan de estabilización.
En el lado de serde, aquí está la breve lista de problemas restantes antes de que podamos dejar de admitir el complemento del compilador existente y recomendar oficialmente Macros 1.1 para todos los usuarios nocturnos: https://github.com/serde-rs/serde/issues/545. Lo único que necesitamos de Rust es que se repare el # 36211. En todo lo demás estamos progresando rápidamente.
Tengo un PR abierto que implementa nuestro rustc_macro sin usar syntex, por lo que podemos dejar de preocuparnos por el tiempo de compilación https://github.com/serde-rs/serde/pull/548.
EDITAR: no importa, # 36211 solo afectó a la antigua implementación de syntex
Intentaré terminar el puerto de Diesel el viernes para poder confirmar que esto hace todo lo que necesitamos en ese sentido.
@dtolnay dado el serde-rs / serde # 548, ¿quedan bloqueadores para serde?
@sgrif increíble, ¡gracias! Una vez que haya hecho eso (o antes), ¿podría comentar aquí con los bloqueadores restantes que haya encontrado?
Sí lo haré.
El martes 27 de septiembre de 2016 a las 7:29 p.m. Alex Crichton [email protected]
escribió:
@dtolnay https://github.com/dtolnay dado serde-rs / serde # 548
https://github.com/serde-rs/serde/pull/548 , ¿quedan algunos
bloqueadores para serde?@sgrif https://github.com/sgrif increíble, ¡gracias! Una vez que hayas hecho eso
(o antes) ¿podría comentar aquí con los bloqueadores restantes que haya
encontrado?-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/rust-lang/rust/issues/35900#issuecomment -250028743,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABdWK7MnA1rpn-WGji8gwAT2JCIqB4LFks5quaa-gaJpZM4JqEAX
.
@alexcrichton
dado serde-rs / serde # 548, ¿quedan bloqueadores para serde?
No, si @ oli-obk o @erickt revisan ese PR hoy o mañana, entonces puedo sacarlo todo al día siguiente y migrar algunos usuarios prominentes como Rusoto.
@dtolnay Estoy haciendo un uso intensivo de serde_macros en Ruma y me gustaría ayudarlo a probar serde_derive también, si necesita más ojos.
De hecho, también uso diesel_codegen, por lo que Ruma es un buen campo de pruebas para la versión Macros 1.1 de estas dos bibliotecas.
Lancé Serde 0.8.10 anunciando que serde_macros está en desuso a favor de Macros 1.1.
Agreguemos una casilla de verificación para "trabajos de documentación de carga": https://github.com/rust-lang/cargo/issues/3132.
@dtolnay hecho!
No estoy seguro si se trata de errores serde_derive o errores de rustc / rustdoc, pero estoy notando dos problemas en los documentos generados:
Deserialize
y Serialize
no aparecen en la sección "Implementaciones de rasgos" para los tipos que usan derivaciones personalizadas.Aparece un literal "///" al comienzo de las cadenas de documentos generadas de tipos que utilizan derivaciones personalizadas.
Este fue un error de syn
. Lancé 0.8.2 con una solución, así que cargo update
para recogerlo.
No sé sobre el segundo, se lo dejo a @alexcrichton.
@jimmycuadra
Deserialize
ySerialize
no aparecen en la sección "Implementaciones de rasgos" para los tipos que usan derivaciones personalizadas.
¡Eso suena bastante sospechoso! Sin embargo, parece que esto es un error en rustdoc, pero quizás no uno nuevo. Por ejemplo, rustdoc no muestra esto con la implementación Copy
:
#[derive(Clone)]
pub struct Point {
x: i32,
y: i32,
}
const _FOO: () = {
impl Copy for Point {}
()
};
Serde-derive usa ese patrón para generar impls. @dtolnay, ¿ serde_macros
tenía la misma forma de generación? ¿Sería fácil alejarse por ahora para resolver el problema de la documentación?
@jimmycuadra quiere abrir un error separado para rastrear el problema de rustdoc sobre eso?
@dtolnay, ¿
serde_macros
tenía la misma forma de generación?
Si.
¿Sería fácil alejarse por ahora para resolver el problema de la documentación?
No. Hasta donde yo sé, esta es la única solución que cumple con estas dos restricciones:
::serde
. Esto es común cuando las personas colocan serde impls detrás de una marca de función en un módulo separado.Consulte https://github.com/serde-rs/serde/issues/159 para obtener más detalles.
@dtolnay ah ok, entonces para aclarar, @jimmycuadra esto no es una regresión de antes, ¿verdad?
Actualización: No del todo terminado con el puerto, pero casi está. No hay más problemas que los que ya he informado o limitaciones menores que sabíamos que encontraríamos. Probablemente sea seguro marcar la casilla "diesel funciona", tendremos un lanzamiento mañana en Macros 1.1 si no sucede esta noche.
Para aquellos que siguen, he abierto # 36945 para renombrar rustc_macro
a proc_macro
prácticamente en todas partes, lo que parece ser el consenso sobre el nombre de esta caja.
Parece que las implementaciones de rasgos que faltan en rustdoc (que ahora se están rastreando en otro número) no son específicas de Macros 1.1. Es seguro ignorarlo.
Intenté portar mi biblioteca mockers
desde el complemento del compilador a macros 1.1 y obtuve un "error: los atributos de derivación personalizados solo se pueden aplicar a elementos de estructura / enumeración". Con el complemento del compilador es posible (aunque algo extraño) usar "derivar" en rasgos. ¿No tengo suerte aquí?
@kriomant interesante! Creo que en realidad puede ser un error en la derivación personalizada hoy, ya que no estoy seguro de que alguna vez haya tenido la intención de permitir que #[derive]
aplique a un rasgo ...
Creo que por ahora, para ser conservadores, es probable que todavía no _estabilicemos_ los rasgos derivados, pero quizás podríamos agregarle una característica inestable. Pensamientos @ rust-lang / lang?
@alexcrichton ¿Cuál es la diferencia entre rasgos y estructuras de un aspecto de custom_derive?
@KalitaAlexey none, es una limitación artificial coincidir con la implementación real derive
@alexcrichton ¿Podríamos extender un apoyo a los rasgos?
Como mencioné anteriormente , es probable que sea un error que la derivación personalizada alguna vez se haya permitido en los rasgos, y esto no se especificó en el RFC, por lo que sería necesario discutir más antes de extender. En cualquier caso, es poco probable que se estabilice el soporte para derivar en rasgo en la primera pasada, pero podríamos considerar una puerta de función separada.
Personalmente, no quisiera agregar derivar en un rasgo, ya que parece mucho más en la línea del territorio de atributos personalizados en lugar de derivarse en sí mismo. (por ejemplo, va en contra del espíritu de #[derive]
como se creó originalmente)
Preferiría que la derivación personalizada siga exactamente las mismas reglas que la derivación regular. _Podríamos_ querer cambiar eso más tarde, pero creo que debería ser RFC (también soy bastante frío con la idea, tbh, pero podría cambiar de opinión con un caso de uso convincente y un manejo decente de varios casos extremos ).
@alexcrichton
Creo que por ahora, para ser conservadores, es probable que no estabilicemos los rasgos derivados por el momento, pero tal vez podríamos agregarle una característica inestable. Pensamientos @ rust-lang / lang?
👍 de mí.
Ok, la característica inestable es buena, pero significa que mi biblioteca no funcionará estable todavía (sin generación de código). Y la sintaxis de macro!(…)
no está cubierta por "macros 1.1" también, ¿estoy en lo correcto?
Estoy revisando el proc macro RFC y el plan era que las cajas de macro deberían declararse #[cfg(macro)]
. No lo requerimos para macros 1.1, pero sí requerimos un tipo de caja especial. Los dos mecanismos son algo ortogonales: el primero describe la fase de compilación, el segundo el tipo de caja. Pero también se superponen un poco: lo último implica lo primero. El primero también escala para declarar macros proc y funciones no macro en las mismas cajas, mientras que el segundo no lo hace.
No estoy seguro de si necesitamos cambiar algo aquí, probablemente podríamos piratear la propuesta de macros proc para que sea compatible con versiones anteriores (omite deliberadamente la descripción del mecanismo de carga de macros y, por lo tanto, los tipos de cajas). Pero algo para reflexionar durante el período de estabilización.
@kriomant correcto, sí
@nrc ahora mismo está definido como cfg(rustc_macro)
(aunque pronto cambiará a proc_macro
). No lo necesitamos, no, pero pensé que iba a ser necesario para el concepto de vincular a una caja en tiempo de compilación y también en tiempo de ejecución. Es decir, compilaríamos proc-macro crate dos veces: una vez con el tipo de caja proc-macro
y una vez con el tipo de caja rlib
, y esta última no se vincularía a libsyntax ni nada parecido ese.
Por ahora, aunque parece bien no _requitarlo_, aunque supongo que eso significaría que en una fecha posterior tiene que optar por el soporte en tiempo de ejecución. (compilando la caja dos veces)
@alexcrichton ¿Podría #[proc_macro_derive]
implicarlo? De la misma manera #[test]
implica #[cfg(test)]
.
(No significa que tengamos que agregarlo ahora, solo que el peor de los casos si agregamos cfg
son las advertencias de artículos no utilizados).
@eddyb ¿Qué hay de extern crate proc_macro;
? Siento que esos también se romperían.
@mystor Es solo una caja normal con un montón de tipos e implicaciones.
Sin embargo, ¿no se vincula también a libsyntax
, lo que significa que si una caja usa una caja proc_macro y quiere realizar una compilación cruzada, también tendrá que cruzar la sintaxis de compilación?
@eddyb sí #[proc_macro_derive]
puede ser ignorado automáticamente, pero el problema es que _también_ necesitamos ignorar todo lo que esas funciones alcanzan de manera transitiva (como extern crate proc_macro
). Aunque es "solo una caja", tiene graves implicaciones en el tiempo de ejecución (vinculación dinámica, no disponible para objetivos de compilación cruzada, etc.).
Tenga en cuenta que las pruebas tienen #[cfg(test)]
, por lo que me parece razonable que todavía demos #[cfg(proc_macro)]
básicamente para el mismo propósito.
@alexcrichton _Idealmente_, la caja no tendría más que lo que se exporta y no requeriría un enlace dinámico ni nada por el estilo, solo libstd
. Sin embargo, pude ver que causaba problemas en los casos #![no_std]
.
Parece que tendríamos que hacer la doble compilación desde el principio si queremos captar todo: desordenado : .
EDITAR : Espera, ¿en qué estoy pensando? Requiere un tipo de caja personalizada, la doble compilación se aplica a cajas _regulares_ que _también_ exportan macros de procedimiento / atributos / deriva / etc. Entonces no es relevante por ahora.
Pero al menos podríamos introducir #[cfg(proc_macro)]
que siempre está configurado para el nuevo tipo de caja.
🔔 ¡ Esta función está entrando en su período de comentarios final con la intención de estabilizarse al final de este ciclo de lanzamiento! 🔔
La razón fundamental para considerar la estabilización ahora es:
Tenga en cuenta que el nombre está cambiando a proc_macro
, según el consenso anterior sobre este hilo. Podemos continuar eliminando este y otros puntos delicados durante el resto de este ciclo de lanzamiento.
Compilar la caja dos veces suena muy burdo: el alcance no funcionará correctamente. Realmente algo como extern! crate needed_for_my_inline_proc_macros;
es una solución mucho mejor.
@aturon qué, ¿la gente no comenzó a usar esto hace días?
@ Ericson2314 Ha sido un poco más largo que eso, pero como explica el comentario de FCP, el _mínimo_ tiempo antes del envío al canal estable es de tres meses a partir de ahora, lo que creemos que es más que suficiente para esta interfaz extremadamente estrecha.
Tenga en cuenta que el FCP en sí es un asunto de 6 semanas que no necesariamente significa que lo pondríamos en el camino hacia la estabilización. Pero al comenzar el proceso al menos ahora, creamos la oportunidad de enviar esta función en 3 meses, suponiendo que no se descubran problemas antes de esa fecha.
Ah ok. Recordé la parte de los 3 meses, pero olvidé la ubicación del FCP dentro de esas 3 bocas, para que no fueran 3 meses desde el 22 de agosto. No importa el momento en el que entonces.
Creo que los problemas de fases que mencioné tienen un impacto incluso en esta pequeña parte de la historia de las macros proc, por lo que me gustaría que se aborden.
@alexcrichton los rustc_copy_clone_marker
y otros rustc_attrs
todavía nos están mordiendo. Consulte https://github.com/serde-rs/serde/issues/577.
#[derive(Copy, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct MyStruct {
value: i64,
}
La solución es cambiar el orden, pero pensé que comprobaría si esto se puede arreglar.
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone)]
pub struct MyStruct {
value: i64,
}
Terminé de migrar https://github.com/sfackler/rust-postgres-derive a macros 1.1 Fue relativamente sencillo, pero el manejo de atributos que son leídos por múltiples derivadas es un gran dolor. Serde se encontró con lo mismo, pero requiere un montón de lógica extraña para expandir ambas derivadas al mismo tiempo: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-internals/ src / lib.rs # L26
No debería bloquear la estabilización, pero creo que es un problema ergonómico relativamente grande en el lado de la autoría que probablemente deberíamos abordar pronto.
Lancé quote
0.3.0 con soporte para elegantes repeticiones de estilo macro_rules!
(gracias @nrc por el empujón). Si está desarrollando macros proc sin cuasi / syntex probablemente desee esto. Ejemplo de uso de postgres-derive:
pub fn enum_body(name: &str, variants: &[Variant]) -> Tokens {
let num_variants = variants.len();
let variant_names = variants.iter().map(|v| &v.name);
quote! {
if type_.name() != #name {
return false;
}
match *type_.kind() {
::postgres::types::Kind::Enum(ref variants) => {
if variants.len() != #num_variants {
return false;
}
variants.iter().all(|v| {
match &**v {
#( // \
#variant_names => true, // |----- new feature
)* // /
_ => false,
}
})
}
_ => false,
}
}
}
Tengo curiosidad, ¿por qué #
y no $
?
EDITAR : Siempre pensé que la sintaxis de citas sería ${...}
pero tal vez estoy demasiado apegado a los literales con plantilla de ES6, incluso si han pasado varios años. \{...}
también funciona, aunque sería útil en otros lugares.
Sin corchetes parece difícil de detectar, pero no debería preocuparme.
Tengo curiosidad, ¿por qué
#
y no$
?
Porque es una macro macro_rules y no puedo hacer lo que quiera: smile : $v
se empareja como un solo token y no hay forma de pasar de $v
a usar la variable v
. En contraste, #v
son dos tokens, por lo que puedo combinarlos por separado y hacer cosas con el ident.
macro_rules! demo {
($tt:tt) => {};
}
fn main() {
demo!($v);
}
¡Espere, todo eso se hace en macro_rules
?! Las macros olvidadas 1.1 se derivaron solo por un momento. Dicho esto, $x
ser un token es un defecto de diseño, en mi opinión. cc @jseyfried
@dtolnay
@alexcrichton, el rustc_copy_clone_marker y otros rustc_attrs todavía nos están picando. Ver serde-rs / serde # 577.
¡Ay, eso parece malo! Actualizaré la lista en la parte superior. ¿Pensamientos de @nrc o @jseyfried sobre cómo podríamos abordar esto? No estoy muy familiarizado con el orden de expansión de todo, pero tal vez cuando se expande un atributo #[derive]
, podría intentar absorber todos los futuros y hacerlo primero.
@sfackler
el manejo de atributos que son leídos por múltiples derivadas es un gran dolor
No estoy seguro de haber seguido el ejemplo al que vinculó, ¿podría explicarlo?
@alexcrichton Tomemos por ejemplo
#[derive(ToSql, FromSql)]
enum Foo {
#[postgres(name = "bar")]
Bar
}
El atributo #[postgres]
se usa para ajustar cómo se generan las implementaciones ToSql
y FromSql
. Debe eliminarse antes de la salida final, ya que de lo contrario es un atributo desconocido, pero las implementaciones ToSql
y FromSql
se ejecutan por separado. Si lo hace de forma ingenua, simplemente generando la implementación y luego eliminando los atributos, la segunda derivación no tendrá las personalizaciones.
serde-derive y postgres-derive solucionan esto ahora mismo al tener la implementación para ambas implicaciones derivadas hacia la misma función que genera ambas a la vez. Tenemos que volver a adjuntar el #[derive]
para el que se está invocando actualmente desde que el compilador lo elimina, y luego enviarlo para expandirlo: https://github.com/sfackler/rust-postgres-derive/blob /master/postgres-derive/src/lib.rs#L18
@sfackler Creo que también puede hacerlo eliminando los atributos adicionales solo cuando no quede ningún derivado del mismo implementador. Su camino podría ser mejor _ encogerse de hombros_.
@sfackler ah ok, tiene mucho sentido. ¡Gracias!
Sin embargo, me pregunto si esa lógica puede ser frágil. Por ejemplo, si está expandiendo ToSql
¿cómo se detecta la expansión futura de FromSql
? ¿Podría estar detrás de un #[cfg]
como @dtolnay mencionado anteriormente, tal vez? ¿Entonces puede que no sea posible detectarlo en todos los casos?
@alexcrichton Sí, es frágil, por lo que una solución real parece importante.
¿No se procesarían #[cfg]
y #[cfg_attr]
antes de las derivaciones?
Dado que la API de macros 1.1 funciona en cadenas, solo puedo imaginar tres soluciones:
Cada opción tiene sus pros y sus contras.
estafa 1: soluciones quebradizas mientras tanto
con 2: algunos atributos no utilizados no serán detectados
con 3: más superficie api para una solución provisional
He considerado envolver atributos en #[used(...)]
para mantenerlos y ponerlos en la lista blanca al mismo tiempo, pero es bastante tonto y demasiado estable.
@alexcrichton
cuando se está expandiendo un atributo
#[derive]
, podría intentar absorber todos los futuros
Me gusta esta idea. Dado que #[cfg]
sy #[cfg_attr]
s se procesan antes que #[derive]
s, #[cfg]
-guarded #[derive]
s no son un problema para este enfoque (o para la solución análoga de @sfackler al problema de eliminación de atributos).
Este enfoque haría que la solución de @sfackler fuera un poco más simple, ya que otras sugerencia de @eddyb simplificaría las cosas).
Una posibilidad para el problema de los atributos de control es agregar devoluciones de llamada posteriores a la expansión, algo similar a las de syntex: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-codegen/src/lib. rs # L23 -L50
Sus derivaciones implícitas pueden ser independientes y no eliminar los atributos de control, y podría registrar un pase que se ejecute después de que se complete toda la expansión para limpiar.
Diesel también está solucionando esto manualmente. https://github.com/diesel-rs/diesel/blob/master/diesel_codegen/src/lib.rs#L101 -L112
@dtolnay, su preocupación anterior sobre múltiples atributos #[derive]
debería solucionarse en breve gracias a @jseyfried
Jugué con esto usando los paquetes syn
y quote
, y tuve una experiencia realmente positiva. Esta será una gran característica cuando se estabilice.
El problema con los atributos está afectando actualmente a mi impl, siendo posterior a la lógica de derivación de serde
s.
Ignorar los atributos no utilizados en el código derivado, o permitir que las expansiones tardías que se ejecutan después de las derivadas regulares los eliminen, me parecen soluciones razonables.
Sería ideal si las macros no tuvieran la opción de mutar el flujo de tokens original, parece una característica peligrosa que podría terminar siendo utilizada en la naturaleza entre ahora y las macros 2.0 y, por lo tanto, difícil de eliminar más adelante.
Entonces, faltaba un punto y coma en una ruta de tipo dentro de una función genérica y recibí el siguiente mensaje de error de rustc. Me pregunto si se podría proporcionar más información, como qué macro de derivación causó el error lex, qué token lo causó, etc.
error: custom derive attribute panicked
--> src/simple.rs:69:1
|
69 | struct C(u64);
| ^^^^^^^^^^^^^^
|
= help: message: Failure parsing derived impl: LexError { _inner: () }
Hice un caso de prueba para LexError, parece que sucedió con el 13 de octubre por la noche (rustup no tenía actualizaciones) https://github.com/keeperofdakeys/proc_macro_derive_test.
@ rust-lang / lang Creo que debemos discutir la cuestión de si las derivaciones pueden modificar su elemento anotado. Aunque es una solución simple para permitirlo, parece ir en contra de las expectativas del usuario y parece bastante impopular. Sigo prefiriendo la solución actual: la simplicidad es agradable y ninguna de las alternativas propuestas me parece tremendamente mejor.
Necesitaremos alguna solución para controlar los atributos si el artículo se puede modificar. No creo que me sienta particularmente convencido de mantener la capacidad de derivar para modificar el elemento, siempre que (en el futuro) las macros de atributos no derivados puedan hacerlo.
Alguien debería definir "modificar". ¿Estamos hablando de modificar los miembros de la estructura o simplemente estamos hablando de eliminar atributos para evitar que el compilador se queje de atributos desconocidos?
Si estamos hablando de modificar los miembros de la estructura, en mi opinión, la mejor solución probablemente sea permitir solo una macro de modificación de estructura en cada estructura que se expanda antes que todas las demás. Eso tendría un comportamiento bien definido y (en mi opinión) esperado.
Si solo estamos hablando de eliminar atributos, probablemente debería haber una forma de integrar atributos en el mecanismo de macros en lugar de dejarlo a discreción de la macro.
Mi intuición sobre cómo se comporta Derive es que agrega impls. Para el usuario final, las derivaciones deberían parecer agregar implicaciones y no hacer nada más. En particular, no deben modificar la estructura de una manera que sea importante para otros derivadores y, por lo tanto, deben ser independientes del orden. ¿Estamos de acuerdo en que así es como debe comportarse una derivada, o la gente cree que las derivadas también deben poder realizar transformaciones en el tipo adjunto?
Si estamos de acuerdo en eso, la pregunta es: ¿queremos que la interfaz que exponemos haga cumplir esto, o queremos dejarlo para que los autores deriven para hacer cumplir esto? Aquí, parece haber dos problemas compensatorios:
serde
y deisel
trabajen con esa restricción presenta muchos desafíos.Y, por supuesto, cómo otros atributos pueden modificar la estructura parece no estar relacionado con cómo derivar, específicamente, debería comportarse.
Vale la pena señalar que la capacidad de eliminar el elemento anotado permite que el sistema actual cumpla una gran cantidad de funciones que de otro modo no haría.
@sgrif ¿Podrías explicar cómo deisel está usando derive? Siento que entiendo cómo serde usa su capacidad para modificar la estructura (para eliminar atributos que informan a la macro que omita o cambie el nombre de los campos en el rasgo de serialización), pero tal vez deisel está usando esto para hacer otra cosa. Cuando dice "eliminar el elemento anotado", parece que está realizando una transformación bastante radical en el elemento.
@withoutboats El 90% es más o menos lo que cabría esperar. No tocamos los elementos proporcionados por el usuario. Sin embargo, eliminamos elementos anotados para piratear macros de bang en el sistema. https://github.com/diesel-rs/diesel/blob/master/diesel/src/macros/macros_from_codegen.rs#L12 -L18. Más allá de eso, la única vez que tocamos algo en el flujo del token de entrada es para eliminar las anotaciones. https://github.com/diesel-rs/diesel/blob/master/diesel_codegen/src/lib.rs#L109 -L120
@sgrif También he abusado de la
Sin embargo, puedo entender el argumento para mantener derivar como una simple macro.
Mi perspectiva es que el objetivo de Macros 1.1 es ser lo más flexible posible para cubrir tantas necesidades como sea posible, con una carga de mantenimiento baja para que pueda estabilizarse rápidamente y actuar como un recurso provisional hasta las macros 2.0. En mi opinión, el diseño actual encaja muy bien en ese papel.
Si estuviéramos hablando de algo destinado a ocupar permanentemente este papel, me opondría mucho más.
Tal vez me equivoque, pero mi lectura del RFC es que se pretende que sea la base del comportamiento de derivar de forma permanente. Es decir, se agregarán más métodos a TokenStream
en el futuro, pero las macros de derivación utilizarán esta API, que actualmente les permite realizar mutaciones arbitrarias en el elemento anotado (y esta capacidad es necesaria para el caso de uso de deisel ).
Me siento bastante negativo sobre permitir que los derivados hagan esto de forma permanente. Si aceptamos que este es un sistema que va a quedar obsoleto, junto con las macros macro_rules
, en algún momento en el futuro, y bajo las macros 2.0, se preferirá una API de derivación diferente que es más restringida, estoy más cómodo con él.
Parece que la intención es apoyar a los decoradores como transformadores que pueden hacer cualquier cosa.
Y derivar expuesto como un simple decorador sin entrada en el atributo.
@withoutboats : serde admite una serie de atributos para modificar la forma en que se generan las impls, por lo que definitivamente necesitamos la capacidad de eliminarlas o ignorarlas después de haberlas procesado. Si pudiera ayudar, de alguna manera podríamos proporcionar al compilador una lista de atributos que deberían ser eliminados, en lugar de que nosotros quisiéramos hacerlo nosotros mismos.
@eddyb Estoy a favor de que los decorados puedan hacer cualquier cosa, pero no de que los derivados sean decoradores desenfrenados (a largo plazo).
@erickt Correcto. Creo que a largo plazo, la solución ideal sería que esos atributos se registren como atributos personalizados no operativos, en lugar de que el derivador sea responsable de eliminarlos. Pero eso no es factible a corto plazo.
Creo que a largo plazo, la solución ideal sería que esos atributos se registren como atributos personalizados no operativos, en lugar de que el derivador sea responsable de eliminarlos. Pero eso no es factible a corto plazo.
No estoy familiarizado con los componentes internos del compilador que hacen que esto sea inviable a corto plazo, pero ¿sería posible que el complemento de derivación personalizada presentara una lista de atributos personalizados que pretende eliminar y luego rechazar cualquier otra transformación en los atributos en el artículo original?
También noté que Diesel no sigue el enfoque de Serde de tener todos sus atributos personalizados bajo un nombre (por ejemplo, #[serde(rename = "name")]
en lugar de #[table_name = "name"]
Diesel). ¿Simplificaría la implementación si solo un nombre de atributo personalizado? se registró en lugar de una lista?
Una posibilidad para el problema de los atributos de control es agregar devoluciones de llamada posteriores a la expansión, algo similar a las de syntex: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-codegen/src/lib. rs # L23 -L50
Sus derivaciones implícitas pueden ser independientes y no eliminar los atributos de control, y podría registrar un pase que se ejecute después de que se complete toda la expansión para limpiar.
Implementé devoluciones de llamada posteriores a la expansión para Macros 1.1 en post-expansion
. Sus derivaciones implícitas pueden ser independientes y no eliminar los atributos de control, y puede registrar un pase que se ejecuta después de que se haya realizado toda la expansión para eliminar los atributos.
Hoy discutimos el problema de modificación / pedido en la reunión del equipo de lang. Había un deseo de cumplir con las expectativas de los usuarios y de simplicidad (en términos de diseño, no tener demasiados trucos en capas en la parte superior y no estar demasiado sujeto a errores difíciles de descifrar / depurar). Se señaló que, aunque la modificación de los datos objetivo es sorprendente, no es insegura. También se consideró que _podríamos_ tender hacia la pureza por sí misma, más que por razones bien motivadas.
Al final, decidimos que las derivadas que no modifican la fuente son probablemente mejores y deberíamos cambiar a ese modelo. Eso podría significar prolongar el período de FCP. No pensamos que debería haber un mecanismo especial para tratar los atributos de eliminación. Más bien, que la forma en que el compilador maneja los atributos debería permitir que los utilizados por una macro permanezcan en el programa. RFC 1755 debería tener esto en cuenta.
Esto retrasará la estabilización de algunos usuarios derivados personalizados. Sin embargo, creemos que la mayoría de los usos de derivados (especialmente aquellos que mantienen a los usuarios fuera de una cadena de herramientas estable) no usan atributos, por lo que, por ejemplo, la mayoría de los usuarios de Serde podrán pasar a estable pronto. Aquellos que necesitan atributos, tomarán algunos ciclos más _pero eso no afectará el caso común_.
cc @alexcrichton , @dtolnay , @sgrif , @erickt - ¿pensamientos?
Los atributos probablemente se usan más comúnmente con Serde y Diesel de lo que sugiere. Solo por mi propia experiencia, estoy bastante seguro de que todos mis programas que usan Serde usan atributos. Con Diesel definitivamente uso atributos, y creo que es necesario para decirle a diesel_codegen qué tabla de base de datos se asigna a la estructura.
Dicho esto, no dejar que la derivación personalizada mute la estructura me parece la elección correcta. Simplemente simplifica todo, evitando muchos casos extremos extraños. Es más importante hacerlo bien que hacerlo rápidamente, por lo que si la función tiene que permanecer inestable un poco más, también parece estar bien.
Se señaló que, aunque la modificación de los datos objetivo es sorprendente, no es insegura.
No es inseguro, a menos que esté derivando un rasgo inseguro.
En mi opinión, uno de los casos de uso de las derivadas personalizadas es implementar de manera segura rasgos marcados como inseguros, como por ejemplo un rasgo que debe describir exactamente el diseño de los miembros de la estructura en la que se implementa.
Mi caja asn1 también requiere atributos para cualquier uso que no sea trivial, por lo que tendría que esperar hasta que aparezcan los atributos personalizados.
¿Sería una buena idea dividir los atributos personalizados rfc en dos?
Las macros de atributos personalizados parecen algo que requerirá mucho si se desarrolla. Por lo tanto, dividir el rfc en dos puede proporcionar atributos estables para paquetes de derivación personalizados antes.
Esto también significa que los atributos están separados por nombres y son intencionales (es decir, se requiere una caja externa para usar atributos para una caja). Puedo prever que esto es bueno para evitar que dos macros de derivación personalizadas usen el mismo nombre de atributo. Una buena expectativa aquí sería usar el nombre de la caja en la que se encuentra el rasgo para los atributos.
¿Hay alguna razón por la que esta implementación no incluya las macros habituales name!
? Simple TokenStream
in, TokenStream
out para macros habituales resultaría extremadamente útil en plagas donde los tiempos de compilación para gramáticas complejas superan los 30 segundos.
@dragostis Para citar el resumen de rfc:
Extraiga una pequeña porción del sistema de macros de procedimiento actual en el compilador, lo suficiente para obtener funciones básicas como el funcionamiento de derivaciones personalizadas, para tener una API finalmente estable. Asegúrese de que estas características no supongan una carga de mantenimiento para el compilador, pero tampoco intente proporcionar suficientes características para el "sistema de macros perfecto" al mismo tiempo. En general, esto debe considerarse un paso incremental hacia una "macros 2.0" oficial.
O en términos más prácticos, se necesitará mucho diseño y pruebas para obtener un sistema macro de procedimientos que funcione bien, al igual que el sistema de cierre que ahora hemos tenido que construir. Las cajas como serde
y diesel
tienen serios problemas de usabilidad sin una función de derivación personalizada adecuada, así que hagamos una medida provisional para solucionarlo ahora; eso también ayuda a las herramientas y la experiencia con una posible macrosistema procedimental. Las cajas syn
y quote
son buenos ejemplos de esto.
@keeperofdakeys Sí, lo tengo. No estoy solicitando una implementación de macros 2.0, solo me pregunto si hay alguna carga involucrada para agregar otro atributo, digamos proc_macro
que es una implementación mínima que simplemente refleja el diseño derivado solo para macros habituales. El ejemplo en pest simplemente derivaría un analizador para un struct
solo que no lo derivaría del struct
sí mismo sino de una gramática simple definida entre {}
. ¡Sin embargo, espero no desenterrar discusiones muertas!
@dragostis Mencioné esta posibilidad en los https://internals.rust-lang.org/t/pre-rfc-extend-macros-1-1-to-support-foo-style-macros/3921
@nrc
También se consideró que podríamos estar tendiendo a la pureza por sí misma, más que por razones bien motivadas.
Una nota sobre esto: en muchos casos en los que hemos tenido que tomar decisiones difíciles, como la parametricidad y la especialización, asegurar de forma estática la noción relevante de "pureza" sería difícil / imposible. Pero en este caso, en realidad es muy sencillo garantizarlo mediante la construcción, y eliminar la importancia del orden para las derivadas parece una simplificación bastante fuerte para los usuarios.
En lo que respecta a la estabilidad, podríamos considerar agregar un mecanismo inestable para ignorar atributos en este momento, que sería estable en la práctica (en el sentido de que la API no sería propensa a romperse) incluso si aún requiere su uso nocturno. Incluso podríamos considerar estabilizar dicho canal lateral, con planes para desaprobarlo en favor de un mecanismo más general una vez que esté disponible. O podríamos considerar la sugerencia de @keeperofdakeys y avanzar rápidamente en la parte de la solución de atributos generales que abordaría la preocupación inmediata.
Desde mi perspectiva, es importante que ninguna de estas preocupaciones impida que las macros 1.1 sean ampliamente utilizables para Serde y al menos "de facto" estables (es decir, no se rompan).
@sgrif
Si estuviéramos hablando de algo destinado a ocupar permanentemente este papel, me opondría mucho más.
Quería hacerme eco de
Mi impresión del uso de atributos en la derivación personalizada fue similar a la impresión de @jimmycuadra , que es que se usan con bastante frecuencia. Creo (pero corrígeme si me equivoco) que los atributos personalizados fueron cruciales para varios casos de uso de diesel.
En ese sentido, no estoy 100% seguro de cuál sería la mejor manera de avanzar con respecto a esos atributos. Sería una lástima estabilizar las macros 1.1 pero dejar a un gran número de usuarios encendidas todas las noches (incluso si es "estable de facto" todas las noches) porque eso frustra un poco el propósito de la estabilización rápida de las macros 1.1 en primer lugar. En otras palabras, si no estamos llevando a la mayoría de los usuarios de macros 1.1 _en realidad_ a Rust estable, creo que la estabilización de macros 1.1 puede esperar.
Sin embargo, un punto que me ha estado molestando es cómo creemos que se verá el atributo personalizado a largo plazo y lo racionalizamos con el sistema actual de macros 1.1. En el RFC actual para atributos personalizados, las cajas tienen que declarar espacios de nombres de atributos en la lista blanca en la parte superior de una caja. Esto significa que usar un atributo personalizado en la derivación personalizada es radicalmente diferente a usar un atributo personalizado en otro lugar. Esa desconexión me preocupa en términos de una perspectiva ergonómica y de "mínima sorpresa", aunque también lo veo como una función forzada para ajustar el RFC (por ejemplo, si me vinculo a la caja serde
tal vez eso pueda incluir automáticamente los atributos de serde en la lista blanca ).
En general, personalmente estaría bien avanzando con la estabilización completa del sistema de macros 1.1 tal como está hoy. O en otras palabras, estabilice el hecho de que las funciones de expansión derivadas personalizadas reciben una estructura y luego deben devolver la estructura también si quieren que se conserve.
Tiendo a cambiar de rustc-serialize a serde _porque_ la generación de código de serde admite atributos de control.
Podríamos extender macros 1.1 para admitir argumentos para el atributo derivar en sí. Eso parece algo bueno en general, y podría abusarse de él para evitar la falta de atributos de control a corto plazo.
Lo mismo ocurre con @jimmycuadra y @sfackler , los atributos son más integrales para Serde de lo que el comentario de
Todavía no tengo una opinión sobre el movimiento correcto aquí, pero sé que si nos estabilizamos sin atributos, consideraría seriamente analizar los comentarios del documento para extraer los atributos. Me imagino que muchas personas no querrían lidiar con un script de compilación solo para poder escribir:
#[serde(skip_serializing)]
.. en vez de:
/// <!-- serde(skip_serializing) -->
@tomaka
En mi opinión, uno de los casos de uso de las derivadas personalizadas es implementar de manera segura rasgos marcados como inseguros, como por ejemplo un rasgo que debe describir exactamente el diseño de los miembros de la estructura en la que se implementa.
¿Qué opinas de mi comentario aquí al respecto? Encontré que era un patrón satisfactorio.
Quiero aclarar una cosa:
¿Eliminar atributos personalizados es lo más importante que hace la gente con la derivación personalizada?
Sé que hay algunos trucos para hacer foo!
expansión en el contexto de un tipo (por ejemplo, @sgrif mencionó un caso de uso de este tipo en diesel, creo que @tomaka también mencionó uno): ¿qué tan centrales son esos casos de uso? ?
La razón por la que pregunto es la siguiente: veo los beneficios de que el mecanismo de derivación solo devuelva una lista de implicaciones adicionales. En particular, significa que los intervalos para la declaración de tipo en sí serían correctos. Agregar una API rápida y sucia al contexto que le permite proporcionar una lista de nombres de atributos para incluir en la lista blanca en el contexto de ese tipo parece una solución bastante fácil para los atributos, y siempre podríamos desaprobarla.
Sin embargo, si queremos habilitar más casos de uso (y, francamente, esos casos de uso son un poco más ... sorprendentes, cuando se piensa en lo que uno espera de derivar), entonces esto no funcionará. En ese caso, probablemente estaría bien con estabilizador como es y la planificación para despreciar la API "de bytes sin formato" en el futuro a favor de una mejor manera para derivar de escritura (después de todo, realmente no esperar que la gente sea usando bytes sin procesar de todos modos, ¿verdad? ¿Pero más bien transmisiones de tokens?) Quizás podríamos darle a la API un nombre un poco más torpe =)
@nikomatsakis La principal necesidad no es eliminar, sino ignorar los atributos. No soy consciente de los inconvenientes graves de ignorarlos. Proporcionar una lista blanca de la función de derivación a través de un parámetro adicional al atributo principal, por ejemplo, debería ser suficiente para todas las necesidades prácticas que son verdaderas derivaciones y no hacks de macro de procedimiento.
Sí, estoy dividido entre dos enfoques sobre esto, los cuales tienen claras desventajas:
EDITAR: pero la lista blanca de @eddyb también suena prometedora.
Quiero decir que @nrc no está proponiendo algo muy diferente (aunque IIRC está hablando de la lista blanca en la caja del usuario), y se vuelve tonto hablar de "marcar atributos como usados" cuando todo lo que tienes es un flujo de tokens.
Las macros de procedimiento arbitrarias para las que he abusado del sistema de macros 1.1 en rust-cpp serían totalmente posibles con un enfoque alternativo que simplemente brinda la capacidad de agregar impls e ignorar atributos.
Creo que tener la capacidad de ignorar atributos es esencial, pero aparte de eso, estaría bien si no pudiera modificar la estructura más allá de ese punto.
No estoy seguro de qué formas de hacks, que @sgrif está utilizando en deisel que no serían _possible_ que hacer en un mundo donde no podemos modificar la estructura en sí, y en su lugar sólo se pueden añadir elementos adicionales.
Permitir que las derivadas personalizadas tomen argumentos, como sugiere @sfackler , podría ser una forma de que todos obtengan la funcionalidad que necesitan y, al mismo tiempo, permitir que se difiera cualquier decisión sobre atributos personalizados. No será tan bonito ni tan legible, pero hará el trabajo. p.ej:
#[derive(Debug, Clone, Serialize(field("bar", rename = "baz")))]
pub struct Foo {
pub bar: String,
}
Este formulario podría quedar obsoleto en favor de los atributos personalizados, una vez que se tome una decisión sobre ellos.
Tanto serde como mi caja requieren atributos de campo / variante. Tratar de emular esto con argumentos derivados no tiene mucho sentido, necesitamos atributos.
Cualquiera que sea la decisión que tomemos para estabilizar esto, sería bueno si el usuario de macros de derivación personalizadas no necesitara modificar su código cuando / si cambiamos a una API de derivación de macros 2.0 (obviamente, los autores de macros de derivación personalizadas lo harán). Parece que la decisión más sensata es darle al compilador una lista de atributos para eliminar de su derivación y, fundamentalmente, esos solo se eliminan después de que se hayan ejecutado todas las macros de derivación. Serde, diesel y mi caja tienen el problema de requerir el mismo atributo en múltiples macros de derivación.
Con el comportamiento de eliminación, no necesitaría la caja posterior a la expansión que @dtolnay hizo para agregar otra macro de
FWIW, la única razón para eliminar esos atributos es mantener el HIR más delgado; en lo que respecta a todo lo demás, solo necesita marcarlos como usados.
¿Eliminar atributos personalizados es lo principal que hace la gente con la derivación personalizada?
¡Sé que hay algunos trucos para hacer foo! expansión en el contexto de un tipo (por ejemplo, @sgrif mencionó un caso de uso de este tipo en diesel, creo que @tomaka mencionó uno también): ¿qué tan centrales son esos casos de uso?
Estoy totalmente de acuerdo con el hecho de que las derivaciones personalizadas no pueden modificar la estructura.
A menudo me quejo por la falta de complementos en Rust estable, así que cuando vi derivaciones personalizadas, las usé como una oportunidad para tener complementos. Pero obviamente no voy a argumentar que las derivaciones personalizadas deban admitir algo para lo que no fueron diseñadas.
Parece que hay una regresión en la última noche. Obteniendo el error
Queryable
es un modo de derivación
al compilar nuestros ejemplos.
#[derive(Queryable)]
pub struct Post {
pub id: i32,
pub title: String,
pub body: String,
pub published: bool,
}
@sgrif Eso fue causado por # 37198, que cambió las derivaciones personalizadas para usar el mismo espacio de nombres que otras macros (de modo que bang!()
macros, #[attribute]
macros y #[derive(custom)]
macros compartan el mismo espacio de nombres).
En ese ejemplo, #[macro_use] extern crate diesel;
importa una macro de explosión llamada Queryable
y #[macro_use] extern crate diesel_codegen;
importa una macro de derivación personalizada también llamada Queryable
, que sobrescribe silenciosamente la macro de explosión (aparte - #[macro_use]
sobrescribir silenciosamente no es ideal, pero no será un problema una vez que podamos importar macros desde cajas externas con use
lugar de #[macro_use]
, ¡pronto!).
Creo que el error se debe a una invocación de macro bang Queryable!();
en la expansión de la derivación personalizada, que se resuelve en la derivación personalizada de diesel_codegen
lugar de la macro bang de diesel
.
@jseyfried ¿Cuál es la razón para usar un solo espacio de nombres para los tres "tipos" de macros? No me parece que tenga sentido tratar los atributos y las macros bang como si ocuparan el mismo espacio de nombres, más de lo que tendría sentido tratar las funciones y los tipos como si ocupasen el mismo espacio de nombres (derivar macros aún menos).
@withoutboats Creo que esa es la analogía incorrecta, prefiero pensar que diferentes tipos de macros comparten un espacio de nombres de la misma manera que lo hacen los diferentes tipos de valores o tipos (por ejemplo, funciones y constantes). Hay un costo de complejidad en tener espacios de nombres y creo que cuantos menos tengamos, mejor, así que la pregunta debería ser ¿por qué necesitamos diferentes espacios de nombres? Claramente, para la compatibilidad con versiones anteriores, necesitamos que las macros estén en un espacio de nombres diferente al de las funciones y los tipos.
El problema real es que no puede usar use
para importar o renombrar macros de forma selectiva. Por lo tanto, un usuario de cajas no tiene la capacidad de sortear los conflictos de nombres de macros. Al mismo tiempo, no esperaría que la simple importación de una caja de ProcMacro chocara con los nombres de macro locales; pensé que las macros de derivación comenzaban con derive_
?
Hay un costo de complejidad en tener espacios de nombres y creo que cuantos menos tengamos, mejor, así que la pregunta debería ser ¿por qué necesitamos diferentes espacios de nombres?
Creo que los elementos deben compartir un espacio de nombres solo si pueden ser ambiguos entre sí. consts y fns están en el mismo espacio de nombres porque ambos se usan como idents en un contexto de expresión. Los tipos y rasgos están en el mismo espacio de nombres debido a la sintaxis de los objetos de rasgos.
Estos tres tipos de macros se invocan de distintas formas. Para mí no tiene sentido que deban compartir un espacio de nombres, porque nunca puede haber ambigüedad en la invocación.
Si bien hay un costo de complejidad de implementación, no creo que haya un costo significativo en la complejidad de _uso_ al espaciarlos por separado. Cuando pienso en el costo de complejidad de una característica del lenguaje, generalmente pienso en la complejidad de uso.
¿Diría que _todos_ los elementos deberían estar en el mismo espacio de nombres si no fuera un cambio importante? Tratando de aclarar tu proceso de pensamiento.
Reflexionando un poco más, estaría bien si dijera que solo debería haber 1 espacio de nombres en el que estén todos los elementos; Todo el patrón de "constructores reales" es un poco confuso en mi opinión, pero esa no es la decisión que tomó Rust para los elementos que no son macro, por lo que me parece que sería más consistente y esperado que los elementos macro ocupen diferentes espacios de nombres.
El problema real es que no puede usar el uso para importar o cambiar el nombre de macros de forma selectiva.
Podrás hacerlo con macros 2.0. El modelo aquí es básicamente un truco temporal.
Pensé que las macros de derivación comenzaban con derive_?
Ese era el antiguo sistema de derivación personalizado, no creo que las macros 1.1 hagan esto.
Si bien existe un costo de complejidad de implementación, no creo que haya un costo significativo en la complejidad de uso
Existe un costo para alguna definición de uso: hace la vida más difícil para las herramientas, en particular (aunque se podría argumentar que no mucho). También creo que no es tanto la complejidad de la implementación lo que importa (aunque los espacios de nombres definitivamente complican el compilador, estoy de acuerdo en que esto no es tan importante) como la complejidad del lenguaje: los usuarios tienen que razonar sobre estas cosas para usar Rust y tiene una repercusión efecto a cosas como la higiene y la sombra, complicando aún más las cosas.
¿Diría que todos los elementos deberían estar en el mismo espacio de nombres si no fuera un cambio importante? Tratando de aclarar tu proceso de pensamiento.
No iría tan lejos; creo que hay ventajas en que los valores y los tipos estén separados, pero ciertamente sería mucho más agradable trabajar con un lenguaje con un solo espacio de nombres de muchas maneras.
pero esa no es la decisión que tomó Rust para los elementos que no son macro
Contrapunto: los módulos y los campos están en el espacio de nombres de valor, aunque los lugares en los que se pueden usar son (creo) distintos de los lugares en los que se pueden usar otros valores.
@caballero
El problema real es que no puede usar
use
para importar o renombrar macros de forma selectiva
Pronto podremos use
macros de extern crate
s detrás de una puerta de función (~ 1 semana), cf https://github.com/rust-lang/rfcs/pull/1561 y # 35896. Podríamos decidir estabilizar las macros use
ing de las cajas de derivaciones personalizadas junto con las propias derivaciones personalizadas.
Pensé que las macros de derivación comenzaban con
derive_
Esto fue cierto para los derivados personalizados de estilo antiguo. Con las derivaciones personalizadas de las macros 1.1, #[derive(Serialize)]
(por ejemplo) espera que Serialize
resuelva en una macro de derivación personalizada.
@sin barcos
¿Cuál es la razón para utilizar un solo espacio de nombres para los tres "tipos" de macros?
- Prioridad: las macros de bang y las macros de atributos han compartido un espacio de nombres durante mucho tiempo, por lo que hasta que eso cambie, creo que las derivaciones personalizadas también deberían compartir ese espacio de nombres.
- Compatibilidad con versiones anteriores: si comenzamos con un solo espacio de nombres de macro, podemos dividirlo de manera compatible con versiones anteriores. Si comenzamos con múltiples espacios de nombres de macro, nos quedamos con ellos para siempre.
- Simplicidad: si cada "tipo" de macro tuviera su propio espacio de nombres, tendríamos cinco espacios de nombres en total. Por lo tanto, después de https://github.com/rust-lang/rfcs/pull/1561 aterriza,
use foo::bar;
podría importar _cinco elementos separados_ llamadosbar
(un valor, un tipo, una macro bang , etc.), y no habría una forma sencilla de, por ejemplo, reexportar la macro bang pero no la macro de derivación personalizada o simplemente importar la macro de derivación personalizada comobaz
.
Compatibilidad con versiones anteriores: si comenzamos con un solo espacio de nombres de macro, podemos dividirlo de manera compatible con versiones anteriores. Si comenzamos con múltiples espacios de nombres de macro, nos quedamos con ellos para siempre.
Esto es convincente, especialmente porque se supone que 1.1 es una solución provisional. : +1:
¿Este código de ruptura para cualquiera que tuviera una macro macro_rules llamada, por ejemplo, PartialEq!
?
No, PartialEq
no está definido en el espacio de nombres de macros hoy en día, ya que no es una derivación personalizada.
#[derive(Foo)]
primero comprueba si Foo
es un "derivado incorporado" ( PartialEq
, Copy
, etc.) antes de buscar un derivado personalizado Foo
en el espacio de nombres de la macro. Las incorporaciones están codificadas en la definición de derive
.
Dicho esto, podríamos descifrar el código como lo describió si finalmente decidimos hacer de PartialEq
una derivación personalizada en el preludio en lugar de una incorporada, por lo que podría ser una buena idea hacer una prueba futura de eso ahora.
Propuesta para el problema de los atributos (asumiendo que trabajamos en un modelo donde las macros derivadas no pueden modificar los elementos en los que están declarados, solo decorar):
MultiItemModifier
. El plan es que las macros 2.0 continúen implementando un rasgo y este rasgo se use para la extensibilidad del mecanismo. La función anotada que es la macro implementa este rasgo. Aunque no usamos el registrador de complementos, esto es esencialmente un desasosiego.CustomDerive
específicamente para la derivación personalizada de macros 1.1. En el caso común, los autores de macros nunca lo ven. Pero tienen la opción de implementar el rasgo directamente, en lugar de usar un atributo en una función (espero que volvamos a usar el atributo en el impl, tal vez necesitemos discutir este mecanismo de registro).declare_attributes
a CustomDerive
que devuelve Vec<String>
. Tiene un impl predeterminado que devuelve un vec vacío.declare_attributes
y este mecanismo no quedaría obsoleto.declare_attributes
se verifican como un prefijo de ruta para los atributos, por ejemplo, si declare_attributes
devolvió vec!["foo::bar"]
, entonces #[foo::bar::baz]
y #[foo::bar::qux]
Se permitiría Pensamientos cc @alexcrichton @jseyfried @dtolnay @sgrif @erickt @ rust-lang / lang
@nrc ¿ #[cfg(..)]
, ya sea por accidente o intencionalmente? ¿Y eso podría cambiarse?
@nrc, desafortunadamente, no puedo ver cómo se vería la implementación dadas esas restricciones, por lo que no estoy completamente seguro de si funcionaría o no.
Dicho esto, soy un poco escéptico sobre si los rasgos y los objetos de rasgo funcionarían, porque ¿dónde están las instancias del rasgo que se está creando?
@nrc ¿Por qué esto tiene que ser imperativo y no puede ser simplemente una lista en el atributo que designa la función de expansión derivada? Por ejemplo, #[proc_macro_derive(Serialize, uses_attrs(serde_foo, serde_bar))]
o algo así.
@ colin-kiegel Me imagino que solo podría aplicarse en el alcance de la aplicación de derivación, en cuyo caso deshabilitar otros atributos probablemente sea el comportamiento esperado, aunque creo que podríamos aplicar cfgs antes de la expansión de macro (esto ha cambiado, pero no puedo recordar el antes vs el después).
@alexcrichton
Dicho esto, soy un poco escéptico sobre si los rasgos y los objetos de rasgo funcionarían, porque ¿dónde están las instancias del rasgo que se está creando?
Hmm, buen punto, supongo que tendremos que abordar esto para macros 2.0
@eddyb Esta es una buena idea y probablemente más fácil de hacer a corto plazo. Mi razón para preferir el enfoque imperativo es que es un mecanismo de extensibilidad general y uno que queremos mantener, mientras que agregar cosas al atributo no se escala demasiado bien y puede que no sea algo con lo que queramos estar atrapados para siempre. . Por otro lado, ciertamente es más fácil de hacer ahora y no parece nada malo tenerlo rondando, así que creo que podría ser una mejor apuesta.
Ponerse al día, he estado fuera del país y luego enfermo. Me gustaría volver a visitar https://github.com/rust-lang/rust/pull/37198 , ya que no creo que tenga sentido que todo tipo de macros ocupe el mismo espacio de nombres. Por lo menos esperaría que las macros de derivación personalizadas estén en ese espacio de nombres como derive_Foo
, o similar. Hay casos de uso para varios tipos de macros que usan el mismo nombre incluso en la biblioteca estándar (por ejemplo, #[cfg]
y cfg!
)
También encuentro un poco incómodo el espacio de nombres compartido. Desde la perspectiva del usuario final, podría parecer como si hubiera algún tipo de intercambiabilidad, ambigüedad o algo interesante, que no es así. Creo que las limitaciones 'aleatorias' como esta podrían dificultar un poco la comprensión de Rust como idioma ...
Sin embargo, creo que probablemente no sea el fin del mundo. Parece que aún se podrían introducir diferentes espacios de nombres en el futuro sin romper demasiado.
@sgrif @ colin-kiegel Describí mi justificación para un solo espacio de nombres macro en https://github.com/rust-lang/rust/issues/35900#issuecomment -256247659.
No creo que tenga sentido que todo tipo de macros ocupen el mismo espacio de nombres
Para ser claros, las macros bang y las macros de atributos siempre han ocupado el mismo espacio de nombres; # 37198 acaba de mover derivaciones personalizadas a este espacio de nombres.
Hay casos de uso para varios tipos de macros que usan el mismo nombre incluso en la biblioteca estándar (por ejemplo,
#[cfg]
ycfg!
)
Alternativamente, cfg
podría verse como una sola macro que se puede usar mediante una invocación de bang o una invocación de atributo (aunque hoy en día no es posible que los usuarios definan una macro que se pueda invocar mediante un bang o un atributo, podríamos decidir permitirlo en macros 2.0).
Desde la perspectiva del usuario final, podría parecer como si hubiera algún tipo de intercambiabilidad
Creo que esto podría mejorarse con mensajes de error claros cuando dos macros entran en conflicto (trabajaré para mejorar los mensajes de hoy).
Parece que aún se podrían introducir diferentes espacios de nombres en el futuro sin romper demasiado
Creo que dividir el espacio de nombres de macros es totalmente compatible con versiones anteriores (corrígeme si me equivoco). Esta es mi principal motivación para mantener un solo espacio de nombres de macro hoy: queremos que las macros 1.1 sean lo más compatibles con el futuro.
Finalmente, creo que los conflictos de macro de bang / atributo vs macro de derivación personalizada serán raros en la práctica, ya que las macros de bang / atributo suelen ser minúsculas y las derivadas personalizadas suelen ser mayúsculas. En otras palabras, las derivaciones personalizadas ya tienen su propio espacio de nombres cuando se siguen esas convenciones de nombres.
Parece que la rotura (https://github.com/diesel-rs/diesel/issues/485) se debe al soporte, por ejemplo, Queryable! { struct S; }
y #[derive(Queryable)] struct S;
. Una vez que las derivaciones personalizadas sean estables, no será necesario admitir Queryable! { struct S; }
por lo que esto ya no será un problema, ¿verdad?
Mientras tanto, creo que podríamos actualizar diesel
para que
Queryable!
todavía se admite sin #[macro_use] extern crate diesel_codegen;
, y#[derive(Queryable)]
, pero no Queryable!
, es compatible con #[macro_use] extern crate diesel_codegen;
.Siéntase libre de enviarme un ping en IRC para discutir: estaría feliz de escribir un PR.
@nrc @alexcrichton
Dicho esto, soy un poco escéptico sobre si los rasgos y los objetos de rasgo funcionarían, porque ¿dónde están las instancias del rasgo que se está creando?
Estoy de acuerdo con @eddyb en que si todo lo que queremos es manejar atributos, en aras de la conveniencia y la conveniencia, deberíamos simplemente extender los atributos.
Pero si _did_ queríamos un sistema de extensión más flexible, entonces había imaginado que usaríamos rasgos, pero esos rasgos se definirían como una colección de métodos que no se refieren a Self
:
trait CustomDerive {
fn foo(); // note: no self
fn bar(); // again, no self
}
Luego lo implementaría en un tipo ficticio como struct my_annotation_type;
(que comparte el mismo nombre que el atributo, ¿supongo?), Y usaríamos la resolución de rasgos para extraer las funciones relevantes como <my_annotation as CustomDerive>::foo
(probablemente al escribir los metadatos, supongo). El punto es que nunca haríamos (o necesitaríamos) una instancia de my_annotation
, es solo un mecanismo de agrupación para agrupar un montón de funciones relacionadas.
Ciertamente, no es lo más elegante que existe, pero no estoy seguro de una mejor manera. Creo que la falta de elegancia es precisamente la razón por la que queríamos comenzar con funciones atribuidas. =)
Con respecto a los espacios de nombres, @sgrif hace un buen caso con los ejemplos #[cfg]
y cfg!
. Ciertamente puedo imaginarme a uno que quiera #[derive(SomeTrait)]
y también tener una macro como SomeTrait! { ... }
que ... hace algo. =) Pero @jseyfried también hace un buen caso con la compatibilidad con versiones anteriores, siempre que no estemos alcanzando limitaciones _ya_.
Tiendo a preferir menos espacios de nombres de forma predeterminada, principalmente debido al dolor que creo que nos trae ahora tener una división de espacio de nombres de valor / tipo. Dicho esto, creo que la mayoría de los puntos débiles conocidos no se aplican aquí:
use foo::bar
podría o no importar un módulo llamado bar
y, por lo tanto, podría (o no) ser relevante para una resolución de nombres como bar::baz
;Pero en gran medida, desde que hemos cruzado el puente, no estoy seguro de que tener una "derivación personalizada" en vivo en su propio espacio de nombres suponga un desafío en particular, ¿verdad?
Podríamos agregar el prefijo derive_
a las macros generadas, para hacerlas pseudo-espacio de nombres. Si quisiéramos hacer ese cambio, ahora sería el momento.
@keeperofdakeys ¿No sería esto equivalente a que los autores de derivaciones personalizadas derive_*
? Independientemente, creo que los nombres de derive_*
son demasiado feos para los usuarios finales.
@nikomatsakis
Pero en gran medida, desde que hemos cruzado el puente, no estoy seguro de que tener una "derivación personalizada" en vivo en su propio espacio de nombres suponga un desafío en particular, ¿verdad?
El algoritmo de resolución de importación puede manejar arbitrariamente muchos espacios de nombres, pero realiza una cantidad de trabajo potencialmente no trivial para cada espacio de nombres. En particular, para cada importación I
y cada espacio S
nombres no utilizado I
falla en S
(cf https: // github. com / rust-lang / rfcs / pull / 1560 # issuecomment-209119266). Esta prueba a menudo requiere un DFS del gráfico de importación global (en el que los vértices son módulos y los bordes son importaciones globales) para buscar importaciones indeterminadas relevantes.
Sin embargo, este trabajo adicional por espacio de nombres podría no marcar la diferencia en la práctica y puede evitarse si es necesario (consulte https://github.com/rust-lang/rfcs/pull/1560#issuecomment-209119266) al costo de restricción menor que solo se aplicaría a los espacios de nombres de macros.
Fusioné los espacios de nombres en # 37198 para mantener nuestras opciones abiertas en su mayoría y porque no pensé que sería limitante en la práctica. Si la gente lo quiere hoy y @ rust-lang / lang está de acuerdo con tener múltiples espacios de nombres macro para siempre, no tengo objeciones.
@nikomatsakis
Creo que sí, tu estrategia funcionaría, aunque de forma inestable. Mi sensación personal es que la rareza no influye (por ejemplo, lo que tenemos hoy está bien para mí), pero debería ser implementable de cualquier manera.
@alexcrichton, como creo que escribí antes, estoy feliz de esperar hasta que necesitemos más poder (si es que alguna vez), y luego podemos hacer algo como el rasgo que describí, o tal vez algo mejor. Por ahora, creo que sería suficiente extender los atributos aplicados a fn
.
Sentí curiosidad y comencé una implementación básica de esta idea (usando un atributo en la función proc_macro para indicar los nombres de los atributos que se marcarán como usados en el elemento).
Eliminé la etiqueta FCP porque parece claro que todavía no hemos alcanzado el punto en el que estamos listos para estabilizarnos. Voy a hacer un esfuerzo para resumir el estado de la conversación y, en particular, para resaltar los errores donde aún necesitamos decisiones firmes y / o contribuciones de código:
Propuestas:
#[proc_macro]
con una lista blanca de atributos, dígale a rustc que los considere "usados" e ignórelosArgumento a favor:
#[derive(Foo)]
vs foo!
) ya cumplen el propósitouse foo::Bar
realmente no puede distinguir a qué espacio de nombres te refieres de todos modos, por lo que hay menos espacio para la confusión si no compartenArgumento en contra:
#[cfg()]
vs cfg!()
ocurre hoy (pero ninguno de estos es derivado personalizado, y esto podría acomodarse dejando que la macro sepa el contexto en el que se está ejecutando )Propuestas:
¿Hay otras preguntas abiertas?
Posible solución al problema de la mutación:
En lugar de que las derivaciones personalizadas tengan el tipo TokenStream -> TokenStream
, tendrían el tipo
Item -> TokenStream
, donde Item
es un tipo opaco que comenzaría con solo dos métodos:
item.tokens()
, que devuelve el TokenStream
que se pasaría hoy, yitem.use_attrs(name)
, que marcaría todos los atributos con el nombre dado como se usa.El TokenStream
devuelto solo incluiría los impl
s derivados.
Eventualmente podríamos agregar a la API de Item
con funciones de conveniencia (por ejemplo, iterando sobre los campos / variantes del elemento) o una API de derivación de nivel superior como la de syntax_ext::deriving::generic
.
Me complacería implementar el permiso de atributos de la lista blanca en #[proc_macro_derive]
(es decir, la segunda propuesta para la pregunta 1 en https://github.com/rust-lang/rust/issues/35900#issuecomment-258315395) o mi propuesta Item -> TokenStream
.
Creo que sería un error estabilizar las derivaciones personalizadas que pueden mutar el elemento subyacente. La información de intervalo que sacrificamos al permitir la mutación ya está causando problemas; por ejemplo, tenemos que elegir entre arreglar # 37563 y arreglar # 36218.
Realmente no veo el atractivo de una lista blanca imperativa, ¿alguien puede pensar en un caso de uso?
No estoy seguro, ¿es intencional / deseado que este código compile:
#![feature(proc_macro)]
#[proc_macro_derive(Whatever)]
struct Foo {}
@eddyb No estoy seguro de que exista un atractivo inherente a las listas blancas imperativas; creo que el beneficio de
Item -> TokenStream
es que es más fácil extender la API de Item
que agregar más tipos de atributos declarativos. Dicho esto, pensando un poco más en esto, es posible que no haya muchos otros casos de uso que requieran un Item
sobre un TokenStream
, entonces TokenStream
-> TokenStream
con una lista blanca declarativa podría ser mejor.
¿Las macros 2.0 reemplazarán a esta api? Si es así, la extensibilidad no es una preocupación real, y la api Item -> TokenStream
no parece muy atractiva.
@keeperofdakeys Según el RFC:
En general, esto debe considerarse un paso incremental hacia una "macros 2.0" oficial.
La intención de las macros 1.1 es estar lo más cerca posible de las macros 2.0 en espíritu e implementación, solo que sin estabilizar grandes cantidades de características. En ese sentido, es la intención que, dado un macros 1.1 estable, podamos superponer funciones de manera compatible con versiones anteriores para llegar a macros 2.0.
https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233449053 y https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233708395 y https: // github. com / rust-lang / rfcs / pull / 1681 # issuecomment -239558657 parecen indicar que sólo se
@eddyb
Realmente no veo el atractivo de una lista blanca imperativa, ¿alguien puede pensar en un caso de uso?
Bueno, podría imaginar un caso en el que los nombres de los atributos se generen dinámicamente de alguna manera (¿según el nombre del tipo o campos, tal vez?) Y, por lo tanto, no se pueden declarar con anticipación. Sin embargo, parece algo artificial y no es una práctica particularmente buena.
Encuentro atractiva la alternativa de Item
podría enriquecerse con el tiempo. es decir, podríamos admitir recorrer el conjunto de campos de una manera muy estructurada, brindar acceso a más datos (como resoluciones de nombres) que podamos tener a nuestra disposición, etc.
Por supuesto, algo de esto también vendrá (_ eventualmente_) de alguna forma de API AST estándar.
Una nota sobre el tiempo: Realmente, realmente quiero ver que Macros 1.1 se estabilice en el próximo ciclo. Las cosas en las que estamos bloqueados se sienten, en última instancia, bastante menores.
Con ese espíritu, mi opinión sobre los temas que describí:
#[proc_macro]
_o_ cambiando el tipo a Item
. También creo que cualquiera que elijamos, potencialmente podríamos adoptar al otro en el futuro si nos parece una buena idea. Quiero ir con lo que se implemente primero. =)#[cfg]
vs cfg!
parece algo que podríamos manejar de otras maneras, o si queremos, podemos introducir los espacios de nombres divididos _entonces_. No parece que tener un espacio de nombres unificado esté dañando el caso de uso de derivación personalizada en particular, que es lo único de lo que estamos hablando de estabilizar de todos modos. ¿Correcto?@nikomatsakis su resumen suena preciso, ¡gracias por escribirlo! Me entristece que no obtengamos macros 1.1 en Rust 1.14, pero entiendo que se trata de cuestiones controvertidas.
Mi sentimiento personal sigue siendo estabilizar todo como está donde la derivación personalizada debe eliminar los atributos y solo hay un espacio de nombres.
No sigo la API Item -> TokenStream
, ¿el flujo de token devuelto aún abarca el elemento original o solo las implicaciones agregadas? ¿Eso significa que debería tomar &mut Item
?
@ est31 tu comentario suena como un error, ¿puedes abrir una edición separada para eso?
Estoy bastante de acuerdo con el comentario de @nikomatsakis . Creo que es importante que los derivados no tengan rienda suelta para mutar el elemento al que están adjuntos, pero todas las implementaciones propuestas me parecen bien.
No parece que tener un espacio de nombres unificado esté dañando el caso de uso de derivación personalizada en particular, que es lo único de lo que estamos hablando de estabilizar de todos modos. ¿Correcto?
El problema surgió porque tiene diesel roto, que actualmente tiene macros llamadas, por ejemplo, Queryable!
que puede ajustar una estructura para obtener Queryable
.
Como alguien que no usa ni mantiene diesel en este momento (es decir, como alguien que no se ve afectado por lo que estoy a punto de proponer: sweat_smile :), creo que lo mejor es mantener 1 espacio de nombres por ahora y que diesel cambie el nombre de esas macros a derive_Queryable!
o algo así. Serán obsoletos cuando esto sea estable de todos modos, ¿verdad?
Creé PR https://github.com/rust-lang/rust/pull/37614 para la función sugerida. Utiliza un atributo separado #[proc_macro_attributes(..)]
, y ya no necesita devolver el artículo en el TokenStream
devuelto.
Presenté # 37637 para averiguar cómo deben tratar las macros proc $crate
.
Solo para aclarar, este caso de uso se considera correcto o un mal uso del sistema:
Genero nuevas estructuras basadas en la estructura y los atributos existentes aquí y me gusta bastante el diseño, ya que me permite consolidar las cosas en una estructura.
Sin embargo, si el sistema pudiera cambiar más adelante para no permitir este tipo de implementación, podría detenerme ahora en lugar de poner más esfuerzo en él (la implementación actual es solo una rápida prueba de concepto).
@Neikos
El principal cambio incompatible hacia atrás será que ya no puede modificar el elemento (no lo devuelve al TokenStream). Yo diría que no se pretende derivar otra cosa que no sea un impl, pero no hay nada que le impida hacer esto. Solo deberá tener cuidado con los conflictos de nombres.
El otro cambio principal es poder proporcionar una lista de nombres de atributos que no deberían desencadenar errores de atributos personalizados en el artículo.
RE: El conflicto de espacio de nombres en Diesel. No estoy seguro de si desaprobaré esas macros o no una vez que esta característica sea estable, dependerá de si todavía hay un deseo por parte de algunos de cosas gratuitas de extensión del compilador. Es dudoso. Sin embargo, sigue siendo útil para probar internamente, pero cambiarle el nombre está bien para eso.
Creo que es lamentable perder la capacidad de modificar el flujo de entrada. Ser capaz de eliminar el elemento que se está anotando nos permite tener macros bang con este sistema también. Entiendo las preocupaciones, pero es una pena perder esa capacidad sobre ellas.
@TheNeikos @sgrif mi punto de vista es que cualquier cosa que modifique seriamente la entrada no es una derivación y, por lo tanto, no debe abordarse aquí. Creo que es algo peligroso (falta de higiene, etc.) utilizar derivaciones personalizadas para macros proc de propósito general, por lo que no estoy dispuesto a fomentarlo.
No poder modificar el elemento significa que se mantiene la información del intervalo del elemento, lo que hace que los mensajes de error en el elemento sean mucho más claros (también significa que los mensajes de error en la salida derivada ya no apuntan al intervalo del elemento, sino al intervalo del atributo derivado). No veo una buena razón para permitir que las personas abusen de esta función si realmente solo queremos macros de procedimiento adecuadas.
@TheNeikos No creo que vayamos a prohibir la generación de nuevas estructuras a través de una derivación siempre que no motives la estructura para la que estás derivando.
En términos de lo que creo que es idiomático; Creo que está bien generar nuevos tipos, pero es mucho mejor si esos tipos son tipos asociados para un rasgo que estás derivando. Por ejemplo, imagina que pudiéramos derivar IntoIterator
para un tipo, eso implicaría crear una estructura Iterator
. Conceptualmente, debería derivar un comportamiento para este tipo, pero ese comportamiento podría no ser "un rasgo implícito".
@sgrif
Creo que es lamentable perder la capacidad de modificar el flujo de entrada. Ser capaz de eliminar el elemento que se está anotando nos permite tener macros bang con este sistema también. Entiendo las preocupaciones, pero es una pena perder esa capacidad sobre ellas.
Hmm, definitivamente soy comprensivo, aunque obviamente (como señaló @nrc ) esto no es para lo que se diseñó el sistema, y tenderá a exponer las diversas asperezas. Probablemente esto no importe en sus casos de uso. Supongo que una pregunta clave es con qué rapidez podemos avanzar en una especie de "bang macros 1.1".
El RP se ha fusionado, por lo que debería poder ver los siguientes cambios en la próxima noche. La función proc_macro_derive ya no debería devolver el elemento (al hacerlo, se generará un error acerca de la definición de un tipo dos veces), y ahora puede proporcionar una lista de nombres de atributos para incluir en la lista blanca como esta #[proc_macro_derive(Derive, attributes(Foo, Bar)]
.
cc @dtolnay , @sgrif ^
que causará roturas pronto desafortunadamente
Sí, presenté https://github.com/serde-rs/serde/issues/614 para rastrear nuestro final.
Creo que he arreglado la rotura en Diesel en https://github.com/diesel-rs/diesel/pull/493 , lo sabré con seguridad una vez que los nightlies se vuelvan a construir.
Entonces, si estoy leyendo este hilo correctamente, dado que una macro de proceso solo puede emitir elementos adicionales adjuntos al elemento inicial, ¿tampoco puede agregar más anotaciones al elemento inicial? (Veo una mención de permitir "anotaciones de hermanos", pero nada más al respecto).
Tengo una macro #[derive(newtype)]
proc en mi caja (pequeña, no publicada) que se expande a un conjunto diferente de otros #[derive()]
s según la estructura que está anotando. Por ejemplo, #[derive(newtype)] struct Foo(u64)
expande a #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] struct Foo(u64);
mientras que #[derive(newtype)] struct Foo(::semver::VersionReq)
expande a #[derive(Clone, Debug, PartialEq)] struct Foo(::semver::VersionReq);
. Entonces, los miembros de la estructura no son modificados por esta macro, pero se le agregan otras derivadas (estas tampoco modifican la estructura).
Hay unas pocas docenas de estructuras de este tipo y cada una tiene diez o más derivadas nuevas después de expandir esta macro. Me gusta que si me doy cuenta de que quiero que todos los nuevos tipos u64
implementen un rasgo más, puedo cambiar el conjunto de derivaciones en un lugar del código de macro newtype
lugar de en cada estructura.
Solía tener una macro macro_rules newtype!
para esto, pero cambié a una macro proc porque:
pub
, etc. se manejan de forma gratuita sin necesidad de un número combinatorio de brazos de coincidencia macro.Desafortunadamente no, ya no puede hacer esto como lo estaba haciendo.
Con respecto a la compatibilidad futura de que esta característica sea estable: dado que no se requiere que la función del complemento sea "pura", será un cambio importante si el orden de los objetos dados a la función procesada cambia en el futuro, o si rustc implementa multihilo compilando?
@ est31 Si tenemos tiempo, deberíamos intentar llevar a cabo el aislamiento de IPC que se ha mencionado.
Constantemente veo un ICE en Diesel después de los cambios más recientes.
../src/librustc_metadata/decoder.rs:490: entrada: id no encontrado: DefIndex (1) en la caja "diesel_codegen" con el número 28
@sgrif ese sería el problema # 37788 que será arreglado por # 37793 (esperemos que termine en la noche de mañana ...).
@ est31 Es demasiado tarde a esta hora para fusionarlo antes de que comience la compilación nocturna.
https://github.com/rust-lang/rust/issues/37839 es un problema con el uso de una caja de bibliotecas que a su vez usa una caja proc_macro. AFAICT ninguna de las pruebas se ve afectada por esto porque compilan solo el módulo de macro proc, o un módulo de macro proc y un módulo bin que hace referencia directamente a la macro proc.
Editar: ¡Ahora arreglado!
@Arnavion El problema que vio con # 37839 está solucionado por una complicación regular, pero permanece roto cuando se usa --target
, como se informa en # 37958. Proporcioné un caso de prueba mínimo usando --target
que aún se rompe.
@rfcbot fcp fusionar
Ahora que se implementó la función de atributo de la lista blanca, creo que deberíamos estabilizar esto. Serde, Diesel y otros usuarios: ahora es su cambio de objeción si el diseño actual no le funciona. =)
cc @sgrif @erickt
El miembro del equipo @nikomatsakis ha propuesto fusionar esto. El siguiente paso lo revisan el resto de equipos etiquetados:
No hay preocupaciones actualmente enumeradas.
Una vez que estos revisores lleguen a un consenso, entrará en su período de comentarios final. Si detecta un problema importante que no se ha planteado en ningún momento de este proceso, ¡hable!
Consulte este documento para obtener información sobre los comandos que pueden darme los miembros del equipo etiquetados.
Me encantaría ver exploradas las macros de bang en un futuro próximo. Aunque sin objeciones.
@rfcbot revisado
Actualmente, si un TokenStream no puede analizar, se devuelve un LexError vacío . ¿Sería posible devolver un mensaje de error mejor aquí, como qué token no se pudo analizar? Aunque los usuarios de su biblioteca probablemente no quieran ver este tipo de mensajes de error.
Sí, eso sería conveniente para los autores de macros. Tuve que recurrir a escribir la transmisión en un archivo y verla en el patio de recreo para encontrar errores.
Creo que los usuarios también se beneficiarán, aunque solo sea para presentar mejores informes de errores contra la macro.
En https://github.com/rust-lang/rust/pull/38140 , estamos obligando a que las declaraciones de derivación personalizadas sean públicas. La teoría es que algún día podríamos querer derivaciones privadas y luego probablemente querremos hacer esa distinción en función de la visibilidad de la función definitoria. En cualquier caso, esta es la opción conservadora. Pensé que lo dejaría macerar durante uno o dos días para que la gente lo vea antes de fusionarse, ya que estamos estabilizando esta función.
# 37480 está cerrado, lo que debería reflejarse en la lista de verificación.
Fijo
@pnkfelix Me tomé la libertad de marcar su casilla por usted, ya que está en PTO y creo que está totalmente de acuerdo. ¡Háganos saber si ese no es el caso!
: bell: Esto ahora está entrando en su período de comentarios final , según la revisión anterior . :campana:
psst @nikomatsakis , no pude agregar la etiqueta final-comment-period
, hágalo.
¿La estabilización inminente supone que los errores conocidos restantes en la lista de verificación en la parte superior se corregirán primero? ¿O los que no bloquean la estabilización?
Hay dos ahora mismo:
mod foo;
algún día en el futuro si queremos sin romper nada de lo que creo.¡Oye! Esta es una auditoría de documentación RFC 1636 . Esta es la primera característica importante que se estabiliza desde que se aceptó RFC 1636, y deberíamos hacer un buen trabajo para seguirla en este caso.
El RFC dice:
Antes de estabilizar una función, las funciones ahora se documentarán de la siguiente manera:
- Características del idioma:
- debe estar documentado en la Referencia de óxido.
- debe estar documentado en el lenguaje de programación Rust.
- puede documentarse en Rust por ejemplo.
- Tanto las características del idioma como los cambios de biblioteca estándar deben incluir:
- una sola línea para el registro de cambios
- un resumen más extenso del anuncio de lanzamiento de formato largo.
¿Cuál es el proceso correcto para esto? ¿Deberíamos agregar elementos de la lista de verificación al comentario principal de este problema o crear un nuevo problema para la documentación de seguimiento? Me parece que deberíamos tener documentación que cumpla estos requisitos en árbol para la versión 1.15.
cc @ rust-lang / docs
¡Gracias @withoutboats ! Es el primero importante, sí. Tenía esto en mi lista para mirar esta mañana, y he aquí, me adelantaste 😄
Siempre me imaginé que la decisión de estabilizar y la estabilización real son independientes. Es decir, @ rust-lang / lang puede decir "esto se puede hacer estable", pero el compromiso para eliminar la puerta también asegura que la documentación exista. En un mundo donde existe el libro inestable , esto llevaría los documentos de allí a los documentos estables; pero hasta entonces, las cosas son un poco incómodas.
Dado que acabamos de tener un lanzamiento, mi plan era hacer algo como esto:
Posiblemente combinando dos y tres.
/ cc @ rust-lang / core, ya que se trata de un problema entre equipos.
@steveklabnik, eso me suena bien, y también estaría bien aterrizando doctores en cualquier momento (incluso antes de terminar FCP). El FCP aquí está una especie de pseudo-terminado de todos modos, ya que accidentalmente tardamos mucho en ingresar.
¡También sería bueno incorporarlos rápidamente para que podamos garantizar un backport seguro a la rama 1.15 beta!
Si soy el primero en llegar a esto, no puede ser tan malo, pero incluyémoslo en errores conocidos: el uso de una macro de tipo dentro de una estructura con una derivación personalizada provoca un ICE https://github.com/rust-lang/ óxido / problemas / 38706
https://github.com/rust-lang/rust/pull/38737 corrige el tipo de macros ICE: heart :
Acabo de crear # 38749 con respecto a la documentación de la caja proc_macro
.
He leído varias veces que Macros 1.1 se estabilizará en 1.15, pero 1.15.0-beta.1 se envió hace dos semanas y al menos extern crate proc_macro;
todavía está habilitado en él, así como en 4ecc85beb nocturno de 2016 -12-28. ¿El plan respaldará el cambio de estabilización?
@SimonSapin sí, ese era el plan, ¡pero tenemos que hacerlo realidad!
Todavía es el plan: p
Si el usuario escribe #[derive(Foo)] #[foo_def = "definition.json"] struct MyStruct;
el controlador de macros no tiene forma de saber cuál es "el directorio actual" y, por lo tanto, no puede encontrar definition.json
.
Esto es por diseño y, por lo tanto, no sería fácil de solucionar, y supongo que es demasiado tarde para solucionarlo de todos modos.
Puede ir Span
-> FileMap
-> nombre de archivo -> directorio. Todo lo que falta es acceder a la información a través de proc_macro
.
También necesitaría decirle al compilador que agregue una dependencia a definition.json
para que la compilación esté sucia si se modifica.
La macro proc puede usar env::var("CARGO_MANIFEST_DIR")
para obtener el directorio que contiene Cargo.toml de la caja que contiene la invocación de la macro. Presumiblemente foo_def
es relativo a eso. Consulte https://github.com/dtolnay/syn/issues/70#issuecomment -268895281.
@tomaka que se puede hacer mutando FileMap, por ejemplo, así es como lo hace la macro include_str!
.
Presumiblemente foo_def es relativo a eso.
Creo que no es muy intuitivo tener que poner la ruta en relación con Cargo.toml.
eso se puede hacer mutando FileMap, por ejemplo, así es como include_str! macro lo hace.
Sí, sé que se puede hacer, simplemente no se puede hacer con la API de macros de procedimiento actual.
El parámetro es Item
. Sería aceptable agregar un método para recuperar un intervalo de ese elemento, pero agregar un método a Item
para pedirle al compilador que agregue una dependencia sería un truco IMO.
Puede ir a Span -> FileMap -> nombre de archivo -> directorio.
¿Están estas API (en particular FileMap
) en una ruta para estabilizarse?
No tienen por qué serlo, de hecho, no quisiera estabilizar ninguno de los componentes internos. En cambio, podemos estabilizar las API que extraen información sobre un Span
(por ejemplo, línea, columna, nombre de archivo).
Recibí este error en una caja mía. ¿Que esta pasando?
``
error: Cannot use
#! [Feature (proc_macro)] and
#! [Feature (custom_attribute)] al mismo tiempo
`` ``
@alexreg Si está usando #[derive]
, ahora es estable. No necesitas #![feature(proc_macro)]
.
@alexreg
proc_macro_derive
s (macros 1.1) ahora son estables; simplemente puede eliminar #![feature(proc_macro)]
.
#[proc_macro_attribute]
aterrizó recientemente detrás de la puerta de características #![feature(proc_macro)]
; estos son incompatibles con #![feature(custom_attribute)]
. #![feature(custom_attribute)]
quedará obsoleto una vez que llegue el reemplazo (https://github.com/rust-lang/rfcs/pull/1755).
@jseyfried Creo que deberíamos cambiar el problema de seguimiento en proc_macro
ya que está cerrado y no contiene información relevante.
Gracias chicos. Eso tiene sentido.
@abonander Sí, #![feature(proc_macro)]
definitivamente debería apuntar a # 38356 ahora, debería haberlo verificado al revisar # 38842. ¿Podrías enviar un PR?
Comentario más útil
Ok, voy a investigar esto hoy y ver qué tan lejos llego.