Rust: 🔬 Problema de seguimiento para tipos genéricos asociados (GAT)

Creado en 2 sept. 2017  ·  67Comentarios  ·  Fuente: rust-lang/rust

Comentario más útil

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 es una actualización (algo breve).

67510 es la última característica importante de ICE/faltante que debe implementarse.

Todos 67 comentarios

Aquí hay una especie de plan de implementación que me esforzaré por mantener actualizado.

  • [ ] Paso uno: agregue soporte en AST y pretty-printing

    • Probablemente el primer paso sea comenzar a analizar los nuevos formularios y agregar soporte para ellos al AST.

    • Vea este comentario para pensamientos más detallados aquí .

    • Deberíamos poder escribir algunas pruebas de solo análisis y también probar la impresora bonita.

    • Cuando llegamos a la reducción de HIR , podemos fallar si hay algún GAT presente

    • También podemos hacer la puerta característica entonces

  • [ ] Más por venir

Permítanme comenzar escribiendo sobre el AST con más detalle. Primero analicemos cómo funciona hoy :

Esta variante de AST define un elemento type Foo: Bar [= Baz]; en una definición de rasgo. Eso incluye los límites ( Bar ) y el valor predeterminado (opcional) Baz . El nombre se define en la estructura TraitItem .

Esta variante de AST define un elemento type Foo = Bar; en una impl de rasgo, que solo incluye el tipo Bar , porque el Foo etc., se define en el ImplItem estructura .

Los métodos son un caso interesante porque ya se pueden hacer genéricos. Esos parámetros genéricos se declaran en el campo Generics de la estructura MethodSig . Esta es una instancia de la estructura Generics .

Mi opinión es que lo mejor que se puede hacer sería "levantar" Generics de los métodos a TraitItem (y ImplItem ) para que se aplique por igual a todas las formas de elementos de rasgo e impl. Por ahora, supongo que no admitiremos constantes genéricas, pero, sinceramente, probablemente se desprendan del trabajo que estamos haciendo en cualquier caso, por lo que serían una pequeña extensión. Creo que el trabajo irá mejor si los planificamos ahora.

Tal vez un primer PR decente sería simplemente hacer ese cambio , manteniendo todas las demás funciones existentes iguales. Es decir, convertiríamos Generics en TraitItem (y ImplItem ) y saldríamos de MethodSig . Suministraríamos Generics vacíos para los no métodos. Trabajaríamos a través del código existente, conectando los genéricos según sea necesario para que funcione.

@nikomatsakis Genial! ¡Muchas gracias! Empecé a experimentar con esto anoche y me enorgullece decir que encontré los mismos lugares que señalaste en tu comentario sobre el AST. :smile: (Dado que esta fue mi primera vez en rustc, ¡lo considero un logro!)

No pensé en elevar los genéricos a TraitItem . Mi enfoque fue poner Generics en TraitItemKind::Type ya que ahí es donde ya está almacenada la declaración de tipo. Su enfoque también tiene sentido, así que trabajaré para implementarlo. Dado que todavía soy completamente nuevo en este código base, me interesa saber cuáles serían las dificultades de mi enfoque si se usara en lugar del que sugirió. ¿Podría darme una idea de su proceso de pensamiento? :smiley:

Aquí está el cambio que habría hecho:

pub enum TraitItemKind {
    // Generics aren't supported here yet
    Const(P<Ty>, Option<P<Expr>>),
    // `Generics` is already a field in `MethodSig`
    Method(MethodSig, Option<P<Block>>),
    // Added `Generics` here:
    Type(Generics, TyParamBounds, Option<P<Ty>>),
    Macro(Mac),
}

Editar: Respuesta de nikomatsakis en Gitter

en cuanto a las trampas de ponerlos en tipo
creo que eso tambien podria funcionar
la razón por la que era reacio a hacer eso
es que realmente queremos hacer las mismas cosas (al menos en teoría) para métodos y tipos
y, como dije, en principio no veo ninguna razón por la que no podamos hacer lo mismo para las constantes
Creo que si solo mueves los Genéricos a la variante de tipo
eso probablemente funcionaría bien, pero si observas, en este momento a menudo tenemos que hacer "una cosa para tipos/constantes, una cosa para métodos" precisamente porque son diferentes
así que sospecho que el código se volverá más uniforme
No estoy muy seguro de cómo irá para ser honesto =) - podría ser un dolor
pero muchas veces hacer que las cosas sean más genéricas de lo que tienen que ser no es tan malo, porque puedes insertar span_bug! llamadas en los casos imposibles por ahora (y luego venimos y los arreglamos)

¡Todo bien! El siguiente paso es extender el analizador. Aquí hay algunos consejos. Comencemos con los elementos de rasgos.

Esta rutina analiza elementos de rasgos . Queremos extender el caso que maneja los tipos asociados para analizar también cosas como type Foo<....> = ...; (también quizás cláusulas where). (El <...> son los "genéricos" que acabamos de agregar al AST).

Actualmente está usando parse_ty_param , que básicamente analiza algo como T: Foo etc. Tendremos que dejar de hacer eso, porque la gramática de las declaraciones de tipo asociadas ya no coincide con la de los parámetros de tipo. Así que probablemente querremos agregar algo como parse_trait_item_assoc_ty . Esto puede comenzar como una especie de clon de parse_ty_param() , pero luego querremos modificarlo para invocar parse_generics() justo aquí. Esa rutina analizará una declaración de genéricos ( <...> ) si hay una presente; de ​​lo contrario, solo devuelve un genérico vacío. Luego, queremos agregar una llamada para analizar las cláusulas where aquí ; puede modelar eso en la llamada que ocurre al analizar los métodos , tenga en cuenta que el resultado se almacena en generics que analizamos anteriormente.

