Rust: Problème de suivi pour "Macros 1.1" (RFC # 1681)

Créé le 22 août 2016  ·  268Commentaires  ·  Source: rust-lang/rust

Problème de suivi pour rust-lang / rfcs # 1681.

cc @alexcrichton

Stabilisation TODO

Tests décisifs:

Traits:

  • Nom de la caisse, actuellement proc_macro
  • Type de caisse, actuellement proc-macro
  • L'attribut #[proc_macro_derive(Foo)]
  • Chargement proc-macro caisses -L et #[macro_use] pour les charger
  • l'observation est une erreur
  • pas d'hygiène
  • passer un jeton pour l'ensemble de la structure et le recevoir en retour
  • Attribut du manifeste de fret, actuellement proc-macro = true

Bogues connus:

  • [] - Le dérivé paniqué peut avoir une durée incorrecte - # 36935
  • [] - flux de jetons avec mod foo fail - # 36691
  • [] - les documents ne sont pas publiés pour proc_macro - # 38749
  • [x] - les attributs personnalisés pour plusieurs modes sont difficiles - https://github.com/rust-lang/rust/issues/35900#issuecomment -252499766
  • [x] - échec du test de chargement pour les bibliothèques de macros proc - # 37480
  • [x] - l'ordre compte toujours - https://github.com/rust-lang/rust/issues/35900#issuecomment -252430957 (corrigé par https://github.com/rust-lang/rust/pull/37067)
  • [x] - Impossible de documenter les caisses rustc-macro - https://github.com/rust-lang/rust/issues/36820 (corrigé dans # 36847)
  • [x] - Cargo se reconstruit trop souvent - https://github.com/rust-lang/rust/issues/36625 (corrigé dans https://github.com/rust-lang/rust/pull/36776)
  • [x] - Les attributs générés par le compilateur rendent la vie des auteurs du dérivé personnalisé difficile - https://github.com/rust-lang/rust/issues/35900#issuecomment -245978831

  • [x] - Créez une caisse rustc_macro

    • [x] - Avoir librustc_macro lien libsyntax . Dépend de librustc_macro en librustc_driver
    • [x] - Balise rustc_macro comme instable avec notre en -tête standard .
    • [x] - Seulement tag rustc_macro avec #![crate_type = "rlib"] , ne produit pas de dylib.
    • [x] - Implémentez l'API de rustc_macro utilisant libsyntax de TokenStream interne
    • [x] - balise rustc_macro avec un élément TokenStream lang pour que le compilateur le sache.
  • [x] - Ajouter l'attribut rustc_macro_derive

    • [x] - validez qu'il est de la forme exacte foo(bar) , pas d'autres arguments / formats

    • [x] - vérifiez qu'il n'est appliqué qu'aux fonctions

    • [x] - vérifiez qu'il n'est appliqué qu'aux fonctions du module racine

    • [x] - vérifiez la signature avec l'élément TokenStream lang ajouté ci-dessus

    • [x] - encode tous les symboles de fonction avec rustc_macro_derive en métadonnées avec le mode de dérivation pour lequel ils sont utilisés.

  • [x] - Ajoutez un type de caisse rustc-macro pour les autres caisses

    • [x] - câbler pour produire un dylib

    • [x] - assurez-vous que le dylib obtient les métadonnées

    • [x] - assurez-vous que rustc-macro crates ne peuvent pas être liés en tant que dylibs

    • [x] - assurez-vous qu'il n'y a aucun élément _reachable_ autre que ceux marqués avec #[rustc_macro_derive]

    • [x] - Ajoutez cfg(rustc_macro) comme directive instable cfg , définissez-la pour le type de caisse rustc-macro

    • [x] - assurez-vous que rustc-macro crates se lient dynamiquement à libsytnax

  • [x] - Remplissez #[macro_use] support pour rustc-macro crates

    • [x] - étendre le chargeur de bibliothèque pour trouver les rustc-macro crates séparément de dylib / rlib suivis aujourd'hui lors du dépôt de caisses

    • [x] - Analyse les métadonnées pour rustc-macro crates pour en savoir plus sur les associations de mode symbole / dérivation

    • [x] - dlopen le dylib

    • [x] - génère une erreur si un mode de dérivation occulte un autre.

  • [x] - Ajouter une intégration de fret

    • [x] - reconnaître rustc-macro similaire à plugin = true

    • [x] - passez --crate-type=rustc-macro lorsque cela dépend

    • [x] - aplomb la même logique hôte / cible pour les caisses rustc-macro que celle présente pour les caisses de plugins (par exemple, compile toujours les caisses rustc-macro pour les hôtes)

  • [x] - Tests

    • [x] - test de fumée chargeant une macro rustc, factice #[derive] trait

    • [x] - les conflits de nom sont une erreur

    • [x] - la compilation pour la mauvaise architecture est une erreur

    • [x] - ne peut pas émettre de crate de type rustc-macro et tout autre élément (par exemple, rustc-macro + dylib) est une erreur

    • [x] - les informations de span ne sont pas horribles

    • [x] - suppression des attributs d'une structure, ainsi que des champs

    • [x] - ajout d'impls à côté d'une structure

    • [x] - la compilation croisée recherche le type de crate hôte rustc-macro

    • [x] - ne chargez pas les dylibs vanille comme types de crate rustc-macro

    • [x] - ne peut pas avoir d'exportations publiques au-delà des fonctions de dérivation de macro dans une caisse rustc-macro

    • [x] - les macros dérivées doivent avoir la signature requise

    • [x] - charge deux caisses de macros en une seule compilation

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

Commentaire le plus utile

Ok, je vais examiner cela aujourd'hui et voir jusqu'où je vais.

Tous les 268 commentaires

J'ai mis à jour la description du problème avec une liste de contrôle de ce qui doit être fait. Ce n'est probablement pas exhaustif, mais cela devrait nous mener à 90% du chemin, espérons-le

Ok, je vais examiner cela aujourd'hui et voir jusqu'où je vais.

De # 35957: nous devrions encore bikeshing le nom de la caisse librustc_macro. En particulier, cela est destiné à être une caisse de longue durée qui aura des éléments essentiels pour tous les auteurs de macro, donc se limiter à rustc_macro (qui dans mon esprit, du moins) est à peu près l'idée 1.1 semble mauvaise. J'avais auparavant voulu libmacro pour cela, mais puisque macro est un mot réservé (et nous pourrions le vouloir comme mot-clé à l'avenir), c'est impossible. @cgswords et moi utilisons libproc_macro, et je pense que ce n'est pas un mauvais nom, même si je ne suis pas satisfait à 100%.

@nrc : D'accord, mes réflexions immédiates sur la dénomination:

  • extern crate macros; - court et agréable, mais peut être lu comme contenant des macros, plutôt que comme code de support pour les écrire
  • extern crate macro_runtime; - exactement ce qu'il dit sur la boîte
  • extern crate metarust; - écrivez Rust, à propos de Rust, pour fonctionner sur Rust
  • extern crate bikeshed; - avec les macros procédurales, vous pouvez avoir la couleur de Rust de votre choix!
  • extern crate macrame; - sonne comme "macro make [r]"; mieux vaut peut-être laisser une future API "sympa" par rapport à la bibliothèque brute.

@nrc Il semble qu'un aspect important de cette question soit la dénomination de nos différents styles de macro dans l'ensemble - en particulier, si nous choisissons libproc_macro , nous nous engageons fermement à la "macro procédurale" terminologie. Je n'ai pas d'opinion tranchée ici, mais je ne suis pas sûr que nous ayons ouvertement exploré l'espace de la terminologie.

Pour être clair, pensez-vous que les macro_rules seraient simplement des "macros", c'est-à-dire la chose par défaut que nous entendons par macros, alors que vous devez qualifier les "macros procédurales"? Cela me semble être un plan assez raisonnable. Et dans ce monde, je dirais que libproc_macro vaut mieux que libmacros .

Ma pensée ici est que toutes les macros sont des "macros", et quand nous devons faire une distinction basée sur l'implémentation (l'utilisation devrait être exactement la même pour tous les types), nous utilisons "macros procédurales" vs "macros par exemple". Je voudrais bannir entièrement "l'extension de syntaxe" et le "plugin du compilateur" (et réutiliser un jour ce dernier pour les plugins réels).

Mais, oui, je souhaite vivement m'éloigner de la terminologie de «macro procédurale».

@nrc a du bon sens pour moi! Bien que «macros par exemple» soit un peu difficile à manier, c'est aussi un terme très évocateur / intuitif. Mon seul souci concernant la "macro procédurale" est que la "procédure" n'est pas une chose dans Rust. Je me demande s'il existe un moyen de rendre la connexion plus directe. "Macro de fonction" n'est pas tout à fait correct, mais vous donne peut-être une idée de ce que je veux dire?

Oui, ce n'est pas tout à fait parfait, mais étant donné que c'est un terme bien connu / utilisé en dehors de Rust, je pense que c'est mieux que de créer notre propre terme. "Macro programmatique" est possible, mais je préfère "procédural".

Le terme de Perl pour désigner l'équivalent le plus proche des "macros procédurales" est "filtres source", ce qui (surtout avec le passage d'AST aux jetons) est une description assez appropriée.

Peut-être que les «transformateurs de syntaxe» ou les «macros programmatiques» fonctionneraient bien en tant que noms? Je n'ai cependant aucun problème avec les macros procédurales.

«Procédurale» est déjà ce que les gens appellent cela, et je pense que c'est clairement compris ce que cela signifie, surtout par opposition aux «macros par exemple». Je ne craindrais pas d'essayer de trouver un nom différent.

J'aime le terme «macro procédurale» pour une utilisation régulière (ou peut-être «macro personnalisée»). J'aime particulièrement utiliser le mot _macro_ pour qu'il soit clair qu'elles peuvent (éventuellement ...) être utilisées de la même manière que les "macros régulières". Pour cette raison, je n'aime pas les "filtres sources" (je m'attends également à ce qu'un filtre supprime simplement les données, pas les transforme, même si je sais que le terme est utilisé pour les deux).

Je suis d' libproc_macro avec libmacros . Je préfère en quelque sorte ce dernier simplement parce que je n'aime pas avoir _ dans les noms de caisse alors que cela peut être facilement évité. =)

Une question: nous attendons-nous jamais à avoir des «routines de support» qui pourraient être utilisées à partir de macros non procédurales? Je ne connais pas de tels plans, mais si nous le faisions et que nous les voulions dans la même caisse, alors libmacros serait un meilleur nom. =)

