Este é um problema de rastreamento para tipos associados genéricos (rust-lang/rfcs#1598)
FAÇAM:
Aqui está um tipo de plano de implementação que tentarei manter atualizado.
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):
Considerando que para um type
definido em um trait, somos codificados para adicionar um parâmetro de tipo vazio rib ( NoTypeParameters
):
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":
Você pode ver que primeiro cria um vetor de tempos de vida, invocando Region::early
para cada um:
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.)
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:
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.
Deixe-me destacar algumas coisas. Primeiro, o método next_early_index
retorna o próximo índice de limite antecipado não atribuído:
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:
Por fim, trazemos isso ao escopo chamando with
novamente:
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:
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:
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...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:
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 inesperadoempty_generics
-- verifica se type Bar<,>
dá um erro, parece okgeneric-associated-types-where.rs
-- verifica se podemos analisar as cláusulas where
nos lugares certos, parece okgeneric_associated_type_undeclared_lifetimes.rs
-- dá erro E0110 inesperadoiterable.rs
-- dá erro E0110 inesperadopointer_family.rs
-- dá erro E0109 inesperadostreaming_iterator.rs
-- dá erro E0110 inesperadopointer_family
parece estar nessa direçãotrait Iterable { type Item; type Iter<'a>: Iterator<Item = &'a Self::Item>; }
trait Foo<'a> { type Item<'a>; }
impl<'a> Foo<'a> for &'a u32 { type Item<'a> = i32; }
pointer_family
tem Self::Pointer<T>
, mas não <Self as PointerFamily>::Pointer<T>
Iterable
acima:<T as Iterable>::Item
-- nenhum parâmetro? Mau.'_
explícito em casos como este.<T as Iterable>::Item<'_>
-- Correto!<T as Iterable>::Item<T>
-- Muitos tipos!trait Foo { type Bar<T, U = T> where T: PartialEq<U>; }
SomeType::Bar<u32>
seria curto para SomeType::Bar<u32,u32>
, o que devemos verificar.O erro E0110 é relatado por prohibit_type_params
:
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
:
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:
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.
@sunjay conseguiu dois PRs incríveis:
Então, por algum tempo, não aconteceu muita coisa. Niko então resumiu o que ainda precisa acontecer neste comentário (2018-03-12). Basicamente (a) escrever testes, (b) corrigir bugs/melhorar o analisador/front-end e (c) a implementação no sistema trait. O ponto (a) foi explicado em mais detalhes neste comentário (2018-03-13).
@gavento conseguiu estender os testes para RFC1598 (GAT) (mesclado em 2018-05-11)
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.
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]; }
@wdanilo : acredito que seja https://github.com/rust-lang/rust/issues/64755.
@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:
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
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 =
").
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).
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.
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.