Una vez que hayamos hecho eso, deberíamos poder agregar algunas pruebas de análisis. Lo haría creando un directorio como src/test/run-pass/rfc1598-generic-associated-types/ y agregando archivos que espera analizar con éxito allí. Ahora mismo no funcionarán bien, pero eso no importa. Simplemente agregue una función principal vacía. Luego, también podemos agregar ejemplos que no deberían analizarse en src/test/ui/rfc1598-generic-associated-types/ (consulte COMPILER_TESTS.md para obtener instrucciones sobre cómo agregar pruebas de interfaz de usuario ).

Algo más: necesitamos presentar este trabajo en este punto, para evitar que las personas usen estas cosas en compilaciones estables. Hay algunas instrucciones para agregar una puerta de características aquí en Forge (consulte la sección final). Deberíamos agregar visit_trait_item y visit_impl_item al visitante en feature_gate.rs ; si ese elemento no es un método, pero tiene genéricos no vacíos, podemos invocar gate_feature_post ( ejemplo ).

Para configurar la resolución de nombres, creo que todo lo que tenemos que hacer es colocar las "costillas" adecuadas (las cosas de resolución de nombres organizan los conjuntos de nombres que están dentro del alcance en costillas; cada costilla representa un nivel de enlace). por ejemplo, para un impl:

impl<A,B> Foo<B> for Vec<A> {
   fn bar<T,U>(x: ...) { 
       for y in ... {
       }
   }
}

Tendríamos las siguientes costillas:

- <A,B> (from the impl)
   - <T,U> (from the `bar` method's generics)
      - `x` (from the parameter list)
          - `y` (from the let)

En general, modelar cosas sobre cómo funcionan los métodos no es una mala idea. También podríamos hacer un poco de "prueba de futuro" aquí, supongo.

Este es el código que incluye los parámetros de tipo de un método (esto es para un método definido en un rasgo):

https://github.com/rust-lang/rust/blob/a35a3abcda67a729edbb7d649dbc663c6feabd4c/src/librustc_resolve/lib.rs#L1890 -L1892

Mientras que para un type definido en un rasgo, estamos codificados para agregar una costilla de parámetro de tipo vacía ( NoTypeParameters ):

https://github.com/rust-lang/rust/blob/a35a3abcda67a729edbb7d649dbc663c6feabd4c/src/librustc_resolve/lib.rs#L1897 -L1901

Ahora que los genéricos están en su lugar en cada elemento de rasgo/impl, creo que probablemente queramos eliminar el manejo de type y extraer el manejo del método para que ocurra en un nivel superior. Para los artículos (p. ej., const ) donde no hay genéricos, entonces la costilla recién introducida debe estar vacía y, por lo tanto, inofensiva (espero).

Otros puntos de interés:

Entiendes la idea.

@petrochenkov : ¿suena bien?

@nikomatsakis

suena bien?

Todo parece correcto.

Próximo paso. Resolución de por vida.

Para bien o para mal, esto se hace actualmente en un bit de código completamente separado de otra resolución de nombres. Esto se debe a que tiene lugar después de que se construye el HIR. Es casi seguro que esto cambiará, pero aún no ha cambiado.

Sin embargo, las ideas básicas son las mismas que en la resolución de nombres normal, excepto que no llamamos a las cosas "costillas" sino "ámbitos". =)

Hay algunas complicaciones leves debido a este concepto de vidas "ligadas tardíamente". Sin embargo, no es realmente relevante aquí: todas las vidas útiles para un tipo asociado genérico estarán "vinculadas en forma anticipada", que es un caso bastante simple. Un tiempo de vida "límite tardío" es un tiempo de vida declarado en un método o función cuyo valor no se proporciona hasta que se invoca el método. No entraré en detalles aquí porque no es tan relevante; lo principal es que no queremos seguir exactamente el mismo modelo para los métodos que para otros tipos de elementos genéricos, a diferencia de la otra resolución de nombres. casos.

Aquí hay un ejemplo de código. Este es el código que visita un impl , struct u otro elemento sin función. En estos casos, como en los GAT, básicamente queremos incluir todos los parámetros de vida útil de un Generics en el alcance y asignarlos a vidas "anteriores":

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L370 -L388

Puedes ver que primero crea un vector de tiempos de vida, invocando Region::early para cada uno:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L376 -L378

A continuación, crea un Scope . La variable next_early_index solo cuenta cuántas vidas útiles limitadas en tiempo de ejecución están dentro del alcance. Dado que este código es específico para los elementos, ese siempre será el número de ciclos de vida vinculados anticipadamente declarados en este elemento actual. (Más adelante veremos un caso en el que estamos incorporando vidas adicionales al alcance, que es más lo que queremos para los GAT).

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L379 -L384

Finalmente, invocamos with() . Este método traerá el alcance al alcance e invocará un cierre. Cualquier ciclo de vida que visitemos dentro de este cierre verá los nombres que acabamos de definir como dentro del alcance:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L385 -L388

Bien, ahora veamos un ejemplo más. Este caso cubre "rasgos impl". Los detalles de lo que está haciendo no son tan importantes (es decir, no tiene que estar bajo el impl Trait desazucarado per se). Baste decir que está poniendo en el alcance algunas nuevas vidas vinculadas tempranamente; eso es precisamente lo que vamos a querer hacer para los GAT.

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L482 -L501

Permítanme resaltar algunas cosas. En primer lugar, el método next_early_index devuelve el siguiente índice de límite anticipado no asignado:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L488

Eso proporciona un punto de partida. A continuación, usamos Region::early de nuevo para crear nuevas definiciones de tiempo de vida enlazadas en tiempo de ejecución que se resolverán contra:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L490 -L492

Finalmente, los incorporamos llamando a with nuevamente:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L494 -L501

Bien, esos son dos ejemplos. Vamos a querer hacer algo muy parecido al segundo. Querremos modificar las definiciones de estos dos métodos:

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L509

https://github.com/rust-lang/rust/blob/ddaebe938b8eb1f5e17570ae8091743972e02bdd/src/librustc/middle/resolve_lifetime.rs#L520

Ambos necesitarán, para los tipos asociados, procesar los genéricos asociados. (Probablemente deberían afirmar, en los otros casos, que los genéricos están vacíos).