(Je pense un peu à, par exemple, le commentaire de

@nikomatsakis : Une question connexe mais subtilement différente est un cas d'utilisation que j'ai https://github.com/rust-lang/rfcs/pull/1561#discussion_r60459479 - voulons-nous que les fonctions d'implémentation des macros procédurales puissent appeler autres fonctions d'implémentation des macros procédurales?

Je vois facilement vouloir autoriser un dérivé personnalisé à en appeler un autre, ce qui rendrait essentiellement les définitions de macros procédurales elles-mêmes capables d'être utilisées en tant que "routines de support"

Mais oui, je pense que l' exemple gensym @dherman est assez convaincant. Bien sûr, si la réponse à ma question ci-dessus est "oui", gensym est à la fois une macro (qui pourrait être utilisée par des macros par exemple) et une fonction de support (qui pourrait être utilisée par des macros procédurales).

J'ai un PR cargo https://github.com/rust-lang/cargo/pull/3064 qui devrait cocher toutes les cases «intégration de fret» dans la liste de contrôle.

J'ai laissé un commentaire sur le PR cargo, mais je pense que nous voulons un type différent de _dépendance_, pas seulement un type différent de _package_. Tout d'abord, je pense que c'est juste mieux d'un point de vue esthétique et ergonomique, mais c'est juste mon avis. Mais j'ai aussi deux raisons concrètes.

  • Dans un avenir avec des déps publics et privés, il est important de savoir qu'il faut savoir que les déps publics des déps procéduraux et les déps publics réguliers ne doivent pas coïncider. Par exemple
  • Dans un futur avec des macros procédurales / quasi-citations en ligne, n'importe quelle bibliothèque peut être utilisée au moment de la compilation.
  • > voudrons-nous que les fonctions d'implémentation des macros procédurales puissent appeler les fonctions d'implémentation d'autres macros procédurales?

Comme cela le montre, la même caisse pourrait être un dépôt régulier d'une autre caisse de macros procédurales, ou un dépôt macro d'une autre caisse.

Serde fonctionne-t-il?

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

(sauf pour les attributs de conteneur dans certains cas # 36211)

Génial, merci pour les mises à jour @dtolnay!

Existe-t-il des documents sur ces macros? Je suppose que le seul exemple de leur utilisation est en serde, n'est-ce pas?

OK, la marchandise a atterri. C'est bien, mais ce serait bien de revoir https://github.com/rust-lang/rust/issues/35900#issuecomment -243976887 quelque temps avant la stabilisation. [Pour ce que ça vaut, je voulais évoquer cela dans la RFC d'origine mais j'ai oublié.]

Je pense que «macros par exemple» et «macros procédurales» pourraient être mieux classées respectivement comme «macros déclaratives» et «macros impératives». Cela donne des parallèles informatifs avec la catégorisation déclarative / impérative plus connue des langages de programmation. Comme l'impératif est traité comme un sur-ensemble ou un synonyme de procédure, il devrait être suffisamment proche pour que les personnes habituées à la terminologie «macro procédurale» puissent faire le saut. Cela devrait également éviter toute confusion avec les concepts de procédure / fonction / méthode dans rust lui-même.
Ce schéma de dénomination nous donne une caisse et un module macro_imp en parallèle macro_rules . macro_rules pourrait éventuellement devenir un module d'une caisse macro_dec plus générale.

@nrc , Lorsque vous faites référence aux "plugins réels", incluez- vous des éléments tels que clippy , des éléments tels que rustw , rustfmt et Rust Language Server , ou une autre catégorie de programme?

Pour ce que ça vaut, je n'aime un peu le nom "par exemple" (puisque les modèles $foo ne sont pas des "exemples" dans mon esprit). Déclaratif vs impératif me semble meilleur.

En jouant avec ça, j'ai remarqué un problème. Il semble que Rust fourni dérive parfois des attributs que le compilateur sait ignorer. Ce contexte est perdu lorsqu'il passe par une dérivation personnalisée. Je m'attendrais à ce que la fonction d'identification donnée en tant que dérivée personnalisée soit une non-opération, mais cela provoquera des erreurs autour de l'ajout #[structural_match] attribut


Script de reproduction

(Dans une caisse nommée 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
}

(dans une autre caisse)

#![feature(rustc_macro)]

#[macro_use] extern crate demo_plugin;

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

La suppression du #[derive(Eq)] fait que tout fonctionne correctement.

@sgrif ah oui en effet merci de me le rappeler!

Il se passe donc plusieurs choses ici:

  • Avec #[derive(PartialEq, Eq)] , le compilateur ajoute silencieusement #[structural_match] car il comprend statiquement que la dérivation PartialEq remplit effectivement ce contrat.
  • Je crois qu'un attribut similaire se produit avec #[derive(Copy, Clone)] avec #[rustc_copy_clone_marker] étant ajouté.
  • Actuellement, cela fonctionne parce que l'étendue de ces attributs dit "autorise l'instabilité interne". Cependant, nous perdons les informations de span lors de l'analyse et de la réparation.

Alors, quelques solutions que nous pourrions faire:

  • Disons conventionnellement que le dérivé personnalisé vient en premier, par exemple #[derive(Foo, Eq, PartialEq)]
  • Fiez-vous à la dérivation personnalisée pour omettre ces attributs
  • N'émettez pas d'attributs personnalisés si nous détectons un dérivé personnalisé
  • Utilisez un mécanisme entièrement différent dans le compilateur pour communiquer ce que ces attributs disent, mais pas via l'AST lui-même.

Je serais en faveur de ne pas émettre ces attributs ou d'utiliser un mécanisme différent. C'est vraiment délicat, cependant, car #[derive(Copy, Foo, Clone)] doit également fonctionner (où le code personnalisé s'exécute au milieu, ce qui pourrait changer les définitions).

Mon plan d'action préféré serait de ne pas émettre ces attributs si nous détectons un dérivé personnalisé. Même cela, cependant, n'est peut-être pas anodin. Pour l'instant, une convention de "coutume d'abord et de norme en dernier" devrait suffire, mais je pense que nous devrions corriger cela avant de stabiliser.

Avertissement: je n'ai qu'une vue extérieure du compilateur - il y en a donc beaucoup que je ne sais pas. ^^

Mais d'après ce que je comprends, l'approche actuelle de la dérivation personnalisée des macros 1.1 ressemble plus à une solution de contournement temporaire. Une solution de contournement temporaire peut se traduire par «assez longtemps». Mais à long terme (= macros 2.0), nous ne ferons plus le trajet aller-retour d'analyse des chaînes, ce qui conduit actuellement à la perte des informations de portée. Je me demande si ces attributs cachés dans l'AST étaient une si mauvaise chose dans un monde de macros 2.0. Peut-être que quelqu'un avec plus de connaissances internes sur le compilateur peut le dire. Si ces attributs cachés ont réellement un sens dans ce monde futur, je soutiendrais que j'opterais pour la solution de contournement en plaçant conventionnellement les dérivés personnalisés à l'avant. Le tout est déjà une solution de contournement de toute façon, n'est-ce pas?

@ colin-kiegel Je crois que vous avez raison en ce sens que ce que nous faisons en ce moment n'est pas à l'épreuve du temps. Disons que vous avez, par exemple:

#[derive(Eq, Foo, PartialEq)]

Aujourd'hui, nous ajoutons l'implémentation Eq , puis exécutons le code personnalisé pour Foo , puis ajoutons une implémentation de PartialEq . La structure pourrait _change_ entre Eq et PartialEq , donc le #[structural_match] ajouté par Eq peut ne pas être correct après l'exécution de Foo .

En ce sens, je conviens qu'ils ne sont pas nécessairement à l'épreuve du temps de toute façon!

Mon sentiment est que les dérivations personnalisées qui reposent sur des changements structurels ne se composent généralement pas très bien, indépendamment de ces attributs cachés. Un dérivé personnalisé v2.0 pourra-t-il changer la structure de l'élément ou sera-t-il limité à la décoration uniquement?

Oui, la capacité sera toujours là, je crois (inhérente à l'interface TokenStream -> TokenStream) bien que je soupçonne que toute implémentation raisonnable de #[derive] conserverait la structure de la structure d'origine.

Je ne suppose pas que nous pourrions exiger que le flux de jetons de sortie ne contienne pas la structure elle-même, pour nous assurer que la structure ne soit pas modifiée? La structure d'entrée TokenStream serait un préfixe immuable? Le gros problème serait de s'assurer d'ignorer les attributs non reconnus que les plugins utilisent une fois la construction terminée. Peut-être que chaque # [derive ()] peut avoir un préfixe (disons que # [derive (Foo)] a le préfixe Foo_) avec lequel les attributs qu'ils comprennent doivent commencer, et après le traitement de chaque dérivation personnalisée, nous supprimons ces attributs?

@mystor ouais le problème avec cette approche est des attributs non reconnus, c'est pourquoi nous avons la structure entière en entrée. C'est généralement plus flexible que de s'appuyer sur un préfixe / suffixe / enregistrement / etc. particulier.

Si un dérivé personnalisé v2.0 pouvait marquer des attributs personnalisés comme _utilisés_, il pourrait être limité à un accès en lecture seule au reste du flux de jetons d'éléments. De cette façon, une meilleure composabilité des dérivés personnalisés pourrait être garantie à l'OMI. Si une macro v2.0 doit changer la structure d'un élément, elle devra utiliser une autre API, mais pas une dérivation personnalisée. De cette façon, le problème avec #[structural_mach] et l'ordre des dérivations (personnalisées) ne serait présent que dans les macros 1.1. Cela aurait-il un sens?

Un autre problème. Si une structure a deux dérives personnalisées différentes et que la seconde panique, la plage d'erreur pointera vers la première, pas celle qui a paniqué.


Script de reproduction

Dans une caisse appelée 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");
}

Dans une autre caisse

#![feature(rustc_macro)]

#[macro_use] extern crate demo_plugin;

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

L'erreur mettra en évidence Foo même si Bar paniqué.

Merci pour le rapport @sgrif! J'ai mis à jour la description de ce problème et j'espère y garder une trace de tous les problèmes en suspens liés aux macros 1.1.

Hmm, l'interaction de custom derive avec #[structural_eq] est quelque chose auquel je n'avais pas pensé auparavant. Cela me dérange plutôt!

Il me semble qu'avoir un moyen d '«ajouter» du texte à un flux de jetons pourrait être une meilleure interface à la fin de la journée ... cela préserverait les informations de span et éviterait ce problème, non?

Un avantage de l'interface plus générale est qu'elle permet aux packages d'avoir des attributs sur les champs, qui peuvent être supprimés lors de l'exécution de la macro. C'est le seul cas d'utilisation réel que je connaisse pour permettre aux macros de dérivation personnalisées de modifier l'élément d'origine.

Je pense que la question de l'interface se résume à savoir si nous voulons que la dérivation personnalisée soit `` juste une autre macro '', auquel cas il semble important d'avoir la même interface que les autres macros procédurales (où nous voulons modifier l'élément d'origine). Ou si cela devrait être sa propre chose avec une interface spéciale, auquel cas l'interface plus restrictive (ajouter) a du sens.

Je noterai que les extensions de syntaxe ont longtemps fait une distinction entre les modificateurs et les décorateurs, et je pense que toutes les personnes impliquées détestaient vraiment cette distinction. Je suis donc un peu réticent à l'idée de faire en sorte que le dérivé personnalisé soit un peu spécial (une alternative qui a été discutée est une sorte de dérivation personnalisée très spéciale, peut-être même un format déclaratif).

@nrc eh bien, cela peut toujours être tokenstream -> tokenstream, où nous n'exposons tout simplement pas une méthode new qui part de zéro, non?

Je suis d'accord qu'il doit être possible d'avoir des attributs personnalisés sur les champs pour qu'un dérivé personnalisé ait vraiment un sens. Mais je crois qu'il peut y avoir de nombreuses façons de faire cela avec le style "append" - cela devrait bien sûr être discuté. Je préférerais certainement le style d'ajout, car je préfère un monde où les macros dérivées ne changent pas l'élément et sont composables, en plus de résoudre le problème #[structural_eq] . C'est juste la bonne quantité de liberté IMO.

Si les gens n'aimaient pas ce destin, je voudrais demander pourquoi, car il y avait évidemment suffisamment de raisons de faire cette distinction auparavant, n'est-ce pas?

(Je suppose que ce que j'ai suggéré n'est qu'un hack temporaire, ne résout pas vraiment le problème de composabilité à plus long terme.)

Mes différentes bibliothèques ont actuellement des macros qui ressemblent à foo!(Bar, parameters...) , qui génèrent une structure Bar partir des paramètres.

Au cours d'une discussion sur IRC, nous avons eu l'idée d'écrire #[derive(Foo)] #[params] struct Bar; place et de remplacer foo! par un #[derive(Foo)] qui générerait le corps de la structure.

Ce n'est évidemment pas un argument fort, mais j'ai vraiment aimé cette idée, car il est plus clair pour l'utilisateur qu'une structure est en cours de construction.

Je me demande si nous pourrions retravailler le #[structural_match] pour être placé sur l'impl généré à la place.

(Ne résout pas vraiment le problème)

Il convient de noter que Serde et Diesel utilisent beaucoup d'attributs personnalisés sur les champs, il existe donc un besoin certain de dériver personnalisé pour permettre le remplacement.

Alors peut-être que je ne pense pas directement au problème #[structural_match] . Après tout, que peut faire un dérivé personnalisé?

  • Si le dérivé personnalisé insère à tort l'attribut #[structural_match] , il devrait échouer le contrôle de stabilité. Sinon, cela semble être un bug en soi! (Compte tenu de la façon complexe et farfelue dont ce code fonctionne, cela ne me surprendrait pas.)
  • Si le dérivé personnalisé le supprime, aucun mal n'est fait.

Désolé d'avoir écrit des tonnes de petits commentaires et d'avoir réfléchi à voix haute, mais il y a une autre préoccupation. Bien qu'une dérivée personnalisée ne puisse pas "falsifier" une annotation #[structural_match] (car elle se terminerait sans la "portée magique"), elle finirait probablement par gâcher la durée de toute annotation existante, à moins que le les dérivés sont appliqués dans le bon ordre, ce qui est regrettable. Fondamentalement, une instance de la non-composabilité dont @ colin-kiegel a parlé, mais sans aucune tentative de modifier la structure en vol.

(En d'autres termes, puisque nous nous appuyons sur le span pour juger si des éléments stables peuvent être utilisés, la perte d'informations de span peut causer des problèmes délicats.)

EDIT: OK, en relisant je vois que je viens de redéfinir ce que @sgrif a déjà signalé. Encore pardon. ;)

C'est aussi un peu dégoûtant, car cela signifie que nous exposons des détails d'implémentation instables au code stable. Idéalement, un code stable ne saurait même jamais que l'annotation #[structural_match] existe.

@nikomatsakis eh bien, d'une manière ou d'une autre, nous devons appliquer différentes contraintes selon que la macro est destinée à être un dérivé personnalisé ou un autre type. Cela signifie un traitement séparé (et une sémantique différente), quelle que soit la signature de la fonction.

@ colin-kiegel

Je préférerais définitivement le style append, car je préfère un monde où les macros dérivées ne changent pas l'élément et sont composables, en plus de résoudre le problème # [structural_eq]. C'est juste la bonne quantité de liberté IMO.

Je pense que les macros mutantes peuvent être composables, même si bien sûr les termes de composabilité sont différents. Il doit clairement y avoir un ensemble de pré- et post-conditions sur le fonctionnement des dérivées, soit imposées par le compilateur, soit par convention. La non-mutation semble être un extrême sur le spectre des invariants que nous pourrions choisir ici, et notons que nous discutons déjà des moyens par lesquels cela peut être adouci (par exemple, les attributs de marquage utilisés). Je pense qu'en général, nous préférerions les conditions les plus simples et les faire appliquer par le compilateur. Cependant, cela est quelque peu subsumé par la question de savoir comment le dérivé personnalisé devrait être traité.

Si les gens n'aimaient pas ce destin, je voudrais demander pourquoi, car il y avait évidemment suffisamment de raisons de faire cette distinction auparavant, n'est-ce pas?

Je ne pense pas qu'il y avait une forte motivation à l'époque. Je pense que c'était facile à mettre en œuvre et «semblait être une bonne idée». Il n'a pas été aimé depuis car il rend l'implémentation plus complexe, il ajoute une distinction pour les auteurs de macros qui n'est généralement pas pertinente, et il a rendu les macros moins flexibles en ayant à choisir de modifier ou de décorer.

J'aimerais beaucoup examiner la conception à long terme du dérivé personnalisé et m'assurer que nous allons dans la bonne direction. Il me semble que les contraintes de la solution 1.1 et la volonté de faire le plus vite possible brouillent les eaux ici et nous perdons de vue la vision plus large.

Je suis d'accord avec @jimmycuadra en ce sens qu'il semble que la prise en charge des attributs personnalisés d'une manière ou d'une autre soit une exigence @nikomatsakis a également raison en ce sens que le traitement actuel de #[derive(PartialEq, Eq)] est inférieur à la moyenne et nous ne devrions pas le stabiliser. Enfin, @mystor a un très bon point que les modes de dérivation personnalisés ne devraient même pas connaître cet attribut magique. Nous voulons certainement en ajouter plus à l'avenir et je ne veux pas que les macros 1.1 nous empêchent de le faire.

Faisant également écho sentiment de @nrc concernant la conception à long terme du dérivé personnalisé, je pense que cela se résume en grande partie à la façon dont #[derive] fonctionne réellement. Si et quand nous prenons en charge des attributs arbitraires, je pense que @nrc a un bon point sur le fait de n'avoir que des modificateurs et de ne pas avoir de décorateurs, mais #[derive] est assez spécial où un dérivé personnalisé ne définit pas un nouvel attribut mais plutôt simplement un existant.

À l'heure actuelle, l'implémentation a une extension stricte de gauche à droite des modes #[derive] . Tous les modes de dérivation internes sont développés dans une boucle et chaque fois qu'un mode de dérivation personnalisé est atteint, nous resérialisons, développons, puis revenons à l'étape 1. Cela signifie à son tour que le compilateur peut exécuter l'attribut #[derive] _ plusieurs fois pour un. type definition_. Cela conduit à un tas de pilosité.

Une proposition que je pourrais avoir est de modifier l'ordre d'expansion de #[derive] :

  • Tout d'abord, tous les attributs dérivés personnalisés des macros 1.1 sont développés un par un. Autrement dit, si vous avez #[derive(Clone, Foo)] nous dériverions d'abord Foo où la structure avait une annotation #[derive(Clone)] . Si cela #[derive] persistait, nous dériverions alors le trait intégré personnalisé Clone .
  • Deuxièmement, tous les attributs dérivés inconnus du compilateur sont développés en attributs #[derive_Bar] . C'est juste un hack de compatibilité ascendante que nous devrions supprimer à un moment donné, et c'est ainsi que les attributs étaient utilisés pour être développés.
  • Enfin, le compilateur étend tous les attributs #[derive] connus et intégrés

Cela a pour effet surprenant que vous ne développez pas de gauche à droite, mais rappelez-vous que ce n'est que #[derive] . Cela donne au compilateur une connaissance maximale de la définition de la structure et quand il développe des traits intégrés, nous savons que la structure du type ne changera jamais.

Comment ça sonne? Je crois que cela résout toutes les contraintes ici?


@nikomatsakis Je ne suis pas sûr que la stratégie consistant à placer l'attribut sur le impl fonctionnera car d'autres modes de dérivation personnalisés pourraient en théorie changer la disposition de la structure, même les types des champs. Cela violerait les hypothèses du compilateur lors de son expansion, je pense.

L'ordre dans lequel les dérivés sont traités a-t-il déjà été officiellement déclaré comme étant de gauche à droite, via la référence Rust ou autre? Plus généralement, l'ordre des attributs importe-t-il jamais? Il semble que ce soit simplement accessoire que cela ait été implémenté de cette façon, et les auteurs de macros n'auraient pas dû se fier à un ordre de gauche à droite. La proposition d'Alex de traiter la coutume dérive d'abord de sorte qu'ils ne voient jamais les attributs magiques ajoutés par le compilateur a beaucoup de sens.

Je voudrais juste ajouter que je n'aime pas l'idée que les dérivés personnalisés peuvent changer la disposition de la structure. Je voudrais pouvoir l'utiliser pour quelque chose qui est sensible à la sécurité. À titre d'exemple, considérons l'implémentation #[derive(Trace)] utilisée par rust-gc .

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

Expansion vers:

struct Foo {
    a: Gc<i32>,
}

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

Cependant, si nous autorisons la modification des champs dans la structure, nous pouvons définir un dérivé personnalisé Evil :

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

Expansion vers:

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

Lequel, si nous les combinons:

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

Expansion vers:

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

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

Ce qui est une implémentation malsaine de Trace . Lorsqu'il est utilisé avec rust-gc , cela permet à b d'être une référence pendante, ce qui est horriblement dangereux et non valable. Cela signifie que Trace n'est plus une chose sûre pour #[derive] sur un type, ce qui est très malheureux.

Personnellement, je pense que tout #[derive] bien comporté ne modifiera pas la mise en page / la composition d'une structure, et si c'est le cas, vous n'avez tout simplement pas de chance. La possibilité pour un dérivé personnalisé de supprimer des attributs est essentielle, et y renoncer n'est pas une étape. De plus, d'autres implémentations qui impliquent une forme de liste blanche ou autre diffèrent considérablement de la simple interface que nous avons aujourd'hui.

En d'autres termes, je ne pense pas que la «pureté» d'avoir #[derive] ne jamais modifier la structure en vaille la peine.

Je me demande simplement s'il y aura un moyen de supprimer des attributs sans lui permettre d'ajouter ou de supprimer des champs (par exemple, en vérifiant que les champs sont les mêmes dans la structure ré-analysée que la structure d'origine, et en faisant une erreur s'ils ne sont pas '' t, mais ne pas se plaindre si d'autres choses sont modifiées).

J'ai un mauvais pressentiment de permettre à derive de changer la structure. L'exemple de @mystor est ce que j'avais en tête quand je parlais de composabilité ... à un niveau élevé (ce n'était peut-être pas le bon terme).

Je pense que les gens l'exploiteront, s'il est disponible. Et cela obligera les consommateurs à raisonner sur les détails des implémentations de dérivation personnalisées et leur ordre d'exécution.

Je préférerais que je puisse dire "hé, je ne sais pas ce que cela fait, mais je comprends l'autre" sans interdépendance. Sinon, ce sera une douleur dans l'orteil et je crois que cela arrivera.

Une macro procédurale faisant quelque chose de malveillant est-elle vraiment différente de toute caisse que vous utilisez pour faire quelque chose de malveillant? Toute caisse peut contenir du code dangereux qui fait quelque chose qu'elle ne devrait pas. On dirait que ce cas correspond simplement à la façon dont vous déterminez la fiabilité de tout code que vous n'avez pas écrit vous-même, par exemple la réputation de la communauté, l'inspection de la source vous-même, etc.

Je ne pense pas que les caisses vont essayer de faire quelque chose de malveillant, je m'attends plutôt à ce qu'elles soient "intelligentes" et qu'elles fassent de bons trucs pour rendre leur implémentation plus efficace ou parce qu'elles le peuvent, et pour que cela brise d'autres implémentations de dérivation personnalisées. Je ne serais pas très surpris si certaines dérivations personnalisées commencent à ajouter des champs aux structures qui ne sont utilisées que dans leurs implémentations parce qu'elles le peuvent, et celles-ci cassent alors quelque chose comme Trace.

@mystor Cela semble pertinent en théorie, mais rappelez-vous que vous devez en fait fournir tous les champs d'une structure dans Rust, donc il est beaucoup moins probable de fonctionner en silence comme ça, qu'en C ++, par exemple.

@alexcrichton

Une proposition que je pourrais avoir est de modifier l'ordre d'expansion de #[derive]

J'aime cette idée. Peut-être que la chose à dire en termes de documentation est simplement que l'ordre d'expansion est "indéfini". Nous avons développé PartialEq / Eq ces derniers temps, mais il n'y a aucune raison stricte de le faire.

Quant aux dérivées modifiant la définition de la structure, je suis d'accord que cela semble assez subtil, mais cela ne me dérange pas trop. Je pense également que les champs "auto-inserting" pourraient être très pratiques - mais je préférerais que ces modificateurs soient des attributs distincts plutôt que d'être placés dans la liste des dérivées, principalement parce que nous ne voulons pas que les gens se fient à l'ordre d'expansion tout à l'heure.

PS. J'envisagerais sérieusement d'utiliser un RNG (déterministe) amorcé par le hachage de crate ou quelque chose comme ça pour réorganiser l'expansion des dérivations personnalisées de l'utilisateur, de sorte que les gens ne puissent pas se fier à l'ordre d'expansion. J'ai toujours voulu faire cela dans un certain contexte pour éviter les dépendances implicites, mais je n'en ai jamais eu l'occasion. ;)

EDIT: J'ai changé d'avis et je ne vois plus de raison d'interdire la mutation de la structure, mais voici mon commentaire original pour le contexte

Donc, d'après ce que je comprends, ce sont les arguments pour permettre à custom #[derive] de muter la structure:

  • Cela pourrait être utile dans certains cas (je n'ai vu aucun exemple, mais je pense qu'ils existent)
  • Nous voulons pouvoir supprimer les attributs une fois qu'ils ont été utilisés
  • Donner plus de pouvoir aux auteurs de dérivation personnalisée

Alors que les arguments pour ajouter des limitations aux implémentations personnalisées #[derive] (comme exiger que le nom de la structure et les champs / noms des champs de la structure restent les mêmes) sont:

  • Cela permet au code généré par un #[derive] de dépendre de la structure du type sur lequel il est dérivé pour la solidité (par exemple #[derive(Trace)] de rust-gc qui _doit_ voir le type de support réel, ou il n'est pas sain, potentiellement d'une manière subtile après utilisation gratuite)
  • Cela réduit la probabilité de dépendances implicites dans les extensions de macro, car il y a moins d'informations véhiculées entre elles via la structure

À mon avis, la possibilité d'écrire des implémentations unsafe trait dérivation personnalisées sonores qui génèrent des impls

Idée 1

Avant d'exécuter une passe de dérivation personnalisée, lisez le nom de la structure et les noms de chacun des champs. Lorsque la passe est terminée et que la structure est ré-analysée, vérifiez si le nom de la structure est le même et si les noms de chacun des champs (et le nombre de champs) sont identiques. s'ils ne le sont pas, générez une erreur et tuez la compilation.

Cela garantirait que les propriétés structurelles de base dont nous nous attendons à ce que les plugins de dérivation personnalisés ne dépendent pas ne soient pas rompus, et signifie que nous avons plus de plugins de dérivation personnalisés. Cela a également l'avantage d'être rétrocompatible avec l'approche actuelle, donc si nous décidons que nous l'aimons mieux à l'avenir, nous pouvons simplement basculer et casser le code de personne. Il gère également le cas des attributs inutilisés, comme aujourd'hui.

Idée 2

Donnez à chaque plugin de dérivation personnalisé la même entrée TokenStream (le texte original écrit dans le programme). Lorsque le résultat est ré-analysé, enregistrez les attributs toujours présents sur la structure de sortie. Si un attribut est présent dans chaque flux de jetons de sortie, alors se plaindre d'un attribut inutilisé.

Cela signifie qu'il est impossible d'avoir des dépendances de commande (car conceptuellement, chaque plugin de dérivation personnalisé fonctionne à partir du même objet d'origine), et il est également impossible de bousiller la structure du plugin. J'aime cette idée car elle garantit que chaque dérivé personnalisé agit d'une manière généralement saine, ne générant que de nouveaux éléments basés sur la structure existante. Cela serait également probablement facile à transformer en une solution dans laquelle nous pourrions transformer la solution actuelle.

TL; DR

En résumé, j'aimerais comprendre quel est l'avantage particulier de permettre la mutation des structures, et pourquoi cela l'emporte sur les problèmes de sécurité de rendre possible #[derive(Trace)] et toujours correct #[derive(Serialize)] etc. Je suis sûr que si nous finissons par suivre la route des structures en mutation, il y aura une bonne raison, mais je serai très triste de changer le nom de mon dérivé personnalisé Trace en #[derive(unsafe_Trace)] .

Je trouve que la solution de

Même si @mystor a un bon point de vue que cela peut conduire à des surprises désagréables, avoir la possibilité de changer le struct semble obligatoire. D'autre part, les caisses combinant les cas d'utilisation de macros procédurales, les problèmes de sécurité _et_ du code non sécurisé semblent plutôt rares.

Hors sujet: cette implémentation permettra-t-elle à la macro de signaler correctement les erreurs?

J'aime l'idée de @nikomatsakis pour randomiser l'ordre d'expansion des

@mystor une troisième option serait de faire ces contrôles de sécurité une seule fois après que toutes les

Je ne vois vraiment pas l'inquiétude autour de la modification de structure. Un champ ne peut pas être ajouté de manière invisible, quelque chose devra se soucier lors de l'initialisation. Si cela vous inquiète vraiment, vous pouvez écrire votre dérivée pour générer du code qui ne parvient pas à se compiler s'il ne voit pas la structure entière assez facilement.

@sgrif est probablement vrai dans la plupart des cas - mais pas autant si vous dérivez et utilisez également le trait par défaut, ou quelque chose d'équivalent.

@sgrif PS: Il est vrai que la plupart des auteurs de rust comprennent probablement ce qui se passe dans leur propre code et peuvent donc ne pas être surpris par les modifications de structure s'ils choisissent d'utiliser une telle macro exprès.

Le cas d'utilisation général pour appliquer des macros sur des structures est certainement une _combination_ de modifications de structure + décorations. Mais je m'attends à ce que le rapport général soit "beaucoup de décorations" avec "seulement quelques retouches". Il est bon d'avoir une séparation claire ici, car cela améliore la lisibilité et la flexibilité.

  • flexibilité: Supposons que vous souhaitiez appliquer deux dérivations personnalisées qui font les deux, c'est-à-dire modifier et décorer. Peu importe comment vous les commandez, vous risquez de ne pas obtenir le résultat souhaité. Cependant, si la modification se produit via un mécanisme différent et que les décorations sont toutes appliquées par la suite, vous avez la possibilité de combiner plusieurs modifications et décorations d'une manière plus contrôlable.
  • lisibilité: Si vous lisez le code de quelqu'un d'autre et qu'il y a 10 dérives appliquées à une structure et que l'une d'elles modifie la structure, vous aurez besoin de plus de temps pour le comprendre.

Donc, même si je trouve cette partie de l' argument de

Cela permet au code généré par un # [derive] personnalisé de dépendre de la structure du type sur lequel il est dérivé pour la solidité (par exemple # [derive (Trace)] de rust-gc qui doit voir le type de support réel, ou il est malsain, potentiellement d'une manière subtile après utilisation gratuite)

Je pense qu'essayer d'appliquer cela en dérivant peut être la mauvaise façon de procéder. Plus précisément, nous voulons probablement la possibilité de modifier les définitions de structure dans le cas général (oui, il ne sera pas transparent, mais quoi). Ce qui signifie que l'idée d'avoir un "son" Trace qui puisse être assuré que la structure ne change pas par la suite pourrait devoir être résolue d'une autre manière. Considérez si les décorateurs sont appliqués de bas en haut:

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

On pourrait penser que le Trace impl peut être écrit de telle manière que _it_ échoue à se compiler si la définition struct change. Par exemple:

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

Notez cependant que #[mangle] peut aussi visser avec votre impl si c'est vraiment diabolique. =) Nous ne pouvons pas faire grand chose ici.

En tant qu'observateur de ces conversations, je serais heureux d'avoir la règle formelle ou informelle selon laquelle #[derive] n'est autorisé qu'à ajouter des blocs impl et d'introduire une annotation fraternelle ( #[mangle(Foo, Bar)] sons bon pour moi 😸) qui est dédié à _changer_ la structure d'un type. #[mangle] pourrait avoir un ordre d'évaluation défini.

Il doit probablement y avoir un ordre d'évaluation défini entre les annotations s'il n'y en a pas déjà.

Je pense que mes opinions à ce sujet se sont détendues. @nikomatsakis fait remarquer que même si nous nous débarrassions des structures en mutation dans # [derive], nous ne nous en sortirions pas en étant capables de faire des hypothèses sur les dispositions de structure dans le code de toute façon. L'astuce let Foo{ ... } semble fonctionner pour s'assurer que la mise en page est correcte dans des cas sains. Nous aurons probablement besoin de le documenter quelque part cependant pour que tout le monde n'ait pas à le découvrir indépendamment.

@nikomatsakis

  • mh .. il pourrait y avoir la règle supplémentaire, que les _ décorateurs personnalisés doivent être appliqués avant derive_, plus les _dérivés ne peuvent pas modifier l'élément_. Cela permettrait encore de durcir / purifier la mécanique dérivée en général.

Mais je suis également heureux de voir qu'il existe un autre moyen de renforcer les dérivés personnalisés _individuellement_ contre les modifications ultérieures. :-)

En ce qui concerne les cas nécessitant de modifier l'intérieur de l'élément auquel l'attribut est appliqué, je viens de tomber sur le fil de discussion "Pre-implementation feedback for Qt with Rust" sur u.r-l.o , et j'ai publié ce message:

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

Une facette notable est qu'ici, je suggère qu'un #[derive] (ou similaire) soit appliqué à un _trait_, plutôt qu'à une structure - et l'élément ajouté à l'intérieur serait une méthode de trait const .

Semblable aux problèmes de fret que j'ai soulevés ci-dessus, je ne suis pas sûr de

#[macro_use]
extern crate double;

importer des macros procédurales depuis une caisse appelée double . Puisque nous utilisons le code d'exécution (comme dans le vrai Ruest) au moment de la compilation, il devrait y avoir une instruction d'importation par incrémentation de phase analogue à (require (for-syntax ...)) de Racket. [La documentation de la raquette est https://docs.racket-lang.org/reference/require.html#% 28form ._% 28% 28lib._racket% 2Fprivate% 2Fbase..rkt% 29._for-meta% 29% 29, Malheureusement, je ne peux pas comprendre comment lier la bonne section.]

Le billet de blog http://blog.ezyang.com/2016/07/what-template-haskell-gets-wrong-and-racket-gets-right/ souligne les erreurs de mise en phase commises dans Template Haskell et peut être intéressant. - Je ne veux pas faire les mêmes erreurs dans Rust.

@ Ericson2314 La différence dans Rust est que double est déjà compilé _pour une phase_ spécifique.
Autrement dit, la caisse exporte uniquement l'interface macro / modificateur, comme si elles étaient définies avec, par exemple macro_rules .
Pouvoir créer des caisses qui exportent à la fois les macros procédurales et les éléments Rust sous-jacents (qui forment la macro procédurale) serait intéressant, mais jusqu'à présent, cela ne semble pas être proposé en aucune manière.

Il peut être judicieux de permettre à la caisse en cours de construction de choisir beaucoup de choses sur quoi et comment exporter, mais le simple fait de prendre un système en gros à partir d'un LISP avec un modèle de compilation différent semble contre-productif.

Ouais @eddyb Je suis sceptique quant à cette méthodologie «créer sait comment elle sera utilisée en aval». Si quelque chose est plus important pour nous que Racket en raison de notre modèle de compilation (je ne sais même pas si Racket peut effectuer une compilation croisée), alors je ne comprends pas votre dernier argument.

Nominé pour la discussion de l'équipe lang sur: un plan de stabilisation.

Du côté de serde, voici la courte liste des problèmes restants avant que nous puissions arrêter de prendre en charge le plugin de compilateur existant et recommander officiellement Macros 1.1 pour tous les utilisateurs nocturnes: https://github.com/serde-rs/serde/issues/545. La seule chose dont nous avons besoin de Rust est que # 36211 soit corrigé. Tout le reste sur lequel nous progressons rapidement.

J'ai un PR ouvert qui implémente notre rustc_macro sans utiliser syntex, nous pouvons donc arrêter de nous soucier du temps de compilation https://github.com/serde-rs/serde/pull/548.

EDIT: peu importe, # 36211 n'a affecté que l'ancienne implémentation syntex

J'essaierai de terminer le port Diesel d'ici vendredi pour que je puisse confirmer que cela fait tout ce dont nous avons besoin à cette fin.

@dtolnay étant donné serde-rs / serde # 548, y a-t-il des bloqueurs restants pour serde?

@sgrif génial, merci! Une fois que vous avez fait cela (ou avant), pourriez-vous commenter ici les autres bloqueurs que vous avez rencontrés?

Oui.

Le mar 27 sept. 2016, 19:29 Alex Crichton [email protected]
a écrit:

@dtolnay https://github.com/dtolnay donné serde-rs / serde # 548
https://github.com/serde-rs/serde/pull/548 , y a-t-il des
bloqueurs pour serde?

@sgrif https://github.com/sgrif génial, merci! Une fois que tu as fait ça
(ou avant) pourriez-vous commenter ici avec les autres bloqueurs que vous
rencontré?

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, affichez-le sur GitHub
https://github.com/rust-lang/rust/issues/35900#issuecomment -250028743,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ABdWK7MnA1rpn-WGji8gwAT2JCIqB4LFks5quaa-gaJpZM4JqEAX
.

@alexcrichton

étant donné serde-rs / serde # 548, reste-t-il des bloqueurs pour serde?

Non, si @ oli-obk ou @erickt passe en revue ce PR aujourd'hui ou demain, je peux tout faire sortir le lendemain et migrer quelques utilisateurs de premier plan comme Rusoto.

@dtolnay J'utilise beaucoup serde_macros dans Ruma et j'aimerais également vous aider à tester serde_derive, si vous avez besoin de plus d'yeux.

En fait, j'utilise aussi diesel_codegen, donc Ruma est un bon terrain de test pour la version Macros 1.1 de ces deux bibliothèques.

J'ai publié Serde 0.8.10 annonçant que serde_macros est obsolète au profit de Macros 1.1.

Ajoutons une case à cocher pour "cargo doc works" - https://github.com/rust-lang/cargo/issues/3132.

@dtolnay terminé!

Je ne sais pas s'il s'agit de bogues serde_derive ou de bogues rustc / rustdoc, mais je remarque deux problèmes dans les documents générés:

  • Un littéral "///" apparaît au début des docstrings générés de types qui utilisent la dérivation personnalisée.
  • Deserialize et Serialize n'apparaissent pas dans la section "Implémentations de traits" pour les types qui utilisent le dérivé personnalisé.

Un littéral "///" apparaît au début des docstrings générés de types qui utilisent la dérivation personnalisée.

C'était un bogue syn . J'ai publié la 0.8.2 avec un correctif donc cargo update pour le récupérer.

Je ne sais pas pour le second, je vais laisser ça à @alexcrichton.

@jimmycuadra

  • Deserialize et Serialize n'apparaissent pas dans la section "Implémentations de traits" pour les types qui utilisent le dérivé personnalisé.

Cela semble assez louche! On dirait que c'est un bogue dans rustdoc, mais peut-être pas un nouveau. Par exemple, rustdoc ne montre pas cela avec l'implémentation Copy :

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

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

Ce modèle est utilisé par serde-derive pour générer des impls. @dtolnay est-ce que serde_macros avait également la même forme de génération? Serait-il facile de s'éloigner pour l'instant pour résoudre le problème de documentation?

@jimmycuadra veut ouvrir un bogue séparé pour suivre le problème rustdoc à ce sujet?

@dtolnay est-ce que serde_macros avait également la même forme de génération?

Oui.

Serait-il facile de s'éloigner pour l'instant pour résoudre le problème de documentation?

Non. À ma connaissance, c'est la seule solution qui répond à ces deux contraintes:

  • Doit prendre en charge serde pas dans la racine de la caisse, c'est-à-dire pas ::serde . Ceci est courant lorsque les gens placent des impls serde derrière un indicateur de fonctionnalité dans un module séparé.
  • Doit pouvoir utiliser les types privés du module environnant.

Voir https://github.com/serde-rs/serde/issues/159 pour plus de détails.

@dtolnay ah ok, donc pour clarifier, @jimmycuadra ce n'est pas une régression d'avant, non?

Mise à jour: pas tout à fait fini avec le port, mais presque là. Aucun problème autre que les choses que j'ai déjà signalées ou des limitations mineures que nous savions que nous rencontrerions. Probablement sûr de cocher la case "diesel works", nous aurons une version demain sur Macros 1.1 si cela ne se produit pas ce soir.

Pour ceux qui suivent, j'ai ouvert # 36945 pour renommer rustc_macro en proc_macro pratiquement partout, ce qui semble être le consensus sur le nom de cette caisse.

Il semble que les implémentations de traits manquantes dans rustdoc (qui sont maintenant suivies dans un autre numéro) ne sont pas spécifiques à Macros 1.1. Sûr à ignorer.

J'ai essayé de porter ma bibliothèque mockers du plugin du compilateur vers les macros 1.1 et j'ai obtenu "erreur: les attributs de dérivation personnalisés ne peuvent être appliqués qu'aux éléments struct / enum". Avec le plugin du compilateur, il est possible (bien que quelque peu étrange) d'utiliser "dériver" sur les traits. Suis-je pas de chance ici?

@kriomant intéressant! Je pense que cela peut en fait être un bogue dans le dérivé personnalisé aujourd'hui, car je ne suis pas sûr qu'il ait jamais été prévu de permettre à #[derive] de s'appliquer à un trait ...

Je pense que pour l'instant, pour être conservateur, nous ne pourrons probablement pas encore _stabiliser_ le dérivé sur les traits, mais nous pourrions peut-être ajouter une fonctionnalité instable pour cela. Réflexions @ rust-lang / lang?

@alexcrichton Quelle est la différence entre les traits et les structures d'un aspect de custom_derive?

@KalitaAlexey none, c'est une limitation artificielle pour correspondre à l'implémentation réelle derive

@alexcrichton Pourrait-on étendre un support aux traits?

Comme je l'ai mentionné ci-dessus , il est probable que le dérivé personnalisé ait jamais été autorisé sur les traits, et cela n'a pas été spécifié dans la RFC, donc plus de discussion devrait avoir lieu avant d'étendre. Dans tous les cas, il est peu probable que la prise en charge de la dérivation sur trait soit stabilisée lors de la première passe, mais nous pourrions envisager une porte de fonctionnalité distincte.

Personnellement, je ne voudrais pas ajouter dériver sur trait car cela semble beaucoup plus dans le sens du territoire d'attributs personnalisés plutôt que de dériver lui-même. (par exemple va à l'encontre de l'esprit de #[derive] tel que créé à l'origine)

Je préférerais que le dérivé personnalisé suive exactement les mêmes règles que le dérivé régulier. Nous _pourrions_ vouloir changer cela plus tard, mais je pense que cela devrait être RFC (je suis également assez froid sur l'idée, tbh, mais je pourrais avoir mon esprit changé par un cas d'utilisation convaincant et une gestion décente de divers cas extrêmes ).

@alexcrichton

Je pense que pour l'instant, pour être conservateur, nous ne pourrons probablement pas encore stabiliser le dérivé sur les traits, mais nous pourrions peut-être ajouter une fonctionnalité instable pour cela. Réflexions @ rust-lang / lang?

👍 de moi.

Ok, la fonctionnalité instable est bonne, mais cela signifie que ma bibliothèque ne fonctionnera pas encore sur stable (sans génération de code). Et la syntaxe macro!(…) n'est pas non plus couverte par les "macros 1.1", ai-je raison?

Je revisite la macro RFC proc et le plan était que les crates macro devraient se déclarer #[cfg(macro)] . Nous n'en avons pas besoin pour les macros 1.1, mais nous avons besoin d'un type de crate spécial. Les deux mécanismes sont quelque peu orthogonaux - le premier décrit la phase de compilation, le second le type de caisse. Mais ils se chevauchent aussi quelque peu - ce dernier implique le premier. Le premier s'adapte également à la déclaration de macros proc et de fonctions non macro dans les mêmes caisses, alors que ce n'est pas le cas.

Je ne sais pas si nous devons changer quoi que ce soit ici, nous pourrions probablement pirater la proposition de macros proc en rétrocompatibilité (cela élide délibérément la description du mécanisme de chargement des macros et donc des types de crate). Mais quelque chose à méditer pendant la période de stabilisation.

@kriomant correct, ouais

@nrc en ce moment qui est en fait défini comme cfg(rustc_macro) (bien que bientôt changé en proc_macro ). Nous n'en avons pas besoin, non, mais je pensais que cela allait être nécessaire pour le concept de liaison à une caisse au moment de la compilation et aussi au moment de l'exécution? Autrement dit, nous compilerions le crate proc-macro deux fois: une fois avec le type crate proc-macro et une fois avec le type crate rlib , et ce dernier ne serait pas lié à libsyntax ou quelque chose comme cette.

Pour le moment, il semble bien de ne pas le demander, même si je suppose que cela signifierait à une date ultérieure que vous devez vous inscrire au support d'exécution? (compiler la caisse deux fois)

@alexcrichton #[proc_macro_derive] pourrait-il l'impliquer? De la même manière #[test] implique #[cfg(test)] .
(Cela ne veut pas dire que nous devons l'ajouter maintenant, juste que le pire des cas si nous ajoutons cfg est des avertissements sur les objets inutilisés.)

@eddyb Et pour extern crate proc_macro; ? J'ai l'impression que cela se briserait aussi.

@mystor C'est juste une caisse ordinaire avec un tas de types et impls.

Mais n'est-il pas aussi lié à libsyntax , ce qui signifie que si une caisse utilisait une caisse proc_macro et voulait effectuer une compilation croisée, elle devrait également croiser la syntaxe de compilation?

@eddyb oui #[proc_macro_derive] peut être automatiquement ignoré, mais le problème est que nous devons également ignorer tout ce que ces fonctions atteignent de manière transitoire (comme le extern crate proc_macro ). Bien que ce ne soit "qu'une caisse", il a de graves implications pour l'exécution (liaison dynamique, non disponible pour les cibles compilées de manière croisée, etc.).

Notez que les tests ont #[cfg(test)] , donc il me semble raisonnable que nous donnions toujours #[cfg(proc_macro)] dans le même but.

@alexcrichton _Ideally_, la caisse n'aurait rien de plus que ce qui est exporté et ne nécessiterait pas de liaison dynamique ou quoi que ce soit du genre, seulement libstd . Cependant, je pouvais voir que cela posait des problèmes dans les cas #![no_std] .

On dirait que nous devrons faire la double compilation depuis le début si nous voulons tout attraper: dis nommé :.

EDIT : Attendez, à quoi je pense même? Il nécessite un type de caisse personnalisé, la double compilation s'applique aux caisses _régulières_ que _aussi_ exportent des macros procédurales / attributs / dérivent / etc. Donc pas pertinent pour le moment.
Mais nous pourrions au moins introduire #[cfg(proc_macro)] qui est toujours défini pour le nouveau type de crate.

🔔 Cette fonctionnalité entre dans sa dernière période de commentaires avec l'intention de se stabiliser à la fin de ce cycle de publication! 🔔

La justification pour envisager la stabilisation maintenant est:

  • La surface API de cette fonctionnalité est, de par sa conception, _extrêmement_ conservatrice. En même temps, il est compatible avec le plan de facto pour les macros procédurales
  • La fonctionnalité est rapidement largement utilisée via Serde, et bientôt Diesel - bien que, notamment, l'utilisation répandue soit en grande partie comme un client de dérive personnalisée.
  • Entrer dans FCP nous donne maintenant trois mois avant que la fonctionnalité ne soit réellement livrée dans stable, ce qui devrait être suffisamment de temps pour détecter les problèmes restants.

Notez que le nom passe à proc_macro , basé sur un consensus antérieur sur ce fil. Nous pouvons continuer à faire du vélo ceci et tout autre point fin pour le reste de ce cycle de publication.

Compiler la caisse deux fois semble très grossier: la portée ne fonctionnera pas correctement. Vraiment quelque chose comme extern! crate needed_for_my_inline_proc_macros; est une solution beaucoup plus agréable.

@aturon quoi, les gens n'ont-ils pas commencé à utiliser il y a quelques jours?

@ Ericson2314 Cela fait un peu plus longtemps que cela, mais comme l'explique le commentaire du FCP, le temps _minimum_ avant l'expédition vers le canal stable est de trois mois à partir de maintenant, ce qui nous semble plus que suffisant pour cette interface extrêmement étroite.

Notez que le FCP lui-même est une affaire de 6 semaines qui ne signifie pas nécessairement que nous le mettrions même sur la voie de la stabilisation. Mais en commençant au moins le processus maintenant, nous créons l'opportunité de livrer cette fonctionnalité en 3 mois, en supposant qu'aucun problème ne soit découvert avant cela.

Ah ok. Je me suis souvenu de la partie des 3 mois, mais j'ai oublié l'emplacement du FCP dans ces 3 bouches - de peur que ce ne soit à 3 mois du 22 août. Peu importe le moment.

Je pense que les problèmes de phasage que j'ai soulevés ont un impact même sur cette petite partie de l'histoire des macros proc, alors j'aimerais que ces problèmes soient résolus.

@alexcrichton les rustc_copy_clone_marker et les autres rustc_attrs nous mordent toujours. Voir https://github.com/serde-rs/serde/issues/577.

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

La solution de contournement consiste à échanger la commande, mais j'ai pensé que je vérifierais si cela pouvait être réparé.

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

J'ai fini de porter https://github.com/sfackler/rust-postgres-derive vers des macros 1.1 C'était relativement simple, mais la gestion des attributs lus par plusieurs dérivées est une énorme douleur. Serde a rencontré la même chose, mais cela nécessite un tas de logique étrange pour développer les deux dérivés en même temps: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-internals/ src / lib.rs # L26

Cela ne devrait pas bloquer la stabilisation, mais je pense que c'est un problème ergonomique relativement important du côté de la paternité que nous devrions probablement aborder bientôt.

J'ai publié quote 0.3.0 avec le support des répétitions de style macro_rules! (merci @nrc pour le coup de

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

Je suis curieux, pourquoi # et non $ ?

EDIT : J'ai toujours pensé que la syntaxe des guillemets serait ${...} mais peut-être que je suis trop attaché aux littéraux basés sur ES6 même si cela fait plusieurs années. \{...} fonctionne aussi bien que cela soit utile ailleurs.
Aucune parenthèse ne semble difficile à repérer mais je ne devrais pas m'inquiéter.

Je suis curieux, pourquoi # et non $ ?

Parce que c'est une macro macro_rules et que je ne peux pas faire ce que je veux: smile :. Un $v est comparé comme un seul jeton et il n'y a aucun moyen de passer de $v à l'utilisation de la variable v . En revanche, #v sont deux jetons afin que je puisse les faire correspondre séparément et faire des choses avec l'ident.

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

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

Attendez que tout cela soit fait en macro_rules ?! Les macros oubliées 1.1 n'ont été dérivées que pendant un moment. Cela dit, $x étant un jeton est un défaut de conception IMO. cc @jseyfried

@dtolnay

@alexcrichton, le rustc_copy_clone_marker et d'autres rustc_attrs nous mordent toujours. Voir serde-rs / serde # 577.

Oh mon Dieu, cela semble mauvais! Je mettrai à jour la liste en haut. Des réflexions sur @jseyfried sur la façon dont nous pourrions y remédier Je ne connais pas très bien l'ordre d'expansion de tout, mais peut-être que lorsqu'un attribut #[derive] est en cours d'extension, il pourrait essayer de prendre tous les futurs et de le faire en premier?

@sfackler

la gestion des attributs lus par plusieurs dérivées est une énorme douleur

Je ne suis pas sûr d'avoir bien suivi l'exemple auquel vous avez fait allusion, pourriez-vous élaborer?

@alexcrichton Prenons par exemple

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

L'attribut #[postgres] est utilisé pour ajuster la façon dont les implémentations ToSql et FromSql sont générées. Il doit être supprimé avant la sortie finale car il s'agit autrement d'un attribut inconnu, mais les implémentations ToSql et FromSql sont exécutées séparément. Si vous le faites de manière naïve en générant simplement l'implémentation, puis en supprimant les attributs, la deuxième dérivation manquera les personnalisations.

serde-derive et postgres-derive piratent ce problème dès maintenant en envoyant l'implémentation des deux impls dérivés à la même fonction qui génère les deux à la fois. Nous devons rattacher le #[derive] pour celui qui est actuellement appelé depuis que le compilateur le supprime, puis l'envoyer pour qu'il soit développé: https://github.com/sfackler/rust-postgres-derive/blob /master/postgres-derive/src/lib.rs#L18

@sfackler Je pense que vous pouvez également le faire en supprimant les attributs supplémentaires uniquement lorsqu'il n'y a plus de dérivé du même implémenteur. Votre chemin pourrait être mieux _shrug_.

@sfackler ah ok, c'est parfaitement logique. Merci!

Je me demande si cette logique peut être fragile cependant. Par exemple, si vous développez ToSql comment l'expansion future de FromSql détectée? Cela pourrait être derrière un #[cfg] comme @dtolnay mentionné ci - - être? Donc, il n'est peut-être pas possible de détecter dans tous les cas?

@alexcrichton Oui, c'est cassant, c'est pourquoi une vraie solution semble importante.

Est-ce que #[cfg] et #[cfg_attr] seraient pas traités avant les dérivations?

Étant donné que l'api des macros 1.1 fonctionne sur des chaînes, je ne peux imaginer que trois solutions:

  1. laissez-le tel quel et attendez les macros 2.0
  2. autoriser les attributs inutilisés dans la dérivation personnalisée
  3. étendre les macros 1.1 api en permettant à une dérivée personnalisée de pousser les noms d'attributs vers une liste blanche spécifique pour l'élément actuel

Chaque option a ses avantages / inconvénients.
con 1: solutions de contournement fragiles pour le moment
con 2: certains attributs inutilisés ne seront pas détectés
con 3: plus de surface api pour une solution provisoire

J'ai envisagé d'encapsuler des attributs dans #[used(...)] pour les conserver et les mettre en liste blanche en même temps, mais c'est assez stupide et trop insta-stable.

@alexcrichton

quand un attribut #[derive] est en cours d'extension, il pourrait essayer de récupérer tous les futurs

J'aime cette idée. Puisque #[cfg] s et et #[cfg_attr] s sont traités avant #[derive] s, #[cfg] -guarded #[derive] s ne sont pas un problème pour cette approche (ou pour la solution analogue de @sfackler au suppression des attributs).

Cette approche rendrait la solution de @sfackler légèrement plus simple, puisque d'autres suggestion de @eddyb simplifierait les choses).

Une possibilité pour le problème des attributs de contrôle est d'ajouter des rappels post-expansion, un peu similaires à ceux de syntex: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-codegen/src/lib. rs # L23 -L50

Vos impls de dérivation peuvent être indépendants et ne pas supprimer les attributs de contrôle, et vous pouvez enregistrer une passe qui s'exécute une fois que toute l'expansion est terminée pour nettoyer.

Diesel travaille également manuellement autour de cela. https://github.com/diesel-rs/diesel/blob/master/diesel_codegen/src/lib.rs#L101 -L112

@dtolnay votre préoccupation précédente concernant plusieurs attributs #[derive] devrait être corrigée sous peu grâce à @jseyfried

J'ai joué avec cela en utilisant les packages syn et quote , et j'ai eu une expérience vraiment positive. Ce sera une excellente fonctionnalité quand il se stabilisera.

Le problème avec les attributs mord actuellement mon impl, étant en aval de la logique de dérivation de serde s.

Ignorer les attributs inutilisés dans le code dérivé ou autoriser les extensions tardives qui s'exécutent après les dérivations régulières pour les supprimer me semble être une solution raisonnable.

Ce serait idéal si les macros n'avaient pas la possibilité de muter le flux de jetons d'origine, cela semble être une fonctionnalité dangereuse qui pourrait finir par être utilisée à l'état sauvage entre maintenant et les macros 2.0 et donc difficile à supprimer plus tard.

J'ai donc eu un point-virgule manquant dans un chemin de type à l'intérieur d'une fonction générique et j'ai reçu le message d'erreur suivant de rustc. Je me demande si plus d'informations pourraient être fournies, comme quelle macro dérivée a causé l'erreur lex, quel jeton l'a provoquée, etc.

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

J'ai fait un cas de test pour le LexError, cela semble se produire avec la nuit du 13 octobre (rustup n'avait pas de mises à jour) https://github.com/keeperofdakeys/proc_macro_derive_test.

@ rust-lang / lang Je pense que nous devons discuter de la question de savoir si les dérivés peuvent modifier leur élément annoté. Bien que ce soit une solution simple pour le permettre, cela semble aller à l'encontre des attentes des utilisateurs et semble assez impopulaire. Je préfère toujours la solution actuelle - la simplicité est agréable, et aucune des alternatives proposées ne me parait follement meilleure.

Nous aurons besoin d'une solution pour contrôler les attributs si l'élément peut être modifié. Je ne pense pas que je sois particulièrement convaincu de conserver la possibilité de dériver de modifier l'élément, aussi longtemps que (à l'avenir) les macros d'attributs non dérivées le pourront.

Quelqu'un devrait définir «modifier». Parlons-nous de la modification des membres de la structure, ou parlons-nous simplement de la suppression des attributs afin d'empêcher le compilateur de se plaindre d'attributs inconnus?

Si nous parlons de modifier les membres de la structure, à mon avis, la meilleure solution est probablement de n'autoriser qu'une seule macro de modification de structure sur chaque structure qui est développée avant toutes les autres. Cela aurait un comportement bien défini et (à mon avis) attendu.

Si nous ne parlons que de suppression des attributs, il devrait probablement y avoir un moyen d'intégrer les attributs dans le mécanisme des macros lui-même au lieu de laisser cela à la discrétion de la macro.

Mon intuition sur le comportement de derive est qu'il ajoute des impls. Pour l'utilisateur final, les dérivés devraient sembler ajouter des impls et ne rien faire d'autre. En particulier, ils ne devraient pas modifier la structure d'une manière qui importera aux autres dérivateurs, et par conséquent, ils devraient être indépendants de l'ordre. Sommes-nous d'accord pour dire que c'est ainsi qu'un dérivé doit se comporter, ou est-ce que les gens pensent que dérivé devrait également être capable d'effectuer des transformations sur le type associé?

Si nous sommes d'accord sur cela, la question devient: voulons-nous que l'interface que nous exposons applique cela, ou voulons-nous laisser le soin aux auteurs de faire respecter cela? Ici, il semble y avoir deux problèmes compensatoires:

  • L'application du contrat comportemental de derive me semble certainement plus être la solution Rust.
  • Faire travailler serde et deisel avec cette contrainte présente de nombreux défis.

Et bien sûr, la façon dont d'autres attributs peuvent modifier la structure ne semble pas liée à la façon dont derive, en particulier, devrait se comporter.

Il convient de noter que la possibilité de supprimer l'élément annoté permet au système actuel de remplir de nombreux rôles qu'il ne le ferait pas autrement.

@sgrif Pourriez-vous nous expliquer comment deisel utilise derive? J'ai l'impression de comprendre comment serde utilise sa capacité à modifier la structure (pour supprimer les attributs qui indiquent à la macro d'omettre ou de renommer des champs dans le trait de sérialisation), mais peut-être que deisel l'utilise pour faire autre chose. Lorsque vous dites "supprimer l'élément annoté", il semble que vous effectuez une transformation assez radicale sur l'élément.

@withoutboats 90% de celui-ci est plus ou moins ce à quoi vous vous attendez. Nous ne touchons pas aux articles fournis par l'utilisateur. Nous supprimons les éléments annotés pour pirater les macros bang dans le système. https://github.com/diesel-rs/diesel/blob/master/diesel/src/macros/macros_from_codegen.rs#L12 -L18. Au-delà de cela, le seul moment où nous touchons quelque chose sur le flux de jetons d'entrée est de supprimer les annotations. https://github.com/diesel-rs/diesel/blob/master/diesel_codegen/src/lib.rs#L109 -L120

@sgrif J'ai également abusé du dérivé personnalisé pour obtenir des macros de bang procédurales. Personnellement, je préférerais de beaucoup qu'il y ait un système de macros procédurales dans les macros 1.1 de sorte que nous ne soyons pas tentés d'abuser de cette fonctionnalité, parce que c'est grossier. Je pense qu'une bonne voie à suivre serait également d'obtenir une histoire de macro de bang procédurale presque identique (potentiellement avec une hygiène lourde mais simple, comme tous les identifiants non présents dans le TokenStream transmis à la macro sont cachés avec hygiène? I ne sais pas exactement à quoi cela ressemblerait) et utilisez-le au lieu d'abuser de la dérivation personnalisée, puis faites en sorte que la dérivation personnalisée ne puisse pas modifier la structure sur laquelle elle se trouve. De cette façon, nous activons encore plus de caisses, améliorons ux et rendons le dérivé sain d'esprit.

Cependant, je peux comprendre l'argument pour garder derive comme une simple macro.

Mon point de vue est que l'objectif de Macros 1.1 est d'être aussi flexible que possible pour combler autant de besoins que possible, avec un faible fardeau de maintenance afin qu'il puisse être stabilisé rapidement et agir comme une étape jusqu'aux macros 2.0. Le design actuel correspond extrêmement bien à ce rôle à mon avis.

Si nous parlions de quelque chose qui devait remplir ce rôle en permanence, je serais beaucoup plus opposé à cela

Peut-être que je me trompe, mais ma lecture de la RFC est que cela est destiné à être la base du comportement de dériver en permanence. Autrement dit, plus de méthodes seront ajoutées à TokenStream à l'avenir, mais les macros dérivées utiliseront cette API, qui leur permet actuellement d'effectuer des mutations arbitraires sur l'élément annoté (et cette capacité est nécessaire pour le cas d'utilisation de deisel ).

Je me sens assez négativement à l'idée de permettre aux dérivés de le faire de manière permanente. Si nous acceptons qu'il s'agit d'un système qui va être obsolète, avec les macros macro_rules , à un moment donné dans le futur, et sous les macros 2.0, une API de dérivation différente qui est plus restreinte sera préférée, je suis plus à l'aise avec elle.

Il semble que l'intention soit de soutenir les décorateurs comme des transformateurs capables de tout faire.
Et dériver exposé comme un simple décorateur sans entrée dans l'attribut.

@withoutboats : serde prend en charge un certain nombre d' attributs pour modifier la façon dont les impls sont générés, et nous avons donc certainement besoin de la possibilité de les supprimer ou de les ignorer après les avoir traités. Si cela peut aider, nous pourrions en quelque sorte fournir une liste d'attributs au compilateur qui devraient être supprimés, plutôt que de vouloir le faire nous-mêmes.

@eddyb Je suis pour que les décors puissent tout faire, mais pas pour que les dérives soient des décorateurs effrénés (sur le long terme).

@erickt C'est vrai. Je pense qu'à long terme, la solution idéale serait que ces attributs soient enregistrés en tant qu'attributs personnalisés sans opération, au lieu que le dérivateur soit responsable de les supprimer. Mais ce n'est pas faisable à court terme.

Je pense qu'à long terme, la solution idéale serait que ces attributs soient enregistrés en tant qu'attributs personnalisés sans opération, au lieu que le dérivateur soit responsable de les supprimer. Mais ce n'est pas faisable à court terme.

Je ne suis pas familier avec les composants internes du compilateur qui rendent cela irréalisable à court terme, mais serait-il possible pour le plugin de dérivation personnalisé de présenter une liste d'attributs personnalisés qu'il a l'intention de supprimer, puis de rejeter toute autre transformation sur les attributs sur le article original?

Je remarque également que Diesel ne suit pas l'approche de Serde consistant à avoir tous ses attributs personnalisés sous un seul nom (par exemple #[serde(rename = "name")] par opposition à #[table_name = "name"] de Diesel.) Simplifierait-il la mise en œuvre si un seul nom d'attribut personnalisé a été enregistré au lieu d'une liste?

Une possibilité pour le problème des attributs de contrôle est d'ajouter des rappels post-expansion, un peu similaires à ceux de syntex: https://github.com/sfackler/rust-postgres-derive/blob/master/postgres-derive-codegen/src/lib. rs # L23 -L50

Vos impls de dérivation peuvent être indépendants et ne pas supprimer les attributs de contrôle, et vous pouvez enregistrer une passe qui s'exécute une fois que toute l'expansion est terminée pour nettoyer.

J'ai implémenté des rappels post-expansion pour les macros 1.1 dans post-expansion . Vos impls de dérivation peuvent être indépendants et ne pas supprimer les attributs de contrôle, et vous pouvez enregistrer une passe qui s'exécute une fois que toute l'expansion est terminée pour supprimer les attributs.

Nous avons discuté du problème de modification / commande aujourd'hui lors de la réunion de l'équipe lang. Il y avait une volonté de répondre aux attentes des utilisateurs, et pour la simplicité (en termes de conception, ne pas avoir trop de hacks superposés, et ne pas être trop sujet à des erreurs difficiles à déchiffrer / déboguer). Il a été noté que même si la modification des données cibles est surprenante, elle n'est pas dangereuse. On sentait aussi que nous «pourrions» tendre vers la pureté pour elle-même, plutôt que pour des raisons bien motivées.

En fin de compte, nous avons décidé que les dérivations qui ne modifient pas la source sont probablement meilleures et nous devrions passer à ce modèle. Cela pourrait signifier prolonger la période du FCP. Nous n'avons pas pensé qu'il devrait y avoir un mécanisme spécial pour traiter les attributs de dépouillement. Au contraire, la façon dont le compilateur gère les attributs devrait permettre à ceux utilisés par une macro de rester dans le programme. La RFC 1755 doit en tenir compte.

Cela retardera la stabilisation de certains utilisateurs de dérivées personnalisées. Cependant, nous pensons que la plupart des utilisations de dérivées (en particulier celles qui empêchent les utilisateurs d'une chaîne d'outils stable) n'utilisent pas d'attributs, donc par exemple, la plupart des utilisateurs de Serde pourront bientôt passer à stable. Ceux qui ont besoin d'attributs prendront quelques cycles plus longs _ mais cela n'affectera pas le cas commun_.

cc @alexcrichton , @dtolnay , @sgrif , @erickt - des pensées?

Les attributs sont probablement plus couramment utilisés avec Serde et Diesel que vous ne le suggérez. D'après ma propre expérience, je suis presque sûr que tous mes programmes qui utilisent Serde utilisent des attributs. Avec Diesel, j'utilise définitivement des attributs, et je pense que c'est nécessaire pour indiquer à diesel_codegen quelle table de base de données correspond à la structure.

Cela dit, ne pas laisser la coutume dériver muter la structure me semble être le bon choix. Cela simplifie simplement le tout, évitant ainsi de nombreux cas extrêmes. Il est plus important de bien faire les choses que de le faire rapidement, donc si la fonctionnalité doit rester instable un peu plus longtemps, cela semble bien aussi.

Il a été noté que même si la modification des données cibles est surprenante, elle n'est pas dangereuse.

Ce n'est pas dangereux, sauf si vous dérivez un trait dangereux.

À mon avis, l'un des cas d'utilisation des dérivés personnalisés est d'implémenter de manière sûre des traits marqués comme dangereux, comme par exemple un trait qui doit décrire exactement la disposition des membres de la structure sur laquelle il est implémenté.

Ma caisse asn1 nécessite également des attributs pour tout sauf une utilisation triviale, donc je devrais effectivement attendre que les attributs personnalisés arrivent.

Serait-ce une bonne idée de diviser les attributs personnalisés rfc en deux?

  1. Un rfc pour fournir la notation et l'espacement des noms pour les attributs personnalisés en général (autorisant les attributs no-op dans stable).
  2. Un rfc pour savoir comment définir et la sémantique autour de l'exécution de macros d'attributs personnalisés.

Les macros d'attributs personnalisés semblent être quelque chose qui nécessitera beaucoup si elles se développent. Ainsi, diviser le rfc en deux peut fournir des attributs stables pour les packages de dérivation personnalisés plus tôt.

Cela signifie également que les attributs sont espacés par nom et intentionnels (c.-à-d. Extern crate est nécessaire pour utiliser des attributs pour un crate). Je peux prévoir que c'est une bonne chose pour empêcher deux macros de dérivation personnalisées utilisant le même nom d'attribut. Une bonne attente ici serait d'utiliser le nom de la caisse dans laquelle se trouve le trait pour les attributs.

Y a-t-il une raison pour laquelle cette implémentation ne comprend pas les macros name! habituelles? Simple TokenStream in, TokenStream out pour les macros habituelles s'avérerait extrêmement utile dans pest où les temps de compilation pour les grammaires complexes dépassent 30s.

@dragostis Pour citer le résumé du rfc:

Extrayez un tout petit fragment du système de macros procédurales actuel dans le compilateur, juste assez pour obtenir des fonctionnalités de base comme le dérivation personnalisée, pour avoir une API finalement stable. Assurez-vous que ces fonctionnalités ne représentent pas un fardeau de maintenance pour le compilateur, mais n'essayez pas non plus de fournir suffisamment de fonctionnalités pour le «système macro parfait» en même temps. Dans l'ensemble, cela devrait être considéré comme une étape progressive vers une "macros 2.0" officielle.

Ou en termes plus pratiques, il faudra beaucoup de conception et de tests pour obtenir un système macro procédural qui fonctionne bien, tout comme le système de fermeture que nous avons maintenant pris pour construire. Les caisses comme serde et diesel ont de graves problèmes d'utilisabilité sans une fonction de dérivation personnalisée appropriée, alors faisons une mesure provisoire pour le résoudre maintenant - cela se produit également pour aider l'outillage et l'expérience d'un possible macro système procédural. Les caisses syn et quote sont de bons exemples.

@keeperofdakeys Oui, j'ai compris. Je ne demande pas une implémentation de macros 2.0, je me demande simplement s'il y a un fardeau à ajouter pour ajouter un autre attribut, disons proc_macro qui est une implémentation minimale qui reflète simplement la conception dérivée uniquement pour les macros habituelles. L'exemple de pest dériverait simplement un analyseur pour un struct seulement, il ne le dériverait pas de struct lui-même mais d'une grammaire simple définie entre {} . J'espère cependant que je ne suis pas en train de creuser des discussions mortes!

@nrc

On a également estimé que nous tendions peut-être à la pureté pour elle-même, plutôt que pour des raisons bien motivées.

Une remarque à ce sujet: dans de nombreux cas où nous avons dû prendre des décisions difficiles, comme la paramétrie et la spécialisation, garantir statiquement la notion pertinente de «pureté» serait difficile / impossible. Mais dans ce cas, il est en fait très simple de le garantir par construction, et éliminer l'importance de la commande pour les dérivés semble être une simplification assez forte pour les utilisateurs.

En ce qui concerne la stabilité, nous pourrions envisager d'ajouter un mécanisme instable pour ignorer les attributs pour le moment, qui serait stable dans la pratique (en ce sens que l'API ne serait pas sujette à la casse) même si elle nécessite toujours une utilisation nocturne. Nous pourrions même envisager de stabiliser un tel canal secondaire, avec des projets de le déprécier au profit d'un mécanisme plus général une fois disponible. Ou nous pourrions considérer la suggestion de @keeperofdakeys et pousser rapidement sur la partie de la solution des attributs généraux qui répondrait au problème immédiat.

De mon point de vue, il est important qu'aucune de ces préoccupations n'empêche de manière significative les macros 1.1 d'être largement utilisables pour Serde et au moins "de facto" stables (c'est-à-dire ne pas casser).

