Rust: Problème de suivi pour RFC 2046, label-break-value

Créé le 27 févr. 2018  ·  135Commentaires  ·  Source: rust-lang/rust

Il s'agit d'un problème de suivi pour la RFC 2046 (rust-lang/rfcs#2046).

Pas:

Questions non résolues :

B-RFC-implemented B-unstable C-tracking-issue F-label_break_value T-lang

Commentaire le plus utile

@sansbateaux

Je voudrais éviter d'ajouter des choix plus largement applicables et très flexibles à la boîte à outils de flux de contrôle de Rust. Je suis uniquement intéressé par l'ajout d'un flux de contrôle ciblant des cas d'utilisation spécifiques et importants et ayant un impact élevé sur l'ergonomie.

Je suis d'accord avec ce principe, et je pense que cette fonctionnalité répond à cette barre (flux de contrôle familier ciblé sur des cas d'utilisation spécifiques et importants). Il y a un certain nombre de fois où j'ai révisé ou écrit du code comme les exemples fournis ci-dessus où je pense que c'est de loin la façon la plus propre et la plus facile à lire pour écrire ce code. La réutilisation de constructions de flux de contrôle existantes comme loop avec un break inconditionnel à la fin induit l'utilisateur en erreur, et les fonctions immédiatement appliquées sont souvent insuffisantes en raison de l'utilisation de ? , await! , ou d'autres constructions de flux de contrôle intermédiaires.

L'utilisation de loop + break prête à confusion, et nous préférerions plutôt que les utilisateurs déclarent leur intention réelle plutôt que de les obliger à abuser d'un outil conçu pour un style de contrôle différent. Ceci est similaire au fait que nous avons même un loop construction, tandis que d' autres langues se contentent de while true { ... } . Faire cela permet d'écrire du code qui exprime une intention plus claire et est donc plus lisible.

De plus, cette fonctionnalité est quelque chose que je m'attendais toujours à

TL; DR : Je pense que cette fonctionnalité prend en charge des cas d'utilisation du monde réel qui ne peuvent être écrits autrement que via des instructions if fortement imbriquées ou en abusant d'autres constructions comme loop-break-value. C'est un petit ajout à la syntaxe de surface qui fait que les blocs se comportent comme je m'attendrais à ce qu'ils se comportent et me permet d'écrire le code que je veux dire plutôt que quelque chose de beaucoup plus hacker.

Tous les 135 commentaires

L'utilisation de "return" aurait des implications intéressantes pour les ? étiquetés (tryop questionmark operator thingy).

Utiliser return comme mot-clé au lieu de break ?

@mark-im et @joshtriplett se sont déjà prononcés contre le retour, mais je participerai étant donné qu'il s'agit apparemment toujours d'une question non résolue.