Así que me metí en esto hoy más temprano. Me gustaría asegurarme de que estoy en el camino correcto:

  • Todo lo que había que hacer era introducir las vidas en el mapa y luego recorrer el elemento de rasgo/impl. La verificación parece estar funcionando, aunque es difícil saberlo (ver más adelante)... puede que solo funcione en el caso simple (ilimitado).
  • Eliminé las prohibiciones de parámetros de tipo para qpath_to_ty y associated_path_def_to_ty en librustc_typeck/astconv.rs para corregir los errores type parameters are not allowed on this type . Creo que eso necesita ser reemplazado con algunos controles. También...
  • Estoy recibiendo bloqueos de typeck ahora. (reescritura, específicamente)

Las fallas de typeck se activan src/test/compile-fail/struct-path-associated-type.rs porque proporciona genéricos a valores que no tienen un tipo asociado.

Si estoy leyendo las cosas bien, necesito al menos agregar una verificación de que los recuentos genéricos asociados coincidan (tratando de averiguar dónde hacerlo...), y también posiblemente hacer otras verificaciones para agregar tipos para nodos, etc.

Voy a trabajar en eso, pero se agradecen los consejos sobre si estoy yendo en la dirección correcta.

¡Hola @brandonson! Detesto disuadir a alguien de piratear el compilador Rust, pero creo que @sunjay ya estaba pirateando activamente las mismas cosas y ha estado persiguiendo este cambio desde el principio, por lo que probablemente tenga sentido que terminen. este cambio también (creo que ya empezaron). No estoy seguro de si hay una manera obvia de paralelizar este esfuerzo (ciertamente es posible, pero tendríamos que jugar los pasos un poco antes).

Sin embargo, si está interesado en encontrar algo para abordar, ¿puedo recomendarle abordar algunos de los errores en este hito ? https://github.com/rust-lang/rust/issues/46472 parece que no se ha hablado, puedo intentar dejar algunos comentarios allí pronto.

Ciertamente, no pretendo pisar los dedos de los pies a nadie, pero no vi nada que indicara que se estaba produciendo un mayor progreso (aunque es cierto que el comentario sobre los próximos pasos es bastante reciente). Realmente, comencé a tratar de resolver esto porque quería GAT varias veces en los últimos días, así que aunque no necesariamente me importaría trabajar en algunas cosas de NLL en el futuro, esto está mucho más arriba en mi lista de cosas por resolver. en este momento.

@sunjay , si todavía está trabajando activamente/planea trabajar en esto, hágamelo saber; no tiene sentido que duplique su trabajo en esto.

Hola @brandonson , gracias por tu entusiasmo y disposición para ayudar. :) De hecho, estoy trabajando activamente en esto. He estado revisando cada parte de la implementación paso a paso mientras trabajaba en estrecha colaboración con Niko para hacerlo bien.

Haré todo lo posible para sacar esto lo antes posible. ¡Realmente quiero esta característica también!

¿Cómo va el progreso? =) No puedo esperar para experimentar con esto en todas las noches <3

Me pregunto cómo funcionará la sección transversal de GAT y const generics y si fue parte de las propuestas cuando se juntaron.

Un ejemplo de lo que quiero decir:

trait Foo {
    type Bar<const N>;
}

Hola, @sunjay , creo que esta es una característica bastante importante, se mencionó mucho en los comentarios de la hoja de ruta de 2018. ¿Cómo está progresando? ¡Gracias por tu trabajo!

Esta es mi característica más deseada en este momento. ¡Espero que esto se convierta en una prioridad y llegue pronto a las noches!

Recientemente me reuní con @sunjay , que ahora mismo está ocupado con la escuela y otras cosas, para tratar de diseñar los próximos pasos aquí. Ellos y yo nos reunimos en algún momento y discutimos la estrategia de implementación general, que culminó con un compromiso en su repositorio donde dejamos un montón de comentarios en línea .

Creo que la estrategia que tiene más sentido en el futuro es doble:

  • Primero, necesitamos redactar algunas pruebas más.
  • Hay algunas deficiencias conocidas en nuestro analizador y algunos otros bits "front-end" del sistema, debemos enumerarlos y corregirlos. Probablemente encontraremos más en las pruebas.
  • En ese momento, estamos listos para comenzar a piratear el sistema de rasgos propiamente dicho:

    • parte de la base ya se ha establecido, por lo que es de esperar que sea en gran medida una tarea de "refactorización"

    • pero necesito escribir en detalle, no hay una descripción escrita del plan que yo sepa y no tengo tiempo para eso en este momento.

Comenzaré tratando de escribir algunas instrucciones con respecto a las pruebas, ya que son más procesables de inmediato, y programaré algún tiempo más tarde esta semana para escribir cómo funcionará el resto del diseño y cómo obtener el código de donde está. ahora a donde tiene que estar.

eso es maravilloso !! La mejor de las suertes para @sunjay en este y todos los demás esfuerzos.

El primer paso será garantizar que tengamos un conjunto completo de pruebas. Las pruebas existentes se pueden encontrar en:

src/test/ui/rfc1598-generic-associated-types

Con solo mirarlos, ya podemos ver parte del trabajo por hacer:

  • [ ] construct_with_other_type.rs -- da un error E0110 inesperado
  • [x] empty_generics -- comprueba que type Bar<,> da un error, parece estar bien
  • [x] generic-associated-types-where.rs -- comprueba que podemos analizar las cláusulas where en los lugares correctos, parece estar bien
  • [ ] generic_associated_type_undeclared_lifetimes.rs -- da un error E0110 inesperado
  • [ ] iterable.rs -- da un error E0110 inesperado
  • [ ] pointer_family.rs -- da un error E0109 inesperado
  • [ ] streaming_iterator.rs -- da un error E0110 inesperado

