Rust: 🔬 Problema de rastreamento para tipos associados genéricos (GAT)

Criado em 2 set. 2017  ·  67Comentários  ·  Fonte: rust-lang/rust

Comentários muito úteis

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 é uma atualização (um pouco concisa).

67510 é o último grande recurso ICE/ausente que precisa ser implementado.

Todos 67 comentários

Aqui está um tipo de plano de implementação que tentarei manter atualizado.

  • [ ] Etapa um: adicionar suporte ao AST e impressão bonita

    • Provavelmente, o primeiro passo é começar a analisar os novos formulários e adicionar suporte para eles no AST.

    • Veja este comentário para pensamentos mais detalhados aqui .

    • Devemos ser capazes de escrever alguns testes somente de análise e também testar o hir da impressora bonita

    • Quando chegamos ao rebaixamento do HIR , podemos errar se algum GAT estiver presente

    • Também podemos fazer o feature gate então

  • [ ] Mais por vir

Deixe-me começar escrevendo sobre o AST com mais detalhes. Primeiro vamos discutir como funciona hoje :

Um item type Foo: Bar [= Baz]; em uma definição de característica é definido por esta variante AST . Isso inclui os limites ( Bar ) e o valor padrão (opcional) Baz . O nome é definido na estrutura TraitItem .

Um item type Foo = Bar; em um trait impl é definido por esta variante AST -- que inclui apenas o tipo Bar , porque o Foo etc é definido no ImplItem estrutura .

Os métodos são um caso interessante porque já podem ser genéricos. Esses parâmetros genéricos são declarados no campo Generics da estrutura MethodSig . Esta é uma instância da estrutura Generics .

Minha opinião é que a melhor coisa a fazer seria "levantar" Generics dos métodos para TraitItem (e ImplItem ) para que se aplique igualmente a todas as formas de itens trait e impl. Por enquanto, não daremos suporte a constantes genéricas, eu acho, mas honestamente elas provavelmente simplesmente se desvinculam do trabalho que estamos fazendo de qualquer forma, então elas seriam uma pequena extensão. Acho que o trabalho será melhor se planejarmos para eles agora.

Talvez um primeiro PR decente seria apenas fazer essa alteração , mantendo todas as outras funcionalidades existentes iguais. Ou seja, colocaríamos mais Generics em TraitItem (e ImplItem ) e de MethodSig . Nós forneceríamos um Generics vazio para não-métodos. Nós trabalharíamos com o código existente, canalizando os genéricos conforme necessário para fazê-lo funcionar.

@nikomatsakis Legal! Muito obrigado! Comecei a experimentar isso ontem à noite e tenho orgulho de dizer que encontrei os mesmos lugares que você apontou em seu comentário sobre o AST. :smile: (Dado que esta foi a minha primeira vez na rustc, considero isso uma conquista!)

Eu não pensei em elevar os genéricos para TraitItem . Minha abordagem foi colocar Generics em TraitItemKind::Type já que é onde a declaração de tipo já está armazenada. Sua abordagem também faz sentido, então trabalharei na implementação disso. Como ainda sou completamente novo nessa base de código, estou interessado em saber quais seriam as armadilhas da minha abordagem se ela fosse usada sobre a que você sugeriu. Você poderia me dar algumas dicas sobre o seu processo de pensamento? :risonho:

Aqui está a mudança que eu teria feito:

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

Edit: Resposta de nikomatsakis no Gitter

sobre as armadilhas de colocá-los no tipo
acho que isso também pode funcionar
a razão pela qual eu estava relutante em fazer isso
é que realmente queremos fazer as mesmas coisas (em teoria, pelo menos) para métodos e tipos
e -- como eu disse -- em princípio não vejo razão para não podermos fazer as mesmas coisas para constantes
Eu acho que se você apenas mover os genéricos para a variante de tipo
isso provavelmente funcionaria bem, mas se você olhar em volta, agora muitas vezes temos que fazer "uma coisa para tipos/consts, uma coisa para métodos" precisamente porque eles são diferentes
então suspeito que o código ficará mais uniforme
Eu não tenho certeza de como vai ser honesto =) -- pode ser uma dor
mas muitas vezes ter as coisas mais genéricas do que precisam ser não é tão ruim, porque você pode inserir span_bug! chamadas nos casos impossíveis por enquanto (e depois voltamos e os consertamos)

