Rust: Problema de rastreamento para "Macros 1.1" (RFC # 1681)

Criado em 22 ago. 2016  ·  268Comentários  ·  Fonte: rust-lang/rust

Problema de rastreamento para rust-lang / rfcs # 1681.

cc @alexcrichton

ESTABILIZAÇÃO TODO

Testes decisivos:

Características:

  • O nome da caixa, atualmente é proc_macro
  • Tipo de caixa, atualmente proc-macro
  • O atributo #[proc_macro_derive(Foo)]
  • Carregando proc-macro engradados com -L e #[macro_use] para carregá-los
  • sombreamento é um erro
  • sem higiene
  • passando um fluxo de token para toda a estrutura e recebendo tudo de volta
  • Atributo de manifesto de carga, atualmente proc-macro = true

Bugs conhecidos:

  • [] - A derivação de pânico pode ter a extensão errada - # 36935
  • [] - streams de token com mod foo fail - # 36691
  • [] - documentos não são publicados por proc_macro - # 38749
  • [x] - atributos personalizados para modos múltiplos são difíceis - https://github.com/rust-lang/rust/issues/35900#issuecomment -252499766
  • [x] - teste de carga falha para libs macro proc - # 37480
  • [x] - o pedido ainda é importante - https://github.com/rust-lang/rust/issues/35900#issuecomment -252430957 (fixado por https://github.com/rust-lang/rust/pull/37067)
  • [x] - Não é possível documentar caixas rustc-macro - https://github.com/rust-lang/rust/issues/36820 (corrigido em # 36847)
  • [x] - A carga é reconstruída com muita frequência - https://github.com/rust-lang/rust/issues/36625 (corrigido em https://github.com/rust-lang/rust/pull/36776)
  • [x] - Atributos gerados pelo compilador dificultam a vida dos autores de derivação personalizada - https://github.com/rust-lang/rust/issues/35900#issuecomment -245978831

  • [x] - Crie uma caixa de rustc_macro

    • [x] - Tenha librustc_macro link para libsyntax . Depende de librustc_macro em librustc_driver
    • [x] - Marque rustc_macro como instável com nosso cabeçalho padrão .
    • [x] - Somente marque rustc_macro com #![crate_type = "rlib"] , não produza um dylib.
    • [x] - Implementar a API de rustc_macro usando libsyntax TokenStream internamente
    • [x] - marque rustc_macro com um item TokenStream lang para que o compilador saiba sobre ele.
  • [x] - Adicionar atributo rustc_macro_derive

    • [x] - valida se está na forma exata foo(bar) , nenhum outro argumento / formato

    • [x] - verifique se ele é aplicado apenas a funções

    • [x] - verifique se ele é aplicado apenas a funções no módulo raiz

    • [x] - verifique a assinatura com o item TokenStream lang adicionado acima

    • [x] - codifica todos os símbolos de função com rustc_macro_derive em metadados junto com o modo de derivação para o qual são usados.

  • [x] - Adicionar um tipo de caixa rustc-macro para outras caixas

    • [x] - conecte-o para produzir um dylib

    • [x] - garante que o dylib obtenha metadados

    • [x] - certifique-se de que rustc-macro engradados não podem ser vinculados como dylibs

    • [x] - certifique-se de que não haja itens _acessíveis_ além daqueles marcados com #[rustc_macro_derive]

    • [x] - Adicione cfg(rustc_macro) como uma diretiva cfg instável, defina-o para o tipo rustc-macro crate

    • [x] - certifique-se de que rustc-macro crates se vinculam dinamicamente a libsytnax

  • [x] - Preencher o suporte de rustc-macro #[macro_use] para rustc-macro engradados

    • [x] - estender o carregador da biblioteca para encontrar rustc-macro engradados separadamente de dylib / rlib rastreados hoje ao alojar engradados

    • [x] - Analisar metadados para rustc-macro crates para aprender sobre os pares de símbolos / modos de derivação

    • [x] - dlopen o dylib

    • [x] - gera um erro se qualquer modo de derivação obscurecer qualquer outro.

  • [x] - Adicionar integração de carga

    • [x] - reconhecer rustc-macro semelhante a plugin = true

    • [x] - passe --crate-type=rustc-macro quando depender dele

    • [x] - sondar a mesma lógica host / alvo para caixas rustc-macro como está presente para caixas de plug-ins (por exemplo, sempre compilar caixas rustc-macro para hosts)

  • [x] - Testes

    • [x] - teste de fumaça carregando uma macro rustc, dummy #[derive] trait

    • [x] - conflitos de nome são um erro

    • [x] - compilar para a arquitetura errada é um erro

    • [x] - não pode emitir tipo de caixa rustc-macro e qualquer outra coisa (por exemplo, rustc-macro + dylib) é um erro

    • [x] - as informações de abrangência não são horríveis

    • [x] - removendo atributos de uma estrutura, bem como campos

    • [x] - adicionando impls próximo a uma estrutura

    • [x] - a compilação cruzada procura o tipo de caixa rustc-macro do host

    • [x] - não carregue dylibs vanilla como tipos de caixa rustc-macro

    • [x] - não pode haver exportações públicas além de funções de derivação macro em uma caixa rustc-macro

    • [x] - derivar macros deve ter assinatura necessária

    • [x] - carrega duas caixas de macro em uma compilação

B-RFC-implemented B-unstable T-lang final-comment-period

Comentários muito úteis

Ok, vou analisar isso hoje e ver até onde chego.

Todos 268 comentários

Eu atualizei a descrição do problema com uma lista de verificação do que precisa ser feito. Provavelmente não é exaustivo, mas deve levar-nos 90% do caminho até lá, esperançosamente

Ok, vou analisar isso hoje e ver até onde chego.

De # 35957: deveríamos mudar o nome da caixa librustc_macro um pouco mais. Em particular, pretende-se que seja um engradado de longa duração que terá o essencial para todos os macro autores, então limitar a rustc_macro (que, em minha mente, pelo menos) é apenas sobre a ideia 1.1 parece ruim. Anteriormente, eu queria libmacro para isso, mas como macro é uma palavra reservada (e podemos querer que ela seja uma palavra-chave no futuro), isso é impossível. @cgswords e eu temos usado libproc_macro, e acho que não é um nome ruim, embora não esteja 100% feliz com isso.

@nrc : Ok, minhas ideias imediatas sobre nomes:

  • extern crate macros; - curto e agradável, mas pode ser lido como contendo macros, em vez de código de suporte para escrevê-los
  • extern crate macro_runtime; - exatamente o que diz na lata
  • extern crate metarust; - escreva Rust, sobre Rust, para operar em Rust
  • extern crate bikeshed; - com macros procedurais, você pode ter a cor de ferrugem que desejar!
  • extern crate macrame; - soa como "macro make [r]"; possivelmente é melhor deixar para uma futura API "legal" sobre a biblioteca bruta.

@nrc Parece que um aspecto importante desta questão é a nomenclatura de nossos vários estilos de macro em geral - em particular, se usarmos libproc_macro , estaremos assumindo um forte compromisso com a "macro procedural" terminologia. Não tenho uma opinião forte aqui, mas não tenho certeza se exploramos abertamente o espaço da terminologia.

Para ser claro, você está pensando que macro_rules seriam simplesmente "macros", ou seja, a coisa padrão que entendemos por macros, enquanto você tem que qualificar "macros procedurais"? Parece um plano bastante razoável para mim. E nesse mundo, eu diria que libproc_macro é melhor do que libmacros .

Meu pensamento aqui é que todas as macros são "macros", e quando precisamos fazer uma distinção com base na implementação (o uso deve ser exatamente o mesmo para todos os tipos), usamos "macros procedurais" vs "macros por exemplo". Eu gostaria de banir totalmente "extensão de sintaxe" e "plugin do compilador" (e um dia reutilizá-lo para plug-ins reais).

Mas, sim, eu quero muito ficar por trás da terminologia de "macro procedimental".

@nrc Faz muito sentido para mim! Embora "macros por exemplo" seja um pouco complicado, é também um termo muito evocativo / intuitivo. Minha única preocupação sobre "macro procedural" é que "procedimento" não é uma coisa no Rust. Eu me pergunto se há uma maneira de tornar a conexão mais direta. "Macro de função" não está certo, mas talvez dê uma ideia do que quero dizer.

Sim, não é muito perfeito, mas dado que é um termo bem conhecido / usado fora do Rust, acho que é melhor do que cunhar nosso próprio termo. "Macro programática" é possível, mas prefiro "procedural".

O termo de Perl para o equivalente mais próximo de "macros procedurais" é "filtros de origem", que (especialmente com a mudança de AST para tokens) é uma descrição bastante adequada.

Talvez 'transformadores de sintaxe' ou 'macros programáticas' funcionassem bem como nomes? Não tenho problemas com macros procedurais.

"Procedural" já é como as pessoas chamam isso, e acho que entendeu claramente o que significa, especialmente em contraste com "macros por exemplo". Eu não me preocuparia em tentar encontrar um nome diferente.

Eu gosto do termo "macro procedural" para uso regular (ou talvez "macro customizada"). Eu particularmente gosto de usar a palavra _macro_ para que fique claro que elas podem (eventualmente ...) ser usadas da mesma maneira que "macros regulares". Por esse motivo, não gosto de "filtros de origem" (também espero que um filtro apenas elimine os dados, não os transforme, embora eu saiba que o termo é usado para ambos).

Estou bem com libproc_macro ou libmacros . Eu meio que prefiro o último porque não adoro ter _ em nomes de caixas, quando pode ser facilmente evitado. =)

Uma pergunta: alguma vez esperamos ter "rotinas de suporte" que pudessem ser usadas a partir de macros não procedurais? Não conheço nenhum desses planos, mas se soubéssemos e os quiséssemos na mesma caixa, libmacros seria um nome melhor. =)