Lugares que carecen de cobertura

  • [ ] actualmente no tenemos muchas pruebas de "uso esperado", es decir, cosas que deberían tener éxito

    • pointer_family parece estar en esa dirección

    • Esperaría algún tipo de prueba de trait Iterable { type Item; type Iter<'a>: Iterator<Item = &'a Self::Item>; }

  • [ ] Pruebas de seguimiento de por vida: generalmente prohibimos el seguimiento de por vida, por lo que deberían ser ilegales:

    • trait Foo<'a> { type Item<'a>; }

    • impl<'a> Foo<'a> for &'a u32 { type Item<'a> = i32; }

  • [ ] La sintaxis "totalmente calificada" no parece estar probada

    • por ejemplo, la prueba pointer_family tiene Self::Pointer<T> , pero no <Self as PointerFamily>::Pointer<T>

  • [ ] Número incorrecto de argumentos para un GAT. por ejemplo, dada la definición Iterable anterior:

    • <T as Iterable>::Item -- ¿sin ningún parámetro? Malo.

    • Tenga en cuenta que podríamos aceptar esto en algunos contextos, ya que en algunos contextos comparables permitimos elidir vidas.

      Podría ir de cualquier manera aquí; Preferiría que la gente escribiera un '_ explícito en casos como este.

    • <T as Iterable>::Item<'_> -- ¡Correcto!

    • <T as Iterable>::Item<T> -- ¡Demasiados tipos!

    • etc., sería bueno tener pruebas que tomen ambos tipos y vidas, por supuesto

  • [ ] ¿Predeterminados en tipos asociados? trait Foo { type Bar<T, U = T> where T: PartialEq<U>; }

    • en ese caso, SomeType::Bar<u32> sería la abreviatura de SomeType::Bar<u32,u32> , lo cual deberíamos verificar.

Corrección de errores inesperados E0110

El error E0110 es informado por prohibit_type_params :

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L912

El primer paso es averiguar desde dónde se invoca. Mi forma preferida de hacerlo es obtener una compilación local y usar -Ztreat-err-as-bug combinado con RUST_BACKTRACE=1 . Pero no puedo mostrarte esos resultados porque rustc aún se está construyendo. :P Entonces, en vez de eso, hice un rg prohibit_type_params rápido, déjame señalar rápidamente un caso sospechoso que veo.

Una invocación es de associated_path_def_to_ty :

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L791 -L803

Como indica el comentario, esto se invoca para resolver el componente Pointer<T> en una ruta como Self::Pointer<T> (tenga en cuenta que los parámetros de tipo se consideran parte de un segmento de ruta, junto con el nombre al que están adjuntos ). Hasta los GAT, los parámetros de tipo no eran legales allí (por ejemplo, T::Item ), por lo que solo tenemos una restricción general:

https://github.com/rust-lang/rust/blob/e65547d4fad0425d1db4f33a4d8134bf2cad939e/src/librustc_typeck/astconv.rs#L810

Claramente esto no funcionará. Deberíamos eliminar esa línea y reemplazarla con algún tipo de verificación de que, si se proporcionan parámetros, coincidan con el número esperado. Para hacer esto, presumiblemente queremos un código de verificación de errores similar al que se encuentra en create_substs_for_ast_path . Probablemente queramos crear un código compartido aquí, particularmente para la contabilidad con valores predeterminados, ¿tal vez podamos reutilizar esa función?

¿Alguien sigue trabajando en esto? Me parece que esto tiene un largo camino por recorrer. GAT es mi RFC más deseado. Si no, me encantaría contribuir con algunas pruebas...

@rickyhan entonces @Centril y @gavento estaban hablando sobre los rasgos de WG sobre dividir el trabajo de prueba, pero no sé si se ha hecho algún progreso. Tal vez puedan intervenir. Creo que un PR sería bienvenido. =)

Lo siento si estoy siendo estúpido, pero ¿este tipo de cosas serán legales con los GAT?

trait Sequencer {
    type Wrap<A>;
    fn chain<A, B, F>(Self::Wrap<A>, F) -> Self::Wrap<B>
        where F: FnOnce(A) -> Self::Wrap<B>;
    fn wrap<A>(A) -> Self::Wrap<A>;
}

¿Cuál es el estado de esto? Sé que Chalk obtuvo recientemente el apoyo de Gat. ¿Está destinado a aterrizar en óxido pronto?

@mark-im Le di una oportunidad la semana pasada. Según tengo entendido, el analizador de sintaxis está ahí (aunque faltan pruebas). Pero la "implementación" aún no está escrita. (consulte https://github.com/rust-lang/rust/issues/44265#issuecomment-330915766 para obtener más detalles)

@quadrupleslap AIUI, eso será posible más adelante, pero al principio, los GAT solo admitirán parámetros de por vida.

@Boscop el RFC especifica que los parámetros de tipo también serán compatibles.

¿Alguien sabe el estado exacto de la implementación de la sintaxis en rustc? Parece estar allí principalmente , pero los límites de mayor rango generan ICE:
http://play.rust-lang.org/?gist=a48959858ed5dd432c2396feae5c3cc1&version=nightly&mode=debug

Al menos necesitaría que se implementara toda la sintaxis para avanzar en el trabajo de tiza. Si alguien todavía está trabajando en la sintaxis, hágamelo saber, o podría ir y arreglarlo yo mismo :)

A mí me parece que la comunicación es el principal problema que ralentiza la implementación de los GAT. Parece que hay al menos algunas personas interesadas en trabajar en esto, pero nadie sabe realmente el estado exacto de implementación y quién ya está trabajando en esto. ¿Qué opinas sobre un canal de Discord (o IRC) solo para hablar sobre la implementación de GAT? Creo que eso sin duda ayudaría.

Además, ciertamente podría contribuir con algunos días de trabajo en las próximas semanas para trabajar en esto (pero todavía no sé nada sobre esta parte del compilador).

Así que pedí un canal dedicado en Discord. Pero en lugar de obtener un canal, aprendí algunas cosas. También busqué todo lo relacionado con los GAT. Así que intentaré resumir toda la información sobre esta función; tal vez sea útil para algunas personas. Pero no sé nada, ¡así que tómalo con pinzas! También: por favor dígame si alguna información es incorrecta para que pueda corregirla.