Tudo bem! O próximo passo é estender o analisador. Aqui estão algumas dicas. Vamos começar com itens de características.

Esta rotina analisa itens de características . Queremos estender o caso que lida com tipos associados para também analisar coisas como type Foo<....> = ...; (também talvez cláusulas where). (O <...> é o "genérico" que acabamos de adicionar ao AST.)

Atualmente ele está usando parse_ty_param , que basicamente analisa algo como T: Foo etc. Teremos que parar de fazer isso, porque a gramática para declarações de tipo associado não corresponde mais àquela para parâmetros de tipo. Então provavelmente vamos querer adicionar algo como parse_trait_item_assoc_ty . Isso pode começar como uma espécie de clone de parse_ty_param() , mas então vamos querer modificá-lo para invocar parse_generics() aqui. Essa rotina analisará uma declaração genérica ( <...> ) se uma estiver presente, caso contrário, ela apenas retornará um genérico vazio. Em seguida, queremos adicionar uma chamada para analisar cláusulas where aqui -- você pode modelar isso na chamada que ocorre ao analisar métodos , observe que o resultado é armazenado no generics que analisamos anteriormente.

Uma vez feito isso, devemos ser capazes de adicionar alguns testes de análise. Eu faria isso criando um diretório como src/test/run-pass/rfc1598-generic-associated-types/ e adicionando arquivos que você espera analisar com sucesso lá. Agora eles não vão funcionar direito, mas isso não importa. Basta adicionar uma função principal vazia. Em seguida, também podemos adicionar exemplos que não devem ser analisados ​​em src/test/ui/rfc1598-generic-associated-types/ (consulte COMPILER_TESTS.md para obter instruções sobre como adicionar testes de interface do usuário ).

Outra coisa -- nós precisamos incluir este trabalho neste momento, para evitar que as pessoas usem essas coisas em compilações estáveis. Existem algumas instruções para adicionar um feature-gate aqui no forge (veja a seção final). Devemos adicionar visit_trait_item e visit_impl_item ao visitante em feature_gate.rs ; se esse item não for um método, mas tiver um genérico não vazio, podemos invocar gate_feature_post ( exemplo ).

Para configurar a resolução de nomes, acho que tudo o que precisamos fazer é colocar as "costelas" apropriadas (o material de resolução de nomes organiza os conjuntos de nomes que estão no escopo em nervuras; cada nervura representa um nível de ligação). por exemplo, para um imp:

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

teríamos as seguintes costelas:

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

Em geral, modelar as coisas sobre como os métodos funcionam não é uma má ideia. Também podemos fazer um pouco de "prova de futuro" aqui, suponho.

Aqui está o código que traz os parâmetros de tipo de um método para o escopo (isso é para um método definido em um trait):

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

Considerando que para um type definido em um trait, somos codificados para adicionar um parâmetro de tipo vazio rib ( NoTypeParameters ):

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

Agora que os genéricos estão em vigor em cada item trait/impl, acho que provavelmente queremos remover a manipulação de type e extrair a manipulação do método para que ocorra em um nível mais alto. Para os itens (por exemplo, const ) onde não há genéricos, a costela recém-introduzida deve estar vazia e, portanto, inofensiva (espero).

Outros pontos de interesse:

Você entendeu a ideia.

@petrochenkov -- parece certo?

@nikomatsakis

soa certo?

Tudo parece correto.

Próxima Etapa. Resolução vitalícia.

Para melhor ou pior, isso é feito atualmente em um bit de código totalmente separado de outra resolução de nomes. Isso ocorre porque ocorre após a construção do HIR. É quase certo que isso vai mudar, mas ainda não mudou.

As ideias básicas são as mesmas da resolução de nomes normal, exceto que não chamamos as coisas de "costelas", mas sim de "escopos". =)

Existem algumas complicações leves por causa desse conceito de vidas "limitadas tardiamente". No entanto, não é realmente relevante aqui - todos os tempos de vida para um tipo genérico associado serão "limitados precocemente", que é o caso simples. Um tempo de vida "vinculado tardio" é um tempo de vida declarado em um método ou função cujo valor não é fornecido até que o método seja invocado. Não entrarei em detalhes aqui porque não é tão relevante -- o principal é que não queremos seguir exatamente o mesmo modelo para métodos que fazemos para outros tipos de itens genéricos, ao contrário da outra resolução de nomes casos.