(Estou pensando um pouco, por exemplo, no comentário de

@nikomatsakis : Uma questão relacionada, mas sutilmente diferente, é um caso de uso que https://github.com/rust-lang/rfcs/pull/1561#discussion_r60459479 - queremos que as funções de implementação de macros procedurais sejam capazes de chamar outras funções de implementação de macros procedurais?

Eu posso ver facilmente o desejo de permitir que um derivado personalizado chame outro, e isso essencialmente tornaria as próprias definições de macros procedurais capazes de serem usadas como "rotinas de suporte"

Mas sim, acho que o exemplo de gensym @dherman é muito atraente. Claro, se a resposta à minha pergunta acima for "sim", gensym é uma macro (que pode ser usada por macros por exemplo) e uma função de suporte (que pode ser usada por macros procedurais).

Tenho um PR de carga https://github.com/rust-lang/cargo/pull/3064 que deve marcar todas as caixas de "integração de carga" na lista de verificação.

Deixei um comentário sobre o PR de carga, mas acho que queremos um tipo diferente de _dependência_, não apenas um tipo diferente de _pacote_. Em primeiro lugar, acho que é apenas melhor estética e ergnomicamente, mas essa é apenas minha opinião. Mas também tenho duas razões concretas.

  • Num futuro com departamentos públicos e privados, é importante saber que é preciso saber que os departamentos públicos de procedimentos e os departamentos públicos regulares não precisam coincidir. Por exemplo
  • No futuro, com macros procedurais / quasi-citoting inline, qualquer biblioteca pode ser usada em tempo de compilação.
  • > queremos que as funções de implementação de macros procedurais sejam capazes de chamar outras funções de implementação de macros procedurais?

Como isso mostra, o mesmo engradado pode ser um dep regular de outro engradado de macros procedurais, ou um dep macro de outro engradado.

Serde funciona?

Sim https://github.com/serde-rs/serde/releases/tag/v0.8.6

(exceto para atributos de contêiner em alguns casos # 36211)

Incrível, obrigado pelas atualizações @dtolnay!

Existem documentos sobre essas macros? Suponho que o único exemplo de uso deles seja na sere, certo?

OK, material de carga pousou. Tudo bem, mas seria bom revisitar https://github.com/rust-lang/rust/issues/35900#issuecomment -243976887 algum tempo antes da estabilização. [Por que vale a pena, eu pretendia trazer isso à tona no RFC original, mas esqueci.]

Eu acho que "macros por exemplo" e "macros procedurais" poderiam ser melhor categorizadas como "macros declarativas" e "macros imperativas", respectivamente. Isso fornece paralelos informativos para a categorização declarativa / imperativa mais amplamente conhecida das linguagens de programação. Como imperativo é tratado como um superconjunto ou sinônimo de procedural, ele deve ser próximo o suficiente para que as pessoas acostumadas com a terminologia "macro procedural" dêem o salto. Também deve evitar qualquer confusão com conceitos de procedimento / função / método na própria ferrugem.
Este esquema de nomenclatura nos dá uma caixa macro_imp e um módulo para paralelizar macro_rules . macro_rules poderia eventualmente se tornar um módulo de uma caixa mais geral macro_dec .

@nrc , quando você se refere a "plug-ins reais", está incluindo coisas como metacollect e clippy , coisas como rustw , rustfmt e o Rust Language Server ou alguma outra categoria de programa?

Pelo que vale a pena, eu não gosto do nome "por exemplo" (já que $foo padrões não são "exemplos" em minha mente). Declarativo vs imperativo soa melhor para mim.

Brincando com isso, percebi um problema. Parece que os derivados fornecidos pelo Rust às vezes adicionam atributos que o compilador sabe que deve ignorar. Este contexto se perde quando passa por uma derivação personalizada. Eu esperaria que a função de identificação fornecida como derivação personalizada fosse autônoma, mas causaria erros ao redor do atributo #[structural_match] sendo adicionado.


Roteiro de reprodução

(Em uma caixa chamada demo_plugin )

#![feature(rustc_macro, rustc_macro_lib)]

extern crate rustc_macro;

use rustc_macro::TokenStream;

#[rustc_macro_derive(Foo)]
pub fn derive_foo(input: TokenStream) -> TokenStream {
    input
}

(em outra caixa)

#![feature(rustc_macro)]

#[macro_use] extern crate demo_plugin;

#[derive(PartialEq, Eq, Foo)]
struct Bar {
    a: i32,
    b: i32,
}

Remover #[derive(Eq)] faz com que tudo funcione bem.

@sgrif ah sim, obrigado por me lembrar!

Portanto, há algumas coisas acontecendo aqui:

  • Com #[derive(PartialEq, Eq)] , o compilador está adicionando silenciosamente #[structural_match] porque estaticamente entende que a derivação PartialEq realmente cumpre esse contrato.
  • Eu acredito que um atributo semelhante surge com #[derive(Copy, Clone)] com #[rustc_copy_clone_marker] sendo adicionado.
  • Atualmente isso funciona porque a extensão desses atributos diz "permite instabilidade interna". Perdemos as informações de intervalo, no entanto, ao analisar e reanalisar.

Então, algumas soluções que podemos fazer:

  • Convencionalmente, diga que a derivação personalizada vem primeiro, por exemplo, #[derive(Foo, Eq, PartialEq)]
  • Confie na derivação personalizada para omitir esses atributos
  • Não emita atributos personalizados se detectarmos derivados personalizados
  • Use um mecanismo totalmente diferente no compilador para comunicar o que esses atributos estão dizendo, mas não por meio do próprio AST.

Eu seria a favor de não emitir esses atributos ou usar um mecanismo diferente. Isso é realmente complicado, porque #[derive(Copy, Foo, Clone)] também precisa funcionar (onde o código personalizado é executado no meio e pode alterar as definições).

Meu curso de ação preferido seria simplesmente não emitir esses atributos se detectarmos derivação personalizada. Mesmo isso, porém, pode não ser trivial. Por enquanto, uma convenção de "personalizado primeiro e padrão por último" deve ser suficiente, mas acho que devemos corrigir isso antes de estabilizar.

Isenção de responsabilidade: eu só tenho uma visão externa do compilador - então, há muitas coisas que eu não sei. ^^

Mas, pelo que entendi, a abordagem atual da derivação personalizada de macros 1.1 é mais como uma solução temporária. A solução temporária pode se traduzir em "um bom tempo". Mas, a longo prazo (= macros 2.0), não faremos mais o string-parsing-round-trip, o que atualmente leva à perda de informações de span. Eu me pergunto se esses atributos ocultos no AST eram uma coisa tão ruim no mundo das macros 2.0. Talvez alguém com mais conhecimento interno sobre o compilador possa dizer. Se esses atributos ocultos realmente fizerem sentido naquele mundo futuro, eu argumentaria que ir para a solução alternativa colocando convencionalmente derivados personalizados na frente. A coisa toda já é uma solução alternativa de qualquer maneira, não é?

@ colin-kiegel, acredito que você esteja certo no sentido de que o que estamos fazendo agora _não_ é à prova de futuro. Digamos que você tenha, por exemplo:

#[derive(Eq, Foo, PartialEq)]

Hoje adicionaríamos a implementação Eq , em seguida, executaríamos o código personalizado para Foo e, em seguida, adicionaríamos uma implementação de PartialEq . A estrutura poderia _change_ entre Eq e PartialEq , então o #[structural_match] adicionado por Eq pode não estar correto após Foo executado.

Nesse sentido, concordo que eles não são necessariamente à prova de futuro em qualquer caso!

Minha impressão é que os costumes derivados que dependem de mudanças estruturais geralmente não irão compor muito bem, independentemente desses atributos ocultos. Um derivado personalizado da v2.0 será capaz de alterar a estrutura do item ou será de alguma forma limitado apenas à decoração?

Sim, a capacidade sempre estará lá, eu acredito (inerente à interface TokenStream -> TokenStream), embora eu suspeite que qualquer implementação razoável de #[derive] manteria a estrutura da estrutura original.

Suponho que não possamos exigir que o tokenstream de saída não contenha a própria estrutura, para garantir que a estrutura não seja alterada. A estrutura de entrada TokenStream seria um prefixo imutável? O grande problema seria ter certeza de ignorar os atributos não reconhecidos que os plug-ins estão usando após a conclusão da construção. Talvez cada # [derive ()] possa ter um prefixo (digamos # [derive (Foo)] tem o prefixo Foo_) com o qual os atributos que eles entendem devem começar, e depois de processar cada derivação personalizada, removemos esses atributos?

@mystor sim, o problema com essa abordagem são os atributos não reconhecidos, é por isso que temos toda a estrutura como entrada. Isso geralmente é mais flexível do que depender de um prefixo / sufixo / registro / etc.

Se uma derivação customizada v2.0 puder marcar os atributos customizados como _used_, ela poderá ser limitada ao acesso _read-only_ ao restante do fluxo de token de itens. Desta forma, uma melhor composição de derivados personalizados poderia ser garantida IMO. Se uma macro v2.0 precisar alterar a estrutura de um item, ela terá que usar outra API, mas não derivação personalizada. Desta forma, o problema com #[structural_mach] e a ordenação das derivações (personalizadas) só estaria presente nas macros 1.1. Isso faria sentido?

Outro problema. Se uma estrutura tiver dois derivados personalizados diferentes e a segunda entrar em pânico, a extensão do erro apontará para a primeira, não para aquela que entrou em pânico.


Roteiro de reprodução

Em uma caixa chamada demo_plugin

#![feature(rustc_macro, rustc_macro_lib)]

extern crate rustc_macro;

use rustc_macro::TokenStream;

#[rustc_macro_derive(Foo)]
pub fn derive_foo(input: TokenStream) -> TokenStream {
    input
}

#[rustc_macro_derive(Bar)]
pub fn derive_bar(input: TokenStream) -> TokenStream {
    panic!("lolnope");
}

Em outra caixa

#![feature(rustc_macro)]

#[macro_use] extern crate demo_plugin;

#[derive(Foo, Bar)]
struct Baz {
    a: i32,
    b: i32,
}

O erro irá destacar Foo mesmo que Bar entrado em pânico.

Obrigado pelo relatório @sgrif! Atualizei a descrição desse problema e espero manter um controle de todos os problemas pendentes relacionados às macros 1.1 também.

Hmm, a interação da derivação personalizada com #[structural_eq] é algo que eu não tinha pensado antes. Isso me incomoda bastante!

Parece-me que ter uma maneira de "anexar" texto a um fluxo de token pode ser uma interface melhor no final do dia ... preservaria as informações de abrangência e evitaria esse problema, não?

Uma vantagem da interface mais geral é que ela permite que os pacotes tenham atributos nos campos, que podem ser removidos quando a macro é executada. Este é o único caso de uso real que conheço para permitir que macros derivadas personalizadas alterem o item original.

Acho que a questão da interface se resume a se queremos que a derivação customizada seja 'apenas outra macro', caso em que parece importante ter a mesma interface que outras macros procedurais (onde queremos modificar o item original). Ou se deve ser algo próprio com uma interface especial, caso em que a interface mais restritiva (anexar) faz sentido.

Notarei que as extensões de sintaxe há muito distinguiam entre modificadores e decoradores, e acho que todos os envolvidos realmente odiavam essa distinção. Portanto, estou um pouco relutante em seguir o caminho de fazer com que a derivação customizada seja um pouco especial (uma alternativa que foi discutida é algum tipo de derivação customizada muito especial, possivelmente até mesmo um formato declarativo).

@nrc bem, ainda pode ser tokenstream -> tokenstream, onde simplesmente não expomos um método new que começa do zero, certo?

Concordo que deve ser possível ter atributos personalizados nos campos para que uma derivação personalizada realmente faça sentido. Mas acredito que pode haver muitas maneiras de fazer isso com o estilo "anexar" - isso, é claro, deve ser discutido. Eu definitivamente prefiro o estilo append, porque prefiro um mundo onde derivar macros não altere o item e seja combinável, além de resolver o problema de #[structural_eq] . Esta é a quantidade certa de liberdade IMO.

Se as pessoas não gostavam dessa distinção, eu gostaria de perguntar por quê, porque obviamente havia razão suficiente para fazer essa distinção antes, não era?

(Acho que o que sugeri é apenas um hack temporário, não resolve realmente o problema de composibilidade de longo prazo.)

Minhas várias bibliotecas atualmente têm macros semelhantes a foo!(Bar, parameters...) , que geram uma estrutura Bar partir dos parâmetros.

Durante alguma discussão no IRC, tivemos a ideia de escrever #[derive(Foo)] #[params] struct Bar; vez disso e substituir foo! por um #[derive(Foo)] que geraria o corpo da estrutura.

Obviamente, não é um argumento forte, mas eu realmente gostei dessa ideia, pois é mais claro para o usuário que uma estrutura está sendo construída.

Eu me pergunto se poderíamos retrabalhar o #[structural_match] para ser colocado no impl gerado.

(Realmente não resolve o problema)

Vale a pena notar que Serde e Diesel fazem muito uso de atributos personalizados nos campos, portanto, há uma necessidade definitiva de derivação personalizada para permitir a substituição.

Então, na verdade, talvez eu não esteja pensando direito sobre a questão de #[structural_match] . Afinal, o que uma derivação personalizada pode fazer?

  • Se a derivação personalizada inserir o atributo #[structural_match] erroneamente, ela deve falhar na verificação de estabilidade. Se não, isso parece um bug em si! (Dada a maneira complexa e maluca como esse código funciona, no entanto, isso não me surpreenderia.)
  • Se a derivação do costume o remover, nenhum dano será causado.

Desculpe por escrever muitos pequenos comentários e pensar em voz alta, mas há uma outra preocupação. Embora uma derivação personalizada possa não ser capaz de "falsificar" uma anotação #[structural_match] (porque terminaria sem o "intervalo mágico"), provavelmente acabaria estragando o intervalo de qualquer anotação existente, a menos que o derivadas são aplicadas na ordem correta, o que é lamentável. Basicamente, uma instância da não-composibilidade da qual @colin-kiegel tem falado, mas sem nenhuma tentativa de modificar a estrutura em andamento.

(Em outras palavras, uma vez que contamos com o intervalo para julgar se o material estável pode ser usado, perder informações do intervalo pode causar alguns problemas complicados.)

EDIT: OK, lendo novamente, vejo que acabei de recuperar o que @sgrif já relatou. Desculpe de novo. ;)

Também é meio grosseiro, porque significa que estamos expondo detalhes de implementação instáveis ​​para código estável. Idealmente, o código estável nunca saberia que a anotação #[structural_match] existe.

@nikomatsakis bem, de uma forma ou de outra, precisamos impor diferentes restrições, dependendo se a macro se destina a ser uma derivação personalizada ou algum outro tipo. Isso significa algum tratamento separado (e semântica diferente), qualquer que seja a assinatura da função.

@ colin-kiegel

Eu definitivamente prefiro o estilo anexar, porque prefiro um mundo onde derivar macros não altere o item e seja combinável, além de resolver o problema # [estrutural_eq]. Esta é a quantidade certa de liberdade IMO.

Acho que as macros mutantes podem ser compostas, embora, é claro, os termos de composibilidade sejam diferentes. Deve haver claramente um conjunto de pré e pós-condições na operação de derivações, sejam aplicadas pelo compilador ou por convenção. A não mutação parece um extremo no espectro de invariantes que podemos escolher aqui, e observe que já estamos discutindo maneiras pelas quais isso pode ser suavizado (por exemplo, atributos de marcação usados). Acho que, em geral, preferiríamos as condições mais simples e que fossem aplicadas pelo compilador. No entanto, isso é subsumido de alguma forma pela questão de como a derivação especial do costume deve ser tratada.

Se as pessoas não gostavam dessa distinção, eu gostaria de perguntar por quê, porque obviamente havia razão suficiente para fazer essa distinção antes, não era?

Não acredito que houvesse uma motivação forte na época. Acho que foi fácil de implementar e 'pareceu uma boa ideia'. Desde então, não foi apreciado porque torna a implementação mais complexa, adiciona uma distinção para autores de macro que geralmente é irrelevante e torna as macros menos flexíveis ao ter que escolher se deseja modificar ou decorar.

Eu gostaria muito de considerar o design de longo prazo do derivado personalizado e garantir que estamos caminhando na direção certa. Parece-me que as restrições da solução 1.1 e o desejo de fazer o máximo possível o mais rápido possível estão turvando as águas aqui e estamos perdendo a visão mais ampla.

Concordo com @jimmycuadra porque parece que o suporte a atributos personalizados de uma forma ou de outra é um requisito difícil. @nikomatsakis também está certo em que o tratamento atual de #[derive(PartialEq, Eq)] é inferior e não devemos estabilizá-lo. Finalmente, @mystor tem um ponto muito bom de que os modos de derivação personalizados nem deveriam saber sobre esse atributo mágico. Devemos querer adicionar mais no futuro e não quero que as macros 1.1 nos impeçam de fazer isso.

Também ecoando o sentimento de @nrc sobre o design de longo prazo de derivação customizada, acho que muito disso se resume a como #[derive] realmente funciona. Se e quando oferecemos suporte a atributos arbitrários, acho que @nrc tem um bom ponto de ter apenas modificadores e não ter decoradores, mas #[derive] é muito especial, onde uma derivação personalizada não define um novo atributo, mas apenas adere a um existente.

No momento, a implementação tem uma expansão estrita da esquerda para a direita dos modos #[derive] . Todos os modos de derivação internos são expandidos em um loop e sempre que um modo de derivação personalizado é atingido, nós resserializamos, expandimos e, em seguida, voltamos ao estágio 1. Isso significa que o compilador pode executar o #[derive] attribute _várias vezes para um tipo definição_. Isso leva a um monte de pelos.

Uma proposta que posso ter é ajustar a ordem de expansão de #[derive] :

  • Primeiro, todos os atributos derivados personalizados das macros 1.1 são expandidos um por um. Ou seja, se você tiver #[derive(Clone, Foo)] , primeiro derivaríamos Foo onde a estrutura tinha uma anotação #[derive(Clone)] . Se aquele #[derive] persistisse, derivaríamos a característica interna customizada Clone .
  • Em segundo lugar, todos os atributos derivados desconhecidos pelo compilador são expandidos para #[derive_Bar] atributos. Este é apenas um hack de compatibilidade com versões anteriores que devemos remover em algum ponto, e é como os atributos usados ​​para serem expandidos.
  • Finalmente, o compilador expande todos os atributos #[derive] conhecidos e integrados

Isso tem o efeito surpreendente de que você não está expandindo da esquerda para a direita, mas lembre-se novamente de que isso é apenas #[derive] . Isso dá ao compilador conhecimento máximo sobre a definição da estrutura e, quando ele está expandindo os traços internos, sabemos que a estrutura do tipo nunca será alterada.

Como isso soa? Eu acredito que isso resolve todas as restrições aqui?


@nikomatsakis Não tenho certeza se a estratégia de colocar o atributo no impl funcionará porque outros modos de derivação personalizados poderiam, em teoria, alterar o layout da estrutura, até mesmo os tipos dos campos. Isso violaria as suposições do compilador quando ele se expandiu pela primeira vez, eu acho.

A ordem em que as derivações são processadas já foi oficialmente declarada como sendo da esquerda para a direita, por meio da referência Rust ou algo assim? De maneira mais geral, a ordem dos atributos é importante? Parece que foi acidental o fato de ter sido implementado dessa forma, e os autores de macro não deveriam depender de uma ordem da esquerda para a direita. A proposta de Alex de processamento personalizado deriva primeiro, de modo que eles nunca vejam atributos mágicos adicionados pelo compilador faz muito sentido.

Gostaria apenas de acrescentar que não gosto da ideia de que derivados personalizados possam alterar o layout da estrutura. Eu gostaria de poder usar isso para algo que seja sensível à segurança. Como exemplo, considere a implementação #[derive(Trace)] usada por rust-gc .

#[derive(Trace)]
struct Foo {
    a: Gc<i32>,
}

Expandindo para:

struct Foo {
    a: Gc<i32>,
}

unsafe impl Trace { // NOTE: Strawman impl
    unsafe fn trace(&self) { Trace::trace(&self.a) }
}

No entanto, se permitirmos a alteração dos campos na estrutura, podemos definir um derivado personalizado Evil :

#[derive(Evil)]
struct Foo {
    a: Gc<i32>,
}

Expandindo para:

struct Foo {
    a: Gc<i32>,
    b: Gc<i32>,
}

Que, se os combinarmos:

#[derive(Trace, Evil)]
struct Foo {
    a: Gc<i32>,
}

Expandindo para:

struct Foo {
    a: Gc<i32>,
    b: Gc<i32>,
}

unsafe impl Trace {
    unsafe fn trace(&self) { Trace::trace(&self.a) }
}

O que é uma implementação incorreta de Trace . Quando usado com rust-gc , isso permite que b seja uma referência pendente, o que é terrivelmente inseguro e doentio. Isso significa que Trace não é mais uma coisa segura #[derive] em um tipo, o que é altamente lamentável.

Pessoalmente, acho que qualquer #[derive] bem-comportado não modificará o layout / composição de uma estrutura e, se isso acontecer, você está sem sorte. A capacidade de um derivado customizado eliminar atributos é crítica e desistir disso é um obstáculo. Além disso, outras implementações que envolvem alguma forma de lista branca ou outras coisas divergem bastante da interface simples que temos hoje.

Dito de outra forma, não acho que a "pureza" de ter #[derive] nunca modificando a estrutura valha o custo.

Eu só me pergunto se haverá uma maneira de eliminar atributos sem permitir adicionar ou remover campos (por exemplo, verificar se os campos são os mesmos na estrutura reanalisada que a estrutura original e errar se não forem t, mas não reclamando se outras coisas forem alteradas).

Tenho um mau pressentimento sobre permitir que o derivado altere a estrutura. O exemplo de @mystor é o que eu tinha em mente quando estava falando sobre composibilidade .. em um nível alto (pode ser que não seja o termo certo).

Acho que as pessoas vão explorar isso, se estiver disponível. E isso forçará os consumidores a raciocinar sobre os detalhes das implementações de derivação customizada e sua ordem de execução.

Eu preferiria que pudesse dizer 'ei, não sei o que essa derivação faz, mas entendo a outra' sem interdependência. Do contrário, será uma dor no dedão e acredito que vai acontecer.

Uma macro procedural fazendo algo malicioso é realmente diferente de qualquer caixa que você usa para fazer algo malicioso? Qualquer caixote pode conter um código inseguro que faz algo que não deveria. Parece que este caso se encaixa na maneira como você determina a confiabilidade de qualquer código que você não escreveu, por exemplo, reputação da comunidade, inspecionando a fonte você mesmo, etc.

Eu não acho que os engradados vão tentar fazer algo malicioso, em vez disso, espero que eles sejam "inteligentes" e façam truques legais para tornar sua implementação mais eficiente ou porque podem, e para quebrar outras implementações derivadas personalizadas. Eu não ficaria muito surpreso se alguns derivados customizados começassem a adicionar campos a structs que são usados ​​apenas em suas implementações porque podem, e então quebram algo como Trace.

@mystor Isso parece relevante na teoria, mas lembre-se de que você realmente precisa fornecer todos os campos de uma estrutura no Rust, então é muito menos provável que funcione silenciosamente assim, do que em C ++, por exemplo.

@alexcrichton

Uma proposta que posso ter é ajustar a ordem de expansão de #[derive]

Eu gosto desta ideia. Talvez o que se deve afirmar em termos de documentação seja simplesmente que a ordem de expansão é "indefinida". Acontece que expandimos PartialEq / Eq nos últimos dias, mas não há uma razão estrita para fazer isso.

Quanto aos derivados modificando a definição da estrutura, concordo que parece um pouco sutil, mas não me incomoda muito. Eu também acho que campos de "inserção automática" podem ser bastante úteis - mas eu prefiro que esses modificadores sejam atributos distintos em vez de colocados na lista de derivação, principalmente porque não queremos que as pessoas confiem no pedido de expansão agora.

PS. Eu consideraria seriamente o uso de um RNG (determinístico) semeado pelo hash da caixa ou algo parecido para reordenar a expansão das derivações personalizadas do usuário, de modo que as pessoas não possam confiar na ordem de expansão. Sempre quis fazer isso em algum contexto para evitar dependências implícitas, mas nunca tive a chance. ;)