Resumen de los esfuerzos de implementación con respecto a los GAT

Desde entonces , no se agregaron pruebas de interfaz de usuario . Y no puedo encontrar ningún otro RP directamente relacionado con los GAT.

Sin embargo , el punto (c) de arriba (el sistema de rasgos) se está trabajando "en secreto". Según tengo entendido, el plan es migrar pronto al nuevo solucionador de rasgos basado en tiza y no hacer que los GAT funcionen en el sistema anterior. La integración del nuevo solucionador de rasgos se realiza mediante el problema de seguimiento "Chalkification" . Ha habido bastantes relaciones públicas relacionadas con la tiza y la tiza. En particular, hay un PR de tiza llamado "Finalizar la implementación de GAT" (fusionado el 24 de mayo de 2018). Entonces parece que el sistema central para los GAT ya está en marcha.

Dicho esto, los "GAT" en tiza son una implementación de prototipo y usarlos en rustc no es solo un use chalk; . Como @scalexm me dijo: "Parece que hay mucho [que hacer]".


Para obtener más información y ayudar, probablemente sea útil echar un vistazo al canal de Discord #wg-traits y al problema de seguimiento de rasgos WG .

Así que @nikomatsakis acaba de crear el canal #wg-traits-gat en el servidor de discordia rust-lang ( únete aquí ). Sería genial si pudiéramos tener a todos los que quieran ayudar allí. Además de algunas personas que conocen el estado de esta característica (en particular, lo que aún queda por hacer y dónde podemos ayudar). Eso debería hacer que la comunicación sea más fácil y rápida, especialmente para personas como yo que aún no están profundamente involucradas/no son parte del grupo de trabajo de rasgos :)

¿Hay alguna actualización sobre esto recientemente? ¿Estamos esperando la integración de Chalk en el compilador? (Tal vez hay un problema separado para eso).

Tal vez haya un problema aparte para eso.

48049

Solo como una nota (posiblemente conocida), este código genera pánico en el compilador:

use typenum::{U1,U2,U3,Unsigned};

trait Array {
    type Of<Elem> ;
}

impl Array for U1 { type Of<T> = [T;1]; }
impl Array for U2 { type Of<T> = [T;2]; }
impl Array for U3 { type Of<T> = [T;3]; }

@varkor impresionante, gracias!

Otra nota más (nuevamente, posiblemente conocida). Con los GAT implementados, podríamos abstraer muy bien los tipos & y &mut , por lo que podríamos dejar de copiar y pegar código constantemente entre las funciones my_func y my_func_mut :) Aquí hay una posible implementación de tal abstracción (también conocida como pseudo HKT):

#![feature(arbitrary_self_types)]
#![feature(generic_associated_types)]

use std::marker::PhantomData;

struct Pure <'t> (PhantomData<&'t()>);
struct Mut  <'t> (PhantomData<&'t()>);

type Ref<M, T> = <M as Acc>::Pat<T>;

trait Acc { type Pat<T: ?Sized>; }
impl<'t> Acc for Pure <'t> { type Pat<T> = PureRef <'t, T>; }
impl<'t> Acc for Mut  <'t> { type Pat<T> = MutRef  <'t, T>; }

struct PureRef <'t, T: ?Sized> (&'t     T);
struct MutRef  <'t, T: ?Sized> (&'t mut T);


/// USAGE ///

struct Buf<T> {
    data: Vec<T>
}

impl<T> Buf<T> {
    fn as_mut<M: Acc>(s: Ref<M, Self>) -> Ref<M, [f32]>
    {
        unimplemented!()
    }
}

Casi compila. También podemos implementarlo sin GAT, pero los tipos explotan:

#![feature(arbitrary_self_types)]

use std::marker::PhantomData;
use std::ops::Deref;

struct Pure <'t> (PhantomData<&'t()>);
struct Mut  <'t> (PhantomData<&'t()>);

type Ref<M, T> = <M as Acc<T>>::Pat;

trait Acc<T: ?Sized> { type Pat; }
impl<'t, T: 't + ?Sized> Acc<T> for Pure <'t> { type Pat = PureRef <'t, T>; }
impl<'t, T: 't + ?Sized> Acc<T> for Mut  <'t> { type Pat = MutRef  <'t, T>; }

struct PureRef <'t, T: ?Sized> (&'t     T);
struct MutRef  <'t, T: ?Sized> (&'t mut T);


/// USAGE ///

struct Buf<T> {
    data: Vec<T>
}

impl<T> Buf<T> {
    fn as_mut<M>(self: Ref<M, Self>) -> Ref<M, [f32]>
    where M: Acc<Self> + Acc<[f32]>,
          Ref<M, Self>: Deref<Target = Self>
    {
        unimplemented!()
    }
}

(esto realmente compila)

Quizás esta sea más una pregunta de soporte, pero creo que podría ser útil para aquellos que visitan esta página comprender esta propuesta porque no me quedó del todo claro en el RFC o en los comentarios aquí:

Con el 1.41 nightly, probé lo siguiente:

pub trait MyTrait {
    type MyType<U>;

    fn f<U>(self, x : <Self as MyTrait>::MyType<U>);
}

Y esto no se compila, el error es "tipo de argumento no permitido" MyType .

Luego eliminé el <U> , que parecía sospechoso, pero pensé en intentarlo:

pub trait MyTrait {
    type MyType<U>;

    fn f<U>(self, x : <Self as MyTrait>::MyType);
}

Que sorprendentemente compiló. Pero luego, cuando escribí un impl:

impl MyTrait for u64 {
    type MyType<U> = U;

    fn f<U>(self, x : <Self as MyTrait>::MyType) -> <Self as MyTrait>::MyType {
        x;
    }
}

Esto asustó al compilador.

Mis preguntas son:

  1. ¿Es posible este tipo de esquema ahora pero lo estoy haciendo mal?
  2. Si no es posible ahora, ¿será posible alguna vez con esta propuesta?
  3. Si es así, ¿hay alguna idea del tipo de línea de tiempo en la que podría hacer esto todas las noches?