Aqui está um pedaço de código de exemplo. Este é o código que visita um impl , struct ou outro item não funcional. Nesses casos, como em GATs, basicamente queremos trazer todos os parâmetros de vida de um Generics para o escopo e mapeá-los para tempos de vida "limitados":

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

Você pode ver que primeiro cria um vetor de tempos de vida, invocando Region::early para cada um:

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

Em seguida, ele cria um Scope . A variável next_early_index está apenas contando quantos tempos de vida com limite inicial estão no escopo. Como esse código é específico para itens, esse sempre será o número de tempos de vida de limite antecipado declarados nesse item atual. (Mais tarde, veremos um caso em que estamos trazendo vidas adicionais para o escopo, que é mais o que queremos para GATs.)

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

Finalmente, invocamos with() . Esse método trará o escopo para o escopo e invocará um encerramento. Quaisquer vidas que visitarmos dentro desse fechamento verão os nomes que acabamos de definir como estando no escopo:

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

OK, agora vamos ver mais um exemplo. Este caso cobre "características impl". Os detalhes do que está fazendo não são tão importantes (ou seja, você não precisa usar a remoção de açúcar impl Trait em si). Basta dizer que está trazendo algumas novas vidas de limite inicial no escopo - é precisamente isso que vamos querer fazer para GATs.

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

Deixe-me destacar algumas coisas. Primeiro, o método next_early_index retorna o próximo índice de limite antecipado não atribuído:

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

Isso fornece um ponto de partida. Em seguida, usamos Region::early novamente para criar novas definições de vida útil com limite antecipado que serão resolvidas em relação a:

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

Por fim, trazemos isso ao escopo chamando with novamente:

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

Ok, esses são dois exemplos. Nós vamos querer fazer algo muito parecido com o segundo. Vamos querer modificar as definições desses dois 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 precisarão, para tipos associados, processar os genéricos associados. (Eles provavelmente devem afirmar, nos outros casos, que os genéricos estão vazios.)

Então eu pulei sobre isso hoje cedo. Gostaria de ter certeza de que estou no caminho certo:

  • Tudo o que precisava ser feito era introduzir os tempos de vida no mapa e então percorrer o item trait/impl? A verificação parece estar funcionando, embora seja difícil dizer (veja mais adiante)... pode estar funcionando apenas no caso simples (ilimitado).
  • Eliminei as proibições de parâmetro de tipo para qpath_to_ty e associated_path_def_to_ty em librustc_typeck/astconv.rs para corrigir os erros type parameters are not allowed on this type . Acho que isso precisa ser substituído por algumas verificações. Também...
  • Estou recebendo falhas de typeck agora. (writeback, especificamente)

As falhas de typeck são acionadas src/test/compile-fail/struct-path-associated-type.rs porque fornece genéricos para valores que não têm um tipo associado.

Se estou lendo as coisas corretamente, preciso pelo menos adicionar uma verificação de que as contagens genéricas associadas correspondem (tentando descobrir onde fazer isso ...) e também possivelmente fazer outras verificações para adicionar tipos para nós etc.

Vou trabalhar nisso, mas dicas sobre se estou indo na direção certa são apreciadas.

Olá @brandonson! Eu detesto desencorajar alguém de hackear o compilador Rust, mas acho que @sunjay já estava hackeando ativamente as mesmas coisas e tem buscado essa mudança desde o início, então provavelmente faz sentido para eles terminarem essa mudança também (acho que eles já começaram). Não tenho certeza se há uma maneira óbvia de paralelizar esse esforço (certamente é possível, mas teríamos que jogar as etapas um pouco antes).

No entanto, se você estiver interessado em encontrar algo para resolver, posso recomendar resolver alguns dos bugs neste marco ? https://github.com/rust-lang/rust/issues/46472 não parece ser falado, posso tentar deixar alguns comentários lá em breve.

Certamente não pretendo pisar no calo de ninguém, mas não vi nada indicando que mais progresso estava realmente ocorrendo (embora admitamos que o comentário sobre os próximos passos seja bastante recente). Realmente, comecei a tentar resolver isso porque queria GATs várias vezes nos últimos dias, então, embora eu não me importe necessariamente de trabalhar com algumas coisas de NLL no futuro, isso está muito mais alto na minha lista de coisas para resolver no momento.

@sunjay , se você ainda estiver trabalhando/planejando trabalhar ativamente nisso, por favor, me avise - não faz sentido eu duplicar seu trabalho nisso.