EDIT: Eu mudei de ideia e não vejo nenhuma razão para proibir mais a mutação da estrutura, mas aqui está meu comentário original para contexto

Então, do meu entendimento, estes são os argumentos para permitir que #[derive] modifique a estrutura:

  • Pode ser útil em alguns casos (não vi nenhum exemplo, mas acredito que existam)
  • Queremos ser capazes de remover atributos, uma vez que eles tenham sido usados
  • Dando mais poder aos autores de derivados personalizados

Embora os argumentos para adicionar limitações às implementações #[derive] (como exigir que o nome da estrutura e os campos / nomes dos campos na estrutura permaneçam iguais) são:

  • Ele permite que o código gerado por um #[derive] dependa da estrutura do tipo do qual está sendo derivado para integridade (por exemplo, #[derive(Trace)] de rust-gc que _deve_ ver o tipo de apoio real, ou não é saudável, potencialmente de uma forma sutil de uso pós-venda)
  • Isso reduz a probabilidade de dependências implícitas em expansões macro, pois há menos informações transmitidas entre elas por meio da estrutura

Na minha opinião, a capacidade de escrever implementações derivadas personalizadas de som que geram unsafe trait impls ou código que depende de código inseguro é extremamente importante, e acredito que há maneiras de alcançar a maioria das habilidades na primeira seção ( com exceção da capacidade de adicionar campos de estrutura) de maneira segura. Se não tivermos algum tipo de restrição, não acho que caixas como rust-gc serão possíveis de implementar de maneira segura. Tenho duas ideias:

Ideia 1

Antes de executar um passe de derivação personalizado, leia o nome da estrutura e os nomes de cada um dos campos. Quando a passagem for concluída e a estrutura for analisada novamente, verifique se o nome da estrutura é o mesmo e se os nomes de cada um dos campos (e a contagem dos campos) são os mesmos. se não estiverem, gere um erro e elimine o build.

Isso garantiria que as propriedades estruturais básicas das quais esperamos que os plug-ins de derivação personalizados dependam não sejam quebradas, e significa que temos mais plug-ins de derivação personalizados de som. Isso também tem a vantagem de ser compatível com as versões anteriores com a abordagem atual, portanto, se decidirmos que gostamos mais no futuro, podemos apenas mudar e quebrar o código de ninguém. Ele também lida com o caso de atributo não utilizado, assim como hoje.

Idéia 2

Dê a cada plugin derivado personalizado a mesma entrada TokenStream (o texto original escrito no programa). Quando o resultado for analisado novamente, registre quais atributos ainda estão presentes na estrutura de saída. Se um atributo estiver presente em cada tokenstream de saída, reclame sobre um atributo não utilizado.

Isso significa que é impossível ter dependências de ordenação (visto que, conceitualmente, todo plug-in de derivação personalizado funciona a partir do mesmo objeto original) e também torna impossível bagunçar a estrutura do plug-in. Eu gosto dessa ideia, pois ela garante que cada derivação customizada atue de maneira sã, gerando apenas novos itens com base na estrutura existente. Isso provavelmente também seria fácil de transformar em qualquer solução em que poderíamos transformar a solução atual.

TL; DR

Em resumo, eu gostaria de entender o que a vantagem particular é de permitir estruturas mutantes, e por que supera as preocupações de segurança de fazer seguro #[derive(Trace)] e sempre correta #[derive(Serialize)] etc. possível. Tenho certeza de que, se acabarmos descendo a rota das estruturas mutantes, haverá um bom motivo, mas ficarei muito triste se mudar o nome do meu Trace personalizado derivado para #[derive(unsafe_Trace)] .

Acho que a solução de @alexcrichton é uma boa opção. Eu definitivamente esperaria que quaisquer alterações que alguns derivados personalizados fizessem, os padrões fossem aplicados a ele.

Embora @mystor tenha um bom argumento de que pode levar a surpresas desagradáveis, ter a possibilidade de alterar o struct parece obrigatório. Por outro lado, as caixas que combinam os casos de uso de macros procedurais, código inseguro _e_ preocupações com segurança parecem bastante incomuns.

Fora do tópico: essa implementação fornecerá uma maneira para a macro relatar erros normalmente?

Eu gosto da ideia de @nikomatsakis para randomizar a ordem de expansão dos derivados personalizados. Isso definitivamente ajudaria a evitar que um costume "ruim" se tornasse muito popular - e não deve exigir muito esforço.

@mystor, uma terceira opção seria fazer essas verificações de segurança apenas uma vez após todas as derivações serem aplicadas. Isso não seria 100% correto (duas derivações podem adicionar e remover o mesmo campo), mas em termos de uma contramedida heurística, deve ser suficiente para evitar quaisquer tentativas de alterar a definição de estrutura em uma derivação personalizada.

Eu realmente não vejo a preocupação em torno da modificação da estrutura. Um campo não pode ser adicionado de forma invisível, algo terá que cuidar durante a inicialização. Se você está _realmente_ preocupado com que isso aconteça, você pode escrever seu derivado para gerar código que falha ao compilar se não ver toda a estrutura facilmente.

@sgrif provavelmente verdadeiro na _maioria_ dos casos - mas não tanto se você também deriva e usa o traço padrão, ou algo equivalente.

@sgrif PS: É verdade que a maioria dos autores de ferrugem provavelmente entende o que está acontecendo em seu próprio código e pode, portanto, não se surpreender com alterações de estrutura se eles escolherem usar tal macro de propósito.

O caso de uso geral para aplicar macros em estruturas certamente é uma _combinação_ de alterações de estrutura + decorações. Mas espero que a proporção geral seja "muitas decorações" com "apenas algumas alterações". É bom ter uma separação clara aqui, porque isso melhora a legibilidade e a flexibilidade.

  • flexibilidade: digamos que você queira aplicar duas derivações personalizadas que fazem as duas coisas, ou seja, alteram e decoram. Não importa como você os solicite, você pode não obter o resultado desejado. Porém, se a alteração acontecer por um mecanismo diferente e as decorações forem todas aplicadas posteriormente, você tem a flexibilidade de combinar várias alterações e decorações de uma forma mais controlável.
  • legibilidade: se você ler o código de outra pessoa e houver 10 derivadas aplicadas a uma estrutura e uma delas alterar a estrutura, você precisará de mais tempo para descobrir isso.

Então, embora eu ache esta parte do argumento de

Ele permite que o código gerado por um # [derivar] personalizado dependa da estrutura do tipo do qual está sendo derivado para integridade (por exemplo, # [derive (Trace)] de rust-gc que deve ver o tipo de suporte real, ou é doentio, potencialmente de uma forma sutil de uso pós-venda)

Acho que tentar impor isso em derivar pode ser a maneira errada de fazer as coisas. Especificamente, nós provavelmente não quer a capacidade de modificar definições de estrutura no caso geral (sim, não vai ser transparente, mas e daí). O que significa que a ideia de ter um "som" Trace que pode ter certeza de que a estrutura não muda depois pode precisar ser resolvida de _outra_ maneira. Considere se os decoradores são aplicados de baixo para cima:

#[mangle] // <-- custom decorator that does bad things to struct definition
#[derive(Trace)]
struct Foo {
    x: T, y: U
}

Pode-se pensar que Trace impl pode ser escrito de tal forma que _it_ não consegue compilar se a definição de struct muda. Por exemplo:

unsafe impl Trace for Foo {
    fn trace(&self) {
        let &Foo { ref x, ref y } = self;
        <T as Trace>::trace(x);
        <U as Trace>::trace(y);
    }
}

Observe, entretanto, que #[mangle] também pode bagunçar seu implemento se ele for realmente diabólico. =) Há muito que podemos fazer aqui.

Como observador dessas conversas, ficaria feliz em ter a regra formal ou informal de que #[derive] só pode adicionar blocos de impl e introduzir uma anotação de irmão ( #[mangle(Foo, Bar)] sons bom para mim 😸) que é dedicado a _alterar_ a estrutura de um tipo. #[mangle] poderia ter uma ordem de avaliação definida.

Provavelmente, deve haver uma ordem de avaliação definida entre as anotações, se ainda não houver.

Acho que minhas opiniões sobre isso foram relaxadas. @nikomatsakis afirma que, mesmo se nos let Foo{ ... } parece funcionar para garantir que o layout esteja correto em casos sãos. Provavelmente precisaremos documentá-lo em algum lugar, para que todos não tenham que descobri-lo independentemente.

@nikomatsakis

  • mh .. pode haver a regra adicional, que _decoradores personalizados devem ser aplicados antes de derive_, além de _derives não podem alterar o item_. Isso ainda permitiria endurecer / purificar a mecânica derivada em geral.

Mas também estou feliz em ver que há outra maneira de proteger derivados personalizados _individualmente_ de alterações posteriores. :-)

Em relação aos casos que precisam modificar o interior do item ao qual o atributo é aplicado, acabei de encontrar o tópico "Feedback de pré-implementação para Qt com Rust" em u.r-l.o e fiz esta postagem:

https://users.rust-lang.org/t/pre-implementation-feedback-for-qt-with-rust/7300/19

Uma faceta notável é que aqui estou sugerindo que um #[derive] (ou similar) seja aplicado a um _trait_, em vez de uma estrutura - e o item adicionado dentro dele seria um método const trait .

Semelhante às questões de carga que levantei acima, não tenho certeza sobre

#[macro_use]
extern crate double;