@sgrif

Si nous parlions de quelque chose qui devait remplir ce rôle en permanence, je serais beaucoup plus opposé à cela

Je voulais faire écho à @withoutboats et préciser que l'intention actuelle est que la surface de l'API des macros 1.1 reste telle

Mon impression de l'utilisation de l' attribut sur Derive personnalisé était similaire à l » @jimmycuadra impression , ce qui est qu'ils sont assez couramment utilisés. Je crois (mais corrigez-moi si je me trompe) que les attributs personnalisés étaient cruciaux pour un certain nombre de cas d'utilisation du diesel.

En ce sens, je ne suis pas sûr à 100% de la meilleure façon d'avancer au sujet de ces attributs. Il serait dommage de stabiliser les macros 1.1 tout en laissant un grand nombre d'utilisateurs allumés tous les soirs (même si c'est "de facto stable" tous les soirs) car cela va quelque peu à l'encontre de l'objectif de stabilisation rapide des macros 1.1 en premier lieu. En d'autres termes, si nous ne tirons pas la majorité des utilisateurs de macros 1.1 sur Rust stable, je pense que la stabilisation des macros 1.1 peut attendre.

Un point qui me dérange cependant, c'est à quoi nous pensons que l'attribut personnalisé ressemblera à long terme et en le rationalisant avec le système actuel de macros 1.1. Dans la RFC actuelle pour les attributs personnalisés, les caisses doivent déclarer les espaces de noms des attributs sur la liste blanche en haut d'une caisse. Cela signifie que l'utilisation d'un attribut personnalisé dans la dérivation personnalisée est radicalement différente de l'utilisation d'un attribut personnalisé ailleurs. Cette déconnexion m'inquiète en termes de perspective ergonomique et de «moindre surprise», bien que je vois aussi cela comme une fonction de forçage pour peaufiner le RFC (par exemple, si je lie les attributs de serde serde ).

Dans l'ensemble, je serais personnellement très bien d'aller de l'avant avec la stabilisation complète du système macros 1.1 tel quel aujourd'hui. Ou en d'autres termes, stabilisez le fait que les fonctions d'expansion de dérivation personnalisées reçoivent une structure et doivent ensuite renvoyer la structure si elles veulent qu'elle soit préservée.

J'ai tendance à passer de rustc-serialize à serde _ parce que_ la génération de code de serde prend en charge les attributs de contrôle.

Nous pourrions étendre les macros 1.1 pour prendre en charge les arguments de l'attribut derive lui-même. Cela semble être une bonne chose à avoir en général, et il pourrait être abusé de contourner le manque d'attributs de contrôle à court terme potentiellement.

Idem @jimmycuadra et @sfackler , les attributs font plus partie intégrante de Serde que le commentaire de

Je n'ai pas encore d'opinion sur la bonne décision ici, mais je sais que si nous nous stabilisons sans attributs, j'envisagerais fortement d'analyser les commentaires de doc pour extraire les attributs. J'imagine que beaucoup de gens ne voudraient pas traiter un script de construction juste pour pouvoir écrire:

#[serde(skip_serializing)]

.. au lieu de:

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

@tomaka

À mon avis, l'un des cas d'utilisation des dérivés personnalisés est d'implémenter de manière sûre des traits marqués comme dangereux, comme par exemple un trait qui doit décrire exactement la disposition des membres de la structure sur laquelle il est implémenté.

Que pensez-vous de mon commentaire ici à cet égard? J'ai trouvé que c'était un modèle satisfaisant.

Je veux clarifier une chose:

La suppression des attributs personnalisés est-elle la principale chose que les gens font avec la dérivation personnalisée?

Je sais qu'il y a des hacks pour faire foo! expansion @sgrif a mentionné un tel cas d'utilisation dans le diesel, je pense que @tomaka en a mentionné un aussi) - à quel point ces cas d'utilisation sont-ils centraux ?