Olá @brandonson , obrigado por sua vontade e vontade de ajudar. :) De fato, estou trabalhando ativamente nisso. Passei por cada parte da implementação passo a passo enquanto trabalhava em estreita colaboração com Niko para acertar.

Farei de tudo para que isso saia o mais rápido possível. Também quero muito esse recurso!

Como está o andamento? =) Mal posso esperar para experimentar isso em todas as noites <3

Gostaria de saber como funcionará o cross section dos genéricos GAT e const e se fazia parte das propostas quando reunidos?

Um exemplo do que quero dizer:

trait Foo {
    type Bar<const N>;
}

Oi @sunjay , acho que esse é um recurso bastante importante, foi fortemente mencionado nos comentários do roteiro de 2018. Como está progredindo? Obrigado pelo seu trabalho!

Este é o meu recurso mais desejado no momento. Espero que isso se torne uma prioridade e encontre seu caminho para todas as noites em breve!

Então, recentemente eu me encontrei com @sunjay , que está ocupado agora com a escola e outras coisas, para tentar definir os próximos passos aqui. Eles e eu nos encontramos em algum momento e discutimos a estratégia geral de implementação, culminando em um commit em seu repositório, onde deixamos vários comentários embutidos .

Acho que a estratégia que faz mais sentido daqui para frente é dupla:

  • Primeiro, precisamos escrever mais alguns testes.
  • Existem algumas falhas conhecidas em nosso analisador e alguns outros bits "front-end" do sistema, precisamos enumerar e corrigi-los. Provavelmente encontraremos mais em testes.
  • Nesse ponto, estamos prontos para começar a hackear o sistema de características:

    • algumas das bases já foram lançadas, então esperamos que seja em grande parte uma tarefa de "refatoração"

    • mas preciso escrever em detalhes, não há descrição escrita do plano que eu conheça e não tenho tempo para isso neste minuto.

Vou começar tentando escrever algumas instruções sobre testes, já que são mais imediatamente acionáveis, e agendar algum tempo ainda esta semana para escrever como o restante do design funcionará e como obter o código de onde está agora para onde ele precisa estar.

isso é maravilhoso !! boa sorte para @sunjay neste e em todos os outros empreendimentos.

O primeiro passo será garantir que tenhamos um conjunto completo de testes. Os testes existentes podem ser encontrados em:

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

Apenas olhando para eles, já podemos ver alguns dos trabalhos a serem feitos:

  • [ ] construct_with_other_type.rs -- dá erro E0110 inesperado
  • [x] empty_generics -- verifica se type Bar<,> dá um erro, parece ok
  • [x] generic-associated-types-where.rs -- verifica se podemos analisar as cláusulas where nos lugares certos, parece ok
  • [ ] generic_associated_type_undeclared_lifetimes.rs -- dá erro E0110 inesperado
  • [ ] iterable.rs -- dá erro E0110 inesperado
  • [ ] pointer_family.rs -- dá erro E0109 inesperado
  • [ ] streaming_iterator.rs -- dá erro E0110 inesperado

Lugares sem cobertura

  • [ ] atualmente não temos muitos testes de "uso esperado" -- ou seja, coisas que devem ser bem-sucedidas

    • pointer_family parece estar nessa direção

    • Eu esperaria algum tipo de teste trait Iterable { type Item; type Iter<'a>: Iterator<Item = &'a Self::Item>; }

  • [ ] testes de sombreamento vitalício -- geralmente proibimos sombreamento vitalício, então eles deveriam ser ilegais:

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

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

  • [ ] sintaxe "totalmente qualificada" não parece ser testada

    • por exemplo, o teste pointer_family tem Self::Pointer<T> , mas não <Self as PointerFamily>::Pointer<T>

  • [ ] Número incorreto de argumentos para um GAT. por exemplo, dada a definição Iterable acima:

    • <T as Iterable>::Item -- nenhum parâmetro? Mau.

    • Observe que podemos aceitar isso em alguns contextos, uma vez que em alguns contextos comparáveis ​​permitimos tempos de vida omissos.

      Eu poderia ir de qualquer maneira aqui; Eu preferiria que as pessoas escrevessem um '_ explícito em casos como este.

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

    • <T as Iterable>::Item<T> -- Muitos tipos!

    • etc, seria bom ter testes que levem os dois tipos e tempos de vida, é claro

  • [ ] Padrões em tipos associados? trait Foo { type Bar<T, U = T> where T: PartialEq<U>; }

    • nesse caso, SomeType::Bar<u32> seria curto para SomeType::Bar<u32,u32> , o que devemos verificar.