Gracias de antemano por su tiempo en responder esto.

@clintonmead Creo que eventualmente debería ser posible, pero la implementación de la característica aún no está terminada (de hecho, el compilador le advierte que puede fallar cuando intente usarlo). No sé cuál es la línea de tiempo.

¿Tal vez valga la pena consultar con los muchachos de Chalk para ver si la integración está lo suficientemente avanzada como para reanudar el trabajo en esta función?

Esto ahora está bloqueado en

  • #30472
  • #67509
  • #67510
  • #67512
  • #67513

¿Podemos actualizar la descripción del problema con los bloqueadores actuales?

Se agregaron más problemas de bloqueo planteados por @DutchGhost

Este sería un paso importante en la emulación de tipos superiores.

Me estoy imaginando algo como esto:

// the plug/unplug idea is from https://gist.github.com/edmundsmith/855fcf0cb35dd467c29a9350481f0ecf

trait Monad /* : Applicative (for pure/return, doesn't matter for this example) */ {
    // Self is like the "f a" in haskell

    /// extract the "a" from "f a"
    type Unplug;

    /// exchange the "a" in "f a" in the type of Self with B
    type Plug<B>: Monad;

    fn bind<B, F>(this: Self, f: F) -> Self::Plug<B>
    where
        F: Fn(Self::Unplug) -> Self::Plug<B>;
}

impl<A> Monad for Option<A> {
    type Unplug = A;
    type Plug<B> = Option<B>;
    fn bind<B, F>(this: Self, f: F) -> Option<B>
    where
        F: Fn(A) -> Option<B> {
        this.and_then(f)
    }
}

Pregunta sobre la implementación propuesta:

Tengo un rasgo que se ve así

trait TradeableResource{
}

y un implementador que se ve así

struct Food(f64);
impl TradeableResource for Food{}

Me gustaría poder limitar todos los implementadores de mi rasgo para que sean un "nuevo tipo" (estructura de tupla de un solo elemento que envuelve un f64).

¿Sería eso posible considerando la implementación propuesta aquí?

Es cierto que lo siguiente parece un poco extraño, pero espero que demuestre lo que quiero poder hacer.

trait TradeableResource{
    type Wrapper<T>:T(f64)
}

@ChechyLevas , que yo sepa, esto no está dentro del alcance de los GADT.

Pero, para empezar, lo que quiere hacer no necesita GADT, por lo que puedo deducir, si está dispuesto a renunciar a la garantía de que es un tipo nuevo y, en cambio, tiene funciones que envuelven y recuperan el valor interno, respectivamente . No es tan conveniente, pero probablemente debería funcionar lo suficientemente bien.

Así que podrías hacer lo siguiente:

trait Traceable resource: From<f64> + Into<f64> { }

(esto requeriría que también implemente From en ambas direcciones, lo que haría a través de una macro)

Me gustaría tener una idea del estado actual de esto. He estado jugando con rasgos asíncronos y vidas un poco.
Una cosa que quería hacer era crear un trait ReadAt<'r> con un tipo ReadAt: Future asociado. Lo cual funciona para muchos casos, excepto cuando quiero usar objetos de rasgos.

Principalmente porque hacer lo siguiente me obligaría a adjuntar una duración no lo suficientemente genérica a R :

impl<'a, 'r, R> ReadAt<'r> for &'a dyn for<'z> ReadAt<'z, ReadAt = R> + 'a {
    type ReadAt = R; // cannot have an `impl<R<'lifetime>>`
    ...
}

Entonces pensé, tal vez esto podría resolverse con GAT, pero me encontré con problemas similares en los que necesitaría algo de magia de sintaxis nuevamente, porque los objetos de rasgos requieren que se escriban tipos asociados:

Me gusta:

trait ReadAt {
    type ReadAt<'r>: Future<Output = io::Result<usize>>;

    fn read_at<'a>(&'a self, buf: &'a mut [u8], at: u64) -> Self::ReadAt<'a>;
}

impl<'d> ReadAt for &'d (dyn ReadAt<HERE> + 'd) {
}

Necesitaría una forma de incluir la vida útil en HERE . Esto, por ejemplo, se acepta, pero en mi opinión no sería suficiente, ya que R es demasiado concreto:

impl<'d, R> ReadAt for &'d (dyn ReadAt<ReadAt = R> + 'd) {
    type ReadAt<'r> = R;

    fn read_at<'a>(&'a self, buf: &'a mut [u8], at: u64) -> Self::ReadAt<'a> {
        (**self).read_at(buf, at)
    }
}

Pero realmente no puedo probar si eso funcionaría de todos modos, ya que no estoy seguro de cómo convertir esto en un objeto de rasgo, ya que no sé cómo obtendría las vidas en el siguiente fragmento:

struct Test<T: ReadAt>(T);

impl<T: ReadAt> Test<T> {
    fn into_trait_object<'a>(&'a self) -> Test<&'a dyn ReadAt<ReadAt = T::ReadAt>> {
        todo!();
    }
}

Dado que T::ReadAt lleva toda una vida que no puedo proporcionar, esto perdería una extensión de sintaxis para dyn ReadAt<ReadAt<'r> = T::ReadAt<'r>> . (O para hacer coincidir los parámetros de por vida, en mi opinión, el fragmento anterior podría funcionar ;-))

Dado que solo estoy usando parámetros de por vida, no parámetros de tipo, creo que esto debería ser técnicamente posible, a menos que los parámetros de por vida puedan afectar vtables y tamaños de tipo de alguna manera.

@jendrikw su código compila y se ejecuta con la última versión nocturna (1.46). Hice algunas pruebas con tipos personalizados, etc., pero me falta el conocimiento de fp para verificar más.

Hoy, traté de usar GAT para algo en lo que pensé que tal vez toda una vida no sería lo suficientemente genérica (sigue el código de trabajo; archivo de código completo ):

/// Helper trait for "stripping indention" from an object reference
pub trait AsUnindented<'ast> {
    type Output;

    /// Returns a reference to the unindented part of `Self`
    fn as_unindented(&'ast self) -> Self::Output;
}