importar macros procedurais de uma caixa chamada double . Uma vez que estamos usando código de tempo de execução (como em Ruest real) em tempo de compilação, deve haver uma instrução de importação de incremento de fase análoga ao (require (for-syntax ...)) do Racket. [Racket doc é https://docs.racket-lang.org/reference/require.html#% 28form ._% 28% 28lib._racket% 2Fprivate% 2Fbase..rkt% 29._for-meta% 29% 29, infelizmente, não consigo descobrir como vincular a seção certa.]

A postagem do blog http://blog.ezyang.com/2016/07/what-template-haskell-gets-wrong-and-racket-gets-right/ aponta os erros de progressão cometidos no Template Haskell e podem ser interessantes- --Eu não quero cometer os mesmos erros em Rust.

@ Ericson2314 A diferença em Rust é que double já está compilado _para uma fase específica_.
Ou seja, a caixa exporta apenas a interface macro / modificadora, como se eles fossem definidos com, por exemplo, macro_rules .
Ser capaz de criar caixas que exportam macros procedurais e os itens Rust subjacentes (que formam a macro procedural) seria interessante, mas até agora não parece ter sido proposto de forma alguma.

Pode fazer sentido permitir que a caixa que está sendo construída escolha muito sobre o que e como exportar, mas apenas pegar um sistema no atacado de um LISP com um modelo de compilação diferente parece contraproducente.

Sim, @eddyb , estou cético em relação a essa metodologia de "criar sabe como será usado". Se alguma coisa, as fases são mais importantes para nós do que o Racket devido ao nosso modelo de compilação (nem tenho certeza se o Racket pode fazer compilação cruzada), então não entendi seu último argumento.

Indicado para discussão da equipe lang sobre: ​​um plano de estabilização.

No lado serde, aqui está uma pequena lista de problemas restantes antes que possamos parar de suportar o plug-in do compilador existente e recomendar oficialmente Macros 1.1 para todos os usuários noturnos: https://github.com/serde-rs/serde/issues/545. A única coisa que precisamos do Rust é que # 36211 seja consertado. Em todo o resto , estamos progredindo rapidamente.

Eu tenho um PR aberto que implementa nosso rustc_macro sem usar o syntex, então podemos parar de nos preocupar com o tempo de compilação https://github.com/serde-rs/serde/pull/548.

EDIT: não importa, # 36211 afetou apenas a implementação do syntex antigo

Vou tentar terminar a porta Diesel até sexta-feira para que possa confirmar que isso faz tudo o que precisamos nesse sentido.

@dtolnay dado serde-rs / serde # 548, há algum bloqueador restante para serde?

@sgrif incrível, obrigado! Depois de fazer isso (ou antes), você poderia comentar aqui com os bloqueadores restantes que encontrou?

Sim, eu vou.

Na terça, 27 de setembro de 2016, 19:29 Alex Crichton [email protected]
escrevi:

@dtolnay https://github.com/dtolnay dado serde-rs / serde # 548
https://github.com/serde-rs/serde/pull/548 , há algum restante
bloqueadores para serde?

@sgrif https://github.com/sgrif incrível, obrigado! Depois de fazer isso
(ou antes) você poderia comentar aqui com quaisquer bloqueadores restantes que você
encontrou?

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/35900#issuecomment -250028743,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ABdWK7MnA1rpn-WGji8gwAT2JCIqB4LFks5quaa-gaJpZM4JqEAX
.

@alexcrichton

dado serde-rs / serde # 548, há algum bloqueador restante para serde?

Não, se @oli-obk ou @erickt avaliarem esse PR hoje ou amanhã, posso enviar tudo no dia seguinte e migrar alguns usuários proeminentes como Rusoto.

@dtolnay Estou fazendo uso intenso de serde_macros em Ruma e gostaria de ajudá-lo a testar serde_derive também, se precisar de mais olhos.

Na verdade, eu uso diesel_codegen também, então Ruma é um bom campo de testes para a versão Macros 1.1 de ambas as bibliotecas.

Eu lancei o Serde 0.8.10 anunciando que serde_macros foi preterido em favor do Macros 1.1.

Vamos adicionar uma caixa de seleção para "documentos de carga funciona" - https://github.com/rust-lang/cargo/issues/3132.

@dtolnay feito!

Não tenho certeza se esses são bugs serde_derive ou rustc / rustdoc, mas estou percebendo dois problemas nos documentos gerados:

  • Um literal "///" aparece no início das docstrings geradas de tipos que usam derivação personalizada.
  • Deserialize e Serialize não aparecem na seção "Implementações de característica" para tipos que usam derivação personalizada.

Um literal "///" aparece no início das docstrings geradas de tipos que usam derivação personalizada.

Este era um bug de syn . Eu lancei o 0.8.2 com uma correção para cargo update para pegá-lo.

Não sei sobre o segundo, vou deixar isso para @alexcrichton.

@jimmycuadra

  • Deserialize e Serialize não aparecem na seção "Implementações de característica" para tipos que usam derivação personalizada.

Isso parece muito suspeito! Parece que este é um bug no rustdoc, mas talvez não um novo. Por exemplo, rustdoc não mostra isso com a implementação Copy :

#[derive(Clone)]           
pub struct Point {         
    x: i32,                
    y: i32,                
}                          

const _FOO: () = {         
    impl Copy for Point {} 
    ()                     
};                         

Esse padrão é usado por serde-derive para gerar impls. @dtolnay serde_macros teve a mesma forma de geração também? Seria fácil sair de agora para resolver o problema da documentação?

@jimmycuadra deseja abrir um bug separado para rastrear o problema do rustdoc sobre isso?

@dtolnay serde_macros teve a mesma forma de geração?

Sim.

Seria fácil sair de agora para resolver o problema da documentação?

Não. Pelo que eu sei, esta é a única solução que atende a essas duas restrições:

  • Deve suportar serde não na raiz da caixa, ou seja, não ::serde . Isso é comum quando as pessoas colocam serde impls atrás de um sinalizador de recurso em um módulo separado.
  • Deve ser capaz de usar tipos privados do módulo circundante.

Consulte https://github.com/serde-rs/serde/issues/159 para obter os detalhes.

@dtolnay ah ok, para esclarecer, @jimmycuadra isso não é uma regressão de antes, certo?

Atualização: Não está bem feito com a porta, mas está quase lá. Nenhum problema além dos que já relatei ou pequenas limitações que sabíamos que encontraríamos. Provavelmente seguro verificar a caixa "diesel funciona", teremos um lançamento amanhã em Macros 1.1 se isso não acontecer esta noite.

Para aqueles que estão acompanhando, abri # 36945 para renomear rustc_macro para proc_macro praticamente todos os lugares, o que parece ser o consenso sobre o nome desta caixa.

Parece que as implementações de características ausentes no rustdoc (que agora estão sendo rastreadas em outra edição) não são específicas para Macros 1.1. É seguro ignorar.

Eu tentei portar minha biblioteca mockers do plugin do compilador para macros 1.1 e recebi “erro: atributos derivados personalizados só podem ser aplicados a itens de struct / enum”. Com o plugin do compilador, é possível (embora um tanto estranho) usar "derivar" nas características. Estou sem sorte aqui?

@kriomant interessante! Acho que pode ser um bug na derivação personalizada hoje, já que não tenho certeza se alguma vez foi pretendido permitir que #[derive] se aplicasse a uma característica ...

Acho que, por enquanto, para sermos conservadores, provavelmente não _estabilizaremos_ derivar-nos-traços ainda, mas talvez pudéssemos adicionar um recurso instável para isso. Pensamentos @ rust-lang / lang?

@alexcrichton Qual é a diferença entre traits e structs de um aspecto do custom_derive?

@KalitaAlexey nenhum, é uma limitação artificial para corresponder à implementação real de derive

@alexcrichton Podemos estender um suporte para traits?

Como mencionei acima , é provável que seja um bug que a derivação customizada já tenha sido permitida em características, e isso não foi especificado no RFC, então mais discussões precisariam acontecer antes de estender. Em qualquer caso, é improvável que o suporte para derivar na característica seja estabilizado na primeira passagem, mas poderíamos considerar um gate de recurso separado.

Eu pessoalmente não gostaria de adicionar derive-on-trait, pois isso parece muito mais na linha do território de atributo personalizado, em vez de derivar em si. (por exemplo, vai contra o espírito de #[derive] originalmente criado)

Eu preferiria que a derivação customizada seguisse exatamente as mesmas regras da derivação regular. Nós _podemos_ querer mudar isso mais tarde, mas acho que deveria ser RFC (também estou muito frio na ideia, tbh, mas eu poderia ter minha mente mudada por um caso de uso atraente e um tratamento decente de vários casos extremos )

@alexcrichton

Acho que, por enquanto, para sermos conservadores, provavelmente não estabilizaremos a derivação sobre as características ainda, mas talvez pudéssemos adicionar um recurso instável para isso. Pensamentos @ rust-lang / lang?

👍 de mim.

Ok, o recurso instável é bom, mas significa que minha biblioteca ainda não funcionará no estável (sem geração de código). E a sintaxe macro!(…) também não é coberta pelas "macros 1.1", estou correto?

Estou revisitando o RFC de macro proc e o plano era que as caixas de macro deveriam se declarar #[cfg(macro)] . Não exigimos isso para macros 1.1, mas exigimos um tipo de caixa especial. Os dois mecanismos são um tanto ortogonais - o primeiro descreve a fase de compilação, o segundo o tipo de caixa. Mas eles também estão um tanto sobrepostos - o último implica o primeiro. O primeiro também pode ser escalado para declarar macros proc e funções não macro nas mesmas caixas, enquanto o último não.

Não tenho certeza se precisamos mudar alguma coisa aqui, provavelmente poderíamos hackear a proposta de macros proc para compatibilidade com versões anteriores (elides deliberadamente descrevendo o mecanismo de carregamento de macros e, portanto, os tipos de caixa). Mas algo a se refletir durante o período de estabilização.

@kriomant correto, sim

@nrc agora que é realmente definido como cfg(rustc_macro) (embora logo mude para proc_macro ). Não exigimos, não, mas achei que seria necessário para o conceito de vincular a um crate um tempo de compilação e também em tempo de execução? Ou seja, compilaríamos a caixa proc-macro duas vezes: uma com o tipo de caixa proc-macro e uma vez com o tipo de caixa rlib , e a última não ligaria a libsyntax ou qualquer coisa semelhante este.

Por agora, embora pareça normal não _requerê-lo_, embora eu ache que isso significaria que, em uma data posterior, você terá que ativar o suporte em tempo de execução. (compilando a caixa duas vezes)

@alexcrichton Poderia #[proc_macro_derive] sugerir isso? Da mesma forma que #[test] implica #[cfg(test)] .
(Não significa que temos que adicioná-lo agora, apenas que o pior caso, se adicionarmos cfg são avisos de itens não utilizados.)

@eddyb E quanto a extern crate proc_macro; ? Eu sinto que aqueles iriam quebrar também.

@mystor É apenas uma caixa normal com um monte de tipos e impls.

Ele também não se vincula a libsyntax , o que significa que se uma caixa usasse uma caixa proc_macro e quisesse fazer a compilação cruzada, também teria que fazer a sintaxe de compilação cruzada?

@eddyb yes #[proc_macro_derive] pode ser ignorado automaticamente, mas o problema é que nós _também_ precisamos ignorar tudo que essas funções alcançam transitivamente (como extern crate proc_macro ). Embora seja "apenas uma caixa", tem graves implicações de tempo de execução (link dinâmico, não disponível para destinos de compilação cruzada, etc).

Observe que os testes têm #[cfg(test)] , então me parece razoável que ainda demos #[cfg(proc_macro)] para basicamente o mesmo propósito.

@alexcrichton _Idealmente_, a caixa não teria nada mais do que o que é exportado e não exigiria link dinâmico ou algo assim, apenas libstd . No entanto, eu poderia ver isso causando problemas em #![no_std] casos.

Parece que teríamos que fazer a compilação dupla desde o início se quisermos pegar tudo: dis nomeado :.

EDIT : Espere, o que estou pensando? Requer um tipo de caixote customizado, compilação dupla se aplica a caixotes _regulares_ que _também_ exportam macros / atributos / derivações procedurais / etc. Portanto, não é relevante por enquanto.
Mas poderíamos pelo menos introduzir #[cfg(proc_macro)] que está sempre definido para o novo tipo de caixa.

🔔 Este recurso está entrando em seu período final de comentários com a intenção de se estabilizar no final deste ciclo de lançamento! 🔔

A razão para considerar a estabilização agora é:

  • A superfície da API desse recurso é, por design, _extremely_ conservadora. Ao mesmo tempo, é compatível com o plano de fato para macros procedimentais
  • O recurso está rapidamente sendo amplamente utilizado na Serde, e logo na Diesel - embora, notavelmente, o uso generalizado seja amplamente como um _cliente_ de derivação personalizada.
  • Ir para o FCP agora nos dá três meses antes que o recurso seja realmente lançado no estável, o que deve ser bastante tempo para detectar quaisquer problemas restantes.

Observe que o nome está mudando para proc_macro , com base no consenso anterior neste tópico. Podemos continuar usando este e outros pontos importantes para o resto deste ciclo de lançamento.

Compilar a caixa duas vezes parece muito rudimentar: o escopo não funcionará corretamente. Realmente algo como extern! crate needed_for_my_inline_proc_macros; é uma solução muito mais agradável.

@aturon o quê, as pessoas não começaram a usar isso dias atrás?

@ Ericson2314 Tem sido um pouco mais longo do que isso, mas como o comentário FCP explica, o tempo _mínimo_ antes de enviar para o canal estável é de três meses a partir de agora, o que acreditamos ser mais do que suficiente para esta interface extremamente estreita.

Observe que o próprio FCP é um caso de 6 semanas de duração, o que não significa necessariamente que o colocaríamos no caminho da estabilização. Mas, pelo menos iniciando o processo agora, criamos a oportunidade de lançar esse recurso em 3 meses, supondo que nenhum problema seja descoberto antes disso.

Ah ok. Lembrei-me da parte dos 3 meses, mas esqueci a localização do FCP dentro dessas 3 bocas --- para que não fossem 3 meses a partir de 22 de agosto. Não importa o tempo então.

Eu acho que as questões de faseamento que mencionei impactam até mesmo esta pequena parte da história das macros proc, então eu gostaria de ver isso resolvido.

@alexcrichton o rustc_copy_clone_marker e outros rustc_attrs ainda estão nos mordendo. Consulte https://github.com/serde-rs/serde/issues/577.

#[derive(Copy, Clone)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
pub struct MyStruct {
    value: i64,
}

A solução alternativa é trocar o pedido, mas decidi verificar se isso pode ser corrigido.

#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone)]
pub struct MyStruct {
    value: i64,
}

Eu terminei de portar https://github.com/sfackler/rust-postgres-derive para macros 1.1 Foi relativamente indolor, mas lidar com atributos que são lidos por múltiplos derivados é uma grande dor. Serde encontrou a mesma coisa, mas requer um monte de lógica estranha para expandir os dois derivados ao mesmo tempo: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-internals/ src / lib.rs # L26

Não deve bloquear a estabilização, mas acho que é uma questão ergonômica relativamente grande do lado da autoria que provavelmente deveríamos resolver logo.

Eu liberei quote 0.3.0 com suporte para repetições sofisticadas no estilo macro_rules! (obrigado @nrc pelo cutucão). Se você está desenvolvendo macros proc sem quasi / syntex você provavelmente quer isso. Exemplo de uso do postgres-derive:

pub fn enum_body(name: &str, variants: &[Variant]) -> Tokens {
    let num_variants = variants.len();
    let variant_names = variants.iter().map(|v| &v.name);

    quote! {
        if type_.name() != #name {
            return false;
        }

        match *type_.kind() {
            ::postgres::types::Kind::Enum(ref variants) => {
                if variants.len() != #num_variants {
                    return false;
                }

                variants.iter().all(|v| {
                    match &**v {
                        #(                           // \
                            #variant_names => true,  //  |----- new feature
                        )*                           // /
                        _ => false,
                    }
                })
            }
            _ => false,
        }
    }
}

Estou curioso, por que # e não $ ?

EDIT : Sempre pensei que a sintaxe de citação seria ${...} mas talvez eu esteja muito apegado aos literais modelados ES6, mesmo que já tenha se passado vários anos. \{...} também funciona, embora seja útil em outro lugar.
Nenhum colchete parece difícil de detectar, mas não devo me preocupar.

Estou curioso, por que # e não $ ?

Porque é uma macro macro_rules e não posso fazer o que quero: smile : $v é correspondido como um único token e não há como ir de $v para usar a variável v . Em contraste, #v são dois tokens, então posso combiná-los separadamente e fazer coisas com o ident.

macro_rules! demo {
    ($tt:tt) => {};
}

fn main() {
    demo!($v);
}

Espere que tudo isso seja feito em macro_rules ?! As macros esquecidas 1.1 foram derivadas apenas por um momento. Dito isso, $x sendo um token é uma falha de design da IMO. cc @jseyfried

@dtolnay

@alexcrichton the rustc_copy_clone_marker e outros rustc_attrs ainda estão nos mordendo. Consulte serde-rs / serde # 577.

Nossa, isso parece ruim! Vou atualizar a lista no topo. Pensamentos de @nrc ou @jseyfried sobre como podemos resolver isso? Não estou muito familiarizado com a ordem de expansão de tudo, mas talvez quando um atributo #[derive] estiver sendo expandido, ele possa tentar engolir todos os atributos futuros e fazer isso primeiro?

@sfackler

manipulação de atributos que são lidos por múltiplos derivados é uma grande dor

Não tenho certeza se segui bem o exemplo que você vinculou, pode explicar?

@alexcrichton Pegue por exemplo

#[derive(ToSql, FromSql)]
enum Foo {
    #[postgres(name = "bar")]
    Bar
}

O atributo #[postgres] é usado para ajustar como as implementações ToSql e FromSql são geradas. Ele precisa ser removido antes da saída final, já que é um atributo desconhecido, mas as implementações ToSql e FromSql são executadas separadamente. Se você fizer isso de maneira ingênua, apenas gerando a implementação e, em seguida, removendo os atributos, a segunda derivação perderá as personalizações.

serde-derive e postgres-derive hack em torno disso agora, fazendo com que a implementação de ambos os impls derivados encaminhe para a mesma função que gera os dois ao mesmo tempo. Temos que reconectar o #[derive] para aquele que está sendo invocado desde que o compilador o remove e, em seguida, enviá-lo para ser expandido: https://github.com/sfackler/rust-postgres-derive/blob /master/postgres-derive/src/lib.rs#L18

@sfackler Acho que você também pode fazer isso tendo os atributos extras removidos apenas quando não houver derivação do mesmo implementador sobrando. Seu caminho pode ser melhor _shrug_.

@sfackler ah ok, faz todo o sentido. Obrigado!

Eu me pergunto se essa lógica pode ser frágil. Por exemplo, se você está expandindo ToSql como a expansão futura de FromSql detectada? Pode estar por trás de um #[cfg] como @dtolnay mencionado acima, talvez? Portanto, pode não ser possível detectar em todos os casos?

@alexcrichton Sim, é frágil, e é por isso que uma solução real parece importante.

#[cfg] e #[cfg_attr] seriam processados ​​antes da derivação?

Como a API de macros 1.1 funciona em strings, só posso imaginar três soluções:

  1. deixe como está e aguarde as macros 2.0
  2. permitir atributos não utilizados na derivação personalizada
  3. estender a API de macros 1.1, permitindo que uma derivação personalizada envie nomes de atributos a uma lista de permissões específica para o item atual

Cada opção tem suas próprias vantagens / desvantagens.
con 1: soluções alternativas frágeis por enquanto
con 2: alguns atributos não utilizados não serão detectados
con 3: mais superfície de API para uma solução provisória

Eu considerei agrupar atributos em #[used(...)] para mantê-los e colocá-los na lista de permissões ao mesmo tempo, mas é muito bobo e muito estável.

@alexcrichton

quando um atributo #[derive] está sendo expandido, ele pode tentar engolir todos os atributos futuros

Eu gosto desta ideia. Como #[cfg] s e #[cfg_attr] s são processados ​​antes de #[derive] s, #[cfg] -guardados #[derive] s não são um problema para esta abordagem (ou para a solução análoga de @sfackler para o problema de remoção de atributos).

Esta abordagem tornaria a solução de @sfackler um pouco mais simples, já que outras sugestão de @eddyb simplificaria as coisas).

Uma possibilidade para o problema de atributos de controle é adicionar callbacks pós-expansão, um pouco semelhantes aos do syntex: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-codegen/src/lib. rs # L23 -L50

Seus impls derivados podem ser independentes e não remover atributos de controle, e você pode registrar uma passagem que é executada depois que toda a expansão é feita para limpar.

Diesel também está trabalhando manualmente em torno disso. https://github.com/diesel-rs/diesel/blob/master/diesel_codegen/src/lib.rs#L101 -L112

@dtolnay sua preocupação anterior sobre vários atributos #[derive] deve ser corrigida em breve, graças a @jseyfried

Eu joguei com isso usando os pacotes syn e quote e tive uma experiência realmente positiva. Esse será um ótimo recurso quando ele se estabilizar.

O problema com os atributos está afetando meu implemento, sendo a jusante da lógica derivada de serde s.

Ignorar atributos não utilizados em código derivado ou permitir expansões tardias que rodam após derivações regulares para removê-los parecem soluções razoáveis ​​para mim.

Seria ideal se as macros não tivessem a opção de transformar o fluxo de token original, parece um recurso perigoso que pode acabar sendo usado livremente entre agora e as macros 2.0 e, portanto, difícil de remover depois.