Corrigindo erros inesperados E0110

O erro E0110 é relatado por prohibit_type_params :

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

O primeiro passo é descobrir de onde isso é invocado. Minha maneira preferida de fazer isso é obter uma compilação local e usar -Ztreat-err-as-bug combinado com RUST_BACKTRACE=1 . Mas não posso mostrar esses resultados porque a ferrugem ainda está em construção. :P Então eu fiz um rápido rg prohibit_type_params , deixe-me ir rapidamente apontar um caso suspeito que eu vejo.

Uma invocação é de associated_path_def_to_ty :

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

Como o comentário indica, isso é invocado para resolver o componente Pointer<T> em um caminho como Self::Pointer<T> (observe que os parâmetros de tipo são considerados parte de um segmento de caminho, juntamente com o nome ao qual estão anexados ). Até os GATs, os parâmetros de tipo não eram legais lá (por exemplo, T::Item ), então temos apenas uma restrição geral:

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

Claramente isso não vai funcionar. Devemos remover essa linha e substituí-la por algum tipo de verificação de que, se os parâmetros forem fornecidos, eles correspondem ao número esperado. Para fazer isso, presumivelmente queremos algum código de verificação de erros semelhante ao encontrado em create_substs_for_ast_path . Provavelmente queremos criar algum código compartilhado aqui, principalmente para contabilizar com padrões - talvez possamos realmente reutilizar essa função?

Alguém ainda está trabalhando nisso? Parece-me que isso tem um longo caminho a percorrer. GAT é o meu RFC mais desejado. Se não, eu adoraria contribuir com alguns testes...

@rickyhan então @Centril e @gavento estavam conversando sobre a discussão dos traços do WG sobre dividir o trabalho de teste, mas não sei se houve algum progresso. Talvez eles possam entrar na conversa. Acho que um PR seria bem-vindo. =)

Desculpe se estou sendo estúpido, mas esse tipo de coisa vai ser legal com GATs?

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>;
}

Qual é o estado disso? Eu sei que o giz ganhou recentemente o apoio do gat. Isso é destinado a pousar em ferrugem em breve?

@mark-im Eu dei uma chance na semana passada. Do meu entendimento, o analisador de sintaxe está lá (embora falte testes). Mas a "implementação" ainda não está escrita. (consulte https://github.com/rust-lang/rust/issues/44265#issuecomment-330915766 para obter mais detalhes)

@quadrupleslap AIUI, isso será possível mais tarde, mas a princípio, os GATs suportarão apenas parâmetros vitalícios.

@Boscop o RFC especifica que os parâmetros de tipo também serão suportados.

Alguém sabe o status exato da implementação da sintaxe no rustc? Parece principalmente lá, mas os limites de classificação mais altos geram ICEs:
http://play.rust-lang.org/?gist=a48959858ed5dd432c2396feae5c3cc1&version=nightly&mode=debug

Eu precisaria pelo menos que toda a sintaxe fosse implementada para avançar no trabalho de calcificação. Se alguém ainda estiver trabalhando na sintaxe, por favor me avise, ou então eu posso corrigir isso sozinho :)

Para mim, parece que a comunicação é o principal problema que retarda a implementação de GATs. Parece haver pelo menos algumas pessoas interessadas em trabalhar nisso, mas ninguém sabe realmente o status exato da implementação e quem já está trabalhando nisso. O que você acha de um canal Discord (ou IRC?) só para falar sobre a implementação de GATs? Acho que isso certamente ajudaria.

Além disso, eu certamente poderia contribuir com alguns dias de trabalho nas próximas semanas para trabalhar nisso (mas eu realmente não sei nada sobre essa parte do compilador ainda).

Então pedi um canal dedicado no Discord. Mas em vez de conseguir um canal, aprendi algumas coisas. Também procurei por tudo relacionado a GATs. Então vou tentar resumir todas as informações sobre esse recurso; talvez seja útil para algumas pessoas. Mas eu não sei nada, então leve isso com um grão de sal! Além disso: por favor me diga se alguma informação está incorreta para que eu possa corrigi-la.

Resumo dos esforços de implementação em relação aos GATs