La raison pour laquelle je pose la question est la suivante: je vois des avantages à ce que le mécanisme de dérivation ne renvoie qu'une liste d'implémentations supplémentaires. En particulier, cela signifie que les étendues pour la déclaration de type elle-même seraient correctes. Ajouter une API rapide et sale au contexte qui vous permet de fournir une liste de noms d'attributs à ajouter à la liste blanche dans le contexte de ce type semble être une solution assez simple pour les attributs, et nous pourrions toujours la déconseiller.

Si toutefois nous voulons activer plus de cas d'utilisation (et franchement ces cas d'utilisation sont un peu plus ... surprenants, quand vous pensez à ce que l'on attend de dériver), alors cela ne fonctionnera pas. Dans ce cas, je serais probablement d'accord avec la stabilisation telle quelle et la planification de désapprouver l'API "from raw bytes" à l'avenir en faveur d'une meilleure façon d'écrire derive (après tout, nous ne nous attendons pas vraiment à ce que les gens soient en utilisant quand même des octets bruts, n'est-ce pas? mais plutôt des flux de jetons?) Nous pourrions peut-être donner à l'API un nom un peu plus maladroit =)

@nikomatsakis Le besoin principal n'est pas de supprimer mais plutôt d'ignorer les attributs. Je ne connais pas de graves inconvénients à les ignorer. Fournir une liste blanche sur la fonction dérivée via un paramètre supplémentaire à l'attribut principal, par exemple, devrait suffire pour tous les besoins pratiques qui sont de vrais dérivés et non des hacks de macro procéduraux.