break (et continue) ne sont utilisables qu'avec une boucle.
Si vous pouvez casser quelque chose, vous pouvez le continuer. (Je ne pense pas qu'il y ait une syntaxe évidente à choisir pour continuer sur un bloc.)

En C, C++, Java, C#, JavaScript et probablement d'autres langages, vous exécutez généralement break une instruction switch afin d'éviter les erreurs. Rust a résolu ce problème beaucoup mieux avec | dans les modèles, mais les personnes venant de ces langages ne verront pas vraiment break comme quelque chose uniquement pour les boucles for. D'autant plus que Java et JavaScript exposent également la fonctionnalité via break et non return .

L'argument des "règles à retenir" fonctionne cependant très bien dans l'autre sens. D'après ce que je peux dire, il s'agit d'un point commun entre les langages mentionnés ainsi que Rust qui ne s'applique qu'aux fonctions et à rien d'autre. Donc, si vous voyez un retour, vous savez qu'une fonction est laissée.

L'étiquetage d'un bloc ne provoque pas d'erreurs sur les ruptures non étiquetées existantes

Tout d'abord, je pense que cela arrive très rarement car la fonction de rupture étiquetée n'est certes pas quelque chose qui sera utilisé 10 fois par 1000 lignes. Après tout, cela ne s'appliquera qu'aux ruptures non étiquetées qui traverseraient la limite du bloc, et non aux ruptures non étiquetées à l'intérieur du bloc. Deuxièmement, les utilisateurs de Rust sont habitués aux plaintes/messages d'erreur du compilateur, ils se feront un plaisir de les corriger ! Troisièmement (c'est le point le plus fort je pense), si au lieu d'étiqueter un bloc vous l'enveloppez dans une boucle, vous devez déjà faire attention aux pauses non étiquetées et il n'y a pas de message d'erreur qui répertorie commodément les instructions de pause, vous avez pour les chasser vous-même :).

D'autant plus que Java et JavaScript exposent également la fonctionnalité via une pause et ne reviennent pas.

C'est pour moi le point mort. rompre avec les blocs est une chose dans de nombreuses langues. retour de blocs ... pas tellement.

Personnellement, je partage le point de vue de break au lieu de return , mais il me semblait que la discussion n'avait pas été résolue sur la RFC... Si vous croyez la question est résolu dans l'équipe lang, cochez la case avec une note =)

Je dis juste que je travaille là-dessus. Vous n'avez pas besoin d'instructions de mentor. Juste pour ne pas dupliquer les efforts. Attendez-vous à un PR bientôt.

Je suis toujours en faveur de return plutôt que de break , mais je peux accepter de ne pas être d'accord ici. Question résolue.

Actuellement (avec rustc 1.28.0-nightly (a1d4a9503 2018-05-20) ) rustc n'autorise pas unsafe sur un bloc étiqueté. Est-ce prévu ?

@topecongiro Oui, je pense qu'il est intentionnel que cela ne soit actuellement autorisé que sur des blocs simples. Cela pourrait changer à l'avenir, mais étant donné qu'il s'agit d'une fonctionnalité de bas niveau et inhabituelle, je suis enclin à ce que ce soit une fonctionnalité plutôt qu'une restriction. (À l'extrême, je ne veux certainement pas de else 'a: { .)

Tout à fait d'accord. Un flux de contrôle dangereux + inhabituel semble être quelque chose à décourager.

À la rigueur, cependant, vous pouvez utiliser:

'a: {
unsafe {...}
}

À droite?

En fait, même si le reste crée une nouvelle portée lexicale, ce n'est pas un bloc. L'ensemble if-else est un bloc (en quelque sorte). Donc non, vous n'obtiendriez pas else 'a: { vous obtiendriez 'a: if ... else { .

else contient un bloc (expression). il n'y a pas de "nouvelle portée lexicale" sans blocs.
Une position de syntaxe de surface encore pire que else serait 'a: while foo 'b: {...} .
(assez intéressant, continue 'a est break 'b , nous pourrions vouloir nous fier à cela au moins en interne)

(assez intéressant, continue 'a est break 'b , nous pourrions vouloir nous fier à cela au moins en interne)

C'est une excellente observation !

Je pense que les étiquettes devraient faire partie des expressions contenant des blocs, et non des blocs eux-mêmes. Nous avons déjà un précédent pour cela avec loop . (En l'occurrence, un bloc simple lui-même est également une expression contenant des blocs. Mais des choses comme if et loop sont des expressions contenant des blocs sans être des blocs, je suppose.)

(Des choses comme while ou for ne devraient pas prendre en charge label-break-value, car elles pourraient ou non renvoyer une valeur selon qu'elles se terminent normalement ou avec un break .)

@eddyb

(assez intéressant, continuez 'a is break 'b, nous pourrions vouloir nous fier à cela au moins en interne)

Seulement si break 'b revérifie la condition de boucle...

@mark-im C'est l'équivalent de 'a: while foo {'b: {...}} , le break ne vérifierait pas la condition de boucle, la boucle elle-même le ferait, car la condition de boucle est vérifiée avant chaque itération du bloc de corps.

Woah, je trouve ça _très_ peu intuitif. Je m'attends break 'b ce que goto 'b , ce qui signifie que nous ne quittons jamais le corps de la boucle et que la condition n'est pas vérifiée à nouveau...

Oh :man_facepalming: Je vois...

C'est pourquoi je n'aime pas étiqueté pause/continuer :/

Eh bien, nous n'avons spécifiquement break signifie toujours "quitter ce bloc" et, étant donné la restriction ci-dessus, il n'y a aucun moyen pour cela de signifier autre chose que "aller à l'endroit après l'accolade fermante associée".

Ma confusion n'était pas spécifique aux blocs internes étranges, mais je ne veux pas vraiment rouvrir la discussion. Cela s'est déjà produit et la communauté a décidé de l'ajouter.

D'accord, je comprends que l'accessibilité est un gros problème avec les langages de programmation... cependant, la coupure étiquetée est extrêmement utile si vous écrivez du code comme moi.

Alors, comment rendre la pause labellisée plus accessible ?

Alors, comment rendre la pause labellisée plus accessible ?

C'est une excellente question. Quelques idées que j'ai eues :

  • Nous devrions collecter des échantillons de la façon dont les gens l'utilisent dans la nature. Nous pouvons rechercher des schémas indésirables ou des habitudes paresseuses.
  • Nous devrions avoir un style opiniâtre quand il est considéré comme une mauvaise pratique d'utiliser une pause/continuer étiquetée.
  • Nous pourrons peut-être ajouter des peluches pour certains modèles qui pourraient être transformés en combinateurs de boucles/itérateurs mécaniquement (je ne peux pas penser à de tels modèles du haut de ma tête, cependant).

En tant que premier échantillon (certes biaisé), ma dernière (et première) rencontre avec une coupure étiquetée dans le code réel n'était pas stellaire : https://github.com/rust-lang/rust/pull/48456/files#diff -3ac60df36be32d72842bf5351fc2bb1dL51. Je suggère respectueusement que si l'auteur original avait fait un peu plus d'efforts, il aurait pu éviter d'utiliser une pause étiquetée dans ce cas... C'est un exemple du genre de pratique que je voudrais décourager si possible.

C'est... pas étiqueté pause ?

En ce qui concerne la façon dont break et continue mêlent, cela a également été mentionné dans la discussion originale sur la RFC par l'auteur de la RFC ; voir https://github.com/rust-lang/rfcs/pull/2046#issuecomment-312680877

Je suppose que le nom break est sous-optimal, mais il est bien établi pour les boucles. Une approche plus "principale" consisterait à utiliser la syntaxe return 'a value , qui continue immédiatement l'exécution "après" le bloc 'a , en utilisant la valeur value pour ce bloc.

@mark-im "ne pas utiliser une fonctionnalité car elle n'est pas accessible" ne "rend pas ladite fonctionnalité accessible".

Comment pouvons-nous modifier les pauses étiquetées pour que j'obtienne toute la puissance expressive du langage tout en arrêtant de vous plaindre du fait que votre cerveau ne peut pas traiter les éléments de contrôle de flux aussi bien que le compilateur ?

(De plus, le code que vous avez lié ne semble pas avoir de rapport avec RFC 2046/label-break-value... avez-vous peut-être lié le mauvais code ?)

C'est... pas étiqueté pause ?

(De plus, le code que vous avez lié ne semble pas avoir de rapport avec RFC 2046/label-break-value... avez-vous peut-être lié le mauvais code ?)

C'est vrai, c'était une suite étiquetée normale avant que je la modifie, mais je pense que les mêmes problèmes existent (et sont peut-être pires puisque le flux de contrôle du reste d'une routine peut être affecté par la valeur que vous retournez).

@mark-im "ne pas utiliser une fonctionnalité car elle n'est pas accessible" ne "rend pas ladite fonctionnalité accessible".

Comment pouvons-nous modifier les pauses étiquetées pour que j'obtienne toute la puissance expressive du langage tout en arrêtant de vous plaindre du fait que votre cerveau ne peut pas traiter les éléments de contrôle de flux aussi bien que le compilateur ?

Désolé, je ne veux pas me plaindre. Si je suis la seule personne à ressentir cela, cela ne me dérange pas de me retirer.

À mon humble avis, c'est un problème fondamental avec break/continue étiqueté : c'est _trop_ expressif, et la seule façon que je connaisse de l'atténuer est d'avoir une utilisation recommandée comme "bon style" (quoi que cela signifie). Par exemple, « n'utilisez qu'une pause étiquetée avec une valeur à partir du début ou de la fin d'un corps de boucle (pas au milieu) ». Cela signifierait que les moyens possibles de quitter une boucle avec une valeur sont faciles à repérer et à raisonner.

Voici comment j'évite goto/labeled break dans d'autres langues : https://gist.github.com/SoniEx2/fc5d3614614e4e3fe131/#file -special-lua-L4-L72

C'est plus lisible ?

Si tel est le cas, nous pouvons peut-être trouver une sorte de système conditionnel basé sur des blocs étiquetés. Semblable à cela , peut-être.

Le point d'arrêt et de continuation sans étiquette est de prévoir les cas où vous ne pouvez pas facilement mettre la condition/valeur dans l'en-tête de boucle. Certaines boucles sont simplement beaucoup plus simples, lisibles, rapides, etc. avec la pause au milieu.

Le point de rupture et de continuation étiquetés dans les boucles est similaire - parfois la seule alternative est d'introduire une nouvelle variable, une nouvelle fonction (abusant ainsi de return ), ou une autre contorsion qui ne fait que rendre les choses plus difficiles à suivre, cependant Malheureusement, une rupture étiquetée peut être alambiquée.

Mais ces deux caractéristiques ne sont même pas le sujet de ce fil. Ils sont assez universels, précisément en raison des améliorations qu'ils offrent pour exprimer un flux de contrôle intrinsèquement complexe. Ce fil consiste plutôt à sortir d'un bloc sans boucle. C'est certainement plus nouveau, et les gens peuvent ne pas savoir chercher un break dehors d'une boucle, bien que l'exigence d'une étiquette signifie que le signal est toujours là une fois que vous savez ce que cela signifie.

C'est ce que je voulais dire à propos de votre exemple, @mark-im- c'était une utilisation assez standard d'une boucle étiquetée , plutôt que d'un bloc étiqueté .

Une approche plus "principale" consisterait à utiliser la syntaxe return 'a value, qui poursuit immédiatement l'exécution "après" le bloc 'a, en utilisant la valeur value pour ce bloc.

Note à propos de break vs return : Je préfère break parce que c'est un flux de contrôle statique. return est dynamique, en ce sens qu'il revient à l'appelant, qui peut se trouver n'importe où. Cela signifie "J'ai accompli ma responsabilité, il n'y a nulle part ailleurs où regarder pour voir ce que je fais." break va toujours quelque part qui a une portée lexicale, tout comme avec les boucles.

Je pense que return 'label expr lit très bien du point de vue « faites ce que je dis » en ce sens qu'il peut être considéré comme « retourner l'expression à l'emplacement « étiquette » . Je ne pense pas que break 'label expr lit aussi bien dans cette perspective...

Isolé des autres langages de programmation, j'aurais donc pu préconiser return 'label expr . Cependant, étant donné le flux de contrôle dans d'autres langages, la réutilisation de return devient soudainement une option beaucoup moins viable et cela me fait pencher en faveur de break 'label expr .

Je suis fermement d'avis que cela devrait être break 'label expr et non return 'label expr . Faire autrement serait totalement incompatible avec notre utilisation actuelle de break 'label dans les boucles.

@SoniEx2 Je pense que je préfère l'extrait que vous avez publié, en grande partie parce que les variables sont un bon moyen de documenter les invariants de boucle. OTOH, Il pourrait être possible de faire la même chose avec les noms d'étiquettes (c'est-à-dire à chaque fois que ce bloc étiqueté est entré, l'invariant P est maintenu). Je suppose que c'est un endroit où il serait bon d'avoir quelques échantillons de code supplémentaires de la nature...

Proposition de stabilisation

La fonctionnalité a été implémentée dans https://github.com/rust-lang/rust/pull/50045 par @est31 et est disponible tous les soirs depuis le 2018-05-16 (+17 semaines) et a donc suffisamment cuit pour la stabilisation. De plus, aucun problème n'a été signalé depuis que le PR qui a mis en œuvre cela a été fusionné. Toutes les questions non résolues ont également été résolues depuis, et il existe un fort consensus sur le fait que break devrait être la syntaxe de surface au lieu de return .

Ainsi, je me déplace pour stabiliser label-break-value (RFC 2046).

@rfcbot fusionner

Reportage

  • Porte de fonctionnalité :

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/feature-gates/feature-gate-label_break_value.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/feature-gates/feature-gate-label_break_value.stderr

  • Diagnostic : https://github.com/rust-lang/rust/blob/master/src/librustc_passes/diagnostics.rs#L285
  • Essais :

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_continue.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_unlabeled_break.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/label/label_break_value_illegal_uses.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/lint/unused_labels.rs

    • https://github.com/rust-lang/rust/blob/master/src/test/ui/run-pass/for-loop-while/label_break_value.rs

A FAIRE avant FCP

@rfcbot concerne les

Le dernier fichier de test a actuellement un FIXME :

// FIXME: ensure that labeled blocks work if produced by macros and in match arms

Cela devrait être résolu avant de se stabiliser.
J'ai écrit quelques tests pour vérifier que le comportement attendu wrt. le FIXME est actuellement implémenté :

// run-pass

#![feature(label_break_value)]

#[test]
fn lbv_match_test() {
    fn test(c: u8, xe: u8, ye: i8) {
        let mut x = 0;
        let y = 'a: {
            match c {
                0 => break 'a 0,
                v if { if v % 2 == 0 { break 'a 1; }; v % 3 == 0 } => { x += 1; },
                v if { 'b: { break 'b v == 5; } } => { x = 41; },
                _ => {
                    'b: {
                        break 'b ();
                    }
                },
            }
            x += 1;
            -1
        };

        assert_eq!(x, xe);
        assert_eq!(y, ye);
    }

    test(0, 0, 0);
    test(1, 1, -1);
    test(2, 0, 1);
    test(3, 2, -1);
    test(5, 42, -1);
    test(7, 1, -1);
}

#[test]
fn lbv_macro_test() {
    macro_rules! mac1 {
        ($target:lifetime, $val:expr) => {
            break $target $val;
        };
    }
    let x: u8 = 'a: {
        'b: {
            mac1!('b, 1);
        };
        0
    };
    assert_eq!(x, 0);
    let x: u8 = 'a: {
        'b: {
            if true {
                mac1!('a, 1);
            }
        };
        0
    };
    assert_eq!(x, 1);
}
// compile-fail

#![feature(label_break_value)]

fn lbv_macro_test_hygiene_respected() {
    macro_rules! mac2 {
        ($val:expr) => {
            break 'a $val;
        };
    }
    let x: u8 = 'a: {
        'b: {
            if true {
                mac2!(2);
            }
        };
        0
    };
    assert_eq!(x, 2);

    macro_rules! mac3 {
        ($val:expr) => {
            'a: {
                $val
            }
        };
    }
    let x: u8 = mac3!('b: {
        if true {
            break 'a 3;
        }
        0
    });
    assert_eq!(x, 3);
    let x: u8 = mac3!(break 'a 4);
    assert_eq!(x, 4);
}

Des tests similaires à ceux-ci devraient être ajoutés avant de passer du FCP proposé au FCP.

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

  • [x] @Centril
  • [x] @aturon
  • [x] @cramertj
  • [x] @eddyb
  • [ ] @joshtriplett
  • [x] @nikomatsakis
  • [ ] @nrc
  • [x] @pnkfelix
  • [x] @scottmcm
  • [ ] @sansbateaux