Portanto, faltava um ponto e vírgula em um caminho de tipo dentro de uma função genérica e recebi a seguinte mensagem de erro de rustc. Gostaria de saber se mais informações poderiam ser fornecidas, como qual macro derivada causou o erro de lex, qual token o causou, etc.

error: custom derive attribute panicked
  --> src/simple.rs:69:1
   |
69 | struct C(u64);
   | ^^^^^^^^^^^^^^
   |
   = help: message: Failure parsing derived impl: LexError { _inner: () }

Fiz um caso de teste para o LexError, parece acontecer com o dia 13 de outubro à noite (rustup não tinha atualizações) https://github.com/keeperofdakeys/proc_macro_derive_test.

@ rust-lang / lang Acho que precisamos discutir se os derivados podem modificar seu item anotado. Embora seja uma solução simples para permitir isso, parece ir contra as expectativas do usuário e parece ser bastante impopular. Eu ainda prefiro a solução atual - a simplicidade é boa e nenhuma das alternativas propostas me parece muito melhor.

Precisaremos de alguma solução para controlar os atributos se o item puder ser modificado. Não acho que me sinta particularmente forte em manter a capacidade de derivar para modificar o item, enquanto (no futuro) macros de atributo não derivadas puderem.

Alguém deve definir "modificar". Estamos falando sobre modificar os membros da estrutura ou apenas sobre remover atributos para evitar que o compilador reclame sobre atributos desconhecidos?

Se estamos falando sobre como modificar os membros da estrutura, na minha opinião a melhor solução é provavelmente permitir apenas uma macro de modificação de estrutura em cada estrutura que é expandida antes de todas as outras. Isso teria um comportamento bem definido e (na minha opinião) esperado.

Se estamos falando apenas sobre a remoção de atributos, provavelmente deve haver uma maneira de integrar atributos no próprio mecanismo de macros, em vez de deixar isso ao critério da macro.

Minha intuição sobre como derivar se comporta é que ele adiciona impls. Para o usuário final, derives deve parecer adicionar impls e nada mais. Em particular, eles não devem modificar a estrutura de uma maneira que importe para outros derivados e, portanto, eles devem ser independentes da ordem. Concordamos que é assim que um derivado deve se comportar ou as pessoas acham que os derivados também devem ser capazes de realizar transformações no tipo anexado?

Se concordarmos com isso, a questão se torna - queremos que a interface que expomos imponha isso, ou queremos deixar para os autores derivarem para impor isso? Aqui, parece haver dois problemas de compensação:

  • Fazer cumprir o contrato comportamental de derivar certamente parece mais a solução Rust para mim.
  • Fazer com que serde e deisel trabalhem com essa restrição apresenta muitos desafios.

E, claro, como outros atributos podem modificar a estrutura parece não ter relação com como derivar, especificamente, deve se comportar.

É interessante notar que a capacidade de remover o item anotado permite que o sistema atual preencha muitas funções que de outra forma não faria.

@sgrif Você poderia explicar como o deisel está usando o derivado? Eu sinto que entendo como serde usa sua capacidade de modificar a estrutura (para remover atributos que informam a macro para omitir ou renomear campos no traço de serialização), mas talvez deisel esteja usando isso para fazer outra coisa. Quando você diz "remova o item anotado", parece que está realizando uma transformação bastante radical no item.

@semoutboats 90% é mais ou menos o que você esperaria. Não tocamos nos itens fornecidos pelo usuário. No entanto, removemos itens anotados para hackear macros bang no sistema. https://github.com/diesel-rs/diesel/blob/master/diesel/src/macros/macros_from_codegen.rs#L12 -L18. Além disso, a única vez em que tocamos em algo no fluxo do token de entrada é para retirar as anotações. https://github.com/diesel-rs/diesel/blob/master/diesel_codegen/src/lib.rs#L109 -L120

@sgrif , também

Posso entender o argumento para manter a derivação como uma macro simples, no entanto.

Minha perspectiva é que o objetivo do Macros 1.1 é ser o mais flexível possível para preencher o maior número de necessidades possível, com uma baixa carga de manutenção para que possa ser estabilizado rapidamente e atuar como um paliativo até macros 2.0. O design atual se encaixa extremamente bem nessa função, na minha opinião.

Se estivéssemos falando sobre algo que deveria preencher permanentemente essa função, eu seria muito mais contra

Talvez eu esteja errado, mas minha leitura da RFC é que ela se destina a ser a base para o comportamento de derivar permanentemente. Ou seja, mais métodos serão adicionados a TokenStream no futuro, mas as macros derivadas estarão usando esta API, que atualmente permite que eles executem mutações arbitrárias no item anotado (e esta capacidade é necessária para o caso de uso do deisel )

Eu me sinto muito mal por permitir que os derivados façam isso permanentemente. Se aceitarmos que este é um sistema que será descontinuado, juntamente com macros macro_rules , em algum ponto no futuro, e sob macros 2.0 uma API de derivação diferente que é mais restrita será preferida, estou mais confortável com isso.

Parece que a intenção é apoiar os decoradores como transformadores que podem tudo.
E derivar exposto como um decorador simples, sem entrada no atributo.

@withoutboats : serde oferece suporte a vários atributos para modificar como os impls são gerados e, portanto, definitivamente precisamos da capacidade de removê-los ou ignorá-los após processá-los. Se isso ajudasse, poderíamos de alguma forma fornecer uma lista de atributos ao compilador que deveria ser eliminada, em vez de querermos fazer isso nós mesmos.

@eddyb Eu sou a favor de decorar ser capaz de fazer qualquer coisa, mas não a favor de ser decorador desenfreado (no longo prazo).

@erickt Right. Acho que, a longo prazo, a solução ideal seria que esses atributos fossem registrados como atributos personalizados não operacionais, em vez de o derivador ser responsável por removê-los. Mas isso não é viável a curto prazo.

Acho que, a longo prazo, a solução ideal seria que esses atributos fossem registrados como atributos personalizados não operacionais, em vez de o derivador ser responsável por removê-los. Mas isso não é viável a curto prazo.

Não estou familiarizado com os componentes internos do compilador que tornam isso inviável no curto prazo, mas seria possível para o plug-in de derivação personalizado apresentar uma lista de atributos personalizados que pretende eliminar e, em seguida, rejeitar quaisquer outras transformações nos atributos no item original?