Oui, je suis déchiré entre deux approches à ce sujet, qui ont toutes deux des inconvénients évidents:

  1. L'approche «dérive simple» - ajustez l'API pour ne produire que des éléments supplémentaires, ce qui rend encore instable pour les utilisateurs d'utiliser des attributs personnalisés dans le cadre de la dérivation et de fermer le trou dans lequel le diesel et d'autres caisses sautent pour obtenir des macros procédurales arbitraires.
  2. L'approche de «l'obsolescence planifiée» - stabilise l'API telle quelle, avec de petits ajustements peut-être comme Niko l'a mentionné à propos de la dénomination, avec l'intention de la déconseiller un jour. Cela obligera tous les auteurs de dérivations personnalisées à réécrire un jour leur code, et permettra la possibilité d'un comportement surprenant pendant la période intermédiaire.

EDIT: mais la liste blanche de @eddyb semble également prometteuse.

Je veux dire que ce n'est pas comme si @nrc proposait quelque chose de très différent (bien que l'IIRC parle de liste blanche dans la caisse de l'utilisateur), et ça devient ridicule de parler de "marquer les attributs comme utilisés" quand tout ce que vous avez est un jeton.

Les macros procédurales arbitraires pour lesquelles j'ai abusé du système de macros 1.1 dans rust-cpp seraient totalement possibles avec une approche alternative qui offre simplement la possibilité d'ajouter des impls et d'ignorer les attributs.