Desde então , não foram adicionados testes de interface do usuário . E não consigo encontrar nenhum outro PR diretamente relacionado a GATs.

No entanto , o ponto (c) de cima (o sistema de traços) está sendo trabalhado "em segredo". Pelo que entendi, o plano é migrar para o novo solucionador de traços baseado em giz em breve e não fazer os GATs funcionarem no sistema antigo. A integração do novo solucionador de características está sendo rastreada pelo problema de rastreamento "Chalkification" . Houve alguns PRs relacionados ao giz e à calcificação. Notavelmente, há um PR de giz chamado "Concluir a implementação de GATs" (fundido em 24/05/2018). Portanto, parece que o sistema central para GATs já está em vigor.

Dito isto, os "GATs" em giz são uma implementação de protótipo e usá-lo em rustc não é apenas um use chalk; . Como @scalexm me disse: "Parece haver muito [para fazer]".


Para obter mais informações e ajudar, provavelmente é útil dar uma olhada no canal #wg-traits Discord e no problema de rastreamento do WG de traços .

Então @nikomatsakis acabou de criar o canal #wg-traits-gat no servidor de discórdia rust-lang ( entre aqui ). Seria ótimo se pudéssemos colocar todos que querem ajudar lá. Além de algumas pessoas que sabem sobre o status desse recurso (em particular, o que ainda precisa ser feito e onde podemos ajudar). Isso deve tornar a comunicação mais fácil e rápida, principalmente para pessoas como eu que ainda não estão profundamente envolvidas/parte do WG de traços :)

Existe alguma atualização sobre isso recentemente? Estamos aguardando a integração do Chalk no compilador? (Talvez haja um problema separado para isso.)

Talvez haja um problema separado para isso.

48049

Apenas como uma nota (possivelmente conhecida), este código entra em pânico no 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 incrível, obrigado!

Ainda outra nota (novamente, possivelmente conhecida). Tendo GATs no lugar, poderíamos abstrair bem os tipos & e &mut , para que pudéssemos parar de copiar e colar constantemente o código entre as funções my_func e my_func_mut :) Aqui está uma possível implementação de tal abstração (aka 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!()
    }
}

Quase compila. Também podemos implementá-lo sem GATs, mas os tipos explodem:

#![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!()
    }
}

(isso realmente compila)

Talvez esta seja mais uma pergunta de suporte, mas acho que pode ser útil para quem vem a esta página entender essa proposta, porque não ficou totalmente claro para mim na RFC ou nos comentários aqui:

Com o 1.41 noturno, tentei o seguinte:

pub trait MyTrait {
    type MyType<U>;

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

E isso falha ao compilar, sendo o erro "argumento de tipo não permitido" MyType .

Em seguida, removi o <U> , que parecia suspeito, mas pensei em tentar:

pub trait MyTrait {
    type MyType<U>;

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

Que surpreendentemente compilou. Mas então quando eu escrevi um impl:

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

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

Isso deixou o compilador em pânico.

Minhas perguntas são:

  1. Esse tipo de esquema é possível agora, mas estou fazendo errado?
  2. Se não for possível agora, será possível sob esta proposta?
  3. Em caso afirmativo, há alguma ideia do tipo de linha do tempo em que eu seria capaz de fazer isso todas as noites?

Agradecemos antecipadamente pelo seu tempo em responder a isso.

@clintonmead Acredito que isso deve ser possível, mas a implementação do recurso ainda não está concluída (na verdade, o compilador avisa que ele pode travar quando você tentar usá-lo!). Não sei qual é a linha do tempo.

Talvez valha a pena verificar com os caras do Chalk para ver se a integração está suficientemente avançada para que o trabalho nesse recurso seja retomado?

Isso agora está bloqueado em

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

Podemos atualizar a Descrição do Problema com os bloqueadores atuais?

Adicionados mais problemas de bloqueio levantados por @DutchGhost

Este seria um passo importante na emulação de tipos superiores.

Estou imaginando algo assim:

// 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)
    }
}

Pergunta sobre a implementação proposta:

Eu tenho um traço que se parece com isso

trait TradeableResource{
}

e um implementador que se parece com isso

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

Eu gostaria de poder limitar todos os implementadores do meu trait para ser um "newtype" (estrutura de tupla de elemento único envolvendo um f64).

Isso seria possível com a implementação proposta sendo considerada aqui?

O seguinte parece um pouco estranho, mas espero que demonstre o que eu quero ser capaz de fazer.

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