Préoccupations :

  • coût-bénéfice (https://github.com/rust-lang/rust/issues/48594#issuecomment-422235234)
  • FIXME-in-tests (https://github.com/rust-lang/rust/issues/48594#issuecomment-421625182)
  • cas d'utilisation (https://github.com/rust-lang/rust/issues/48594#issuecomment-422281176)

Une fois qu'une majorité de réviseurs approuvera (et aucun ne s'y opposera), cela entrera dans sa dernière période de commentaires. 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 tagués peuvent me donner.

@rfcbot concerne le rapport coût-bénéfice

De la proposition RFC FCP :

Un autre groupe a estimé que le « coût-bénéfice » de cette fonctionnalité pour la langue n'était pas tout à fait à la hauteur. En d'autres termes, la complexité accrue que la fonctionnalité apporterait - en d'autres termes, la possibilité que les gens l'utilisent réellement et que vous deviez lire leur code, je suppose, ainsi que la taille globale du langage - n'était pas à la hauteur de son utilité.

Je ne pense toujours pas que nous devrions avoir cette fonctionnalité du tout. Depuis qu'il a été mis en œuvre, a-t-il été beaucoup utilisé? Dans les cas où il a été utilisé, est-il nettement pire d'utiliser des fonctions ? S'il y a un avantage ici, l'emporte-t-il sur le coût de rendre la langue plus grande et plus complexe ?

@nrc Je partage le même souci. Je comprends l'argument pour l'avoir disponible afin que les macros puissent l'utiliser, mais en même temps, je préférerais de loin ne pas l'avoir du tout.

Je ne veux pas aux arguments rechapés du fil RFC original, mais je pense que ce point est raisonnable de poser des questions sur les expériences avec cette fonctionnalité. Quelles utilisations a-t-il vu ?

les analyseurs syntaxiques manuscrits, principalement... (J'aime mes analyseurs syntaxiques manuscrits >.<)

serait plus utile/plus facile à utiliser avec label-propagate ( try_foo() 'bar? ).

@rfcbot concerne les cas d'utilisation

Résumant une discussion sur Discord : nous aimerions voir des cas d'utilisation concrets pour cela, à partir du code réel, qui ne sont pas plus clairs lorsqu'ils sont réécrits pour ne pas utiliser cette fonctionnalité.

~FWIW, mon implémentation de EXPR is PAT repose sur le fait que break 'label value soit disponible au moins dans AST, je ne sais pas comment le désucrage pourrait fonctionner sans cela.
L'implémentation de EXPR? dans le compilateur repose également sur break 'label value .~

~ C'est une sorte de bloc de construction de base pour des fonctionnalités de flux de contrôle plus importantes, il peut donc être nécessaire de l'implémenter dans le compilateur de toute façon.
Donc, le "coût" ici est probablement juste le rendre disponible dans la syntaxe de surface.~

EDIT : j'ai totalement mal interprété le problème, je pensais qu'il s'agissait d'environ loop { ... break 'label value ... } , pas de bloquer { ... break 'label value ... } .
Je n'ai jamais eu l'occasion d'essayer celui-ci car j'oublie toujours qu'il est déjà implémenté.

@petrochenkov S'adressant à @joshtriplett sur Discord, ils ont souligné qu'ils s'inquiétaient de la complexité face à l'utilisateur, et non de la mise en œuvre du langage.

Je pense que l'augmentation de la complexité est minime : pour les lecteurs, ce que cela signifie devrait être évident car le concept existe déjà en boucles, etc.
Sinon, je devrais utiliser une boucle avec une instruction break inconditionnelle, ce qui est moins clair et en fait, il y a même un problème (never_loop) à ce sujet. Donc je pense qu'il y a un avantage.

En ce qui concerne les cas d'utilisation, ce sujet a déjà été abordé dans la RFC. J'ai indiqué mon cas d'utilisation ici . Voir également le cas d'utilisation répertorié par @scottmcm directement ci-dessous. Peut-être qu'il y en a plus dans le fil, idk. @joshtriplett est-ce que cela résout la question du cas d'utilisation ?

Je suis d'accord avec @nrc et @joshtriplett et je souhaite également soulever une préoccupation de processus ici : nous avons provisoirement accepté cette RFC avec une mise en garde explicite selon laquelle la stabilisation a été bloquée lors de la révision des questions que @joshtriplett ont soulevées, mais celle de @Centril La proposition de fusion ne mentionne pas du tout ce problème de blocage et traite cela comme une fusion très standard "la fonctionnalité a été cuite". Je ne blâme pas @Centril pour cela, mais une panne de processus : si nous acceptons provisoirement des fonctionnalités avec des bloqueurs non résolus sur la stabilisation, nous devons suivre ces bloqueurs.

Cela m'inquiétait au niveau de l'ensemble de nos processus de voir que cela s'est déroulé pendant 2 jours et demi sans que le problème de blocage ne soit évoqué, et avec la plupart des membres de l'équipe ayant déjà coché leur case. Il est concevable, puisque nous n'avons plus besoin d'un consensus actif de tous les membres, que cela aurait pu entrer dans le FCP sans même que le bloqueur soit levé. Cela ressemble à une subversion de l'accord précédent qui m'a amené à accepter la fusion du RFC, et je pense que cela est entièrement dû à un suivi insuffisant des informations.

@withoutboats Oui, exactement. Cela me rend un peu moins enclin, à l'avenir, à accepter les choses sur la base du "nous allons XYZ pendant le processus de stabilisation", jusqu'à ce que nous ayons un processus en place qui rend extrêmement improbable que cela soit manqué.

@sansbateaux

Je ne blâme pas @Centril pour cela, mais une panne de processus : si nous acceptons provisoirement des fonctionnalités avec des bloqueurs non résolus sur la stabilisation, nous devons suivre ces bloqueurs.

Mes excuses néanmoins ; Je n'étais pas au courant de la mise en garde, mais j'aurais quand même dû vérifier de telles choses lorsque j'ai créé le problème de suivi.

Cela ressemble à une subversion de l'accord précédent qui m'a amené à accepter la fusion du RFC, et je pense que cela est entièrement dû à un suivi insuffisant des informations.

J'aurais dû lui poser une question non résolue ; Je crois que l'erreur de processus s'est produite à ce moment-là.
Quant à savoir comment améliorer le processus ; Je pense qu'il est important que les questions non résolues parviennent au


Quant à la proposition de fusion fcp ; Personnellement, je pense que ce serait utile pour des raisons d'uniformité et pour une utilisation par des macros. Cependant, si vous pensez qu'il est trop tôt pour proposer une stabilisation ; n'hésitez pas à annuler la proposition :)

@est31

Sinon, je devrais utiliser une boucle avec une instruction break inconditionnelle,

Ou restructurer le code pour éviter l'un ou l'autre.

J'ai indiqué mon cas d'utilisation ici .

Pourquoi ne pas remplacer le break 'pseudo_return par return Ok(vectors); ?

comme je l'ai mentionné ici , ceci est utile pour les analyseurs syntaxiques écrits à la main (même sans label-propagate ( try_foo() 'bar? )).

label-break-value permet l'impérisation aisée d'un code par ailleurs fonctionnel. généralement, le code impératif a tendance à être plus lisible que le code fonctionnel.

Ou restructurer le code pour éviter l'un ou l'autre.

Bien sûr, Rust est terminé. Mais la restructuration pourrait être un peu difficile. Dans l'ensemble, vous pouvez rejeter presque toutes les fonctionnalités de sucre (et c'est la fonctionnalité de sucre) sur la base "vous pouvez simplement utiliser les méthodes existantes".

Pourquoi ne pas remplacer le break 'pseudo_return par return Ok(vectors);?

En fait dans ce cas vous avez raison, on peut remplacer le break par un retour Ok. Mais dans d'autres cas, vous souhaiterez peut-être effectuer le traitement par la suite, etc. Le vérificateur d'emprunt fonctionne mal au-delà des limites de la fonction, vous ne pouvez pas factoriser chaque bloc de ce type dans une fonction.

Quoi qu'il en soit, j'ai rompu mon silence sur les commentaires sur les caractéristiques linguistiques par des moyens officiels, et je le regrette. Tous les mêmes points, ressassés maintes et maintes fois. Cette merde est une perte de temps, désolé. Alors n'attendez pas d'autres commentaires de ma part.

@ est31 J'apprécie vraiment que vous nous fournissiez des détails ; Je vous remercie.

Il y a un problème d'accessibilité pour tester et utiliser ces choses, en raison de l'exigence de Rust tous les soirs.

Je cible stable. J'espère que cela pourra être résolu un jour.

Si nous voulons des cas d'utilisation, en voici un que j'ai rencontré il y a quelque temps ; essentiellement une élaboration du "do processing after " de L , u8 , u , U , }{ R , }), et des jetons multi-octets avec des "espaces" dans le nombre d'octets utilisés (pas sûr que cela ait du sens sans l'exemple). La fonction get_token ressemble actuellement à ceci :

fn get_token(&mut self) -> Token {
    match decode_byte(self.source) {
        // ...

        // repeat four times with small variations for b'U', b'L', and b'R':
        Some((b'u', rest)) => match decode_byte(rest) {
            Some((b'"', rest)) => self.string_literal(Utf16String, rest),
            Some((b'\'', rest)) => self.char_constant(Utf16Char, rest),
            Some((b'R', rest)) => match decode_byte(rest) {
                Some((b'"', rest)) => self.raw_string_literal(Utf16String, rest),
                _ => self.identifier(rest),
            },
            Some((b'8', rest)) => match decode_byte(rest) {
                Some((b'"', rest)) => self.string_literal(Utf8String, rest),
                Some((b'\'', rest)) => self.char_constant(Utf8Char, rest),
                Some((b'R', rest)) => match decode_byte(rest) {
                    Some((b'"', rest)) => self.raw_string_literal(Utf8String, rest),
                    _ => self.identifier(rest),
                },
                _ => self.identifier(rest),
            },
            _ => self.identifier(rest),
        },

        // ...

        // the "gap" mentioned above is here: single-byte '.' and triple-byte '...' but no double-byte '..':
        Some((b'.', rest)) => match decode_byte(rest) {
            Some((b'0'..=b'9', rest)) => self.number(rest),
            // note the _inner to avoid shadowing the outer `rest` used by the inner `Dot` case:
            Some((b'.', rest_inner)) => match decode_byte(rest_inner) {
                Some((b'.', rest)) => self.make_token(Ellipsis, rest),
                _ => self.make_token(Dot, rest),
            },
            _ => self.make_token(Dot, rest),
        },

        // ...
    }
}

Remarquez les pyramides de _ => self.identifier(rest) s (répétées quatre fois pour u , U , R , et L ) et _ => self.make_token(Dot, rest) s, formant une sorte de style de continuation-passage où identifier , string_literal , etc. doivent également tous appeler make_token .

J'aurais aimé consolider les choses dans un style moins continu en utilisant break -from-block, et je l'ai presque fait via les loop s, mais j'ai considéré cette version trop étrange à lire. Pour être plus précis:

  • J'aurais déplacé tous les appels make_token vers un seul emplacement après le match decode_byte(self.source) , et je l'aurais inséré - il est petit et contient unsafe avec ses invariants confirmés par get_token .
  • J'aurais utilisé break 'label self.string_literal(..) pour court-circuiter une fois que j'aurais trouvé un " ou ' , puis j'aurais combiné tous les appels de self.identifier(..) jusqu'à la fin de ce bras de correspondance .

    • J'ai peut-être aussi pu linéariser l'explosion combinatoire des préfixes - vérifier u / u8 / U / L , puis vérifier R . Cela utilise moins de break 'label s, mais toujours une poignée.

  • J'aurais utilisé break 'label (Ellipsis, rest) pour court-circuiter une fois que j'aurais trouvé un ... , puis j'aurais combiné les deux (Dot, rest) s jusqu'à la fin de ce bras de correspondance.

Dans l'ensemble, il s'agit essentiellement du "flux de contrôle aplati avec if + retour anticipé", sans qu'il soit nécessaire d'extraire les éléments dans une fonction distincte. C'est extrêmement précieux dans ce cas pour plusieurs raisons :

  • La plupart de ces fonctions seraient de minuscules fonctions à usage unique sans nom valable, servant uniquement à rendre les choses encore moins lisibles que ce style de continuation-y.
  • Certaines de ces fonctions nécessiteraient un tas de paramètres supplémentaires qui, autrement, seraient simplement des locaux simples avec des types inférés.

    • Ou alternativement, des fermetures, qui dans certains de ces cas entraîneraient des problèmes de vérificateur d'emprunt.

  • Comme je l'ai mentionné ci-dessus, des invariants de sécurité sont maintenus dans toutes les fonctions ici. Plus le code de machine à états est linéaire ici, mieux c'est, d'autant plus que les gens reviennent plus tard pour apporter de petites modifications aux nouveaux types de jetons.