impl<'ast, T: 'ast> AsUnindented<'ast> for Indented<T> {
    type Output = &'ast T;

    #[inline]
    fn as_unindented(&'ast self) -> &'ast T {
        &self.data
    }
}

impl<'ast, T: 'ast> AsUnindented<'ast> for crate::Block<T>
where
    T: AsUnindented<'ast> + 'ast,
{
    type Output = crate::View<'ast, T, fn(&'ast T) -> T::Output>;

    #[inline]
    fn as_unindented(&'ast self) -> Self::Output {
        crate::View::new(self, T::as_unindented)
    }
}

Luego, intenté usar GAT en el siguiente código:

pub trait AsUnindented {
    type Output<'ast>;

    /// Returns a reference to the unindented part of `Self`
    fn as_unindented<'ast>(&'ast self) -> Self::Output<'ast>;
}

impl<T> AsUnindented for Indented<T> {
    type Output<'ast> = &'ast T;

    #[inline]
    fn as_unindented<'ast>(&'ast self) -> &'ast T {
        &self.data
    }
}

impl<T> AsUnindented for crate::Block<T>
where
    T: AsUnindented,
{
    type Output<'ast> = crate::View<'ast, T, fn(&'ast T) -> T::Output<'ast>>;

    #[inline]
    fn as_unindented<'ast>(&'ast self) -> Self::Output<'ast> {
        crate::View::new(self, T::as_unindented)
    }
}

Eso no funciona. Falla con E0309 ( the parameter type 'T' may not live long enough ) para ambas implementaciones de rasgos. No estoy seguro de cómo solucionar esto o si tengo algún concepto erróneo. El compilador quiere que adjunte un límite en T en el nivel impl , pero en ese nivel, no hay 'ast vida, y no quiero restringir T: 'static (y algo como for<'ast> T: 'ast no funciona).

editar : Intenté aplicar GAT a otra parte de mi base de código , arroja solo un error (por ahora), pero ese no se puede solucionar simplemente ya que depende de una solución para mi problema mencionado anteriormente.

El límite de supervivencia se puede agregar al tipo asociado (usando Self: 'ast en la versión del rasgo). Esta prueba muestra cómo debería ser esto:
https://github.com/rust-lang/rust/blob/db4826dd6ca48663a0b4c5ab0681258999017c7d/src/test/ui/generic-associated-types/iterable.rs#L6 -L21

bueno, eso solo funciona parcialmente...


error de mensajes

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:33:5
   |
33 |     type Output<'ast> where T: 'ast = &'ast T;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:45:5
   |
45 |     type Output<'ast> where T: 'ast = crate::View<'ast, T, fn(&'ast T) -> T::Output<'ast>>;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `T` may not live long enough
  --> src/indention.rs:59:5
   |
59 | /     type Output<'ast2>
60 | |         where
61 | |             T: 'ast2,
62 | |             O: 'ast2
63 | |         = crate::View<'ast2, T, crate::view::MapViewFn<F, fn(F::Output<'ast2>) -> O::Output<'ast2>>>;
   | |_____________________________________________________________________________________________________^
   |
   = help: consider adding an explicit lifetime bound `T: 'ast2`...
   = note: ...so that the type `T` will meet its required lifetime bounds

error[E0309]: the parameter type `O` may not live long enough
  --> src/indention.rs:59:5
   |
59 | /     type Output<'ast2>
60 | |         where
61 | |             T: 'ast2,
62 | |             O: 'ast2
63 | |         = crate::View<'ast2, T, crate::view::MapViewFn<F, fn(F::Output<'ast2>) -> O::Output<'ast2>>>;
   | |_____________________________________________________________________________________________________^
   |
   = help: consider adding an explicit lifetime bound `O: 'ast2`...
   = note: ...so that the type `O` will meet its required lifetime bounds

Parece que el código pasa una etapa (anteriormente, tenía errores similares, pero entre ellos, apareció otra categoría de error que consistía solo en E0107 s) hm.

editar : me perdí la primera declaración ( where Self: 'ast ). Esa parte ya está arreglada. Trato de continuar.


contexto adicional

ok, el siguiente fragmento:

impl<'ast, T, F, O> AsUnindented for crate::View<'ast, T, F>
where
    Self: Clone,
    F: crate::view::ViewFn<T, Output = &'ast O>,
    O: AsUnindented + 'ast,
{
    type Output<'ast2>
        where
            T: 'ast2,
        = crate::View<'ast, T, crate::view::MapViewFn<F, fn(&'ast O) -> O::Output<'ast>>>;

    #[inline]
    fn as_unindented<'ast2>(&'ast2 self) -> Self::Output<'ast2> {
        self.clone().map::<for<'x> fn(&'x O) -> O::Output<'x>, _>(O::as_unindented)
    }
}

errores con:

error[E0631]: type mismatch in function arguments
  --> src/indention.rs:66:67
   |
66 |         self.clone().map::<for<'x> fn(&'x O) -> O::Output<'x>, _>(O::as_unindented)
   |                                                                   ^^^^^^^^^^^^^^^^
   |                                                                   |
   |                                                                   expected signature of `for<'x> fn(<F as view::ViewFn<T>>::Output<'x>) -> _`
   |                                                                   found signature of `for<'x> fn(&'x O) -> _`

y sé que <F as view::ViewFn<T>>::Output<'x> ==> &'ast O y &'x O no son iguales, pero no sé cómo solucionarlo (el compilador no acepta el límite F: for<'x> crate::view::ViewFn<T, Output = &'x O> ).
Los otros errores de intento con:

error[E0582]: binding for associated type `Output` references lifetime `'x`, which does not appear in the trait input types
  --> src/indention.rs:56:39
   |
56 |     F: for<'x> crate::view::ViewFn<T, Output = &'x O>,
   |                                       ^^^^^^^^^^^^^^

No sé cómo expresar un límite de forall<'x> en una asignación de tipo de salida de rasgos, por ejemplo