@ChechyLevas , tanto quanto sei, isso não está no escopo dos GADTs.

Mas o que você quer fazer não precisa de GADTs para começar, pelo que posso perceber, se você estiver disposto a desistir da garantia de que é um novo tipo e, em vez disso, precisar de funções que envolvam e recuperem o valor interno, respectivamente . Não é tão conveniente, mas provavelmente deve funcionar bem o suficiente.

Então você poderia fazer o seguinte:

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

(isso exigiria que você também implementasse From em ambas as direções, o que eu faria por meio de uma macro)

Eu gostaria de ter um pouco de um sentimento para o estado atual disso. Eu tenho brincado um pouco com traços assíncronos e vidas.
Uma coisa que eu queria fazer era criar um trait ReadAt<'r> com um tipo ReadAt: Future associado. O que funciona para muitos casos, exceto quando quero usar objetos de traço.

Principalmente porque fazer o seguinte me forçaria a anexar um tempo de vida não genérico o suficiente 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>>`
    ...
}

Então pensei que talvez isso pudesse ser resolvido com GATs, mas me deparei com problemas semelhantes em que precisaria de alguma magia de sintaxe novamente, porque os objetos de característica exigem que os tipos associados sejam escritos:

Como:

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) {
}

Eu precisaria de uma maneira de incluir a vida útil em HERE . Isso, por exemplo, é aceito, mas IMO não seria suficiente, pois R é muito 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)
    }
}

Mas eu realmente não posso testar se isso funcionaria de qualquer maneira, já que não tenho certeza de como transformar isso em um objeto de traço, pois não sei como obter as vidas no seguinte trecho:

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!();
    }
}

Como T::ReadAt leva uma vida inteira que não posso fornecer, isso perderia uma extensão de sintaxe para dyn ReadAt<ReadAt<'r> = T::ReadAt<'r>> . (Ou para os parâmetros de vida útil correspondentes IMO, o trecho acima pode funcionar ;-))

Como estou usando apenas parâmetros de vida útil, não parâmetros de tipo, acho que isso deve ser tecnicamente possível, a menos que os parâmetros de vida possam afetar vtables e tamanhos de tipo de alguma forma?

@jendrikw seu código compila e roda com o mais recente nightly(1.46). Fiz alguns testes com tipos customizados etc. mas me falta o conhecimento de fp para verificar mais.

Hoje, tentei usar GATs para algo em que pensei que talvez uma vida inteira não fosse genérico o suficiente (código de trabalho segue; arquivo 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)
    }
}

Então, tentei usar GATs no seguinte 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)
    }
}

Isso não funciona. Ele falha com E0309 ( the parameter type 'T' may not live long enough ) para ambas as implementações de características. Não tenho certeza de como corrigir isso ou se tenho algum equívoco. O compilador quer que eu anexe um limite em T no nível impl , mas nesse nível, não há tempo de vida 'ast e não quero restringir T: 'static (e algo como for<'ast> T: 'ast não funciona).

edit : Tentei aplicar GATs a outra parte da minha base de código , ele lança apenas um erro (por enquanto), mas esse não pode ser simplesmente corrigido, pois depende de uma correção para o meu problema mencionado.

O limite de sobrevivência pode ser adicionado ao tipo associado (usando Self: 'ast na versão do traço). Este teste mostra como deve ser:
https://github.com/rust-lang/rust/blob/db4826dd6ca48663a0b4c5ab0681258999017c7d/src/test/ui/generic-associated-types/iterable.rs#L6 -L21

Bem, isso só funciona parcialmente...


mensagens de erro

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 o código passa um estágio (anteriormente, ele errou com erros semelhantes, mas entre eles, apareceu outra categoria de erro que consistia apenas em E0107 s) hm.

edit : eu perdi a primeira declaração ( where Self: 'ast ). Essa parte já está corrigida. Eu tento continuar.


contexto adicional

bom, o seguinte trecho:

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

erros com:

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) -> _`

e sei que <F as view::ViewFn<T>>::Output<'x> ==> &'ast O e &'x O não são iguais, mas não sei como corrigi-lo (o compilador não aceita o limite F: for<'x> crate::view::ViewFn<T, Output = &'x O> ).
Os outros tentam erros com:

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>,
   |                                       ^^^^^^^^^^^^^^

Eu não sei como expressar um limite forall<'x> em uma atribuição de tipo de saída de traço, por exemplo

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