J'écrirais le tout mais je suppose que je n'ai jamais commis cette tentative (probablement parce que j'ai rencontré tous les problèmes que j'ai énumérés ci-dessus) et j'ai déjà passé assez de mots ici. :)

@SergioBenitez pourriez-vous nous expliquer l'utilisation par http://rocket.rs/ de label_break_value et votre point de vue sur la stabilisation ?

@Centril Bien sûr ! Voici l'essentiel :

fn transform(request: &Request, data: Data) -> Transform<Outcome<_, _>> {
    let outcome = 'o: {
        if !request.content_type().map_or(false, |ct| ct.is_form()) {
            break 'o Forward(data);
        }

        let mut form_string = String::with_capacity(min(4096, LIMIT) as usize);
        if let Err(e) = data.read_to_string(&mut form_string) {
            break 'o Failure(FormDataError::Io(e));
        }

        Success(form_string)
    };

    Transform::Borrowed(outcome)
}

En utilisant cette fonctionnalité, j'évite :

  • Diviser le bloc en une fonction différente pour les premiers "retours".
  • Ajout de Transform::Borrowed à chaque valeur "de retour" dans le bloc.
  • Inexactitude possible si un autre Transform est renvoyé dans différents cas.

    • Remarque : il s'agit d'un invariant particulier à Rocket et à ce morceau de code.

J'étais assez content de voir que cela existait. C'est exactement ce que je veux écrire. Cela étant dit, je peux clairement écrire cela différemment pour ne pas dépendre de cette fonctionnalité.

@SergioBenitez Merci ! Je me demande si vous pourriez (éventuellement) réécrire ceci avec try { .. } ? Ainsi:

fn transform(request: &Request, data: Data) -> Transform<Outcome<_, _>> {
    Transform::Borrowed(try {
        if !request.content_type().map_or(false, |ct| ct.is_form()) {
            Forward(data)?;
        }

        let mut form_string = String::with_capacity(min(4096, LIMIT) as usize);
        if let Err(e) = data.read_to_string(&mut form_string) {
            Failure(FormDataError::Io(e))?;
        }

        form_string
    })
}

@Centril Oui, cela fonctionnerait tant qu'il n'y a qu'un seul chemin de réussite, ce qui est le cas ici.

Ou restructurer le code pour éviter l'un ou l'autre.

En faisant cela, vous courez le risque d'ajouter des branches ou des appels de sous-programmes supplémentaires qui ne peuvent pas être résolus par l'optimiseur. Pour moi, une telle transformation de flux de contrôle semble être une tâche assez compliquée.

Dans les cas où il a été utilisé, est-il nettement pire d'utiliser des fonctions ? S'il y a un avantage ici, l'emporte-t-il sur le coût de rendre la langue plus grande et plus complexe ?

Une petite fonction peut être intégrée, mais néanmoins, vous gagneriez une surcharge de code inutile ou, disons, des duplications de code stupides.

Je préférerais le code binaire le plus élégant possible. Vous pouvez le faire par un flux de contrôle un peu plus avancé, presque identique à un retour anticipé d'une fonction.

Je me demande si vous pourriez (éventuellement) réécrire ceci avec try { .. } ? Ainsi:
[...]

Cela semble un peu déroutant, car des branches sont introduites, juste pour être optimisées. Mais dans certaines situations, on peut avoir besoin des deux. Ainsi, ayant

'a: {try{...}}

ou

'a: try {...}

serait sympa.

Nous aimerions voir des cas d'utilisation concrets pour cela, à partir du code réel, qui n'apparaissent pas plus clairement lorsqu'ils sont réécrits pour ne pas utiliser cette fonctionnalité.

N'hésitez pas à me déshonorer :

Ce n'est peut-être pas le meilleur code, mais je voulais que les choses fonctionnent du tout.

Je voudrais reformuler expérimentalement la boucle principale en tant que machine à états en termes de style de passage de continuation. Mais les continuations et les appels de queue forcés sont un autre sujet.

Ce serait très bien de voir cette fonctionnalité stabilisée. J'ai de nombreux cas d'utilisation dans lesquels cette syntaxe simplifie et clarifie considérablement l'intention. Pour les longues sections impliquant l'exécution d'opérations faillibles et le déballage de leurs résultats, cette syntaxe est superbe.

Je suis d'accord avec @zesterer que la stabilisation de cette fonctionnalité serait un avantage pour l'écosystème et rendrait certains modèles moins ennuyeux à écrire :+1:

FWIW, je l'ai récemment utilisé dans le compilateur pour la première fois.
Le modèle est le même que dans les exemples précédents - nous vérifions plusieurs conditions et arrêtons de faire ce que nous faisons si l'une d'entre elles est vraie.
https://github.com/rust-lang/rust/blob/21f26849506c141a6760532ca5bdfd8345247fdb/src/librustc_resolve/macros.rs#L955 -L987

@erickt a également écrit du code qui voulait l'utiliser pour la même raison : vérifier plusieurs conditions, éclater une fois que l'une d'entre elles devient fausse. Vous pouvez faire semblant avec des fermetures appelées immédiatement ou des let _: Option<()> = try { ... None?; .. }; mais les deux sont assez bidons.

@withoutboats @nrc @joshtriplett Avez-vous des idées sur les cas d'utilisation présentés jusqu'à présent par diverses personnes ?

Ping @withoutboats @nrc @joshtriplett :) -- lors de la dernière réunion de

Je me suis senti poussé par le billet de blog de greydon à écrire un commentaire ici sur les raisons pour lesquelles je ne veux pas stabiliser cette fonctionnalité. J'ai l'impression que cela a été un peu injuste de ma part d'avoir accepté cette expérience ; il est difficile de prouver un résultat négatif, mais je n'ai aucune idée du type d'échantillon de code qui pourrait surmonter mon opposition à l'ajout de cette forme de flux de contrôle au langage.

Mon objection globale est que je pense simplement que Rust n'a pas besoin de constructions de flux de contrôle de branchement plus ouvertes. Nous avons des correspondances et des boucles, puis en plus de ces types de données algébriques, du sucre de syntaxe, de l'abstraction des fonctions et des macros pour créer une vaste gamme de modèles de flux de contrôle que tout utilisateur ordinaire peut gérer. Cela donne déjà l'impression que cela devient écrasant, et j'ai personnellement dû adopter certaines règles pour gérer la complexité de l'arbre de décision (par exemple, j'ai une règle à ne jamais atteindre pour les combinateurs en première passe : seulement si c'est évident après le en fait, ce serait mieux en tant que combinateurs).

Je voudrais éviter d'ajouter des choix plus largement applicables et très flexibles à la boîte à outils de flux de contrôle de Rust. Je suis uniquement intéressé par l'ajout d'un flux de contrôle ciblant des cas d'utilisation spécifiques et importants et ayant un impact élevé sur l'ergonomie. Cette construction, à mon avis, a les attributs exactement inversés : elle est largement applicable, extrêmement malléable et seulement légèrement plus pratique que les alternatives.

De plus, je pense que cette fonctionnalité a une autre qualité vraiment négative : l'irrégularité due à la rétrocompatibilité. Il est très irrégulier que break sorte de blocs que s'ils sont étiquetés, lorsqu'il sort de boucles non étiquetées. Cela rend la fonctionnalité plus difficile à comprendre qu'elle ne le serait si elle était régulière, exacerbant les attributs négatifs exactement là où je pense qu'ils sont les pires - la compréhensibilité.

Je pense également qu'il y a beaucoup de caractéristiques bien plus importantes sur lesquelles se concentrer, et puisque nous avons une division claire à ce sujet, je préférerais simplement reporter tout examen plutôt que d'essayer de passer par un long processus de consensus sur cette proposition.

Je pense que la bonne chose à faire est de trouver chaque caisse qui utilise cette fonctionnalité et de réécrire le code afin de ne pas utiliser cette fonctionnalité, alors.

@withoutboats Merci d'avoir

Je vais aller de l'avant et être audacieux ici :

@rfcbot annuler
@rfcbot reporter

La proposition de

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

  • [ ] @Centril
  • [ ] @aturon
  • [ ] @cramertj
  • [ ] @eddyb
  • [x] @joshtriplett
  • [ ] @nikomatsakis
  • [ ] @nrc
  • [ ] @pnkfelix
  • [ ] @scottmcm
  • [x] @sansbateaux

Aucune préoccupation actuellement répertoriée.

Une fois qu'une majorité de réviseurs approuvera (et aucun ne s'y opposera), cela entrera dans sa dernière période de commentaires. 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 tagués peuvent me donner.

@sansbateaux

Je voudrais éviter d'ajouter des choix plus largement applicables et très flexibles à la boîte à outils de flux de contrôle de Rust. Je suis uniquement intéressé par l'ajout d'un flux de contrôle ciblant des cas d'utilisation spécifiques et importants et ayant un impact élevé sur l'ergonomie.

Je suis d'accord avec ce principe, et je pense que cette fonctionnalité répond à cette barre (flux de contrôle familier ciblé sur des cas d'utilisation spécifiques et importants). Il y a un certain nombre de fois où j'ai révisé ou écrit du code comme les exemples fournis ci-dessus où je pense que c'est de loin la façon la plus propre et la plus facile à lire pour écrire ce code. La réutilisation de constructions de flux de contrôle existantes comme loop avec un break inconditionnel à la fin induit l'utilisateur en erreur, et les fonctions immédiatement appliquées sont souvent insuffisantes en raison de l'utilisation de ? , await! , ou d'autres constructions de flux de contrôle intermédiaires.

L'utilisation de loop + break prête à confusion, et nous préférerions plutôt que les utilisateurs déclarent leur intention réelle plutôt que de les obliger à abuser d'un outil conçu pour un style de contrôle différent. Ceci est similaire au fait que nous avons même un loop construction, tandis que d' autres langues se contentent de while true { ... } . Faire cela permet d'écrire du code qui exprime une intention plus claire et est donc plus lisible.

De plus, cette fonctionnalité est quelque chose que je m'attendais toujours à

TL; DR : Je pense que cette fonctionnalité prend en charge des cas d'utilisation du monde réel qui ne peuvent être écrits autrement que via des instructions if fortement imbriquées ou en abusant d'autres constructions comme loop-break-value. C'est un petit ajout à la syntaxe de surface qui fait que les blocs se comportent comme je m'attendrais à ce qu'ils se comportent et me permet d'écrire le code que je veux dire plutôt que quelque chose de beaucoup plus hacker.

@cramertj Désolé, je suis un peu confus. On dirait que vous et @withoutboats / @joshtriplett dites exactement le contraire l'un de l'autre ?