Também observo que Diesel não segue a abordagem de Serde de ter todos os seus atributos personalizados sob um nome (por exemplo, #[serde(rename = "name")] em oposição a #[table_name = "name"] Diesel). Isso simplificaria a implementação se apenas um único nome de atributo personalizado foi registrado em vez de uma lista?

Uma possibilidade para o problema de atributos de controle é adicionar callbacks pós-expansão, um pouco semelhantes aos do syntex: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-codegen/src/lib. rs # L23 -L50

Seus impls derivados podem ser independentes e não remover atributos de controle, e você pode registrar uma passagem que é executada depois que toda a expansão é feita para limpar.

Implementei callbacks pós-expansão para Macros 1.1 em post-expansion . Seus impls derivados podem ser independentes e não remover atributos de controle, e você pode registrar uma passagem que é executada depois que toda a expansão é feita para remover atributos.

Discutimos a questão da modificação / pedido hoje na reunião da equipe lang. Havia um desejo de atender às expectativas do usuário, e por simplicidade (em termos de design, não ter muitos hacks sobrepostos e não estar muito sujeito a erros difíceis de decifrar / depurar). Observou-se que, embora a modificação dos dados de destino seja surpreendente, não é insegura. Também se sentiu que nós _podemos_ estar tendendo à pureza por si mesma, ao invés de por razões bem motivadas.

No final, decidimos que os derivados que não modificam a fonte são provavelmente melhores e devemos mudar para esse modelo. Isso pode significar prolongar o período de FCP. Não achamos que deveria haver um mecanismo especial para lidar com atributos de remoção. Em vez disso, a maneira como o compilador trata os atributos deve permitir que aqueles usados ​​por uma macro permaneçam no programa. A RFC 1755 deve levar isso em consideração.

Isso atrasará a estabilização de alguns usuários de derivação personalizada. No entanto, acreditamos que a maioria dos usos de derivados (especialmente aqueles que mantêm os usuários fora de uma cadeia de ferramentas estável) não usam atributos, então, por exemplo, a maioria dos usuários do Serde será capaz de mudar para estável em breve. Aqueles que precisam de atributos, levarão alguns ciclos a mais _mas isso não afetará o caso comum_.

cc @alexcrichton , @dtolnay , @sgrif , @erickt - pensamentos?

Os atributos são provavelmente mais comumente usados ​​com Serde e Diesel do que você sugere. Por experiência própria, tenho certeza de que todos os meus programas que usam o Serde usam atributos. Com Diesel, eu definitivamente uso atributos e acho que é necessário para dizer a diesel_codegen qual tabela de banco de dados mapeia para a estrutura.

Dito isso, não permitir que o custom derive a mutação da estrutura parece ser a escolha certa para mim. Isso apenas simplifica tudo, evitando muitos casos extremos estranhos. É mais importante acertar do que fazer rapidamente; portanto, se o recurso tiver que ficar instável por mais tempo, também não haverá problema.

Observou-se que, embora a modificação dos dados de destino seja surpreendente, não é insegura.

Não é inseguro, a menos que você esteja derivando um traço inseguro.

Na minha opinião, um dos casos de uso de derivações customizadas é implementar de forma segura características marcadas como inseguras, como por exemplo uma característica que deve descrever exatamente o layout dos membros da estrutura em que é implementada.

Minha caixa asn1 também requer atributos para qualquer coisa, menos para o uso trivial, então eu precisaria esperar até que os atributos personalizados aterrissassem.

Seria uma boa ideia dividir os atributos personalizados rfc em dois?

  1. Um rfc para fornecer a notação e namespacing para atributos personalizados em geral (permitindo atributos não operacionais no stable).
  2. Um rfc para saber como definir e a semântica em torno da execução de macros de atributos personalizados.

As macros de atributos personalizados parecem algo que exigirá muito para ser aperfeiçoado. Portanto, dividir o rfc em dois pode fornecer atributos estáveis ​​para pacotes derivados personalizados mais cedo.

Isso também significa que os atributos são espaçados por nomes e intencionais (isto é, a caixa externa é necessária para usar atributos para uma caixa). Posso prever que isso é uma boa coisa para evitar duas macros derivadas personalizadas usando o mesmo nome de atributo. Uma boa expectativa aqui seria usar o nome da caixa em que a característica está para os atributos.

Existe alguma razão pela qual esta implementação não inclui as macros name! usuais? Simples TokenStream in, TokenStream out para macros usuais seria extremamente útil em pragas onde os tempos de compilação para gramáticas complexas excedem 30s.

@dragostis Para citar o resumo do rfc:

Extraia um fragmento muito pequeno do sistema de macros procedurais de hoje no compilador, apenas o suficiente para obter recursos básicos, como derivação customizada, para ter uma API eventualmente estável. Certifique-se de que esses recursos não representem um fardo de manutenção para o compilador, mas também não tente fornecer recursos suficientes para o "sistema de macro perfeito" ao mesmo tempo. No geral, isso deve ser considerado um passo incremental em direção a uma "macros 2.0" oficial.

Ou, em termos mais práticos, será necessário muito design e testes para obter um sistema macro procedural que funcione bem, assim como o sistema de fechamento que criamos agora. Caixas como serde e diesel têm sérios problemas de usabilidade sem um recurso de derivação personalizado adequado, então vamos tomar uma medida provisória para consertá-lo agora - isso também ajuda a ferramentas e experiência com um possível sistema macro processual. As caixas syn e quote são bons exemplos disso.

@keeperofdakeys Sim, entendi. Não estou pedindo uma implementação de macros 2.0, estou apenas me perguntando se há algum fardo envolvido para adicionar outro atributo, digamos proc_macro que é uma implementação mínima que simplesmente espelha o design derivado apenas para macros comuns. O exemplo em praga simplesmente derivaria um analisador para struct mas não o derivaria do próprio struct , mas de uma gramática simples definida entre {} . Espero não estar desenterrando discussões mortas!

@nrc

Também se sentiu que poderíamos estar tendendo para a pureza por si mesma, e não por razões bem motivadas.

Uma observação sobre isso: em muitos casos em que tivemos que tomar decisões difíceis, como parametricidade e especialização, garantir estaticamente a noção relevante de "pureza" seria difícil / impossível. Mas, neste caso, é realmente muito simples garanti-lo por construção, e eliminar a importância da ordem para derivadas parece uma simplificação bastante forte para os usuários.

No que diz respeito à estabilidade, poderíamos considerar a adição de um mecanismo instável para ignorar atributos agora, que seria estável na prática (na medida em que a API não estaria sujeita a quebrar) mesmo se ainda exigir o uso noturno. Poderíamos até considerar a estabilização de tal canal lateral, com planos de descontinuá-lo em favor de um mecanismo mais geral, uma vez disponível. Ou poderíamos considerar a sugestão de @keeperofdakeys e avançar rapidamente na parte da solução de atributos gerais que trataria da preocupação imediata.

Do meu ponto de vista, é importante que nenhuma dessas preocupações bloqueie significativamente as macros 1.1 de serem amplamente utilizáveis ​​para Serde e pelo menos estáveis ​​"de fato" (ou seja, não quebrando).

@sgrif

Se estivéssemos falando sobre algo que deveria preencher permanentemente essa função, eu seria muito mais contra

Eu queria ecoar @withoutboats e esclarecer que a intenção atual é que a superfície da API das macros 1.1 permaneça como está, mesmo quando enviarmos as macros 2.0. Isso é algo que poderíamos reconsiderar - poderíamos pensar mais como macro_rules de hoje, com a intenção de descontinuar assim que o sistema final estiver em vigor.

Minha impressão do uso atributo em derive costume era semelhante ao de @jimmycuadra impressão , o que é que eles estão muito comumente usado. Eu acredito (mas me corrija se eu estiver errado) que os atributos customizados foram cruciais para vários casos de uso de diesel.

Nesse sentido, não estou 100% certo de qual seria a melhor maneira de avançar sobre esses atributos. Seria uma pena estabilizar as macros 1.1, mas ainda assim deixar um grande número de usuários ligados à noite (mesmo que seja "de fato estável" todas as noites) porque isso de alguma forma anula o propósito de estabilização rápida de macros 1.1 em primeiro lugar. Em outras palavras, se não estivermos puxando a maioria dos usuários de macros 1.1 _atualmente_ para o Rust estável, acho que podemos esperar por estabilizar macros 1.1.

Um ponto que tem me incomodado é como achamos que o atributo customizado será a longo prazo e racionalizando isso com o sistema de macros 1.1 atual. No RFC atual para atributos personalizados, as caixas devem declarar namespaces de atributos na lista de permissões no topo de uma caixa. Isso significa que usar um atributo personalizado na derivação personalizada é radicalmente diferente do que usar um atributo personalizado em outro lugar. Essa desconexão me preocupa em termos de uma perspectiva ergonômica e de "menos surpresa", embora eu também veja isso como uma função de forçar para ajustar o RFC (por exemplo, se eu vincular à caixa serde talvez isso possa colocar atributos de serde automaticamente na lista de permissões )

No geral, eu pessoalmente estaria bem em avançar com a estabilização completa do sistema macros 1.1 como está hoje. Ou, em outras palavras, estabilize o fato de que as funções de expansão de derivação customizada recebem uma estrutura e, em seguida, devem retornar a estrutura também se quiserem preservá-la.

Eu tendo a mudar de rustc-serialize para serde _because_ A geração de código de serde suporta atributos de controle.

Poderíamos estender as macros 1.1 para oferecer suporte a argumentos para o próprio atributo derivar. Isso parece uma coisa boa de se ter em geral, e pode ser abusado para contornar a falta de atributos de controle potencialmente no curto prazo.

Idem @jimmycuadra e @sfackler , os atributos são mais essenciais para o Serde do que o comentário de @nrc os faz parecer. Quase todos os casos de uso sem atributos também seriam atendidos por rustc-serialize. Se quisermos tirar as pessoas de folga todas as noites, podemos fazer isso substituindo Serde por rustc-serialize.

Ainda não tenho uma opinião sobre o movimento certo aqui, mas sei que se estabilizarmos sem atributos, consideraria fortemente a análise de comentários de documentos para retirar atributos. Imagino que muitas pessoas não gostariam de lidar com um script de construção apenas para poderem escrever:

#[serde(skip_serializing)]

.. ao invés de:

/// <!-- serde(skip_serializing) -->

@tomaka

Na minha opinião, um dos casos de uso de derivações customizadas é implementar de forma segura características marcadas como inseguras, como por exemplo uma característica que deve descrever exatamente o layout dos membros da estrutura em que é implementada.

O que você acha do meu comentário aqui a esse respeito? Achei esse um padrão satisfatório.

Quero esclarecer uma coisa:

A remoção de atributos personalizados é a principal coisa que as pessoas fazem com a derivação personalizada?

Eu sei que existem alguns hacks para fazer foo! expansão no contexto de um tipo (por exemplo, @sgrif mencionou tal caso de uso em diesel, acho que @tomaka mencionou um também) - quão centrais são esses casos de uso ?

A razão pela qual pergunto é esta: vejo benefícios em ter o mecanismo de derivação apenas retornando uma lista de impls adicionais. Em particular, isso significa que os intervalos para a própria declaração de tipo estariam corretos. Adicionar uma API rápida e suja ao contexto que permite fornecer uma lista de nomes de atributos à lista de permissões no contexto desse tipo parece uma correção fácil o suficiente para atributos, e poderíamos sempre descontinuá-la.

Se, entretanto, quisermos habilitar mais casos de uso (e francamente esses casos de uso são um pouco mais ... surpreendentes, quando você pensa sobre o que se espera da derivação), isso não funcionará. Nesse caso, eu provavelmente ficar bem com a estabilização como é e planejamento para depreciar a API "de bytes brutos" no futuro em favor de uma melhor maneira de escrever derive (afinal, nós realmente não esperar que as pessoas sejam usando bytes brutos de qualquer maneira, certo? mas, em vez de fluxos de token?) Podemos dar à API um nome um pouco mais pesado

@nikomatsakis A principal necessidade não é remover, mas sim ignorar os atributos. Não estou ciente das sérias desvantagens de ignorá-los. Fornecer uma lista de permissões na função de derivação por meio de um parâmetro adicional para o atributo principal, por exemplo, deve ser suficiente para todas as necessidades práticas que são derivadas verdadeiras e não hacks de macro procedimentais.

Sim, estou dividido entre duas abordagens sobre isso, ambas com desvantagens claras:

  1. A abordagem de "derivações simples" - ajusta a API para produzir apenas itens adicionais, tornando-a ainda instável para os usuários usarem atributos personalizados como parte da derivação, e fechando o buraco que o diesel e outras caixas estão atravessando para obter macros procedurais arbitrárias.
  2. A abordagem de "obsolescência planejada" - estabilizar a API como está, com pequenos ajustes possivelmente como Niko mencionou sobre nomenclatura, com a intenção de descontinuá-la algum dia. Isso exigirá que todos os autores de derivações customizadas algum dia reescrevam seu código, e permitirá a possibilidade de um comportamento surpreendente no período intermediário.

EDITAR: mas a lista de permissões de @eddyb também parece promissora.

Quero dizer, não é como se @nrc estivesse propondo algo muito diferente (embora IIRC esteja falando sobre lista de permissões na caixa de usuários), e é bobo falar sobre "marcar atributos como usados" quando tudo o que você tem é um fluxo de token.

As macros procedurais arbitrárias para as quais tenho abusado do sistema de macros 1.1 em rust-cpp seriam totalmente possíveis com uma abordagem alternativa que simplesmente fornece a capacidade de adicionar impls e ignorar atributos.

Eu realmente acho que ter a habilidade de ignorar atributos é essencial, mas fora isso, eu ficaria bem em ser incapaz de modificar a estrutura além desse ponto.

Não tenho certeza de quais formas de hacks que @sgrif está usando no deisel que não seriam _possíveis_ de fazer em um mundo onde não podemos modificar a própria estrutura e, em vez disso, podemos apenas adicionar itens adicionais.

Permitir que derivados personalizados recebam argumentos, conforme sugerido por

#[derive(Debug, Clone, Serialize(field("bar", rename = "baz")))]
pub struct Foo {
  pub bar: String,
}

Posteriormente, esse formulário pode ser descontinuado em favor dos atributos customizados, uma vez que uma decisão sobre eles seja tomada.

Tanto serde quanto minha caixa requerem atributos de campo / variantes. Tentar emular isso com argumentos derivados não faz muito sentido, precisamos de atributos.

Seja qual for a decisão que tomarmos para estabilizar isso, seria bom se o usuário de macros derivadas customizadas não precisasse modificar seu código quando / se mudarmos para uma API de derivação 2.0 de macros (obviamente os autores das macros derivadas customizadas irão). Parece que a decisão mais sensata é dar ao compilador uma lista de atributos para remover para sua derivação e, criticamente, esses são removidos somente após _todos_ as macros derivadas serem executadas. Serde, diesel e my crate têm o problema de exigir o mesmo atributo em relação a várias macros derivadas.

Com o comportamento de remoção, eu não precisaria da caixa de pós-expansão que @dtolnay fez para adicionar outra macro derivar, para remover os atributos após o resto ter sido executado.

FWIW, a única razão para retirar esses atributos é manter o HIR mais fino - no que diz respeito a todo o resto, você só precisa marcá-los como usados.

A remoção de atributos personalizados é a principal coisa que as pessoas fazem com a derivação personalizada?

Eu sei que existem alguns hacks para fazer foo! expansão no contexto de um tipo (por exemplo, @sgrif mencionou tal caso de uso em diesel, acho que @tomaka também mencionou um) - quão centrais são esses casos de uso?

Estou totalmente de acordo com o fato de que as derivações personalizadas não podem modificar a estrutura.

Costumo reclamar da falta de plug-ins no Rust estável, então, quando vi os derivados personalizados, usei-os como uma oportunidade de ter plug-ins. Mas obviamente não vou argumentar que os derivados personalizados devem apoiar algo para o qual não foram projetados.

Parece que há uma regressão na última noite. Obtendo o erro

Queryable é um modo de derivação

ao compilar nossos exemplos.

#[derive(Queryable)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}

@sgrif Isso foi causado por # 37198, que foi alterado para usar o mesmo namespace de outras macros (de modo que bang!() macros, #[attribute] macros e #[derive(custom)] macros compartilham o mesmo namespace).

Nesse exemplo, #[macro_use] extern crate diesel; importa uma macro bang chamada Queryable e #[macro_use] extern crate diesel_codegen; importa uma macro derivada personalizada também chamada Queryable , que substitui silenciosamente a macro bang (à parte - #[macro_use] substituir silenciosamente não é o ideal, mas não será um problema, uma vez que podemos importar macros de caixas externas com use vez de #[macro_use] , em breve!).

Eu acredito que o erro é causado por uma invocação da macro bang Queryable!(); na expansão da derivação customizada, que resolve a derivação customizada de diesel_codegen vez da macro bang de diesel .

@jseyfried Qual é a razão de usar um único namespace para todos os três "tipos" de macros? Não me parece que faça sentido tratar atributos e macros bang como ocupando o mesmo namespace, mais do que faria sentido tratar funções e tipos como ocupando o mesmo namespace (derivar macros ainda menos).

@semoutboats Acho que é a analogia errada, prefiro pensar que diferentes tipos de macros compartilham um namespace da mesma forma que diferentes tipos de valores ou tipos (por exemplo, funções e consts). Existe um custo de complexidade em ter namespaces e eu acredito que quanto menos tivermos, melhor, então a pergunta deveria ser por que precisamos de namespaces diferentes? Claramente, para back compat, precisamos que as macros estejam em um namespace diferente das funções e tipos.

O verdadeiro problema é que você não pode usar use para importar seletivamente ou renomear macros. Portanto, um usuário de crate não tem capacidade de contornar conflitos de nomes de macro. Ao mesmo tempo, eu não esperaria simplesmente importar uma caixa ProcMacro para chocar com nomes de macro locais - eu pensei que as macros derivadas começassem com derive_ ?

Existe um custo de complexidade em ter namespaces e eu acredito que quanto menos tivermos, melhor, então a pergunta deveria ser por que precisamos de namespaces diferentes?

Acho que os itens devem compartilhar um namespace apenas se puderem ser ambíguos uns com os outros. consts e fns estão no mesmo namespace porque são usados ​​como idents em um contexto de expressão. Tipos e características estão no mesmo namespace devido à sintaxe dos objetos de características.

Esses três tipos de macros são todos chamados de maneiras distintas. Não faz sentido para mim que eles devam compartilhar um namespace, porque nunca pode haver uma ambigüidade na invocação.

Embora haja um custo de complexidade de implementação, não acho que haja um custo significativo em _use_ complexidade por namespaces separadamente. Quando penso no custo da complexidade de um recurso de linguagem, geralmente penso na complexidade de uso.


Você diria que _todos_ os itens deveriam estar no mesmo namespace se não fosse uma alteração significativa? Tentando esclarecer sobre seu processo de pensamento.

Refletindo um pouco mais, eu ficaria bem em dizer que deveria haver apenas 1 namespace em que todos os itens estão - eu até meio que prefiro; todo o padrão de "construtores reais" é meio confuso para a IMO - mas essa não é a decisão que Rust tomou para itens não macro, então para mim parece que seria mais consistente e esperado que itens macro ocupassem espaços de nomes diferentes.

O verdadeiro problema é que você não pode usar para importar seletivamente ou renomear macros.

Você poderá fazer isso com macros 2.0. O modelo aqui é basicamente um hack temporário.

Achei que as macros derivadas começassem com derivar_?

Esse era o antigo sistema de derivação personalizado, não acho que as macros 1.1 façam isso.

Embora haja um custo de complexidade de implementação, não acho que haja um custo significativo na complexidade de uso

Há um custo para alguma definição de uso - torna a vida mais difícil para as ferramentas, em particular (embora alguém possa argumentar que não é muito). Eu também acho que não é tanto a complexidade de implementação que importa (embora os namespaces definitivamente complicem o compilador, eu concordo que isso não seja tão importante) quanto a complexidade da linguagem - os usuários precisam raciocinar sobre essas coisas para usar o Rust e isso tem uma conseqüência efeito para coisas como higiene e sombreamento, complicando ainda mais as coisas.

Você diria que todos os itens deveriam estar no mesmo namespace se não fosse uma alteração significativa? Tentando esclarecer sobre seu processo de pensamento.

Eu não iria tão longe - acho que há benefícios em valores e tipos separados, mas certamente uma linguagem com um único namespace seria muito mais agradável de se trabalhar de várias maneiras.

mas essa não é a decisão que Rust tomou para itens não macro

Contraponto: módulos e campos estão no namespace de valor, embora os locais em que eles podem ser usados ​​sejam (eu acho) distintos dos locais em que outros valores podem ser usados.

@keeperofdakeys

O verdadeiro problema é que você não pode usar use para importar seletivamente ou renomear macros

Seremos capazes de use macros de extern crate s por trás de um portão de recursos em breve (cerca de 1 semana), cf https://github.com/rust-lang/rfcs/pull/1561 e # 35896. Podemos decidir estabilizar as macros use ing de caixas de derivação personalizadas junto com as próprias derivações personalizadas.

Achei que as macros derivadas começassem com derive_

Isso era verdade para os derivados personalizados do estilo antigo. Com macros 1.1 derivadas customizadas, #[derive(Serialize)] (por exemplo) espera que Serialize resolva para uma macro derivada customizada.

@withoutboats

Qual é a razão de usar um único namespace para todos os três "tipos" de macros?

  • Precedência: macros bang e macros de atributo compartilham um namespace há muito tempo, então, até essas alterações, acho que os derivados personalizados também devem compartilhar esse namespace.
  • Compatibilidade com versões anteriores: Se começarmos com um único namespace de macro, podemos dividi-lo de forma compatível com versões anteriores. Se começarmos com vários namespaces de macro, ficaremos presos a eles para sempre.
  • Simplicidade: se cada "tipo" de macro tivesse seu próprio namespace, teríamos cinco namespaces no total. Assim, após https://github.com/rust-lang/rfcs/pull/1561 aterrissar, use foo::bar; poderia importar _cinco itens separados_ nomeados bar (um valor, um tipo, uma macro bang etc.), e não haveria uma maneira simples de, por exemplo, reexportar a macro bang, mas não a macro de derivação customizada ou apenas importar a macro de derivação customizada como baz .

Compatibilidade com versões anteriores: Se começarmos com um único namespace de macro, podemos dividi-lo de forma compatível com versões anteriores. Se começarmos com vários namespaces de macro, ficaremos presos a eles para sempre.

Isso é convincente, especialmente porque o 1.1 deve ser um paliativo. : +1:

Isso quebra o código para qualquer pessoa que tenha uma macro macro_rules chamada, por exemplo, PartialEq! ?

Não, PartialEq não está definido no namespace da macro hoje, pois não é uma derivação personalizada.
#[derive(Foo)] primeiro verifica se Foo é uma "derivação interna" ( PartialEq , Copy , etc.) antes de procurar uma derivação personalizada Foo no namespace da macro. Os builtins são embutidos em código na definição de derive .

Dito isso, poderíamos quebrar o código conforme você descreveu se eventualmente decidirmos fazer PartialEq um derivado personalizado no prelúdio em vez de um embutido, então pode ser uma boa ideia fazer uma prova futura para isso agora.

Proposta para o problema de atributos (assumindo que trabalhamos em um modelo onde derivar macros não pode modificar os itens em que são declarados, apenas decorar):

  • nos bastidores, macros derivadas personalizadas implementam um traço, atualmente MultiItemModifier . O plano é que as macros 2.0 continuem a implementar uma característica e esta característica seja usada para extensibilidade do mecanismo. A função anotada que é a macro implementa esse traço. Embora não usemos o registrador de plug-ins, isso é essencialmente uma desavença.
  • devemos separar uma característica CustomDerive especificamente para derivação personalizada de macros 1.1. No caso comum, os autores de macro nunca o veem. Mas eles têm a opção de implementar a característica diretamente, em vez de usar um atributo em uma função (espero que reutilizemos o atributo no impl, talvez precisemos discutir esse mecanismo de registro).
  • adicionamos uma função declare_attributes a CustomDerive que retorna Vec<String> . Ele tem um impl padrão retornando um vec vazio.
  • Se os autores da macro implementarem esse método, quaisquer atributos nomeados exatamente como uma das strings retornadas serão considerados pertencentes à macro. Qualquer um desses atributos nunca é pesquisado como uma macro e não aciona o lint do atributo personalizado. Os atributos são deixados no código pela expansão derivada, mas são marcados como usados. Qualquer atributo que não seja tocado por uma expansão derivada ainda acionaria o lint de atributo não utilizado (mas não o lint de atributo personalizado).
  • Podemos, no futuro, introduzir atributos de escopo que podem ser usados ​​por macros, incluindo derivados personalizados, que seriam _além_ de declare_attributes e este mecanismo não seria descontinuado.
  • Alternativa: as strings retornadas por declare_attributes são verificadas como um prefixo de caminho para atributos, por exemplo, se declare_attributes retornou vec!["foo::bar"] , então #[foo::bar::baz] e #[foo::bar::qux] seria permitido.

Pensamentos? cc @alexcrichton @jseyfried @dtolnay @sgrif @erickt @ rust-lang / lang

@nrc esse mecanismo permitiria desabilitar atributos _used_ como #[cfg(..)] - por acidente ou propositalmente? E isso poderia ser mudado?

@nrc, infelizmente, não consigo ver como seria a implementação, dadas essas restrições, então não tenho certeza se funcionaria ou não.

Dito isso, sou um tanto cético sobre se traços e objetos de traços funcionariam, porque onde estão as instâncias do traço sendo criadas?

@nrc Por que isso precisa ser imperativo e não pode ser apenas uma lista no atributo que designa a função expansora derivw? Por exemplo, #[proc_macro_derive(Serialize, uses_attrs(serde_foo, serde_bar))] ou algo parecido.

@ colin-kiegel eu imagino que só poderia se aplicar no escopo do aplicativo derivado, caso em que desativar outros atributos é provavelmente o comportamento esperado, embora eu ache que podemos aplicar cfgs antes da expansão da macro (isso mudou, mas eu não consigo me lembrar o antes vs depois).

@alexcrichton

Dito isso, sou um tanto cético sobre se traços e objetos de traços funcionariam, porque onde estão as instâncias do traço sendo criadas?

Hmm, bom ponto, suponho que precisaremos resolver isso para macros 2.0

@eddyb Esta é uma boa ideia e provavelmente mais fácil de fazer a curto prazo. Minha razão para preferir a abordagem imperativa é que é um mecanismo de extensibilidade geral e que queremos manter, ao passo que adicionar coisas ao atributo não escala muito bem e pode não ser algo que queremos para sempre . Por outro lado, certamente é mais fácil de fazer agora e não parece uma coisa ruim de se ter por perto, então acho que pode ser uma aposta melhor.

Ficar atualizado - estive fora do país e depois adoeci. Gostaria de revisitar https://github.com/rust-lang/rust/pull/37198 , pois não acho que faça sentido que todos os tipos de macros ocupem o mesmo namespace. Eu, no mínimo, esperaria que as macros derivadas personalizadas estivessem nesse namespace como derive_Foo ou semelhante. Existem casos de uso para vários tipos de macros usando o mesmo nome, mesmo na biblioteca padrão (por exemplo, #[cfg] e cfg! )

Eu também acho o namespace compartilhado um pouco estranho. Do ponto de vista do usuário final, pode parecer que existe algum tipo de intercambialidade, ambiguidade ou algo interessante acontecendo - o que não existe. Acho que limitações "aleatórias" como essa podem dificultar a compreensão do Rust como uma linguagem.

No entanto, acho que provavelmente não é o fim do mundo. Parece que diferentes namespaces ainda podem ser introduzidos no futuro, sem grandes interrupções.

@sgrif @ colin-kiegel Descrevi meu raciocínio para um único namespace de macro em https://github.com/rust-lang/rust/issues/35900#issuecomment -256247659.

Não acho que faça sentido todos os tipos de macros ocuparem o mesmo namespace

Para ser claro, macros bang e macros de atributo sempre ocuparam o mesmo namespace; # 37198 acabou de mover derivações personalizadas para este namespace.

Existem casos de uso para vários tipos de macros usando o mesmo nome, mesmo na biblioteca padrão (por exemplo, #[cfg] e cfg! )

Alternativamente, cfg pode ser visto como uma única macro que pode ser usada por meio de uma invocação bang ou de atributo (embora não seja possível hoje para os usuários definirem uma macro que pode ser invocada por meio de um bang ou atributo, podemos decidir permiti-lo em macros 2.0).

Da perspectiva do usuário final, pode parecer que existe algum tipo de intercambialidade

Acho que isso poderia ser melhorado com mensagens de erro claras quando duas macros entrarem em conflito (vou trabalhar para melhorar as mensagens de hoje).

Parece que diferentes namespaces ainda podem ser introduzidos no futuro, sem quebrar muito

Eu acredito que dividir o namespace macro é totalmente compatível com versões anteriores (me corrija se eu estiver errado). Esta é minha principal motivação para manter um único namespace de macro hoje - queremos que as macros 1.1 sejam o mais compatíveis possível no futuro.

Finalmente, acho que os conflitos de macro bang / attribute vs macro de derivação customizada serão raros na prática, já que as macros bang / attribute são geralmente minúsculas e as derivações customizadas geralmente são maiúsculas. Em outras palavras, as derivações personalizadas já têm seu próprio namespace quando essas convenções de nomenclatura são seguidas.

Parece que a quebra (https://github.com/diesel-rs/diesel/issues/485) é causada pelo suporte, por exemplo, Queryable! { struct S; } e também #[derive(Queryable)] struct S; . Assim que as derivações personalizadas estiverem estáveis, não haverá necessidade de oferecer suporte a Queryable! { struct S; } então isso não será mais um problema, certo?

Enquanto isso, acredito que poderíamos atualizar diesel para que

  • Queryable! ainda é compatível sem #[macro_use] extern crate diesel_codegen; , e
  • #[derive(Queryable)] , mas não Queryable! , é compatível com #[macro_use] extern crate diesel_codegen; .

Sinta-se à vontade para me enviar um ping no IRC para discutir - Eu ficaria feliz em escrever um PR.

@nrc @alexcrichton

Dito isso, sou um tanto cético sobre se traços e objetos de traços funcionariam, porque onde estão as instâncias do traço sendo criadas?

Concordo com @eddyb que se tudo o que queremos é lidar com atributos, no interesse da conveniência e conveniência, devemos apenas estender os atributos.

Mas se nós _disse_ queríamos um sistema de extensão mais flexível, então imaginei que usaríamos características, mas essas características seriam definidas como uma coleção de métodos que não se referem a Self :

trait CustomDerive {
     fn foo(); // note: no self
     fn bar(); // again, no self
}

Você então o implementaria em um tipo fictício como struct my_annotation_type; (compartilhando o mesmo nome do atributo, eu acho?), E usaríamos a resolução de traço para extrair as funções relevantes como <my_annotation as CustomDerive>::foo (provavelmente ao escrever os metadados, eu acho). O ponto é, nunca faríamos (ou precisaríamos) uma instância de my_annotation , é apenas um mecanismo de agrupamento para agrupar um monte de funções relacionadas.

Certamente não é a coisa mais elegante de todas, mas não tenho certeza de uma maneira melhor? Acho que a deselegância é exatamente por que queríamos começar com funções atribuídas. =)

Com relação aos namespaces, @sgrif é um bom caso com os exemplos #[cfg] e cfg! . Eu posso certamente imaginar alguém querendo #[derive(SomeTrait)] e também ter alguma macro como SomeTrait! { ... } que ... faz algo. =) Mas @jseyfried também é um bom caso de compatibilidade com versões anteriores - contanto que não estejamos atingindo as limitações _jseyfried.

Eu tendo a preferir menos namespaces por padrão, principalmente por causa da dor que eu acho que ter uma divisão de namespace de valor / tipo nos traz agora. Dito isso, acho que a maioria dos pontos problemáticos conhecidos não se aplica aqui:

  • a divisão tipo / valor é um problema na resolução de nomes porque use foo::bar pode ou não estar importando um módulo chamado bar e, portanto, pode (ou não) ser relevante para uma resolução de nome como bar::baz ;
  • a divisão tipo / valor é uma espécie de dor para os genéricos em relação às constantes, embora essa dor também seja devido à divisão sintática entre tipos e valores.

Mas, em grande parte, desde que cruzamos a ponte, não tenho certeza de que ter "derivação personalizada" ao vivo em seu próprio namespace traga algum desafio específico, certo?

Poderíamos adicionar o prefixo derive_ às macros geradas, para torná-las pseudo-namespaces. Se quiséssemos fazer essa mudança, agora seria a hora.

@keeperofdakeys Isso não seria equivalente a autores de derivações personalizadas derive_* ? Independentemente disso, acho que derive_* nomes são muito feios para usuários finais.

@nikomatsakis

Mas, em grande parte, desde que cruzamos a ponte, não tenho certeza de que ter "derivação personalizada" ao vivo em seu próprio namespace traga algum desafio específico, certo?

O algoritmo de resolução de importação pode manipular arbitrariamente muitos namespaces, mas realiza uma quantidade de trabalho potencialmente não trivial para cada namespace. Em particular, para cada importação I e cada namespace não utilizado S , isso prova que a resolução I falha em S (cf https: // github. com / rust-lang / rfcs / pull / 1560 # issuecomment-209119266). Essa prova geralmente requer um DFS do gráfico de importação glob (em que os vértices são módulos e as arestas são importações glob) para pesquisar importações indeterminadas relevantes.

No entanto, esse trabalho extra por namespace pode não fazer diferença na prática e pode ser evitado se necessário (cf https://github.com/rust-lang/rfcs/pull/1560#issuecomment-209119266) ao custo de um restrição secundária que se aplicaria apenas aos namespaces de macro.

Eu combinei os namespaces em # 37198 para manter nossas opções abertas e porque não achei que seria uma limitação na prática. Se as pessoas querem hoje e @ rust-lang / lang aceita ter vários namespaces de macro para sempre, não tenho objeções.

@nikomatsakis

Acho que sim, sua estratégia funcionaria, embora de forma instável. Meu sentimento pessoal é que a estranheza não exerce influência (por exemplo, o que temos hoje está bom para mim), mas deve ser implementado de qualquer maneira.

@alexcrichton como acho que escrevi antes, fico feliz em esperar até que precisemos de mais energia (se necessário) - e então podemos fazer algo como o traço que descrevi, ou talvez algo melhor. Por enquanto, acho que apenas estender os atributos aplicados a fn seria suficiente.

Fiquei curioso e comecei uma implementação básica dessa ideia (usando um atributo na função proc_macro para denotar os nomes dos atributos a serem marcados como usados ​​no item).

Limpei a tag FCP porque parece claro que ainda não chegamos _bastante_ ao ponto em que estamos prontos para estabilizar. Vou fazer um esforço para resumir o estado da conversa e, em particular, para destacar os erros em que ainda precisamos de decisões firmes e / ou contribuições de código:

Pergunta 1: As macros derivadas de forma personalizada devem ser capazes de alterar o item sobre o qual estão passando?

  • Ninguém acha que eles deveriam fazer isso _na prática_ é claro
  • É outro modo, que foi inicialmente rejeitado no espírito de YAGNI
  • Derivados que alteram o conjunto de nomes de campo, etc., não são compostos bem, tornando a ordem do aplicativo visível;
    o perigo disso é mitigado por duas coisas:
  • Por outro lado, se dissermos que a derivação personalizada só precisa retornar os novos impls

    • a informação de amplitude é melhor

    • derivados personalizados são mais simples de escrever

  • _Mas_ muitos derivados personalizados usam atributos personalizados para orientar sua expansão, e eles irão gerar avisos / erros

    • A técnica atual é retirá-los do AST

  • _Também: _ queremos divulgar isso para o mundo o mais rápido possível, não queremos fazer experimentações demoradas ou mudanças drásticas

Propostas:

  • Mantenha tudo como está, talvez descontinue mais tarde

    • Expediente, um tanto infeliz

  • Estenda #[proc_macro] com uma lista de permissões de atributos, diga a rustc para considerá-los "usados" e ignorá-los

Pergunta 2: as derivações personalizadas devem compartilhar o mesmo namespace que outras macros?

Argumento para:

Argumento contra:

Propostas:

  • Dividido em namespace "derivado personalizado"
  • Mantenha o status quo

Outras coisas?

Existem outras questões em aberto?

Solução potencial para o problema de mutação:

Em vez de derivar personalizado com o tipo TokenStream -> TokenStream , eles teriam o tipo
Item -> TokenStream , onde Item é um tipo opaco que começaria com apenas dois métodos:

  • item.tokens() , que retorna o TokenStream que seria passado hoje, e
  • item.use_attrs(name) , que marcaria todos os atributos com o nome dado como usados.

O TokenStream retornado incluiria apenas os impl s derivados.
Poderíamos eventualmente adicionar à API Item com funções de conveniência (por exemplo, iteração sobre os campos / variantes do item) ou uma API derivada de nível superior como a de syntax_ext::deriving::generic .

Eu ficaria feliz em implementar a permissão de atributos na lista branca em #[proc_macro_derive] (ou seja, a segunda proposta para a pergunta 1 em https://github.com/rust-lang/rust/issues/35900#issuecomment-258315395) ou minha Item -> TokenStream proposta.

Acho que seria um erro estabilizar derivados personalizados que podem transformar o item subjacente. A informação de span que sacrificamos ao permitir a mutação já está causando problemas - por exemplo, temos que escolher entre consertar # 37563 e consertar # 36218.

Eu realmente não vejo o apelo de uma lista de permissões imperativa. Alguém pode criar um caso de uso?

Não tenho certeza, é intencional / desejado que este código compile:

#![feature(proc_macro)]

#[proc_macro_derive(Whatever)]
struct Foo {}

@eddyb Não tenho certeza se há um apelo inerente à lista de permissões obrigatórias - acho que o benefício de
Item -> TokenStream é que é mais fácil estender a API de Item do que adicionar mais tipos de atributos declarativos. Dito isso, pensando um pouco mais sobre isso, pode não haver muitos outros casos de uso que requeiram Item vez de TokenStream , então TokenStream -> TokenStream com uma lista branca declarativa pode ser melhor.

As macros 2.0 substituirão esta API? Nesse caso, a extensibilidade não é uma preocupação real e a Item -> TokenStream api não parece muito atraente.

@keeperofdakeys De acordo com o RFC:

No geral, isso deve ser considerado um passo incremental em direção a uma "macros 2.0" oficial.

A intenção das macros 1.1 é ser o mais próximo possível das macros 2.0 em espírito e implementação, apenas sem estabilizar grandes quantidades de recursos. Nesse sentido, a intenção é que, dada uma macro estável 1.1, possamos sobrepor recursos compatíveis com versões anteriores para chegar às macros 2.0.

https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233449053 e https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233708395 e https: // github. com / rust-lang / rfcs / pull / 1681 # issuecomment -239558657 parecem indicar que apenas depreciações "limitadas" são esperadas quando as macros 2.0 chegarem. Em particular: "o padrão de árvores de token de string-ifying se tornaria não preferencial, se não fosse realmente obsoleto".

@eddyb

Eu realmente não vejo o apelo de uma lista de permissões imperativa. Alguém pode criar um caso de uso?

Bem, eu poderia imaginar um caso em que os nomes dos atributos são gerados dinamicamente de alguma forma (com base no nome do tipo ou campos, talvez?) - e, portanto, não se pode declará-los antecipadamente. Mas parece meio artificial e não é uma prática particularmente boa.

Acho a alternativa de @jseyfried atraente - não por causa da natureza imperativa de marcar os atributos como usados, mas sim porque a API Item pode ficar mais rica com o tempo. ou seja, podemos oferecer suporte para percorrer o conjunto de campos de uma forma muito estruturada, fornecer acesso a mais dados (como resoluções de nomes) que possamos ter à nossa disposição, etc.

Claro, parte disso virá (_eventualmente_) de alguma forma de API AST padrão também.

Uma nota sobre o tempo: eu realmente quero ver as Macros 1.1 se estabilizarem no próximo ciclo. As coisas nas quais estamos bloqueados parecem, em última análise, bastante insignificantes.

Com esse espírito, minha opinião sobre os problemas que descrevi:

  1. Estou feliz com uma #[proc_macro] extension declarativa _ou_ alterando o tipo para Item . Também acho que, seja qual for a nossa escolha, poderemos potencialmente adotar o outro no futuro se parecer uma boa ideia. Eu meio que quero ir com o que for implementado primeiro. =)
  2. Em relação aos vários namespaces, acho que devemos mantê-los no mesmo namespace por enquanto. Para mim, a combinação de compatibilidade com versões anteriores (ou seja, manter nossas opções abertas) com o fato de que a capitalização já distingue macros de "derivação personalizada" de outras coisas na prática é atraente. Distinguir #[cfg] vs cfg! parece algo que poderíamos tratar de outras maneiras, ou se quisermos, podemos introduzir os namespaces divididos _então_. Não parece que ter um namespace unificado está prejudicando o caso de uso de derivação personalizada em particular, que é a única coisa que estamos falando sobre estabilização. Direito?

@nikomatsakis, seu resumo parece correto, obrigado por escrevê-lo! Estou triste porque não teremos macros 1.1 no Rust 1.14, mas entendo que essas são questões controversas.

Meu sentimento pessoal é estabilizar tudo como está, onde a derivação personalizada deve remover os atributos e há apenas um namespace.

Não sigo bem a API Item -> TokenStream . O fluxo de token retornado ainda abrange o item original ou apenas os impls adicionados? Isso significa que deve levar &mut Item ?

@ est31 seu comentário parece um bug. Você pode abrir um problema separado para isso?

Eu concordo inteiramente com o comentário de @nikomatsakis . Acho importante que os derivados não tenham rédea solta para modificar o item ao qual estão anexados, mas todas as implementações propostas parecem boas para mim.

Não parece que ter um namespace unificado está prejudicando o caso de uso de derivação personalizada em particular, que é a única coisa que estamos falando sobre estabilização. Direito?

O problema surgiu porque ele quebrou o diesel, que atualmente tem macros chamadas, por exemplo, Queryable! quais você pode envolver uma estrutura para derivar Queryable para ele.

Como alguém que não usa ou mantém diesel agora (ou seja, como alguém que não é afetado pelo que estou prestes a propor: sweat_smile :), acho que a melhor coisa é manter 1 namespace por agora e para diesel mudar o nome de essas macros para derive_Queryable! ou algo parecido. Eles serão descontinuados quando estiver estável de qualquer maneira, certo?

Criei PR https://github.com/rust-lang/rust/pull/37614 para o recurso sugerido. Ele usa um atributo separado #[proc_macro_attributes(..)] , e você não precisa mais devolver o item no TokenStream retornado.

Eu preenchi o número 37637 para descobrir como as macros proc devem tratar $crate .

Só para esclarecer, este caso de uso é considerado bom ou um uso indevido do sistema:

Eu gero novas estruturas com base na estrutura e atributos existentes aqui e gosto bastante do design, pois me permite consolidar as coisas em uma estrutura.

No entanto, se o sistema for alterado posteriormente de forma a não permitir esse tipo de implementação, posso simplesmente parar agora, em vez de colocar mais esforço nele (a implementação atual é apenas uma rápida prova de conceito).

@TheNeikos

A principal alteração incompatível com versões anteriores será que você não pode mais modificar o item (você não o devolve no TokenStream). Eu diria que derivar qualquer coisa que não seja um implante não é intencional, mas não há nada que impeça você de fazer isso. Você só precisa ter cuidado com conflitos de nomes.

A outra mudança principal é ser capaz de fornecer uma lista de nomes de atributos que não devem acionar erros de atributos personalizados no item.

RE: O conflito de namespace em Diesel. Não tenho certeza se irei descontinuar essas macros ou não quando esse recurso estiver estável, isso vai depender se ainda há um desejo de algum material gratuito de extensão do compilador. É duvidoso. Ainda é útil para testar internamente, mas renomear é bom para isso.

Eu acho que é uma pena perder a capacidade de modificar o fluxo de entrada. Ser capaz de remover o item que está sendo anotado nos permite ter macros bang com este sistema também. Eu entendo as preocupações, mas é uma pena perder essa capacidade em relação a elas.

@TheNeikos @sgrif meu ponto de vista é que qualquer coisa que modifique seriamente a entrada não é uma derivação e, portanto, não deve ser abordada aqui. Eu acho que é um pouco perigoso (falta de higiene, etc.) usar derivadas personalizadas para macros proc de propósito geral, então não estou interessado em encorajá-lo.

Não ser capaz de modificar o item significa que a informação de amplitude do item é mantida, o que torna as mensagens de erro no item muito mais claras (também significa que as mensagens de erro na saída derivada não apontam mais para a amplitude do item, mas para a amplitude do atributo derivado). Não vejo um bom motivo para permitir que as pessoas abusem desse recurso se realmente quisermos apenas macros procedurais adequadas.

@TheNeikos Não acho que iremos proibir a geração de novas estruturas por meio de uma derivação, desde que você não motive a estrutura para a qual está derivando.

Em termos do que considero idiomático; Acho que não há problema em gerar novos tipos, mas é muito melhor se esses tipos forem tipos associados para uma característica que você está derivando. Por exemplo, imagine se pudéssemos derivar IntoIterator para um tipo - isso envolveria a criação de uma estrutura Iterator . Conceitualmente, você deve derivar um comportamento para esse tipo, mas esse comportamento pode não ser "um implemento de traço".

@sgrif

Eu acho que é uma pena perder a capacidade de modificar o fluxo de entrada. Ser capaz de remover o item que está sendo anotado nos permite ter macros bang com este sistema também. Eu entendo as preocupações, mas é uma pena perder essa capacidade em relação a elas.

Hmm, eu definitivamente simpatizo, embora obviamente (como observou @nrc ) não seja para isso que o sistema foi projetado e ele tenderá a expor as várias arestas. Isso provavelmente não importa em seus casos de uso. Eu acho que uma questão chave é com que rapidez podemos avançar em um tipo de coisa "bang macros 1.1".

O PR foi mesclado, então você poderá ver as seguintes mudanças na próxima noite. A função proc_macro_derive não deve mais retornar o item (fazer isso irá disparar um erro sobre um tipo sendo definido duas vezes), e agora você pode fornecer uma lista de nomes de atributos para whitelist como este #[proc_macro_derive(Derive, attributes(Foo, Bar)] .

cc @dtolnay , @sgrif ^

isso vai causar quebra em breve, infelizmente

Sim, eu preenchi https://github.com/serde-rs/serde/issues/614 para rastrear do nosso lado.

Acho que consertei a quebra no Diesel em https://github.com/diesel-rs/diesel/pull/493 , saberei com certeza quando os noturnos estiverem se formando novamente.

Portanto, se estou lendo este tópico corretamente, uma vez que uma macro proc só pode emitir itens extras anexados ao item inicial, ela também não pode adicionar mais anotações ao item inicial? (Vejo uma menção de permitir "anotações de irmãos", mas nada mais sobre isso.)

Eu tenho uma macro #[derive(newtype)] proc na minha caixa (pequena, não publicada) que se expande para um conjunto diferente de outros #[derive()] s com base na estrutura que está anotando. Por exemplo, #[derive(newtype)] struct Foo(u64) expande para #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] struct Foo(u64); enquanto #[derive(newtype)] struct Foo(::semver::VersionReq) expande para #[derive(Clone, Debug, PartialEq)] struct Foo(::semver::VersionReq); . Portanto, os membros da estrutura não são modificados por essa macro, mas outros derivados são adicionados a ela (também não modificam a estrutura).

Existem algumas dezenas dessas estruturas e cada uma tem dez ou mais novas derivações depois que essa macro é expandida. Eu gosto disso, se eu perceber que quero que todos os u64 newtypes implementem mais uma característica, posso apenas alterar o conjunto de derivações em um lugar no código de macro newtype vez de em cada estrutura única.

Eu costumava ter uma macro macro_rules newtype! para isso, mas mudei para uma macro proc porque:

  • A presença ou ausência de comentários de documentos, presença ou ausência de pub , etc, são tratados gratuitamente sem a necessidade de um número combinatório de braços de combinação de macro.
  • Mesmo que eu escrevesse os braços combinados de macro combinatória, seria difícil encontrar uma ordem em que eles não entrassem em conflito.

Infelizmente não, você não pode mais fazer isso como fazia.

Em relação à compatibilidade futura deste recurso estar estável: como a função do plugin não precisa ser "pura", será uma alteração significativa se a ordem dos objetos dados à função processada mudar no futuro, ou se rustc implementar multi-threaded compilando?

@ est31 Se tivermos tempo, devemos tentar acabar com o isolamento IPC que já foi mencionado.

Estou constantemente vendo um ICE no Diesel após as mudanças mais recentes.

../src/librustc_metadata/decoder.rs:490: entrada: id não encontrado: DefIndex (1) na caixa "diesel_codegen" com o número 28

@sgrif esse seria o problema # 37788 que será corrigido pelo # 37793 (esperamos que termine amanhã à noite ...).

@ est31 É muito tarde a esta hora para que seja mesclado antes que a compilação noturna comece.

https://github.com/rust-lang/rust/issues/37839 é um problema com o uso de uma caixa de lib que usa uma caixa proc_macro. AFAICT nenhum dos testes é afetado por isso porque eles compilam apenas o módulo de macro proc ou um módulo de macro proc e um módulo bin que referencia diretamente a macro proc.

Editar: agora corrigido!

@Arnavion O problema que você viu com o # 37839 foi corrigido para complicações regulares, mas continua quebrado ao usar --target , conforme relatado em # 37958. Eu forneci um caso de teste mínimo usando --target que ainda falha.

@rfcbot fcp merge

Agora que o recurso de atributo da lista de permissões foi implementado , acho que devemos estabilizar isso! Serde, Diesel e outros usuários - agora é sua mudança para objetar se o projeto atual não estiver funcionando para você. =)

cc @sgrif @erickt

O membro da equipe @nikomatsakis propôs fundir isso. A próxima etapa é revisada pelo restante das equipes marcadas:

  • [x] @alexcrichton
  • [x] @aturon
  • [x] @brson
  • [x] @eddyb
  • [x] @japaric
  • [x] @michaelwoerister
  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @pnkfelix
  • [x] @vadimcn
  • [x] @withoutboats
  • [x] @wycats

Nenhuma preocupação listada atualmente.

Assim que esses revisores chegarem a um consenso, isso entrará em seu período final para comentários. Se você identificar uma questão importante que não foi levantada em algum ponto deste processo, fale!

Consulte este documento para obter informações sobre quais comandos os membros da equipe marcados podem me dar.

Eu adoraria ver macros bang exploradas em um futuro próximo. Sem objeções, entretanto.

@rfcbot revisado

Atualmente, se um TokenStream falhar em analisar, um LexError vazio será retornado . Seria possível retornar uma mensagem de erro melhor aqui, como qual token falhou ao analisar? Embora os usuários de sua biblioteca provavelmente não queiram ver esse tipo de mensagem de erro.

Sim, isso seria conveniente para autores de macro. Tive de recorrer a escrever o fluxo em um arquivo e visualizá-lo no playground para encontrar erros.

Acho que os usuários também se beneficiarão, nem que seja para enviar melhores relatórios de bugs contra a macro.

Em https://github.com/rust-lang/rust/pull/38140 , estamos forçando as declarações de derivação personalizadas a se tornarem públicas. A teoria é que podemos um dia querer derivadas privadas e então provavelmente quereremos fazer essa distinção com base na visibilidade da função definidora. Em qualquer caso, esta é a escolha conservadora. Pensei em deixá-lo marinar por um ou dois dias para que as pessoas vissem antes de mesclar, já que estamos estabilizando esse recurso.

# 37480 está fechado, o que deve ser refletido na lista de verificação.

Fixo

@pnkfelix Tomei a liberdade de verificar sua caixa para você, já que você está no PTO e acredito que

: bell: Agora está entrando em seu período de comentário final , conforme a revisão acima . :Sino:

psst @nikomatsakis , não consegui adicionar o rótulo final-comment-period , faça-o.

A estabilização iminente presume que os bugs conhecidos restantes na lista de verificação no topo serão corrigidos primeiro? Ou não estão bloqueando a estabilização?

Existem dois agora:

  • # 36935 tem um comentário dizendo que está resolvido.
  • # 36691 não parece me bloquear, podemos permitir que eles se expandam para mod foo; algum dia no futuro, se quisermos, sem quebrar nada do que eu acredito.

Ei! Esta é uma auditoria de documentação RFC 1636 . Esse é o primeiro recurso importante a se estabilizar desde que o RFC 1636 foi aceito, e devemos fazer um bom trabalho em segui-lo neste caso.

A RFC afirma:

Antes de estabilizar um recurso, os recursos agora serão documentados da seguinte forma:

  • Características da linguagem:

    • deve ser documentado na Referência de Ferrugem.

    • deve ser documentado em The Rust Programming Language.

    • pode ser documentado em Rust by Example.

  • Os recursos de linguagem e as alterações padrão da biblioteca devem incluir:

    • uma única linha para o changelog

    • um resumo mais longo para o anúncio de lançamento de formato longo.

Qual é o processo certo para isso? Devemos adicionar itens da lista de verificação ao comentário principal deste problema ou criar um novo problema para rastrear a documentação? Parece-me que deveríamos ter a documentação atendendo a esses requisitos na árvore até o lançamento 1.15.

cc @ rust-lang / docs

Obrigado @withoutboats ! É o primeiro grande, sim. Eu tinha isso na minha lista para olhar esta manhã, e eis que você chegou antes de mim 😄

Minha imaginação disso sempre foi que a decisão de estabilizar e a estabilização real são separadas. Ou seja, @ rust-lang / lang pode dizer "isso pode se tornar estável", mas o commit para remover o portão também garante a existência da documentação. Em um mundo onde existe o livro instável , isso puxaria os documentos de lá para os documentos estáveis; mas até então, as coisas estão um pouco estranhas.

Considerando que acabamos de lançar um lançamento, meu plano era fazer algo assim:

  1. Espere que saia do FCP
  2. Consiga alguns documentos. (Eu estava planejando escrevê-los neste caso)
  3. Faça a estabilização PR.

Possivelmente combinando dois e três.

/ cc @ rust-lang / core, uma vez que este é um problema entre equipes.

@steveklabnik, isso parece bom para mim, e eu estaria ok para quiser (mesmo antes da conclusão do FCP). O FCP aqui está meio pseudo-acabado, pois acidentalmente demoramos muito para entrar.

Também seria bom obtê-los rapidamente para que possamos garantir um backport seguro para o branch beta 1.15!

Se eu for o primeiro a atingir isso, não pode ser tão ruim, mas vamos incluí-lo em bugs conhecidos: usar uma macro de tipo dentro de uma estrutura com uma derivação personalizada causa um ICE https://github.com/rust-lang/ ferrugem / questões / 38706

https://github.com/rust-lang/rust/pull/38737 corrige as macros de tipo ICE: coração :

Acabei de criar # 38749 sobre a documentação da caixa proc_macro .

Eu li várias vezes que as Macros 1.1 serão estabilizadas no 1.15, mas o 1.15.0-beta.1 foi enviado há duas semanas e pelo menos extern crate proc_macro; ainda está protegido por recursos nele, bem como no 4ecc85beb noturno de 2016 -12-28. O plano é apoiar a mudança de estabilização?

@SimonSapin sim, esse era o plano, mas precisamos fazer acontecer!

Ainda é o plano: p

Se o usuário escrever #[derive(Foo)] #[foo_def = "definition.json"] struct MyStruct; o manipulador da macro não tem como saber o que é "o diretório atual" e, portanto, não pode encontrar definition.json .

Isso ocorre intencionalmente e, portanto, não seria fácil de consertar, e acho que é tarde demais para consertar de qualquer maneira.

Você pode ir Span -> FileMap -> nome do arquivo -> diretório. Só falta acessar as informações por meio de proc_macro .

Você também precisaria dizer ao compilador para adicionar uma dependência a definition.json para que a compilação fique suja se for modificada.

A macro proc pode usar env::var("CARGO_MANIFEST_DIR") para obter o diretório que contém Cargo.toml da caixa que contém a invocação da macro. Presumivelmente, foo_def é relativo a isso. Consulte https://github.com/dtolnay/syn/issues/70#issuecomment -268895281.

@tomaka que pode ser feito através da mutação filemap, por exemplo, esta é a forma como o include_str! macro faz.

Presumivelmente, foo_def é relativo a isso.

Acho que não é muito intuitivo ter que colocar o caminho relativo ao Cargo.toml.

isso pode ser feito alterando o FileMap, por exemplo, é assim que o include_str! macro faz isso.

Sim, eu sei que isso pode ser feito, simplesmente não pode ser feito com a API de macros procedurais atual.

O parâmetro é Item . Seria aceitável adicionar um método para recuperar um intervalo desse item, mas adicionar um método a Item para pedir ao compilador para adicionar uma dependência seria um hack IMO.

Você pode ir Span -> FileMap -> nome do arquivo -> diretório.

Essas APIs (em particular FileMap ) estão em um caminho a ser estabilizado?

Eles não precisam ser, na verdade, eu não gostaria de estabilizar nenhum dos internos. Podemos, em vez disso, estabilizar APIs que extraem informações sobre Span (por exemplo, linha, coluna, nome de arquivo).

Acabei de receber esse erro em uma caixa minha. O que está acontecendo?

`` error: Cannot use #! [Feature (proc_macro)] and #! [Feature (custom_attribute)] ao mesmo tempo
`` ``

@alexreg Se você estiver usando #[derive] , ele está estável agora. Você não precisa de #![feature(proc_macro)] .

@alexreg
proc_macro_derive s (macros 1.1) agora estão estáveis ​​- você pode simplesmente remover #![feature(proc_macro)] .

#[proc_macro_attribute] desembarcou recentemente atrás do portão de recursos #![feature(proc_macro)] ; estes são incompatíveis com #![feature(custom_attribute)] . #![feature(custom_attribute)] será descontinuado assim que a substituição chegar (https://github.com/rust-lang/rfcs/pull/1755).

@jseyfried Acho que devemos alterar o problema de rastreamento em proc_macro pois ele está fechado e não contém informações relevantes.

Obrigado rapazes. Isso faz sentido.

@abonander Sim, #![feature(proc_macro)] definitivamente deveria apontar para # 38356 agora - eu deveria ter verificado isso ao revisar # 38842. Você poderia enviar um PR?

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