nem analisa ("esperado um de + , , , :: ou > , encontrado = ").

67510 faixas sendo capazes de gravar esse limite. Esse exemplo também pode precisar de normalização lenta (#60471).

O tipo de saída de traço de self.clone().map é realmente o tipo de O::as_unindented , ou seja, o tipo de argumento do mapa
Não sei o que é crate::View , mas a função map pode esperar um argumento diferente, portanto a incompatibilidade de tipo.

@sighoya Linkei o repositório em posts anteriores, aqui está um link direto para o impl de crate::view::ViewFn::map

O que o compilador disse sobre isso?:

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

Ele não analisa (ainda).

@matthewjasper ,

De Rust Nomicon , o seguinte analisa:

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

É por causa do uso de Output<'x> = &'x 0> que ele não analisa?

Sim, Output<'x>=... não analisa nessa posição.

Eu tenho um create, grdf que não compila mais com rustc 1.46.0-nightly . Alguma coisa mudou em torno do GAT recentemente?

Meu caso é um pouco estranho, pois tive que usar um truque para fazê-lo funcionar em primeiro lugar. Eu essencial ter um traço Graph com tipos de iterador associados. Para fazê-lo funcionar, coloquei todos os iteradores em outra característica, 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>;

    ...
}

Eu tenho uma implementação de Graph que funcionou bem até agora:

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;

    ...
}

Agora que atualizei o rustc, ele falha com o seguinte:

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

Isso não faz muito sentido para mim, pois T já está vinculado a 'a em Iter ...

Ok, fiz funcionar novamente adicionando alguns limites where .
No traço:

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

e na implementação:

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

Mas não tenho certeza de entender a semântica exata dos limites where , principalmente na implementação (primeira vez que vejo isso). E também por que funcionou anteriormente.

EDIT: Eu até consegui remover meu hack desagradável e colocar os iteradores associados no próprio trait.

Adicionados mais problemas de bloqueio levantados por @DutchGhost

Você pode adicionar https://github.com/rust-lang/rust/issues/74684 também :)

Eu não gosto de escrever , por favor, apresse os posts, mas esse recurso está parado há quase três anos, e acredito que seja um dos novos recursos mais desejados.

Onde estamos com isso? O resumo de status mais recente aqui é de @LukasKalbertodt de junho de 2018 . Está esperando a "calcificação" ?

Observação ingênua: quase todos os meus usos desejados para GATs exigem apenas um parâmetro de vida útil. Uma versão reduzida e vitalícia dos GATs seria mais simples de entregar?

https://github.com/rust-lang/rust/issues/44265#issuecomment -568247656 é uma atualização (um pouco concisa).

67510 é o último grande recurso ICE/ausente que precisa ser implementado.

Esta RFC tornaria Monad e Functor possíveis diretamente? Ou há mais trabalho que precisa ser feito em HKT?

Este RFC tornaria Monad e Functor possíveis diretamente? Ou há mais trabalho que precisa ser feito em HKT?

@ibraheemdev Os estados RFC

Isso não adiciona todos os recursos que as pessoas desejam quando falam sobre tipos de tipo superior. Por exemplo, não habilita características como Mônada. Algumas pessoas podem preferir implementar todos esses recursos juntos de uma só vez. No entanto, esse recurso é compatível com outros tipos de polimorfismo de tipo superior e não impede sua implementação de forma alguma. Na verdade, ele abre o caminho resolvendo alguns detalhes de implementação que também afetarão outros tipos de bondade mais alta, como a aplicação parcial.

@ibraheemdev : meu sentimento é que a maneira mais idiomática de tornar mônadas e functores possíveis seria introduzir características associadas genéricas , mas tipos associados genéricos certamente são um pré-requisito.

Existe um caminho para Monad que não requer GAT?

@ibraheemdev Hipoteticamente sim, seria possível implementar HKTs diretamente e depois implementar um traço Monad no topo. No entanto, não há trabalho acontecendo nessa direção, e provavelmente nunca haverá, porque essa abordagem não resolve realmente os problemas que Rust tem: https://twitter.com/withoutboats/status/1027702531361857536

Talvez eu esteja errado, mas acho que o 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 um caminho para Monad que não requer GAT?

Sim, veja Método para emular tipos de alta qualidade em Rust . Ele ainda funciona em estável agora.

Esta página foi útil?
0 / 5 - 0 avaliações