(fwiw, je suis d'accord avec @withoutboats /@joshtriplett)

@mark-im Je ne suis pas d'accord avec eux pour dire que cette fonctionnalité devrait être fermée. Je pense qu'il devrait être fusionné (comme indiqué par mes commentaires et coché ci-dessus).

Je suis d'accord avec @withoutboats que nous ne devrions pas ajouter de nouveaux outils inconnus qui ne sont pas motivés par des cas d'utilisation spécifiques et importants. Je pense que cette fonctionnalité se sentira familier et est motivé par des objectifs particuliers, importants cas d' utilisation.

@sansbateaux

J'ai l'impression que cela a été un peu injuste de ma part d'avoir accepté cette expérience ; il est difficile de prouver un résultat négatif, mais je n'ai aucune idée du type d'échantillon de code qui pourrait surmonter mon opposition à l'ajout de cette forme de flux de contrôle au langage.

Je ne pense pas que la barre que nous avons fixée était "nous montrer qu'aucun code ne pourrait exister qui pourrait être mieux écrit avec label-break-value" - c'est "nous montrer que le code spécifique pour lequel nous voulons label-break-value pourrait être plus clairement écrit d'une autre manière." Cette conversation a commencé lorsque vous et d'autres avez demandé des exemples motivants d'utilité de cette fonctionnalité, et de nombreuses personnes sur ce fil (y compris moi-même) ont fourni des exemples. Ils n'étaient pas convaincants pour vous ou pour @joshtriplett , alors je vous demande maintenant comment vous

Je vais aller de l'avant et être audacieux ici :

@rfcbot annuler

Proposition de @scottmcm annulée.

Il est très irrégulier pour break de sortir des blocs uniquement s'ils sont étiquetés, quand il sort de boucles non étiquetées.

Je ne sais pas si cela a été mentionné dans le fil ou non, mais le fait que les blocs ne puissent pas être ciblés par break; rend les blocs étiquetés qualitativement plus puissants que les loop s étiquetés dans un certain sens.

Avec des blocs étiquetés, vous pouvez créer une macro de flux de contrôle qui prend en charge les break; intérieur, l'infra de la macro étant complètement invisible pour l'utilisateur.
Avec les boucles étiquetées, il y a toujours un risque que des break; étiquette soient utilisés et soient détectés par l'infra macro plutôt que par la cible prévue.

Dans mon prototype EXPR is PAT j'ai utilisé les loop habituels pour le désucrage au départ, mais les choses se sont cassées à cause du problème susmentionné, je n'ai pas pu amorcer le compilateur en particulier.
Les blocs étiquetés n'étaient pas implémentés à l'époque, j'ai donc dû introduire un " loop " spécial "non ciblable" dans AST et l'utiliser dans le désucrage.

Comme indiqué par ma case à cocher, je suis favorable à la stabilisation de cela dès maintenant. Je pense qu'il s'agit d'une extension naturelle du langage qui facilite les macros ainsi que le flux de contrôle qui ne s'adapte pas si facilement aux modèles fonctionnels. Même si NLL rend les durées de vie non lexicales, je pense que pouvoir annoter des blocs avec des durées de vie me semble également utile sur le plan pédagogique, tout comme l'ascription de type.

Cependant, comme le consensus a été difficile à atteindre, dans l'intérêt de le trouver, je suggérerais que nous essayions d'accélérer le travail sur try { ... } ainsi que d'accélérer le travail sur l'expérimentation de https://github.com /rust-lang/rust/issues/53667 (ou en même temps que la syntaxe EXPR is PAT @petrochenkov).

Désolé, quel est le statut de ceci maintenant ? Sommes-nous en FCP ou non ?

@mark-im Puisqu'aucune des étiquettes proposed-final-comment-period ou final-comment-period n'est sur la question, nous ne sommes pas dans FCP ou une proposition pour une.

(et nous n'avons jamais été dans FCP, bien qu'il y ait eu une proposition initiale d'entrer dans FCP avec toutes les cases sauf trois cochées)

@scottmcm

Je vais aller de l'avant et être audacieux ici :

Cette répétition de la formulation de

Je suis persuadé que c'est effectivement une bonne idée. J'ai été très opposé à cette fonctionnalité auparavant, et je ne pense toujours pas que ce soit une fonctionnalité qui devrait jamais être utilisée par les programmeurs. Les cas d'utilisation ci-dessus sont quelque peu convaincants, mais je pense qu'ils pourraient encore être mieux pris en compte dans des fonctions plus petites. Le cas que j'ai trouvé vraiment convaincant est celui de la sortie des macros. Si l'utilisateur peut insérer return une manière ou d'une autre, cela empêche la traduction en fonctions. Cependant, ce que nous voulons vraiment, c'est tout simplement goto . Je me demande si la "grande" solution n'est pas les macros qui peuvent produire des HIR, plutôt que des jetons, mais c'est un peu là-bas. En attendant, ce serait bien d'avoir cette fonctionnalité pour les auteurs de macros, donc dans l'ensemble, je pense que nous devrions nous stabiliser.

Quelqu'un a-t-il essayé de réécrire les choses d'une manière plus lente, moins intuitive et moins ergonomique ?

Casser des blocs, c'est comme un goto ergonomique. Il n'a à peu près aucun des problèmes de goto, mais est tout aussi puissant.

Sur la base du revirement de propose , encore une fois, de stabiliser label_break_value .
Mon rapport se trouve sur https://github.com/rust-lang/rust/issues/48594#issuecomment-421625182 .

@rfcbot fusionner
@rfcbot concerne les

Pour m'assurer que @joshtriplett et @withoutboats aient le temps de soulever toutes les préoccupations qu'ils pourraient encore avoir,

@rfcbot concerne donner-aux-bateaux-et-josh-le-temps-pour-soulever-toutes-les-inquiétudes-qu'ils-pourraient-avoir-encore-

En guise de note de processus pour ce problème et pour tous les autres, veuillez éviter d'annuler les propositions dans les deux sens... si vous pensez que quelque chose ne devrait pas avancer, utilisez simplement les préoccupations pour cela.

Le membre de l'équipe @Centril a proposé de fusionner cela. L'étape suivante est l'examen par le reste des membres de l'équipe tagués :

  • [x] @Centril
  • [x] @aturon
  • [x] @cramertj
  • [x] @eddyb
  • [x] @joshtriplett
  • [ ] @nikomatsakis
  • [ ] @nrc
  • [x] @pnkfelix
  • [ ] @scottmcm
  • [ ] @sansbateaux

Préoccupations :

Une fois qu'une majorité d'examinateurs approuve (et au plus 2 approbations sont en attente), cela entrera dans sa dernière période de commentaires. 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 tagués peuvent me donner.

@rfcbot concerne le blocage

J'ai déjà exprimé ma position, à savoir que Rust ne devrait pas avoir cette fonctionnalité. Je doute que ce soit une priorité suffisamment élevée pour le projet pour que je puisse consacrer du temps à m'engager dans un processus de consensus sur cette proposition à tout moment dans un proche avenir.

@rfcbot résoudre donner-bateaux-et-josh-temps-pour-soulever-toutes-inquiétudes-ils-pourraient-encore-avoir

Le 5 janvier 2019 à 09:18:29 PST, Mazdak Farrokhzad [email protected] a écrit :

@rfcbot résoudre
donnez-aux-bateaux-et-le-temps-de-josh-pour-soulever-toutes-les-inquiétudes-qu'ils-pourraient-encore-avoir

Nous avons déjà soulevé des inquiétudes, et elles se résument à « cela ne devrait pas être dans la langue ». Ces inquiétudes ne disparaissent pas.

Je préfère la notion de nrc selon laquelle les macros devraient être capables de générer des IR. Cela semble être une solution tout à fait raisonnable pour cela et pour de nombreuses possibilités futures, sans nécessairement ajouter à la syntaxe de surface du langage.

@joshtriplett

Nous avons déjà soulevé des inquiétudes, et elles se résument à « cela ne devrait pas être dans la langue ». Ces inquiétudes ne disparaissent pas.

Pour être clair, je voulais dire les enregistrer avec @rfcbot car le bot ne verra pas les anciennes préoccupations enregistrées après l'annulation d'une proposition. J'ai soulevé cette préoccupation parce que je sais que vous avez des préoccupations, par courtoisie envers vous.

Je préfère la notion de nrc selon laquelle les macros devraient être capables de générer des IR. Cela semble être une solution tout à fait raisonnable pour cela et pour de nombreuses possibilités futures, sans nécessairement ajouter à la syntaxe de surface du langage.

Je n'ai aucune idée de ce à quoi cela ressemblerait ou que c'est une meilleure idée ; cela semble plutôt spéculatif (dans le sens de "cela peut prendre des années avant qu'un design ne soit même accepté") et plus coûteux que la solution plutôt bon marché de break 'label expr qui présente également des avantages au-delà d'être le résultat d'une macro expansion.

_En tant que note de processus pour ce problème et pour tous les autres, veuillez éviter d'annuler les propositions dans les deux sens... si vous pensez que quelque chose ne devrait pas avancer, utilisez simplement les préoccupations pour cela._

Je suis certainement d'accord avec cela. Cependant, je ne pense pas qu'une préoccupation soit le mécanisme approprié ici. Une préoccupation semble être "si cela était résolu, j'approuverais". Ici, le problème est "cela ne devrait pas du tout avancer, "fusionner" n'est pas la bonne cible, j'aimerais plutôt "fermer"". Cela ne semble pas mieux géré par le mécanisme d'une "préoccupation".

@joshtriplett En tant que point d'intérêt, pourriez-vous m'indiquer les préoccupations soulevées que vous avez mentionnées précédemment ? J'ai parcouru le fil RFC d'origine et je ne sais pas à quoi il est spécifiquement fait référence. Merci.

@rfcbot concerne l'ergonomie et l'optimisation/performance (exemple non-rouille de performance et ergonomie réduite https://gist.github.com/SoniEx2/fc5d3614614e4e3fe131#file-special-lua )

idk si j'utilise ce droit

(notez comment certains des "désucrages" (en fait, je dirais que c'est tout le contraire du désucrage - goto est un primitif, les boucles dessugar en goto) sont assez affreux)

(notez également que lua n'a rien étiqueté, ce qui vous oblige à utiliser goto. même dans ce cas, je pense qu'au moins un break-block serait toujours nécessaire, mais probablement avec un "déssucre" plus propre.)

@rfcbot préoccupation devrait-fermer-pas-fusionner

(Comme suggéré par @centril sur Discord.)

Je ne sais pas si c'est un argument en faveur de la stabilisation ou contre, mais notez que c'est trivialement facile à encoder dans Rust stable aujourd'hui :

fn main() {
    'foo: for _ in 0..1 {
        println!("break");
        break 'foo;
        println!("broken");
    }
}

Donc, d'une part, nous devrions simplement stabiliser cela car cela fait à peine grandir le langage, élidant juste for _ in 0..1 , d'autre part nous ne devrions pas stabiliser cela car il existe un moyen facile de le faire aujourd'hui (pour quand c'est vraiment nécessaire) et nous ne devrions pas encourager l'utilisation d'un tel anti-modèle.

cela semble un peu déroutant et l'étiquette est inutile.

casser des blocs est beaucoup moins déroutant et nécessite l'étiquette.

Je ne comprends pas pourquoi vous pensez que briser des blocs est un anti-modèle. ce qui s'applique à X ne s'applique pas nécessairement à pas-tout à fait-X. Je peux peut-être acheter un ordinateur pour 299,99 $, mais pas 299,98 $, même s'ils sont "fondamentalement" identiques.

@nrc

Je ne sais pas si c'est un argument en faveur de la stabilisation ou contre, mais notez que c'est trivialement facile à encoder dans Rust stable aujourd'hui :

    'foo: for _ in 0..1 {
        break 'foo;
    }

Donc d'une part, nous devrions juste stabiliser cela car il fait à peine grandir le langage, élidant juste for _ in 0..1 ,

'a: for _ in 0..1 { break 'a } peut fonctionner si le but explicite est de ne jamais faire écrire par personne 'a: { ... break 'a e; ... } ; cependant, il a l'inconvénient de générer des IR indésirables que le vérificateur de type, MIR et LLVM doivent traiter, ce qui aggrave les temps de compilation (au moins par rapport à LBV).