Je pense qu'avoir la capacité d'ignorer les attributs est essentiel, mais à part cela, je serais bien de ne pas pouvoir modifier la structure au-delà de ce point.

Je ne suis pas sûr des formes de hacks que @sgrif utilise dans deisel et qu'il ne serait pas possible de faire dans un monde où nous ne pouvons pas modifier la structure elle-même, et à la place ne pouvons ajouter que des éléments supplémentaires.

Permettre aux dérivés personnalisés de prendre des arguments, comme suggéré par

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

Ce formulaire pourrait plus tard être désapprouvé au profit des attributs personnalisés, une fois qu'une décision à leur sujet est prise.

Serde et ma caisse nécessitent des attributs de champ / variante. Essayer d'imiter cela avec des arguments de dérivation n'a pas beaucoup de sens, nous avons besoin d'attributs.

Quelle que soit la décision que nous prenons pour stabiliser cela, ce serait bien si l'utilisateur des macros de dérivation personnalisées n'a pas besoin de modifier son code lorsque / si nous basculons vers une API dérivée de macros 2.0 (évidemment les auteurs des macros de dérivation personnalisées le feront). Il semble que la décision la plus judicieuse soit de donner au compilateur une liste d'attributs à supprimer pour votre dérivation, et de manière critique, ceux-ci ne sont supprimés qu'après l'exécution des macros _all_ derive. Serde, diesel et ma caisse ont tous le problème d'exiger le même attribut sur plusieurs macros de dérivation.

Avec le comportement de décapage, je n'aurais pas besoin de la caisse de post-expansion créée par dérivation, pour

FWIW, la seule raison de supprimer ces attributs est de garder le HIR plus mince - en ce qui concerne tout le reste, il vous suffit de les marquer comme utilisés.

La suppression des attributs personnalisés est-elle la principale chose que les gens font avec la dérivation personnalisée?

Je sais qu'il y a des hacks à faire foo! l'expansion dans le contexte d'un type (par exemple, @sgrif a mentionné un tel cas d'utilisation dans le diesel, je pense que @tomaka en a également mentionné un) - à quel point ces cas d'utilisation sont-ils centraux?

Je suis tout à fait d'accord avec le fait que les dérivations personnalisées ne peuvent pas modifier la structure.

Je me plaint souvent du manque de plugins dans Rust stable, alors quand j'ai vu des dérivations personnalisées, je les ai utilisées comme une opportunité d'avoir des plugins. Mais je ne vais évidemment pas affirmer que les dérivés personnalisés doivent prendre en charge quelque chose pour lequel ils n'ont pas été conçus.

On dirait qu'il y a une régression dans le dernier soir. Obtenir l'erreur

Queryable est un mode de dérivation

lors de la compilation de nos exemples.

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

@sgrif Cela a été causé par # 37198, qui a changé les dérives personnalisées pour utiliser le même espace de noms que les autres macros (de sorte que les macros bang!() macros #[attribute] macros #[derive(custom)] partagent toutes le même espace de noms).

Dans cet exemple, #[macro_use] extern crate diesel; importe une macro bang nommée Queryable et #[macro_use] extern crate diesel_codegen; importe une macro de dérivation personnalisée également nommée Queryable , qui écrase silencieusement la macro bang (à part - #[macro_use] écrasement silencieux n'est pas idéal, mais ne sera pas un problème une fois que nous pourrons importer des macros depuis des caisses externes avec use au lieu de #[macro_use] , bientôt!).

Je crois que l'erreur est causée par un appel de macro bang Queryable!(); dans l'expansion du dérivé personnalisé, qui résout le dérivé personnalisé de diesel_codegen au lieu de la macro bang de diesel .

@jseyfried Quelle est la raison d'utiliser un seul espace de noms pour les trois "types" de macros? Cela ne me semble pas logique de traiter les attributs et les macros bang comme occupant le même espace de noms, pas plus qu'il ne serait logique de traiter les fonctions et les types comme occupant le même espace de noms (dériver encore moins les macros).

@withoutboats Je pense que ce n'est pas la

Le vrai problème est que vous ne pouvez pas utiliser use pour importer ou renommer des macros de manière sélective. Ainsi, un utilisateur de crate n'a pas la capacité de contourner les conflits de noms de macro. En même temps, je ne m'attendrais pas à ce que l'importation d'une caisse ProcMacro soit en conflit avec les noms de macro locaux - je pensais que les macros de dérivation commençaient par derive_ ?

Il y a un coût de complexité à avoir des espaces de noms et je pense que moins nous en avons, mieux c'est, alors la question devrait être de savoir pourquoi avons-nous besoin d'espaces de noms différents?

Je pense que les éléments ne devraient partager un espace de noms que s'ils peuvent être ambigus les uns avec les autres. consts et fns sont dans le même espace de noms car ils sont tous deux utilisés comme idents dans un contexte d'expression. Les types et les traits sont dans le même espace de noms en raison de la syntaxe des objets de trait.

Ces trois types de macros sont toutes invoquées de manière distincte. Cela n'a pas de sens pour moi qu'ils partagent un espace de noms, car il ne peut jamais y avoir d'ambiguïté dans l'invocation.

Bien qu'il y ait un coût de complexité d'implémentation, je ne pense pas qu'il y ait un coût significatif dans la complexité de _utilisation_ en les espaçant séparément. Quand je pense au coût de complexité d'une fonctionnalité de langage, je pense généralement à la complexité d'utilisation.


Diriez-vous que _tous_ les éléments devraient être dans le même espace de noms s'il ne s'agissait pas d'un changement radical? Essayer de clarifier votre processus de pensée.

En réfléchissant un peu plus, je serais bien de dire qu'il ne devrait y avoir qu'un seul espace de noms dans lequel tous les éléments se trouvent - je le préfère même en quelque sorte; l'ensemble du modèle de "vrais constructeurs" est un peu déroutant pour l'OMI - mais ce n'est pas la décision prise par Rust pour les éléments non macro, donc il me semble qu'il serait plus cohérent et attendu que les éléments macro occupent différents espaces de noms.

Le vrai problème est que vous ne pouvez pas utiliser use pour importer ou renommer des macros de manière sélective.

Vous pourrez le faire avec les macros 2.0. Le modèle ici est essentiellement un hack provisoire.

Je pensais que les macros de dérivation commençaient par derive_?

C'était l'ancien système de dérivation personnalisé, je ne pense pas que les macros 1.1 le permettent.

Bien qu'il y ait un coût de complexité de mise en œuvre, je ne pense pas qu'il y ait un coût significatif en complexité d'utilisation

Il y a un coût pour une certaine définition de l'utilisation - cela rend la vie plus difficile pour les outils, en particulier (bien que l'on puisse ne pas discuter beaucoup). Je pense aussi que ce n'est pas tant la complexité de la mise en œuvre qui compte (bien que les espaces de noms compliquent définitivement le compilateur, je conviens que ce n'est pas si important) que la complexité du langage - les utilisateurs doivent raisonner à ce sujet pour utiliser Rust et cela a un effet d'entraînement effet à des choses comme l'hygiène et les ombres, compliquant encore les choses.

Diriez-vous que tous les éléments devraient être dans le même espace de noms s'il ne s'agissait pas d'un changement de rupture? Essayer de clarifier votre processus de pensée.

Je n'irais pas aussi loin - je pense qu'il y a des avantages à séparer les valeurs et les types, mais il est certain qu'un langage avec un seul espace de noms serait beaucoup plus agréable à utiliser à bien des égards.

mais ce n'est pas la décision prise par Rust pour les éléments non macro

Contrepoint: les modules et les champs sont dans l'espace de noms de valeur, bien que les endroits où ils peuvent être utilisés soient (je pense) distincts des endroits où d'autres valeurs peuvent être utilisées.

@keeperofdakeys

Le vrai problème est que vous ne pouvez pas utiliser use pour importer ou renommer des macros de manière sélective

Nous pourrons bientôt use macros à partir de extern crate s derrière une fonctionnalité gate (~ 1 semaine), cf https://github.com/rust-lang/rfcs/pull/1561 et # 35896. Nous pourrions décider de stabiliser les macros use ing à partir de caisses de dérivation personnalisées avec les dérivations personnalisées elles-mêmes.

Je pensais que les macros de dérivation commençaient par derive_

Cela était vrai pour les dérivés personnalisés à l'ancienne. Avec les macros 1.1 dérivées personnalisées, #[derive(Serialize)] (par exemple) s'attend Serialize ce que

@sans bateaux

Quelle est la raison d'utiliser un seul espace de noms pour les trois "types" de macros?

  • Priorité: les macros bang et les macros d'attributs partagent un espace de noms depuis longtemps, donc jusqu'à ce que cela change, je pense que les dérivations personnalisées devraient également partager cet espace de noms.
  • Compatibilité ascendante: si nous commençons avec un seul espace de noms de macro, nous pouvons le fractionner de manière rétrocompatible. Si nous commençons avec plusieurs espaces de noms de macros, nous sommes coincés avec eux pour toujours.
  • Simplicité: si chaque macro "kind" avait son propre espace de noms, nous aurions cinq espaces de noms au total. Ainsi, après https://github.com/rust-lang/rfcs/pull/1561 land, use foo::bar; pourrait importer _cinq éléments séparés_ nommés bar (une valeur, un type, une macro bang , etc.), et il n'y aurait pas de moyen simple, par exemple, de réexporter la macro bang mais pas la macro de dérivation personnalisée ou simplement d'importer la macro de dérivation personnalisée en tant que baz .

Compatibilité ascendante: si nous commençons avec un seul espace de noms de macro, nous pouvons le fractionner de manière rétrocompatible. Si nous commençons avec plusieurs espaces de noms de macros, nous sommes coincés avec eux pour toujours.

C'est convaincant, d'autant plus que 1.1 est censé être un palliatif. : +1:

Ce code de rupture est-il destiné à tous ceux qui avaient une macro macro_rules appelée par exemple PartialEq! ?

Non, PartialEq n'est pas défini dans l'espace de noms de macro aujourd'hui car il ne s'agit pas d'un dérivé personnalisé.
#[derive(Foo)] vérifie d'abord si Foo est un "dérivé intégré" ( PartialEq , Copy , etc.) avant de rechercher un dérivé personnalisé Foo dans l'espace de noms de macro. Les fonctions intégrées sont codées en dur dans la définition de derive .

Cela étant dit, nous pourrions casser le code comme vous l'avez décrit si nous décidons finalement de faire de PartialEq un dérivé personnalisé dans le prélude au lieu d'une dérivation intégrée, donc cela pourrait être une bonne idée de faire une preuve future pour cela maintenant.

Proposition pour le problème des attributs (en supposant que nous travaillons sur un modèle où les macros dérivées ne peuvent pas modifier les éléments sur lesquels elles sont déclarées, seulement décorer):

  • dans les coulisses, les macros de dérivation personnalisées implémentent un trait, actuellement MultiItemModifier . Il est prévu que les macros 2.0 continueront à implémenter un trait et ce trait sera utilisé pour l'extensibilité du mécanisme. La fonction annotée qui est la macro implémente ce trait. Bien que nous n'utilisions pas le registraire de plugins, il s'agit essentiellement d'une désuétude.
  • nous devrions séparer un trait CustomDerive spécifiquement pour les macros 1.1 dérivées personnalisées. Dans le cas courant, les auteurs de macro ne le voient jamais. Mais ils ont la possibilité d'implémenter le trait directement, plutôt que d'utiliser un attribut sur une fonction (je pense que nous réutiliserions l'attribut sur l'impl, peut-être que nous devons discuter de ce mécanisme d'enregistrement).
  • on ajoute une fonction declare_attributes à CustomDerive qui renvoie Vec<String> . Il a un impl par défaut retournant un vec vide.
  • Si les auteurs de macros implémentent cette méthode, tous les attributs nommés exactement comme l'une des chaînes renvoyées sont considérés comme appartenant à la macro. Un tel attribut n'est jamais recherché comme une macro et ne déclenche pas l'attribut personnalisé lint. Les attributs sont laissés dans le code par développement de dérivation, mais sont marqués comme utilisés. Tout attribut de ce type qui n'est pas touché par une expansion de dérivation déclencherait toujours l'attribut non utilisé lint (mais pas l'attribut personnalisé lint).
  • Nous pourrions à l'avenir introduire des attributs étendus que les macros, y compris les dérivés personnalisés, peuvent utiliser, ceux-ci seraient _en plus_ à declare_attributes et ce mécanisme ne serait pas obsolète.
  • Alternative: les chaînes retournées par declare_attributes sont vérifiées comme préfixe de chemin pour les attributs, par exemple, si declare_attributes retourné vec!["foo::bar"] , alors #[foo::bar::baz] et #[foo::bar::qux] serait autorisé.

Pensées? cc @alexcrichton @jseyfried @dtolnay @sgrif @erickt @ rust-lang / lang

@nrc Ce mécanisme permettrait-il de désactiver les attributs _used_ comme #[cfg(..)] - soit par accident ou volontairement? Et cela pourrait-il être changé?

@nrc, malheureusement, je ne vois pas vraiment à quoi ressemblerait l'implémentation étant donné ces contraintes, je ne suis donc pas tout à fait certain que cela fonctionnerait ou non.

Cela étant dit, je suis quelque peu sceptique quant à savoir si les traits et les objets de trait fonctionneraient, car où sont les instances du trait en cours de création?

@nrc Pourquoi cela doit-il être impératif et ne peut-il pas être simplement une liste dans l'attribut désignant la fonction d'expansion dérivée? Par exemple, #[proc_macro_derive(Serialize, uses_attrs(serde_foo, serde_bar))] ou quelque chose du genre.

@ colin-kiegel J'imagine que cela ne pourrait s'appliquer que dans le cadre de l'application de dérivation, auquel cas la désactivation d'autres attributs est probablement un comportement attendu, bien que je pense que nous pourrions appliquer cfgs avant l'expansion de la macro (cela a changé, mais je ne me souviens pas l'avant vs après).

@alexcrichton

Cela étant dit, je suis quelque peu sceptique quant à savoir si les traits et les objets de trait fonctionneraient, car où sont les instances du trait en cours de création?

Hmm, bon point, je suppose que nous devrons résoudre ce problème pour les macros 2.0

@eddyb C'est une bonne idée et probablement plus facile à faire à court terme. La raison pour laquelle je préfère l'approche impérative est qu'il s'agit d'un mécanisme d'extensibilité général et que nous voudrons conserver, alors que l'ajout d'éléments à l'attribut ne s'adapte pas trop bien et ce n'est peut-être pas quelque chose avec lequel nous voulons rester éternellement. . D'un autre côté, c'est certainement plus facile à faire maintenant et ne semble pas être une mauvaise chose à faire, donc je pense que ce serait peut-être un meilleur pari.