where
    F: crate::view::ViewFn<T, for<'x> Output<'x> = &'x O>,

ni siquiera analiza ("se esperaba uno de + , , , :: o > , se encontró = ").

67510 pistas pudiendo escribir ese límite. Ese ejemplo también puede necesitar una normalización diferida (#60471).

¿Es el tipo de salida de rasgo de self.clone().map realmente el tipo de O::as_unindented , es decir, el tipo de argumento del mapa?
No sé qué es crate::View , pero la función map puede esperar un argumento diferente, por lo tanto, el tipo no coincide.

@sighoya He vinculado el repositorio en publicaciones anteriores, aquí hay un enlace directo a la impl de crate::view::ViewFn::map

¿Qué dice el compilador sobre esto?:

where
    for<'x> F: crate::view::ViewFn<T, Output<'x> = &'x O>,

No analiza (todavía).

@matthewjasper ,

Desde Rust Nomicon , los siguientes análisis:

where for<'a> F: Fn(&'a (u8, u16)) -> &'a u8,

¿Es por el uso de Output<'x> = &'x 0> que no analiza?

Sí, Output<'x>=... no analiza en esa posición.

Tengo una creación, grdf que ya no se compila con rustc 1.46.0-nightly . ¿Ha cambiado algo en torno a GAT recientemente?

Mi caso es un poco extraño ya que tuve que usar un truco para que funcionara en primer lugar. Esencialmente tengo un rasgo Graph con tipos de iteradores asociados. Para que funcione, he colocado todos los iteradores en otro rasgo, Iter :

pub trait Iter<'a, T: 'a> {
    type Triples: Iterator<Item = Triple<&'a T>>;
    type Subjects: Iterator<Item = (&'a T, Self::Predicates)>;
    type Predicates: Iterator<Item = (&'a T, Self::Objects)>;
    type Objects: Iterator<Item = &'a T>;
}

pub trait Graph<T = crate::Term> {
    /// Iterators.
    type Iter<'a>: Iter<'a, T>;

    ...
}

Tengo una implementación de Graph que funcionó bien hasta ahora:

impl<'a, T: 'a + Hash + Eq> crate::Iter<'a, T> for Iterators {
    type Objects = Objects<'a, T>;
    type Predicates = Predicates<'a, T>;
    type Subjects = Subjects<'a, T>;
    type Triples = Iter<'a, T>;
}

impl<T: Hash + Eq> crate::Graph<T> for HashGraph<T> {
    type Iter<'a> = Iterators;

    ...
}

Ahora que actualicé rustc falla con lo siguiente:

error[E0309]: the parameter type `T` may not live long enough
  --> src/hash_dataset.rs:50:2
   |
50 |     type Iter<'a> = Iterators;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: consider adding an explicit lifetime bound `T: 'a`...
   = note: ...so that the type `T` will meet its required lifetime bounds

Esto no tiene mucho sentido para mí ya que T ya está vinculado por 'a en Iter ...

De acuerdo, hice que funcionara nuevamente agregando algunos límites where .
En el rasgo:

type Iter<'a>: Iter<'a, T> where T: 'a;

y en la implementación:

type Iter<'a> where T: 'a = Iterators;

Pero no estoy seguro de entender la semántica exacta de los límites where , particularmente en la implementación (primera vez que lo veo). Y también por qué funcionó anteriormente.

EDITAR: incluso he podido eliminar mi truco desagradable y poner los iteradores asociados en el rasgo mismo.

Se agregaron más problemas de bloqueo planteados por @DutchGhost

También puede agregar https://github.com/rust-lang/rust/issues/74684 :)

No me gusta escribir , por favor, date prisa en las publicaciones, pero esta función se ha estancado durante casi tres años y creo que se ubica como una de las nuevas funciones más deseadas.

¿Dónde estamos con esto? El resumen de estado más reciente aquí es de @LukasKalbertodt de junio de 2018 . ¿Está esperando la "calificación" ?

Observación ingenua: casi todos mis usos deseados para GAT requieren solo un parámetro de por vida. ¿Sería más fácil entregar una versión reducida de GAT de por vida?

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 es una actualización (algo breve).

67510 es la última característica importante de ICE/faltante que debe implementarse.

¿Esta RFC haría posible directamente Monad y Functor ? ¿O hay más trabajo por hacer en HKT?

¿Este RFC haría posibles Monad y Functor directamente? ¿O hay más trabajo por hacer en HKT?

@ibraheemdev Los estados RFC

Esto no agrega todas las características que la gente quiere cuando habla de tipos superiores. Por ejemplo, no habilita rasgos como Monad. Algunas personas pueden preferir implementar todas estas características juntas a la vez. Sin embargo, esta característica es compatible con otros tipos de polimorfismo de tipo superior y no impide implementarlos de ninguna manera. De hecho, allana el camino al resolver algunos detalles de implementación que también afectarán otros tipos de bondad superior, como la aplicación parcial.

@ibraheemdev : mi sensación es que la forma más idiomática de hacer posibles las mónadas y los funtores sería introducir rasgos asociados genéricos , pero los tipos asociados genéricos son sin duda un requisito previo.

¿Existe una ruta a Monad que no requiera GAT?

@ibraheemdev Hipotéticamente sí, sería posible implementar HKT directamente y luego implementar un rasgo Monad en la parte superior. Sin embargo, no hay trabajo en esa dirección, y probablemente nunca lo habrá, porque ese enfoque realmente no resuelve los problemas que tiene Rust: https://twitter.com/withoutboats/status/1027702531361857536

Tal vez me equivoque, pero creo que GAT permite algo como

trait MonadFamily {
    type Monad<T>;
    fn pure<T>(inner: T) -> Self::Monad<T>;
    fn bind<T, U, F: FnOnce(T) -> U>(this: Self::Monad<T>, f: F) -> Self::Monad<U>;
}

@ibraheemdev

¿Existe una ruta a Monad que no requiera GAT?

Sí, consulte Método para emular tipos de tipo superior en Rust . Incluso funciona en estable ahora.

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