d'un autre côté, nous ne devons pas stabiliser cela car il existe un moyen facile de le faire aujourd'hui (pour quand c'est vraiment nécessaire) et nous ne devons pas encourager l'utilisation d'un tel anti-modèle.

Je pense que nous ne sommes pas d'accord sur le fait que LBV est un anti-modèle. En fin de compte, nous avons choisi d'imprégner Rust d'un flux de contrôle impératif plutôt que de simplement faire de Rust un langage de programmation fonctionnel. Bien que j'aurais peut-être préféré ne pas avoir un tel flux de contrôle, c'est un fait accompli. La question est alors de savoir si LBV est en quelque sorte plus illisible ou problématique que d'autres mécanismes impératifs dans Rust. Je ne pense pas que ce soit le cas.

Bien que je pense que les fonctions doivent être courtes (verticalement) et peu profondes (indentation), il vaut mieux être longues et peu profondes que longues et profondes. Je trouve que LBV aide à éviter la profondeur dans certains des flux les plus compliqués. LBV me semble également lisible car il indique explicitement où il va sauter, ce qui rend le flux bien compris. Dans l'ensemble, je trouve que LBV est l'un des flux de contrôle impératif les moins problématiques.

  1. 'a: for _ in 0..1 { break 'a }
  2. 'a: { ... break 'a e; ... }

ceux-ci peuvent sembler similaires, mais ils ne sont pas tout à fait les mêmes. alors que le premier est sans doute un anti-modèle, le second ne l'est sans doute pas.

un peu comme loop {} vs while true {} (accordé loop peut renvoyer une valeur alors que while ne peut pas, mais ignorons cela un peu)

@nrc Cela n'a pas tout à fait le même comportement cependant : https://github.com/rust-lang/rust/issues/48594#issuecomment -450246249

En particulier, le comportement de break; et continue; est différent. Considérez ce code hypothétique :

some_macro! {
   ...
   break;
   ...
}

Si some_macro s'étend à 'a { ... } , cela a un comportement différent que s'il s'étend à 'a: for _ 0..1 { ... }

@SoniEx2

Quelqu'un a-t-il essayé de réécrire les choses d'une manière plus lente, moins intuitive et moins ergonomique ?

Veuillez essayer de communiquer vos préoccupations d'une manière plus positive et productive. Se moquer des autres ne nous mène nulle part et fait que les gens se sentent mal. Nous sommes tous ici parce que nous voulons nous assurer que Rust est le meilleur langage de programmation possible, et une partie de ce processus consiste à s'assurer que la communauté est aussi positive et encourageante que possible.

Peut-être suis-je victime d'un gros malentendu. Je ne prétendrai pas être un expert en LLVM, en interne de Rust ou en des choses similaires. Cela dit, j'ai une certaine expérience rudimentaire de la conception de compilateurs et je ne comprends pas bien quel est le problème. Si quelqu'un pouvait expliquer les choses, j'apprécierais vraiment.

Ma façon de voir les choses :