Être rattrapé - j'ai été hors du pays et ensuite malade. Je voudrais revoir https://github.com/rust-lang/rust/pull/37198 , car je ne pense pas qu'il soit logique que toutes sortes de macros occupent le même espace de noms. Je m'attendrais à tout le moins à ce que les macros de dérivation personnalisées soient dans cet espace de noms en tant que derive_Foo , ou similaire. Il existe des cas d'utilisation pour plusieurs types de macros utilisant le même nom, même dans la bibliothèque standard (par exemple #[cfg] et cfg! )

Je trouve également l'espace de noms partagé un peu gênant. Du point de vue de l'utilisateur final, il peut sembler qu'il y avait une sorte d'interchangeabilité, d'ambiguïté ou quelque chose d'intéressant qui se passe - ce qui n'est pas le cas. Je pense que des limitations «aléatoires» comme celle-ci pourraient rendre la compréhension de Rust en tant que langage un peu plus difficile.

Cependant, je pense que ce n'est probablement pas la fin du monde. Il semble que différents espaces de noms pourraient encore être introduits à l'avenir sans trop se casser.

@sgrif @ colin-kiegel J'ai décrit ma justification pour un espace de noms de macro unique dans https://github.com/rust-lang/rust/issues/35900#issuecomment -256247659.

Je ne pense pas qu'il soit logique que toutes sortes de macros occupent le même espace de noms

Pour être clair, les macros bang et les macros d'attributs ont toujours occupé le même espace de noms; # 37198 vient de déplacer les dérivations personnalisées dans cet espace de noms.

Il existe des cas d'utilisation pour plusieurs types de macros utilisant le même nom même dans la bibliothèque standard (par exemple #[cfg] et cfg! )

Alternativement, cfg pourrait être considéré comme une macro unique qui peut être utilisée via un appel bang ou un appel d'attribut (alors qu'il n'est pas possible aujourd'hui pour les utilisateurs de définir une macro qui peut être invoquée via un bang ou un attribut, nous pourrions décider de l'autoriser dans les macros 2.0).

Du point de vue de l'utilisateur final, il peut sembler qu'il y avait une sorte d'interchangeabilité

Je pense que cela pourrait être amélioré avec des messages d'erreur clairs lorsque deux macros sont en conflit (je vais travailler sur l'amélioration des messages d'aujourd'hui).

Il semble que différents espaces de noms pourraient encore être introduits à l'avenir sans trop se casser

Je crois que le fractionnement de l'espace de noms des macros est entièrement rétrocompatible (corrigez-moi si je me trompe). C'est ma principale motivation pour garder un seul espace de noms de macro aujourd'hui - nous voulons que les macros 1.1 soient aussi compatibles que possible pour l'avenir.

Enfin, je pense que les conflits entre macro bang / attribut et macro dérivée personnalisée seront rares dans la pratique, car les macros bang / attribut sont généralement en minuscules et les dérivations personnalisées sont généralement en majuscules. En d'autres termes, les dérivations personnalisées ont déjà leur propre espace de noms lorsque ces conventions de dénomination sont respectées.

Il semble que la casse (https://github.com/diesel-rs/diesel/issues/485) soit causée par le support, par exemple Queryable! { struct S; } ainsi que #[derive(Queryable)] struct S; . Une fois que les dérivations personnalisées seront stables, il ne sera plus nécessaire de prendre en charge Queryable! { struct S; } donc ce ne sera plus un problème, non?

En attendant, je pense que nous pourrions mettre diesel jour

  • Queryable! est toujours pris en charge sans #[macro_use] extern crate diesel_codegen; , et
  • #[derive(Queryable)] , mais pas Queryable! , est pris en charge avec #[macro_use] extern crate diesel_codegen; .

N'hésitez pas à m'envoyer un ping sur IRC pour discuter - je serais heureux d'écrire un PR.

@nrc @alexcrichton

Cela étant dit, je suis quelque peu sceptique quant à savoir si les traits et les objets de trait fonctionneraient, car où sont les instances du trait en cours de création?

Je suis d'accord avec @eddyb que si tout ce que nous voulons, c'est gérer les attributs, dans l'intérêt de la commodité et de l'opportunité, nous devrions simplement étendre les attributs.

Mais si nous voulions un système d'extension plus flexible, alors j'avais imaginé que nous utiliserions des traits, mais ces traits seraient définis comme une collection de méthodes qui ne font pas référence à Self :

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

Vous l'implémenteriez ensuite sur un type factice comme struct my_annotation_type; (partageant le même nom que l'attribut, je suppose?), Et nous utiliserions la résolution de trait pour extraire les fonctions pertinentes comme <my_annotation as CustomDerive>::foo (probablement lors de l'écriture des métadonnées, je suppose). Le fait est que nous ne ferions jamais (ou n'aurions pas besoin) d'une instance de my_annotation , c'est juste un mécanisme de regroupement pour regrouper un tas de fonctions liées.

Certainement pas la chose la plus élégante qui soit, mais je ne suis pas sûr d'une meilleure solution? Je pense que l'inélégance est précisément la raison pour laquelle nous voulions commencer par les fonctions attribuées. =)

En ce qui concerne les espaces de noms, @sgrif fait un bon cas avec les exemples #[cfg] et cfg! . Je peux certainement imaginer quelqu'un voulant #[derive(SomeTrait)] et aussi avoir une macro comme SomeTrait! { ... } qui ... fait quelque chose. =) Mais @jseyfried fait également un bon cas avec la rétrocompatibilité - tant que nous n'atteignons pas les limitations _déjà_.

J'ai tendance à préférer moins d'espaces de noms par défaut, principalement à cause de la douleur que je pense qu'avoir une division d'espace de noms valeur / type nous apporte maintenant. Cela dit, je pense que la plupart des problèmes connus ne s'appliquent pas ici:

  • le fractionnement type / valeur est un problème dans la résolution de nom car use foo::bar peut ou non importer un module nommé bar , et peut donc (ou non) être pertinent pour une résolution de nom comme bar::baz ;
  • la division type / valeur est un peu pénible pour les génériques par rapport aux constantes, bien que cette douleur soit également due à la division syntaxique entre les types et les valeurs.

Mais dans une large mesure, depuis que nous avons traversé le pont, je ne suis pas sûr que le fait d'avoir "custom derive" en direct dans son propre espace de noms apporte un défi particulier, n'est-ce pas?

Nous pourrions ajouter le préfixe derive_ aux macros générées, pour les rendre pseudo-espacées. Si nous voulions faire ce changement, ce serait le moment.

@keeperofdakeys Cela ne serait-il pas équivalent au fait que les auteurs de derive_* ? Quoi qu'il en soit, je pense que les noms derive_* sont trop laids pour les utilisateurs finaux.

@nikomatsakis

Mais dans une large mesure, depuis que nous avons traversé le pont, je ne suis pas sûr que le fait d'avoir "custom derive" en direct dans son propre espace de noms apporte un défi particulier, n'est-ce pas?

L'algorithme de résolution d'importation peut gérer arbitrairement de nombreux espaces de noms, mais il effectue une quantité de travail potentiellement non triviale pour chaque espace de noms. En particulier, pour chaque import I et chaque namespace non utilisé S , cela prouve que la résolution I échoue dans S (cf https: // github. com / rust-lang / rfcs / pull / 1560 # issuecomment-209119266). Cette preuve nécessite souvent un DFS du graphe d'importation global (dans lequel les sommets sont des modules et les arêtes sont des importations globales) pour rechercher des importations indéterminées pertinentes.

Cependant, ce travail supplémentaire par espace de noms peut ne pas faire de différence en pratique et peut être évité si nécessaire (cf https://github.com/rust-lang/rfcs/pull/1560#issuecomment-209119266) au prix d'un restriction mineure qui ne s'appliquerait qu'aux espaces de noms de macro.

J'ai fusionné les espaces de noms dans # 37198 pour garder principalement nos options ouvertes et parce que je ne pensais pas que cela serait limitatif en pratique. Si les gens le veulent aujourd'hui et que @ rust-lang / lang est d'accord pour avoir plusieurs espaces de noms de macro pour toujours, je n'ai aucune objection.

@nikomatsakis

Je pense que oui, votre stratégie fonctionnerait, bien que par hasard. Mon sentiment personnel est que l'étrangeté ne pèse pas son poids (par exemple, ce que nous avons aujourd'hui me convient), mais elle devrait être réalisable de quelque manière que ce soit.

@alexcrichton comme je pense l'avoir écrit auparavant, je suis heureux d'attendre que nous ayons besoin de plus de puissance (si jamais) - et ensuite nous pourrons faire quelque chose comme le trait que j'ai décrit, ou peut-être quelque chose de mieux. Pour l'instant, je pense qu'il suffit d'étendre les attributs appliqués à un fn .

Je suis devenu curieux et j'ai commencé une implémentation de base de cette idée (en utilisant un attribut sur la fonction proc_macro pour désigner les noms des attributs à marquer comme utilisés sur l'élément).

J'ai effacé le tag FCP car il semble clair que nous n'avons pas encore atteint le point où nous sommes prêts à nous stabiliser. Je vais faire un effort pour résumer l'état de la conversation et, en particulier, mettre en évidence les erreurs où nous avons encore besoin de décisions fermes et / ou de contributions au code:

Question 1: Les macros personnalisées doivent-elles être capables de faire muter l'élément sur lequel elles marchent?

  • Personne ne pense qu'ils devraient faire cela dans la pratique bien sûr
  • C'est un autre mode, qui a été initialement rejeté dans l'esprit de YAGNI
  • Les dérivés qui changent l'ensemble des noms de champs, etc. ne se composent pas bien, ce qui rend l'ordre d'application visible;
    le danger de cela est atténué par deux choses:
  • D'un autre côté, si nous disons que le dérivé personnalisé n'a besoin que de renvoyer les nouveaux impls

    • les informations de portée sont meilleures

    • les dérivations personnalisées sont plus simples à écrire

  • _Mais_ de nombreux dérivés personnalisés utilisent des attributs personnalisés pour guider leur expansion, et ceux-ci généreront des avertissements / erreurs

    • La technique actuelle consiste à les retirer de l'AST

  • _Aussi: _ nous voulons sortir ce truc dans le monde dès que possible, nous ne voulons pas faire de longues expérimentations ou des changements drastiques

Les propositions:

  • Gardez tout tel quel, peut-être obsolète plus tard

    • Rapide, quelque peu malheureux

  • Étendez #[proc_macro] avec une liste blanche d'attributs, dites à rustc de les considérer comme "utilisés" et ignorez-les

Question 2: Les dérivés personnalisés doivent-ils partager le même espace de noms que les autres macros?

Argument pour:

Argument contre:

Les propositions:

  • Diviser en espace de noms "dérivation personnalisée"
  • Gardez le statu quo

Autres choses?

Y a-t-il d'autres questions ouvertes?

Solution potentielle au problème de mutation:

Au lieu de dérivées personnalisées de type TokenStream -> TokenStream , elles auraient plutôt le type
Item -> TokenStream , où Item est un type opaque qui commencerait par seulement deux méthodes:

  • item.tokens() , qui renvoie le TokenStream qui serait passé aujourd'hui, et
  • item.use_attrs(name) , ce qui marquerait tous les attributs avec le nom donné comme étant utilisés.

Le TokenStream retourné n'inclurait que les impl s.
Nous pourrions éventuellement ajouter à l'API de Item avec des fonctions pratiques (par exemple, itérer sur les champs / variantes de l'élément) ou une API de dérivation de niveau supérieur comme celle de syntax_ext::deriving::generic .

Je serais heureux de mettre en œuvre l' autorisation des attributs de la liste blanche dans #[proc_macro_derive] (c'est-à-dire la deuxième proposition pour la question 1 dans https://github.com/rust-lang/rust/issues/35900#issuecomment-258315395) ou ma proposition Item -> TokenStream .

Je pense que ce serait une erreur de stabiliser les dérivés personnalisés qui peuvent muter l'élément sous-jacent. Les informations de span que nous sacrifions en autorisant la mutation posent déjà des problèmes - par exemple, nous devons choisir entre corriger # 37563 et réparer # 36218.

Je ne vois vraiment pas l'attrait de la liste blanche impérative, est-ce que quelqu'un peut proposer un cas d'utilisation?

Je ne suis pas sûr, est-ce intentionnel / souhaité que ce code compile:

#![feature(proc_macro)]

#[proc_macro_derive(Whatever)]
struct Foo {}

@eddyb Je ne sais pas s'il y a un attrait inhérent à la liste blanche impérative - je pense que l'avantage de
Item -> TokenStream est qu'il est plus facile d'étendre l'API de Item que d'ajouter plus de types d'attributs déclaratifs. Cela étant dit, en y réfléchissant un peu plus, il se peut qu'il n'y ait pas beaucoup d'autres cas d'utilisation qui nécessitent un Item sur un TokenStream , donc TokenStream -> TokenStream avec une liste blanche déclarative pourrait être mieux.

Les macros 2.0 remplaceront-elles cette API? Si c'est le cas, l'extensibilité n'est pas vraiment un problème, et l'api Item -> TokenStream ne semble pas très convaincant.

@keeperofdakeys Selon la RFC:

Dans l'ensemble, cela devrait être considéré comme une étape progressive vers une "macros 2.0" officielle.

L'intention des macros 1.1 est d'être aussi proche que possible des macros 2.0 dans l'esprit et la mise en œuvre, sans toutefois stabiliser de grandes quantités de fonctionnalités. En ce sens, l'intention est que, étant donné une macros 1.1 stable, nous pouvons superposer des fonctionnalités de manière rétrocompatible pour accéder aux macros 2.0.

https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233449053 et https://github.com/rust-lang/rfcs/pull/1681#issuecomment -233708395 et https: // github. com / rust-lang / rfcs / pull / 1681 # issuecomment -239558657 semblent indiquer que seules des dépréciations "limitées" sont attendues lorsque les macros 2.0 arrivent. En particulier: "le modèle d'arborescences de jetons de création de chaînes deviendrait non préféré, sinon réellement obsolète".

@eddyb

Je ne vois vraiment pas l'attrait de la liste blanche impérative, est-ce que quelqu'un peut proposer un cas d'utilisation?

Eh bien, je pourrais imaginer un cas où les noms des attributs sont générés dynamiquement d'une manière ou d'une autre (sur la base du nom du type ou des champs, peut-être?) - et par conséquent, on ne peut pas les déclarer à l'avance. Cela semble assez artificiel, et pas comme une pratique particulièrement bonne.

Je trouve l' alternative de Item pourrait devenir plus riche avec le temps. c'est-à-dire que nous pourrions prendre en charge le parcours de l'ensemble des champs d'une manière très structurée, fournir un accès à plus de données (comme les résolutions de noms) que nous pourrions avoir à notre disposition, etc.

Bien sûr, une partie de cela viendra (_eventuellement_) d'une forme d'API AST standard.

Une note sur le timing: je veux vraiment, vraiment voir les macros 1.1 se stabiliser dans le prochain cycle. Les éléments sur lesquels nous sommes bloqués semblent, en fin de compte, assez mineurs.

Dans cet esprit, mon point de vue sur les problèmes que j'ai décrits:

  1. Je suis satisfait soit d'une extension déclarative #[proc_macro] _ou_ changeant le type en Item . Je pense aussi que quel que soit notre choix, nous pourrions potentiellement adopter l'autre à l'avenir si cela nous semblait une bonne idée. Je veux en quelque sorte choisir celui qui est mis en œuvre en premier. =)
  2. En ce qui concerne les espaces de noms multiples, je pense que nous devrions les garder dans le même espace de noms pour le moment. Pour moi, la combinaison de la compatibilité ascendante (c'est-à-dire de garder nos options ouvertes) avec le fait que la capitalisation distingue déjà les macros «dérivées personnalisées» d'autres choses dans la pratique est convaincante. Distinguer #[cfg] vs cfg! ressemble à quelque chose que nous pourrions gérer d'une autre manière, ou si nous le voulons, nous pouvons introduire les espaces de noms fractionnés _alors_. Il ne semble pas que le fait d'avoir un espace de noms unifié nuit en particulier au cas d'utilisation du dérivé personnalisé, qui est la seule chose dont nous parlons de toute façon de stabiliser. Droite?

@nikomatsakis votre résumé semble précis, merci de l'avoir rédigé! Je suis triste que nous n'obtenons pas de macros 1.1 dans Rust 1.14, mais je comprends que ce sont des problèmes litigieux.

Mon sentiment personnel reste de stabiliser tout tel quel, là où le dérivé personnalisé doit supprimer les attributs et il n'y a qu'un seul espace de noms.

Je ne suis pas tout à fait l'API Item -> TokenStream , le jeton retourné englobe-t-il toujours l'élément d'origine ou simplement les impls ajoutés? Cela veut-il dire que cela devrait prendre &mut Item ?

@ est31 votre commentaire ressemble à un bogue, pouvez-vous ouvrir un autre numéro pour cela?

Je suis à peu près entièrement d' accord avec le commentaire de

Il ne semble pas que le fait d'avoir un espace de noms unifié nuit en particulier au cas d'utilisation du dérivé personnalisé, qui est la seule chose dont nous parlons de toute façon de stabiliser. Droite?

Le problème est survenu parce qu'il a cassé le diesel, qui a actuellement des macros appelées par exemple Queryable! lesquelles vous pouvez envelopper une structure pour en dériver Queryable .

En tant que personne qui n'utilise ni ne maintient le diesel pour le moment (c'est-à-dire en tant que personne qui n'est pas touchée par ce que je suis sur le point de proposer: sweat_smile :), je pense que le mieux est de garder 1 espace de noms pour le moment et que diesel change le nom ces macros à derive_Queryable! ou quelque chose comme ça. Ils seront de toute façon obsolètes quand cela sera stable, non?

J'ai créé PR https://github.com/rust-lang/rust/pull/37614 pour la fonctionnalité suggérée. Il utilise un attribut séparé #[proc_macro_attributes(..)] , et vous n'avez plus besoin de retourner l'article dans le TokenStream renvoyé.

J'ai déposé # 37637 pour comprendre comment les macros proc devraient traiter $crate .

Juste pour clarifier, ce cas d'utilisation est-il considéré comme correct ou une mauvaise utilisation du système:

Je produis nouveaux struct en fonction de la structure existante et les attributs ici et je aime bien la conception que cela me permet de consolider les choses dans une struct.

Cependant, si le système pouvait changer plus tard de manière à interdire ce type d'implémentation, je pourrais simplement m'arrêter maintenant au lieu de mettre plus d'efforts dedans (l'implémentation actuelle est juste une rapide preuve de concept).

@TheNeikos

Le principal changement incompatible vers l'arrière sera que vous ne pourrez plus modifier l'élément (vous ne le renvoyez pas dans TokenStream). Je dirais que dériver autre chose qu'un impl n'est pas prévu, mais rien ne vous empêche de faire cela. Vous devrez juste faire attention aux conflits de noms.

L'autre changement principal est de pouvoir fournir une liste de noms d'attributs qui ne devraient pas déclencher d'erreurs d'attributs personnalisés sur l'élément.

RE: Le conflit d'espace de noms dans Diesel. Je ne sais pas si je désapprouverai ces macros ou non une fois que cette fonctionnalité sera stable, cela dépendra du fait que certains souhaitent toujours des éléments gratuits d'extension de compilateur. C'est douteux. Toujours utile pour tester en interne, mais le renommer est très bien pour cela.

Je pense qu'il est malheureux de perdre la possibilité de modifier le flux d'entrée. La possibilité de supprimer l'élément annoté nous permet également d'avoir des macros avec ce système. Je comprends les préoccupations, mais c'est dommage de perdre cette capacité à leur place.

@TheNeikos @sgrif mon point de vue est que tout ce qui modifie sérieusement l'entrée n'est pas un dérivé et n'est donc pas destiné à être abordé ici. Je pense qu'il est quelque peu dangereux (manque d'hygiène, etc.) d'utiliser des dérivations personnalisées pour des macros proc à usage général, donc je ne veux pas l'encourager.

Ne pas pouvoir modifier l'élément signifie que les informations d'étendue de l'élément sont conservées, ce qui rend les messages d'erreur sur l'élément beaucoup plus clairs (cela signifie également que les messages d'erreur dans la sortie dérivée ne pointent plus vers l'étendue de l'élément, mais l'étendue de l'attribut de dérivation). Je ne vois pas de bonne raison pour laisser les gens abuser de cette fonctionnalité si nous voulons vraiment juste des macros procédurales appropriées.

@TheNeikos Je ne pense pas que nous allons interdire la génération de nouvelles structures via un dérivé tant que vous ne motivez pas la structure pour laquelle vous dérivez.

En termes de ce que je pense est idiomatique; Je pense que c'est bien de générer de nouveaux types, mais c'est beaucoup mieux si ces types sont des types associés pour un trait que vous dérivez. Par exemple, imaginez si nous étions capables de dériver IntoIterator pour un type - cela impliquerait de créer une structure Iterator . Conceptuellement, vous devriez dériver un comportement pour ce type, mais ce comportement n'est peut-être pas «un trait implicite».

@sgrif

Je pense qu'il est malheureux de perdre la possibilité de modifier le flux d'entrée. La possibilité de supprimer l'élément annoté nous permet également d'avoir des macros avec ce système. Je comprends les préoccupations, mais c'est dommage de perdre cette capacité à leur place.

Hmm, je suis vraiment sympathique, bien que de toute évidence (comme @nrc l'a noté) ce n'est pas pour cela que le système a été conçu, et il aura tendance à exposer les différents bords rugueux. Cela n'a probablement pas d'importance dans vos cas d'utilisation. Je suppose qu'une question clé est de savoir avec quelle rapidité nous pouvons passer à une sorte de "bang macros 1.1".

Le PR a été fusionné, vous devriez donc pouvoir voir les changements suivants dans la nuit suivante. La fonction proc_macro_derive ne devrait plus retourner l'élément (cela déclenchera une erreur sur un type étant défini deux fois), et vous pouvez maintenant fournir une liste de noms d'attributs à ajouter à la liste blanche comme ceci #[proc_macro_derive(Derive, attributes(Foo, Bar)] .

cc @dtolnay , @sgrif ^

qui causera bientôt la casse malheureusement

Oui, j'ai déposé https://github.com/serde-rs/serde/issues/614 pour suivre de notre côté.

Je pense que j'ai corrigé la casse de Diesel dans https://github.com/diesel-rs/diesel/pull/493 , je le saurai à coup sûr une fois que les nightlies seront à nouveau construites.

Donc, si je lis correctement ce fil, puisqu'une macro proc ne peut émettre que des éléments supplémentaires ajoutés à l'élément initial, elle ne peut pas non plus ajouter plus d'annotations à l'élément initial? (Je vois une mention autorisant les "annotations de frères et sœurs", mais rien d'autre à ce sujet.)

J'ai une macro #[derive(newtype)] proc dans ma caisse (minuscule, non publiée) qui se développe en un ensemble différent d'autres #[derive()] s en fonction de la structure qu'il annote. Par exemple, #[derive(newtype)] struct Foo(u64) développe en #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] struct Foo(u64); alors que #[derive(newtype)] struct Foo(::semver::VersionReq) développe en #[derive(Clone, Debug, PartialEq)] struct Foo(::semver::VersionReq); . Ainsi, les membres de la structure ne sont pas modifiés par cette macro, mais d'autres dérivées y sont ajoutées (elles ne modifient pas non plus la structure).

Il existe quelques dizaines de structures de ce type et chacune a une dizaine de nouvelles dérivations après le développement de cette macro. J'aime le fait que si je réalise que je veux que tous les nouveaux types u64 implémentent un autre trait, je peux simplement changer l'ensemble des dérivations à un endroit dans le code macro newtype au lieu de sur chaque structure unique.

J'avais l'habitude d'avoir une macro macro_rules newtype! pour cela, mais je suis passé à une macro proc parce que:

  • La présence ou l'absence de commentaires doc, la présence ou l'absence de pub , etc. sont gérées gratuitement sans avoir besoin d'un nombre combinatoire de bras de correspondance de macro.
  • Même si j'ai écrit les bras de correspondance macro combinatoire, trouver un ordre où ils ne sont pas en conflit les uns avec les autres était difficile.

Malheureusement non, vous ne pouvez plus faire cela comme vous le faisiez.

Concernant la compatibilité future de cette fonctionnalité étant stable: comme la fonction plugin n'est pas obligée d'être "pure", ce sera un changement radical si l'ordre des objets donnés à la fonction traitée change dans le futur, ou si rustc implémente le multi thread compiler?

@ est31 Si nous avons le temps, nous devrions essayer de retirer l'isolement IPC qui a été mentionné.

Je vois constamment un ICE dans Diesel après les changements les plus récents.

../src/librustc_metadata/decoder.rs:490: entrée: id introuvable: DefIndex (1) dans la caisse "diesel_codegen" avec le numéro 28

@sgrif ce serait le problème # 37788 qui sera corrigé par # 37793 (espérons que cela se terminera dans la nuit de demain ...).

@ est31 Il est trop tard à cette heure pour le fusionner avant le début de la compilation nocturne.

https://github.com/rust-lang/rust/issues/37839 est un problème avec l'utilisation d'une lib crate qui utilise elle-même une crate proc_macro. AFAICT aucun des tests n'est affecté par cela car ils ne compilent que le module macro proc, ou un module macro proc et un module bin qui référence directement la macro proc.

Edit: maintenant corrigé!

@Arnavion Le problème que vous avez vu avec # 37839 est résolu pour une complication régulière, mais il reste cassé lors de l'utilisation de --target , comme indiqué dans # 37958. J'ai fourni un cas de test minimal en utilisant --target qui ne fonctionne toujours pas.

@rfcbot fcp fusionner

Maintenant que la fonctionnalité d'attribut sur liste blanche a été implémentée , je pense que nous devrions stabiliser cela! Serde, Diesel et autres utilisateurs - est maintenant votre changement d'objet si la conception actuelle ne fonctionne pas pour vous. =)

cc @sgrif @erickt

Le membre de l'équipe @nikomatsakis a proposé de fusionner cela. L'étape suivante est l'examen par le reste des équipes marquées:

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

Aucun problème actuellement répertorié.

Une fois que ces examinateurs parviendront à un consensus, cela entrera dans sa période de commentaires finale. Si vous repérez un problème majeur qui n'a été soulevé à aucun moment de ce processus, veuillez en parler!

Consultez ce document pour plus d'informations sur les commandes que les membres de l'équipe marqués peuvent me donner.

J'adorerais voir des macros bang explorées dans un proche avenir. Aucune objection cependant.

@rfcbot a évalué

Actuellement, si un TokenStream ne parvient pas à analyser, une LexError vide est renvoyée . Serait-il possible de renvoyer un meilleur message d'erreur ici, comme le jeton qui n'a pas pu être analysé? Bien que les utilisateurs de votre bibliothèque ne souhaitent probablement pas voir ce type de messages d'erreur.

Oui, ce serait pratique pour les auteurs de macros. J'ai dû recourir à l'écriture du flux dans un fichier et à le visualiser dans la cour de récréation pour trouver des erreurs.

Je pense que les utilisateurs en bénéficieront également, ne serait-ce que pour déposer de meilleurs rapports de bogue sur la macro.

Sur https://github.com/rust-lang/rust/pull/38140 , nous forçons les déclarations de dérivation personnalisées à être publiques. La théorie étant que nous pourrions un jour vouloir des dérivations privées et ensuite nous voudrons probablement faire cette distinction en fonction de la visibilité de la fonction de définition. En tout cas, c'est le choix conservateur. Je pensais que je le laisserais mariner pendant un jour ou deux pour que les gens le voient avant de fusionner, car nous stabilisons cette fonctionnalité.

# 37480 est fermé, ce qui devrait être reflété dans la liste de contrôle.

Fixé

@pnkfelix J'ai pris la liberté de cocher votre case pour vous, puisque vous êtes sur PTO et je crois que vous êtes totalement à bord. Faites-nous savoir si ce n'est pas le cas!

: bell: Ceci entre maintenant dans sa période de commentaires finale , selon l' examen ci-dessus . :cloche:

psst @nikomatsakis , je n'ai pas pu ajouter le label final-comment-period , veuillez le faire.

La stabilisation imminente suppose-t-elle que les bogues connus restants dans la liste de contrôle en haut seront corrigés en premier? Ou est-ce que ceux-ci ne bloquent pas la stabilisation?

Il y en a deux en ce moment:

  • # 36935 a un commentaire disant que c'est résolu.
  • # 36691 ne me semble pas bloquant, nous pouvons permettre à ceux-ci de s'étendre à mod foo; un jour dans le futur si nous voulons sans casser quoi que ce soit que je crois.

Hey! Il s'agit d'un audit de documentation RFC 1636 . C'est la première caractéristique majeure à stabiliser depuis que la RFC 1636 a été acceptée, et nous devrions faire un bon travail de la suivre dans ce cas.

Le RFC déclare:

Avant de stabiliser une fonctionnalité, les fonctionnalités seront désormais documentées comme suit:

  • Caractéristiques linguistiques:

    • doit être documenté dans la référence Rust.

    • doit être documenté dans le langage de programmation Rust.

    • peut être documenté dans Rust par exemple.

  • Les fonctionnalités linguistiques et les modifications de bibliothèque standard doivent inclure:

    • une seule ligne pour le changelog

    • un résumé plus long pour l'annonce de la version longue.

Quel est le bon processus pour cela? Devrions-nous ajouter des éléments de liste de contrôle au premier commentaire de ce problème ou créer un nouveau problème pour le suivi de la documentation? Il me semble que nous devrions avoir une documentation répondant à ces exigences dans l'arborescence d'ici la version 1.15.

cc @ rust-lang / docs

Merci @withoutboats ! C'est le premier grand, ouais. J'avais eu ça sur ma liste à regarder ce matin, et voilà, tu m'as battu 😄

J'ai toujours imaginé que la décision de stabiliser et la stabilisation réelle sont séparées. Autrement dit, @ rust-lang / lang peut dire "cela peut être rendu stable", mais le commit de supprimer la porte garantit également que la documentation existe. Dans un monde où le livre instable existe , cela tirerait les documents de là vers les documents stables; mais jusque-là, les choses sont un peu gênantes.

Étant donné que nous venons de sortir, mon plan était de faire quelque chose comme ceci:

  1. Attendez que cela quitte FCP
  2. Obtenez des documents. (J'avais l'intention de les écrire dans ce cas)
  3. Faites le PR de stabilisation.

Combinant éventuellement deux et trois.

/ cc @ rust-lang / core, car il s'agit d'un problème entre équipes.

@steveklabnik cela me semble bien, et je serais d'accord pour débarquer des documents à tout moment (même avant la fin du FCP). Le FCP ici est en quelque sorte pseudo-fini de toute façon car nous avons accidentellement mis longtemps à entrer.

Il serait également bon de les intégrer rapidement afin que nous puissions garantir un backport sécurisé vers la branche bêta 1.15!

Si je suis le premier à toucher cela, cela ne peut pas être si grave, mais incluons-le sous les bogues connus: l'utilisation d'une macro de type dans une structure avec une dérivation personnalisée provoque un ICE https://github.com/rust-lang/ rouille / issues / 38706

https://github.com/rust-lang/rust/pull/38737 corrige les macros de type ICE: heart :. Y a-t-il une chance d'obtenir ce rétroporté en bêta? Raison d'être: il semble mauvais que deux nouvelles fonctionnalités importantes, l'une publiée en 1.13 et l'autre publiée en 1.15, plantent le compilateur lorsque vous les utilisez ensemble.

Je viens de créer # 38749 concernant la documentation de la caisse proc_macro .

J'ai lu plusieurs fois que les macros 1.1 seraient stabilisées dans la version 1.15, mais la version 1.15.0-beta.1 expédiée il y a deux semaines et au moins extern crate proc_macro; est toujours dépendante ainsi que dans la nuit 4ecc85beb 2016 -12-28. Le plan est-il de rétroporter le changement de stabilisation?

@SimonSapin oui, c'était le plan, mais nous devons y arriver!

C'est toujours le plan: p

Si l'utilisateur écrit #[derive(Foo)] #[foo_def = "definition.json"] struct MyStruct; le gestionnaire de macro n'a aucun moyen de savoir ce qu'est "le répertoire courant" et ne peut donc pas trouver definition.json .

C'est par conception et ne serait donc pas facile à résoudre, et je suppose qu'il est de toute façon trop tard pour résoudre ce problème.

Vous pouvez aller Span -> FileMap -> filename -> répertoire. Tout ce qui manque est d'accéder aux informations via proc_macro .

Vous devrez également dire au compilateur d'ajouter une dépendance à definition.json afin que la construction soit sale si elle est modifiée.

La macro proc peut utiliser env::var("CARGO_MANIFEST_DIR") pour obtenir le répertoire contenant Cargo.toml de la caisse contenant l'appel de la macro. Vraisemblablement foo_def est relatif à cela. Voir https://github.com/dtolnay/syn/issues/70#issuecomment -268895281.

@tomaka qui peut être fait par muter FileMap, par exemple c'est comment le include_str! macro fait.

Vraisemblablement foo_def est relatif à cela.

Je pense que ce n'est pas très intuitif d'avoir à mettre le chemin par rapport au Cargo.toml.

cela peut être fait en mutant FileMap, par exemple, c'est ainsi que include_str! macro le fait.

Oui, je sais que cela peut être fait, cela ne peut tout simplement pas être fait avec l'API de macros procédurales actuelle.

Le paramètre est un Item . Il serait acceptable d'ajouter une méthode pour récupérer une étendue de cet élément, mais ajouter une méthode à Item pour demander au compilateur d'ajouter une dépendance serait un hack IMO.

Vous pouvez aller Span -> FileMap -> filename -> répertoire.

Ces API (en particulier FileMap ) sont-elles sur un chemin à stabiliser?

Ils ne doivent pas l'être, en fait je ne voudrais stabiliser aucun des éléments internes. Nous pouvons, à la place, stabiliser les API qui extraient des informations sur un Span (par exemple, ligne, colonne, nom de fichier).

Je viens d'avoir cette erreur dans une de mes caisses. Que se passe-t-il?

`` error: Cannot use #! [Feature (proc_macro)] and #! [Feature (custom_attribute)] en même temps
`` ``

@alexreg Si vous utilisez #[derive] , c'est stable maintenant. Vous n'avez pas besoin de #![feature(proc_macro)] .

@alexreg
proc_macro_derive s (macros 1.1) sont maintenant stables - vous pouvez simplement supprimer #![feature(proc_macro)] .

#[proc_macro_attribute] récemment atterri derrière la porte des fonctionnalités #![feature(proc_macro)] ; ceux-ci sont incompatibles avec #![feature(custom_attribute)] . #![feature(custom_attribute)] sera obsolète une fois le remplacement arrivé (https://github.com/rust-lang/rfcs/pull/1755).

@jseyfried Je pense que nous devrions changer le problème de suivi sur proc_macro car il est fermé et ne contient pas d'informations pertinentes.

Merci les gars. Ça a du sens.

@abonander Ouais, #![feature(proc_macro)] devrait définitivement pointer vers # 38356 maintenant - j'aurais dû le vérifier lors de l'examen de # 38842. Pouvez-vous soumettre un PR?

Cette page vous a été utile?
0 / 5 - 0 notes