1) Cela ne change pas l'ergonomie du contrôle de flux. Il existe déjà des constructions comme celle-ci dans Rust telles que 'a: loop { if x { break 'a; } break; } .

2) Cela ne change pas particulièrement l'ergonomie des retours de blocs : les boucles sont déjà capables de retourner des valeurs.

Pour moi, cela ressemble plus à l'achèvement intuitif d'un ensemble de fonctionnalités que Rust possède déjà - la généralisation de ces fonctionnalités pour tous (ou au moins plus) blocs.

En termes de préoccupations concernant le fait que cela soit trop similaire à goto , je suis encore plus confus. Cela n'ajoute rien qui n'est pas déjà possible dans Rust, et cela ne permet pas les sauts en arrière (le principal problème qui entraîne une mauvaise utilisation de goto régressant en code spaghetti), donc j'échoue pour comprendre quelles ramifications cette fonctionnalité pourrait avoir dans codegen, car cela semble effectivement n'être que du sucre de syntaxe pour une boucle toujours interrompue.

Quelqu'un pourrait-il m'expliquer en termes plus précis quels problèmes existent?

Sur le plan de la procédure, à l'OMI, il n'est pas approprié de signaler des problèmes de style concern blocking ou concern should-close-not-merge : ce problème ne répertorie pas un problème avec la fonctionnalité telle que proposée ou tout ce que nous pourrions travailler à résoudre- - c'est juste un perma-bloquant. Je ne pense pas que le mécanisme de préoccupation de rfcbot devrait être utilisé pour bloquer les fonctionnalités de manière permanente, mais uniquement pour aider à suivre la résolution des problèmes et s'assurer qu'ils sont traités / résolus de manière appropriée. Ma compréhension du processus était que l'intention était d'utiliser une case à cocher non cochée pour marquer votre désaccord, et que les préoccupations devaient être utilisées pour signaler des problèmes spécifiques et pouvant être discutés concernant la fonctionnalité et sa fonctionnalité.

J'aurais pu déposer des problèmes similaires "Je n'aime pas ça" sur d'autres fonctionnalités avec lesquelles je n'étais personnellement pas d'accord (voir par exemple uniform_paths), mais je ne pense pas que l'obstruction à l'infini soit un moyen productif de concevoir un langage.

S'il y a des préoccupations spécifiques sur la façon dont cette fonctionnalité interagit avec d'autres parties du langage qui devraient être discutées/résolues (par exemple, « cela semble être évité par try { ... } » ou « cela aide à activer un- fonctions idiomatiquement grandes") Je pense qu'il serait plus productif de les classer de cette façon.

@cramertj Je n'aurais pas déposé une telle préoccupation sous cette forme si @Centril ne l'avait pas explicitement suggéré. (Personnellement, j'aurais préféré que le FCP n'ait pas été proposé.)

Que proposeriez - vous que le processus approprié pour « cela devrait être fermé », si quelqu'un a déposé un P-FCP avec rfcbot pour autre chose que « proche »? « assurez-vous qu'ils sont traités / résolus de manière appropriée » sonne l'équivalent de « ça va se stabiliser un jour, que faut-il pour y arriver ? ». Cela ne laisse pas de chemin dans le flux car "cela ne doit jamais être stabilisé, cela ne doit pas faire partie du langage".

Ma compréhension du processus était que l'intention était d'utiliser une case à cocher non cochée pour marquer votre désaccord

Ensuite, nous devrons modifier le processus pour exiger que toutes les cases à cocher soient cochées pour continuer.

Je ne crois pas que l'obstruction à l'infini soit un moyen productif de concevoir un langage.

Je ne crois pas non plus. J'aimerais trouver un chemin pour conclure cela aussi, j'aimerais juste que cela se termine dans une direction différente.

Pour mémoire, sur la base des expériences avec ce RFC, je ne pense pas que ce soit une nouvelle fois une bonne idée de répondre à un RFC avec une mise en garde de "nous pouvons évaluer/répondre pendant la stabilisation si nous devons procéder", car cela me semble clair cela produit des problèmes de procédure critiques comme celui-ci.

Nous avons besoin d'un chemin pour dire "oui, je peux voir comment cette caractéristique individuelle rendrait la langue plus expressive, mais sur la base de l'évaluation globale de la langue, elle ne porte pas son poids, et je ne veux pas étendre davantage le surface de la langue de cette manière". Je pense que nous devons régulièrement faire cet appel, de peur que chaque fonctionnalité proposée ne soit considérée comme inévitable sous une forme ou une autre et comme une simple question de réprimer les objections et de persister.

Je vais répéter ce que j'ai déjà dit car cela semble avoir été manqué : https://github.com/rust-lang/rust/issues/48594#issuecomment -451795597

J'ai essentiellement parlé des différences entre l'utilisation d'une boucle et l'utilisation d'un bloc de rupture.

principalement, les blocs de rupture ont une syntaxe différente, une sémantique (légèrement) différente (à tout le moins par rapport à une rupture sans étiquette), ils signifient une intention différente et quelques autres choses mineures.

rfcbot prend-il en charge les anti-inquiétudes ?

@joshtriplett

Nous avons besoin d'un chemin pour dire "oui, je peux voir comment cette caractéristique individuelle rendrait la langue plus expressive, mais sur la base de l'évaluation globale de la langue, elle ne porte pas son poids, et je ne veux pas étendre davantage le surface de la langue de cette manière". Je pense que nous devons régulièrement faire cet appel, de peur que chaque fonctionnalité proposée ne soit considérée comme inévitable sous une forme ou une autre et comme une simple question de réprimer les objections et de persister.

Oui, c'est un danger malheureux de notre processus actuel que l'acceptation ou le rejet d'une fonctionnalité nécessite un consensus complet de l'équipe appropriée. Lorsque l'équipe lang est aussi nombreuse qu'elle l'est maintenant, il est difficile de toujours y parvenir. est un code qui peut être mieux écrit en utilisant cette fonctionnalité, mais nous ne sommes pas convaincus que cela vaut les coûts que vous voyez ici. Je ne suis pas sûr d'un moyen de vous convaincre que ces cas sont une motivation suffisante, car il semble que continuer à fournir des exemples (y compris l'exemple de macro, qui est littéralement impossible à écrire dans un autre style) ne suffit pas.

De même, je suis assez convaincu que je resterai personnellement convaincu que cette fonctionnalité devrait être fusionnée : IMO, non seulement elle tire son poids à travers une variété d'exemples où c'est la meilleure/seule option, mais le langage est en fait plus simple avec son ajout (puisque ne pas autoriser les blocs étiquetés est surprenant pour moi étant donné que nous autorisons d'autres constructions étiquetées).

Si vous êtes d'accord avec mon résumé ci-dessus de votre position, alors il semble que nous soyons dans une impasse malheureuse (et, je crois, historique !) sans processus. Une option consiste à interpréter les règles actuelles de rfcbot comme je l'ai fait ci-dessus pour signifier "aucune case à cocher ne signale votre désaccord, trois membres sont nécessaires pour annuler la majorité", mais je ne pense pas que ce soit ainsi que les règles étaient signifiées lorsqu'elles ont été introduites, donc Je pense qu'il était fallacieux de ma part de suggérer que vous deviez suivre cette procédure (bien que je l'aie fait moi-même ailleurs).

J'ai déjà entendu des suggestions selon lesquelles nous pourrions introduire une limite de temps pour les fonctionnalités allant de approuvé-> implémenté-> stabilisé, et que nous devrions "fermer automatiquement" les fonctionnalités qui prennent du retard dans le but d'éviter un arriéré toujours croissant. Quelque chose comme ça pourrait résoudre ce cas, où je (et, je pense, plusieurs autres membres de l'équipe) ne cocherai pas les cases à cocher pour fermer, ni ne marquerez-vous une case à cocher pour accepter (j'ai encore même maintenant peur de dire cela que je' Je fais un dernier effort pour te convaincre ! :sourire:).

Je crains qu'avec des équipes toujours plus nombreuses, nous perdions la capacité de parvenir à un consensus sur les fonctionnalités et qu'il soit difficile de diriger le langage de manière cohérente, en particulier au sein de l'équipe lang-design. Les limites de taille d'équipe semblent être une solution évidente à cela : il est possible de recevoir les commentaires d'un grand nombre de personnes, mais tout à fait impossible de parvenir à un consensus absolu au sein d'un groupe suffisamment grand. D'autres diront probablement que les caractéristiques litigieuses ne devraient pas être fusionnées dans le but de protéger la langue des erreurs. Personnellement, je pense que la communauté ne nous laissera probablement pas commettre beaucoup de ces erreurs sans avertissement adéquat, mais c'est une pensée.

Je vais essayer de démarrer un fil distinct pour discuter de l'évolution du processus ici, et en attendant, je demanderais aux personnes qui interviennent sur ce fil de ne publier que s'il y a de nouveaux cas d'utilisation critiques à considérer qui sont particulièrement différents de ceux ci-dessus, ou s'il y a une raison importante et inconsidérée pour laquelle cette fonctionnalité ne devrait pas être ajoutée à la langue. La lecture de toute l'histoire de ces mégathreads est difficile, mais elle le devient encore plus lorsque les messages sont répétés encore et encore, ou lorsque le fil est rempli de commentaires inutiles. (il dit avoir maintenant tapé l'un des commentaires les plus longs de tout le fil XD)

TL; DR : Je pense que nous sommes coincés, nous devrions avoir un processus pour cela - je vais commencer cette conversation ailleurs et la lier ici. Dans le cas contraire, s'il vous plaît ne pas commenter , sauf si vous avez de nouveaux renseignements importants qui doivent être pris en considération.

@cramertj

c'est un danger malheureux de notre processus actuel que l'acceptation ou le rejet d'une fonctionnalité nécessite un consensus complet de l'équipe appropriée

Je considérerais honnêtement cela comme une caractéristique.

D'après les commentaires ci-dessus, j'ai pensé qu'il ne manquait que des exemples de code pour expliquer pourquoi cette fonctionnalité est importante, mais il semble maintenant que vous soyez d'accord pour dire qu'il existe un code qui peut être mieux écrit en utilisant cette fonctionnalité, mais vous n'êtes pas convaincu que cela en vaille la peine les coûts que vous voyez ici.

Je pense toujours que beaucoup d'exemples pourraient être écrits d'une autre manière. Cela n'ajoute aucune expressivité irreprésentable à la langue. Je pensais à l'origine que cela pourrait être motivé avec suffisamment d'exemples, mais plus je vois d'exemples qui pourraient utiliser cette fonctionnalité, plus je suis d'accord avec @withoutboats que cette fonctionnalité ne devrait tout simplement pas entrer dans la langue.

(A noter également : la caisse proc-macro-rules @nrc qui utilisait label_break_value semble avoir été réécrite pour l'éviter.)

Une option consiste à interpréter les règles actuelles de rfcbot comme je l'ai fait ci-dessus pour signifier "aucune case à cocher ne signale votre désaccord, trois membres sont nécessaires pour annuler la majorité", mais je ne pense pas que ce soit ainsi que les règles étaient signifiées lorsqu'elles ont été introduites, donc Je pense qu'il était fallacieux de ma part de suggérer que vous deviez suivre cette procédure (bien que je l'aie fait moi-même ailleurs).

Je pense que c'est la bonne procédure pour "s'abstenir", mais pas pour s'opposer.

IMO, non seulement il tire son poids à travers une variété d'exemples où c'est la meilleure/seule option, mais le langage est en fait plus simple avec son ajout (puisque ne pas autoriser les blocs étiquetés est surprenant pour moi étant donné que nous autorisons d'autres constructions étiquetées).

Je trouve généralement les arguments d'orthogonalité convaincants, mais celui-ci en particulier, je le trouve peu convaincant, car personnellement, j'aurais préféré ne pas non plus avoir de coupures de boucle étiquetées dans la langue.

Je crains qu'avec des équipes toujours plus nombreuses, nous perdions la capacité de parvenir à un consensus sur les fonctionnalités et qu'il soit difficile de diriger le langage de manière cohérente, en particulier au sein de l'équipe lang-design.

Je m'inquiète non seulement de la direction mais de l' arrêt . Il y a une certaine fatalité qui surgit parfois, où le processus semble concentré sur la recherche d'un chemin vers « oui » et il n'y a pas de bord de graphique dans l'organigramme qui mène à « non », juste « pas encore ».

De nombreux articles ont été écrits en 2018 et 2019 sur la croissance des fonctionnalités de la langue, et je pense que dans l'équipe linguistique, nous devons sérieusement prendre en compte la surface totale de la langue. Et j'ai l'impression que nous n'avons pas de bons processus qui nous encouragent à le faire.

@joshtriplett

Cela n'ajoute aucune expressivité irreprésentable à la langue.

Pour être très clair: il ne fait exactement cela. Il n'y a actuellement aucun moyen d'exprimer ce code qui n'interfère pas avec d'autres constructions de flux de contrôle, ce qui (comme d'autres l'ont souligné) est particulièrement souhaitable dans les macros, où ce serait la seule construction qui ne peut pas être ciblée via des pauses non étiquetées, permettant aux auteurs de macros de rompre avec les sections sans risquer de chevauchement avec un break fourni par l'utilisateur.

cela rend également le code plus lisible car vous n'avez pas besoin de zigzaguer (si nous n'avions pas d'étiquettes du tout - voir l'exemple de Lua) ou de faire des trucs bizarres avec des boucles. cela a également un petit avantage en termes de performances même si llvm devrait être capable d'optimiser le code en zigzag.

@joshtriplett

Je pense toujours que beaucoup d'exemples pourraient être écrits d'une autre manière. Cela n'ajoute aucune expressivité irreprésentable à la langue. Au départ, je pensais que cela pourrait être motivé par suffisamment d'exemples, mais plus je vois d'exemples qui _pourraient_ utiliser cette fonctionnalité, plus je suis d'accord avec @withoutboats que cette fonctionnalité ne devrait tout simplement pas entrer dans la langue.

Est-ce quelque chose que nous pourrions creuser peut-être?

Je trouve généralement les arguments d'orthogonalité convaincants, mais celui-ci en particulier, je le trouve peu convaincant, car personnellement, j'aurais préféré ne pas non plus avoir de coupures de boucle étiquetées dans la langue.

Je trouve ce raisonnement étrange (à moins que vous ne souhaitiez supprimer les ruptures de boucle étiquetées avec une édition). Il ne semble pas approprié de baser les décisions de conception sur ce que vous souhaiteriez ne pas voir dans la langue. Il est là, et nous devrions donc nous demander si cet ajout est cohérent avec cela. Sinon, il y a beaucoup de choses que j'aurais pu faire différemment à propos de Rust, mais je ne devrais pas et je ne peux pas.

Je ne m'inquiète pas seulement de la direction mais de l'arrêt. Il y a une certaine fatalité qui surgit parfois, où le processus semble concentré sur la recherche d'un chemin vers « oui » et il n'y a pas de bord de graphique dans l'organigramme qui mène à « non », juste « pas encore ».

Pas encore est sa propre forme de « non » dans le sens où il ne se stabilisera pas sans un oui. De plus, il y a un non : convaincre le reste d'entre nous que la LBV est une mauvaise idée / pas suffisamment motivée pour des raisons X, Y et Z. Je n'ai pas encore entendu beaucoup d'arguments concrets de ce genre. Vous avez aussi clairement démontré ici qu'il n'y a pas de fatalité.

Il y a eu de nombreux articles écrits en 2018 et 2019 sur la croissance des fonctionnalités dans la langue, et je pense que dans l'équipe linguistique, nous devons prendre en compte _sérieusement_ la surface totale de la langue. Et j'ai l'impression que nous n'avons pas de bons processus qui nous encouragent à le faire.

Personnellement , je pense que beaucoup de ces postes sont soit au sujet de la durabilité (mais pas aussi bien son libellé comme @nikomatsakis a fait ...) ou que beaucoup de gens ne comprennent pas comment l'équipe linguistique fonctionne (c. - nous déjà envisager sérieusement au total superficie). La syntaxe de surface totale de Rust a probablement diminué au cours de la dernière année, pas augmenté. LBV n'augmente pas cela notablement et on pourrait faire valoir qu'il réduit en fait le nombre de productions dans le langage et rend la syntaxe plus uniforme.

La simple combinaison de productions grammaticales ne réduit pas la surface de la langue.

Réduire le nombre de branches conditionnelles que l'on peut prendre revient sans doute à réduire la surface de la langue.

Je ne pense pas que cette fonctionnalité particulière aille dans un sens ou dans l'autre, en tant que telle, elle est probablement neutre. Mais par exemple , des choses que les constructions de langage Unify (Bool permettent, anyone?) Font rétrécir surface.

Pour ce que ça vaut, Common Lisp, qui est similaire à Rust dans le sens où c'est un langage multi-paradigmes avec des macros, a cette fonctionnalité (nommée block / return-from ). Semblable à ce qui a été discuté ici, dans Common Lisp, cette construction est souvent utile lors de l'écriture de macros et pour exprimer un flux de contrôle irréductiblement complexe.

(block foo
  (return-from foo "value"))

Mon sentiment est que dans Common Lisp, cette fonctionnalité est considérée comme réussie. Cela n'apparaît pas dans les conversations sur les fonctionnalités qui rendent le langage difficile à apprendre ou à mettre en œuvre.

il y a l'inclusion

early return from block ⊂ exceptions ⊂ call/cc

Par exemple, dans Scheme, l'émulation suivante de return-from est réalisable :

(define call/cc call-with-current-continuation)

(define-syntax block
    (syntax-rules ()
        ((_ label statements ...)
            (call/cc (lambda (label) (begin statements ...))))))

(block foo
    (display "Visible text")
    (foo "value")
    (display "Phantom text"))

Dans Scheme, call/cc est considéré comme un succès autant que controversé. Je trouve cela particulièrement intéressant car il faut d'abord formuler un objet pour pouvoir en parler. Même si vous considérez call/cc comme un défaut, cela a rendu le langage plus substantiel dans un sens intellectuel.

Le langage blocs nommés .
C'est très utile et pratique.

return, break et continue sont implémentés en tant que macros utilisant cette fonctionnalité.

@jhpratt Veuillez lire la discussion dans le numéro.

Répétant certains commentaires que j'ai faits sur IRLO à la suggestion de @Centril :

En discutant de cette fonctionnalité, j'ai suggéré l'inspiration suivante (contrairement à la version habituelle liée à la boucle):

Les boucles ne sont pas vraiment mon inspiration ici, de toute façon. Quand je regarde les blocs try, je pense que "mec, il serait utile de pouvoir écrire de courts calculs locaux avec des retours précoces sans avoir à se donner la peine de créer une fermeture". Les blocs try fonctionnent pour le "Je veux retourner un impl Try" plutôt contraint et ne prennent en charge aucune sorte d'étiquettes.

Ce qui précède est mon principal cas d'utilisation pour cela : j'aime le style de retour précoce. try {} blocs try {}? pour les blocs imbriqués) et faire vous chausserez votre type dans une certaine forme monadique qui n'est pas un fourre-tout (comme en témoignent les cas d'utilisation fournis).

J'ai également fait quelques observations sur C++, où ne pas avoir de coupure/continuation d'étiquette est très pénible, surtout en l'absence d'itérateurs de type Rust. Par exemple, il n'y a aucun moyen de continuer une boucle externe à partir d'une boucle interne sans un éventuellement-UB goto ou un break local plus un continue dehors de la boucle interne.

Au moins, l'absence de vérificateur d'emprunt dans C++ rend l'astuce lambda en ligne moins douloureuse, ce qui prend effectivement en charge LVB, car le retour dans le lambda en ligne est transformé en quelque chose comme LVB par l'inliner de LLVM. Quelque chose comme ça est... un peu plus discutable dans Rust.

Je dois également souligner que cette fonctionnalité est approximativement équivalente en termes d'expressivité à celle de Go goto , qui au moment de la compilation impose de ne pas sauter par-dessus les déclarations et ainsi de suite (franchement, si Rob Pike pensait que goto était acceptable, étant donné l'histoire d'essayer de résoudre les problèmes de C++, je lui ferais un peu confiance sur celui-là).

De plus, si nous allons entrer dans l'art antérieur, Kotlin fournit également cette fonctionnalité exacte, sous la forme de return<strong i="24">@label</strong> expr; .

Dans les langages c'ish, j'utilise souvent des étiquettes, même sans aucun goto entrant, comme emplacements pour les points d'arrêt stables sous les boucles, car le débogueur peut reconnaître break function:label .
Même sans consensus sur la rupture, les étiquettes pourraient être sympas.

Edit: Un obstacle potentiel est que les étiquettes suivent généralement la convention de dénomination des symboles, si je comprends la RFC, ces étiquettes ne suivent pas la convention de dénomination des symboles où ', n'est pas valide dans un nom de symbole.
Je ne suis pas sûr, par exemple, Dwarf ou gdb lui-même s'il y a en fait un problème ici.

Edit2 :
Assez sûr qu'il y a de la fumée à cela, si nous regardons le comportement de citation des étiquettes normales basées sur c,
dans le débogueur, gdb va au moins traiter les guillemets, pour les guillemets plutôt qu'une partie du nom du symbole. Les résultats suivants dans

Point d'arrêt 1 à 0x401114 : fichier, ligne 1.
citation inégalée

echo "void main() { } void hmm() { umm: return; }" | gcc -g -x c -;
gdb -ex "b hmm:'umm'" -ex "b hmm:'umm" -batch ./a.out

Et je ne pense pas que cela puisse être affecté par la prise en charge spécifique du langage rust dans gdb, car je pense que cette citation se produit avant la correspondance des symboles.

Edit : Le navire a probablement navigué sur ce point en raison des étiquettes de boucle existantes.

Un point mineur en faveur d'un retour anticipé des blocs serait la programmation du contrat du pauvre. On ajoute simplement des déclarations assert en tant que pré- et post-conditions. Afin de maintenir le confort, il devrait être possible de remplacer return par break 'ret pour permettre cette construction :

let value = 'ret: {
    // ...
};
assert!(postcondition(value));
return value;

C'est une solution imparfaite, cependant, car return devrait être interdit à l'intérieur du bloc.

Ajout d'une note indiquant que je voulais cette fonctionnalité mais que je ne l'ai pas utilisée parce que je ne savais pas qu'elle existait, ce qui, à mon avis, est un modificateur négatif sur "les gens n'en veulent pas, comme en témoigne le peu de personnes qui l'utilisent".

J'ai réinventé indépendamment

J'ai joué avec ça aujourd'hui, et c'est _super_ pratique en blocs async . Cependant, il semble rencontrer des problèmes lorsqu'il est combiné avec la fonctionnalité try_blocks :

#![feature(try_blocks, label_break_value)]

fn main() {
    let _: Result<(), ()> = try {
        'foo: {
            Err(())?;
            break 'foo;
        }
    };
}
error[E0695]: unlabeled `break` inside of a labeled block
 --> src/main.rs:6:20
  |
6 |             Err(())?;
  |                    ^ `break` statements that would diverge to or through a labeled block need to bear a label

error: aborting due to previous error

Les blocs d'essai sont une erreur.

... ne pouvez-vous pas étiqueter le ? lui-même ? (comme dans Err(()) 'foo?; )

Je ne suis pas du tout d'accord pour dire que les blocs d'essai sont une erreur, bien qu'il s'agisse d'une discussion distincte et qu'il ne vaut probablement pas la peine d'aller et venir ici.

Dans cet exemple particulier, cela pourrait être faisable, mais cela est très minimisé par rapport au code réel que j'ai, où 'foo contient un bon morceau de code et plusieurs ? s.

@SoniEx2

Les blocs d'essai sont une erreur.

Ce commentaire est inapproprié. Le commentaire de @jonhoo signalait une interaction (vraisemblablement) buggée. Indépendamment de ses opinions sur les blocs try (ou label-break-value), il est clair qu'ils devraient interagir en douceur.

ils devraient, avec la syntaxe Err(()) 'foo?; .

@jonhoo Je soupçonne que vous voyez des détails sur la façon dont try est supprimé du sucre -- pouvez-vous le classer en tant que problème séparé et nous pouvons déplacer la discussion sur les correctifs possibles ?

Le RFC dit que

'BLOCK_LABEL: { EXPR }

est le sucre syntaxique pour

'BLOCK_LABEL: loop { break { EXPR } }

J'ai essayé de faire cette substitution et le code se compile, avec un avertissement concernant le code inaccessible.

#![feature(try_blocks, label_break_value)]

fn main() {
    let _: Result<(), ()> = try {
        'foo: loop {
            break {
                Err(())?;
                break 'foo;
            }
        }
    };
}

@nikomatsakis @ciphergoth déposé comme https://github.com/rust-lang/rust/issues/72483.

Je constate que je ne m'y oppose plus. Je me serais opposé plus fortement au concept initial de rupture étiquetée s'il avait été examiné aujourd'hui, mais étant donné que ce concept existe, je ne pense pas qu'il soit logique pour moi de continuer à m'opposer à son application à des blocs arbitraires.

(Cela s'applique au formulaire actuel, en utilisant break , pas à toute autre syntaxe.)

@rfcbot résoudre devrait-fermer-pas-fusionner
@rfcbot examiné

@joshtriplett Pour ce que ça vaut, j'ai trouvé cela extrêmement utile dans les blocs async , car c'est le seul moyen de faire un "retour anticipé". Cela signifie qu'au lieu d'écrire :

async {
  // do thing a
  if thing_a_failed {
    // handle specially (note, _not_ ?)
  } else {
    // do thing b
    if thing_b_failed {
      // handle specially (note, _not_ ?)
    } else {
      // do thing c, etc..
    }
  }
}

Je peux écrire:

async {
  'block {
  // do thing a
  if thing_a_failed {
    // handle specially (note, _not_ ?)
    break 'block;
  }

  // do thing b
  if thing_b_failed {
    // handle specially (note, _not_ ?)
    break 'block;
  }

  // do thing c, etc..
  }
}

C'est parfaitement analogue à la façon dont vous pouvez revenir tôt avec return dans les fonctions/fermetures, et avec continue/break dans les boucles. Certes, ce serait bien si je n'avais pas besoin du bloc supplémentaire ( async 'block { une possibilité ?), mais il bat définitivement les if-s imbriqués.

Permettre aux blocs asynchrones d'être directement annotés avec des étiquettes semble être une très bonne extension de cette fonctionnalité.

@rfcbot fcp annuler

Je vais annuler le FCP ici car il a été bloqué pour toujours. Nous devrions probablement discuter si nous voulons faire avancer cet avenir. Si rien d'autre, il semble qu'il devrait être mis à jour pour prendre en compte les blocs asynchrones, et il semble que cela ajoute un nouveau cas d'utilisation pour la fonctionnalité.

Proposition de @nikomatsakis annulée.

Notez qu'il n'y a pas d'ambiguïté sur la sémantique de cette proposition en présence de blocs asynchrones : la définition dans la RFC s'applique toujours c'est-à-dire

'BLOCK_LABEL: { EXPR }

est simplement du sucre syntaxique pour

'BLOCK_LABEL: loop { break { EXPR } }

sauf que les pauses ou les continuations non étiquetées qui se lieraient à la boucle implicite sont interdites à l'intérieur de l'EXPR.

Notez que vous pouvez (au plus tôt) return partir de blocs asynchrones, au lieu d'étiqueter break , donc l'étiquetage des blocs asynchrones n'a pas beaucoup de sens :

let fut = async {
    return 42;
    0
};

println!("{}", fut.await); // prints 42

( aire de jeux )

@WaffleLapkin En fait, je suis juste venu ici pour noter que j'en ai été récemment informé moi async _spécifiquement_ est inférieure à J'ai d'abord pensé.

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