Rust: Problème de suivi pour l'opérateur `?` Et les blocs `try` (RFC 243, fonctionnalités` question_mark` et `try_blocks`)

Créé le 5 févr. 2016  ·  340Commentaires  ·  Source: rust-lang/rust

Problème de suivi pour rust-lang / rfcs # 243 et rust-lang / rfcs # 1859.

Problèmes de mise en œuvre:

  • [x] ? opérateur qui équivaut à peu près à try! - # 31954
  • [x] try { ... } expression - https://github.com/rust-lang/rust/issues/39849

    • [x] résoudre la question de syntaxe do catch { ... }


    • [x] déterminer si les blocs catch doivent "envelopper" la valeur du résultat (d'abord adressé dans https://github.com/rust-lang/rust/issues/41414, maintenant en cours de réinstallation dans https://github.com/rust- lang / rust / issues / 70941)

    • [] Résoudre les problèmes d'inférence de type ( try { expr? }? nécessite actuellement une annotation de type explicite quelque part).

  • [x] régler la conception du trait Try (https://github.com/rust-lang/rfcs/pull/1859)

    • [x] implémentez le nouveau trait Try (à la place de Carrier ) et convertissez ? pour l'utiliser (https://github.com/rust-lang/rust/pull / 42275)

    • [x] ajoutez des impls pour Option et ainsi de suite, et une famille de tests appropriée (https://github.com/rust-lang/rust/pull/42526)

    • [x] améliorer les messages d'erreur comme décrit dans la RFC (https://github.com/rust-lang/rust/issues/35946)

  • [x] réservez try dans la nouvelle édition
  • [x] bloquer try{}catch (ou les autres identifiants suivants) pour laisser l'espace de conception ouvert pour l'avenir et indiquer aux utilisateurs comment faire ce qu'ils veulent avec match
A-error-handling B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-try_blocks Libs-Tracked T-lang T-libs

Commentaire le plus utile

@ mark-im Je ne pense pas que nous puissions raisonnablement passer de l'un à l'autre après la stabilisation. Aussi mauvais que je considère un emballage Ok, un emballage Ok incohérent qui essaie de deviner si vous le voulez ou non serait encore pire.

Tous les 340 commentaires

La RFC qui l'accompagne traite d'un désugarage basé sur un retour / pause étiqueté, l'obtenons-nous aussi ou y aura-t-il juste un traitement spécial pour ? et catch dans le compilateur?

EDIT: Je pense que le retour / pause étiqueté est une excellente idée distincte de ? et catch , donc si la réponse est non, j'ouvrirai probablement un RFC séparé pour cela.

Le retour / pause étiqueté est purement à des fins explicatives.

Le vendredi 5 février 2016 à 15:56, Jonathan Reem [email protected]
a écrit:

La RFC qui l'accompagne traite d'un désugarage basé sur un retour / pause étiqueté
recevons-nous cela aussi ou y aura-t-il simplement un traitement spécial? et
attraper dans le compilateur?

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -180551605.

Une autre question non résolue que nous devons résoudre avant de stabiliser est de savoir ce que devrait être le contrat auquel impl s de Into doit obéir - ou si Into est même le bon trait à utiliser pour la remontée d'erreur ici. (Peut-être que cela devrait être un autre élément de la liste de contrôle?)

@reem

Je pense que le retour / pause étiqueté est une excellente idée ... Je vais probablement ouvrir un RFC séparé pour cela.

Je vous en prie!

Au sujet du trait Carrier , voici un exemple essentiel d'un tel trait que j'ai écrit au début du processus RFC.
https://gist.github.com/thepowersgang/f0de63db1746114266d3

Comment cela est-il traité lors de l'analyse?

struct catch {
    a: u8
}

fn main() {

    let b = 10;
    catch { a: b } // struct literal or catch expression with type ascription inside?

}

@petrochenkov Eh bien, la définition ne pouvait pas affecter l'analyse, mais je pense que nous avons toujours une règle d'anticipation, basée sur le deuxième jeton après { , : dans ce cas, donc cela devrait toujours être analysé comme un littéral struct.

Également

let c1 = catch { a: 10 };
let c2 = catch { ..c1 }; // <--

struct catch {}
let c3 = catch {}; // <--

+ https://github.com/rust-lang/rfcs/issues/306 si (quand!) implémenté.
Il semble qu'il n'y ait pas de conflits en dehors des littéraux de structure.

Compte tenu des exemples ci-dessus, je suis pour la solution la plus simple (comme d'habitude) - traitez toujours catch { dans les positions de l'expression comme le début d'un bloc catch . Personne n'appelle leurs structures catch toute façon.

Ce serait plus facile si un mot-clé était utilisé au lieu de catch .

@bluss ouais, override semble être le seul qui soit proche. Ou nous pourrions utiliser do , heh. Ou une combinaison, même si je ne vois pas de bonnes immédiatement. do catch ?

do est le seul qui semble être proche de l'OMI. Une soupe de mots clés avec do comme préfixe est un peu irrégulière, ne ressemble à aucune autre partie de la langue? Est-ce que while let une soupe de mots clés? Celui-là se sent bien maintenant, quand vous y êtes habitué.

port try! pour utiliser ?

? ne peut-il pas être porté pour utiliser try! place? Cela permettrait le cas d'utilisation où vous voulez obtenir un chemin de retour Result , par exemple lors du débogage. Avec try! c'est assez simple, il vous suffit de remplacer la macro au début du fichier (ou dans lib / main.rs):

macro_rules! try {
    ($expr:expr) => (match $expr {
        Result::Ok(val) => val,
        Result::Err(err) => {
            panic!("Error occured: {:?}", err)
        }
    })
}

Vous obtiendrez une trace de pile de panique à partir de la première occurrence de try! dans le chemin de retour Result . En fait, si vous faites try!(Err(sth)) si vous découvrez une erreur au lieu de return Err(sth) , vous obtenez même la trace complète de la pile.

Mais lors du débogage de bibliothèques étrangères écrites par des personnes qui n'ont pas implémenté cette astuce, on se fie à l'utilisation de try! quelque part plus haut dans la chaîne. Et maintenant, si la bibliothèque utilise l'opérateur ? avec un comportement codé en dur, obtenir un stacktrace devient presque impossible.

Ce serait bien si le remplacement de try! affectait également l'opérateur ? .

Plus tard, lorsque le système de macros obtiendra plus de fonctionnalités, vous pourrez même paniquer! uniquement pour des types spécifiques.

Si cette proposition nécessite une RFC, veuillez me le faire savoir.

Idéalement, ? pourrait simplement être étendu pour fournir directement la prise en charge de la trace de pile, plutôt que de compter sur la possibilité de remplacer try! . Ensuite, cela fonctionnerait partout.

Les traces de pile ne sont qu'un exemple (bien que très utile, me semble-t-il).
Si le trait Carrier est fait pour fonctionner, peut-être que cela peut couvrir de telles extensions?

Le dim 7 février 2016 à 16 h 14, Russell Johnston [email protected]
a écrit:

Idéalement? pourrait simplement être étendu pour fournir un support de trace de pile directement,
plutôt que de se fier à la possibilité de remplacer try !. Alors ça marcherait
partout.

-
Répondez directement à cet e-mail ou affichez-le sur GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -181118499.

Sans vouloir spéculer, je pense que cela pourrait fonctionner, mais avec quelques problèmes.

Considérons le cas habituel où l'on a du code retournant une Result<V,E> . Maintenant, nous devrions permettre à plusieurs implémentations du trait Carrier de coexister. Afin de ne pas tomber sur ? , l'utilisateur doit importer le la mise en oeuvre.

Cela obligerait tout le monde, même ceux qui ne veulent pas déboguer, à importer leur implémentation de trait souhaitée lors de l'utilisation de ? , il n'y aurait aucune option pour une valeur par défaut prédéfinie.

Peut-être que E0117 peut également être un problème si vous implémentations personnalisées Carrier pour Result<V,E> , où tous les types sont en dehors des limites, donc libstd devrait déjà fournir un ensemble d'implémentations de Carrier trait avec les cas d'utilisation les plus utilisés (implémentation triviale, et implémentation panic! ing, peut-être plus).

Avoir la possibilité de remplacer via une macro fournirait une plus grande flexibilité sans la charge supplémentaire sur l'implémenteur d'origine (ils n'ont pas à importer l'implémentation souhaitée). Mais je vois aussi que rust n'a jamais eu d'opérateur basé sur une macro auparavant, et que l'implémentation de ? via une macro n'est pas possible si catch { ... } est censé fonctionner, du moins pas sans éléments de langue supplémentaires ( return_to_catch , throw , étiqueté break avec param comme utilisé dans RFC 243).

Je suis d'accord avec n'importe quelle configuration qui permet d'obtenir Result stacktraces avec un chemin de retour Err , tout en n'ayant qu'à modifier une très petite quantité de code, de préférence en haut du fichier. La solution doit également fonctionner indépendamment de la manière et de l'emplacement d'implémentation du type Err .

Juste pour parler du bikeshedding: catch in { ... } coule plutôt bien.

catch! { ... } est un autre choix de rétro-compatibilité.

De plus, je ne m'attends pas à ce que cela change quoi que ce soit, mais une note que cela va casser les macros multi-bras qui acceptaient $i:ident ? , de la même manière que l'attribution de type a cassé $i:ident : $t:ty .

N'exagérez pas la compatibilité ascendante, traitez simplement catch comme un mot-clé lorsqu'il est suivi de { (peut-être uniquement en position d'expression, mais je ne suis pas sûr que cela change beaucoup en termes de compatibilité).

Je peux aussi imaginer des problèmes possibles qui n'impliquent pas de littéraux de structure (par exemple let catch = true; if catch {} ); mais je préfère un changement de rupture mineur à une syntaxe plus moche.

N'avons-nous pas un pour ajouter de nouveaux mots clés, de toute façon? Nous pourrions offrir une sorte de from __future__ opt-in pour la nouvelle syntaxe; ou spécifiez un numéro de version du langage rust sur la ligne de commande / dans Cargo.toml.
Je doute fort qu'à long terme, nous ne pourrons travailler qu'avec les mots clés déjà réservés. Nous ne voulons pas que nos mots clés aient chacun trois significations différentes, selon le contexte.

Je suis d'accord. Ce n'est même pas la première RFC où cela est apparu (https://github.com/rust-lang/rfcs/pull/1444 est un autre exemple). Je pense que ce ne sera pas le dernier. (Aussi default de https://github.com/rust-lang/rfcs/pull/1210, bien que ce ne soit pas une RFC que je suis favorable.) Je pense que nous devons trouver un moyen d'ajouter des mots-clés honnêtes à Dieu au lieu d'essayer de comprendre comment pirater ad hoc la grammaire pour chaque nouveau cas.

L'argument entier pour ne pas réserver plusieurs mots-clés avant la version 1.0 n'était-il pas que nous introduisions certainement un moyen d'ajouter de nouveaux mots-clés à la langue de manière rétrocompatible (éventuellement en optant explicitement), alors cela ne servait à rien? Il semble que ce soit le bon moment.

@japaric Êtes-vous intéressé à relancer votre ancien PR et à le reprendre?

@aturon Mon implémentation désuète simplement foo? de la même manière que try!(foo) . Cela ne fonctionnait également que sur les appels de méthode et de fonction, c'est- foo.bar()? dire que baz()? fonctionnent mais quux? et (quux)? ne le font pas. Cela conviendrait-il pour une mise en œuvre initiale?

@japaric Quelle était la raison pour laquelle il était limité aux méthodes et aux appels de fonction? L'analyse ne serait-elle pas plus facile en tant qu'opérateur postfix général?

Quelle était la raison de la limiter aux méthodes et aux appels de fonction?

moyen le plus simple (pour moi) de tester l'expansion ?

L'analyse ne serait-elle pas plus facile en tant qu'opérateur postfix général?

Probablement

@japaric Il serait probablement bon de le généraliser à un opérateur postfix complet (comme @eddyb le suggère), mais c'est bien de débarquer ? avec le simple désugarage et d'ajouter ensuite catch plus tard.

@aturon D'accord , j'examinerai la version postfix d'ici la semaine prochaine si personne ne me bat :-).

rebasé / mis à jour mon PR dans # 31954 :-)

Qu'en est-il de la prise en charge de la fourniture de traces de pile? Est-ce prévu?

Je déteste être le gars +1, mais les traces de pile ont sauvé une bonne partie de mon temps dans le passé. Peut-être sur les builds de débogage, et en frappant le chemin d'erreur, le? L'opérateur pourrait ajouter le fichier / la ligne à un Vec dans Result? Peut-être que le Vec pourrait également être débogué uniquement?

Et cela pourrait être exposé ou transformé en une partie de la description de l'erreur ...

Je continue à me heurter à la situation où je veux utiliser try! / ? intérieur des itérateurs retournant Option<Result<T, E>> . Malheureusement, cela ne fonctionne pas vraiment actuellement. Je me demande si le trait porteur pourrait être surchargé pour prendre en charge cela ou est-ce que cela entrerait dans un From plus générique

@hexsel Je souhaite vraiment que le type Result<> porte un vec de pointeurs d'instructions dans le débogage et? y ajouterait. De cette façon, les informations DWARF pourraient être utilisées pour créer une trace de pile lisible.

@mitsuhiko Mais comment pouvez-vous créer et faire correspondre un motif Result ? C'est juste un enum atm.

En ce qui concerne l'emballage Option , je crois que vous voulez Some(catch {...}) .

Actuellement, mon habitude est de faire try!(Err(bla)) au lieu de return Err() , afin que je puisse remplacer la macro try plus tard par une macro qui panique, afin d'obtenir une trace arrière. Cela fonctionne bien pour moi, mais le code que je traite est de très bas niveau et est à l'origine des erreurs. Je devrai quand même éviter ? si j'utilise un code externe qui renvoie Result .

@eddyb, il aurait besoin de la prise en charge du langage pour transporter des valeurs cachées en plus que vous devez manipuler par d'autres moyens. Je me demandais si cela pouvait être fait autrement mais je ne vois pas comment. Le seul autre moyen aurait été une boîte d'erreur standardisée qui peut contenir des données supplémentaires, mais il n'est pas nécessaire d'avoir des erreurs encadrées et la plupart des gens ne le font pas.

@mitsuhiko Je peux penser à une nouvelle méthode (par défaut) sur le trait Error et TLS.
Ce dernier est parfois utilisé par les désinfectants.

@eddyb qui ne fonctionne que si le résultat peut être identifié et qui nécessite qu'il soit encadré ou il se déplacera en mémoire à mesure qu'il montera dans la pile.

@mitsuhiko Le TLS? Pas vraiment, il vous suffit de pouvoir comparer l'erreur par valeur.

Ou même juste par type (avec la liaison des entrées et des sorties From ), combien d'erreurs simultanées vous voulez que les traces de pile devront jamais être propagées simultanément?

Je suis contre l'ajout Result hacks de compilateurs spécifiques à

@eddyb l'erreur fait

le nombre d'erreurs simultanées à partir desquelles vous voulez que les traces de pile doivent être propagées simultanément

Toute erreur interceptée et renvoyée comme une erreur différente.

Je suis contre l'ajout de hacks de compilateur spécifiques à Result, personnellement, lorsque des solutions plus simples fonctionnent.

Je ne vois pas comment une solution plus simple fonctionne mais il me manque peut-être quelque chose.

Vous pouvez enregistrer le pointeur d'instruction à chaque ? et le corréler avec le type d'erreur.
"Toute erreur détectée et renvoyée comme une erreur différente." Mais comment conserveriez-vous ces informations si elles étaient cachées dans Result ?

Mais comment conserveriez-vous ces informations si elles étaient masquées dans Result?

Vous n'avez pas besoin de stocker ces informations dans le résultat. Ce que vous devez stocker, c'est un identifiant unique pour l'origine de l'échec afin que vous puissiez le corréler. Et comme le trait d'erreur n'est qu'un trait et n'a pas de stockage, il pourrait être stocké dans le résultat à la place. Le pointeur d'instruction vec lui-même n'aurait en aucun cas à être stocké dans result, qui pourrait aller à TLS.

Une façon serait que vous invoquiez une méthode failure_id(&self) sur le résultat et qu'elle renvoie un i64 / uuid ou quelque chose qui identifie l'origine de l'échec.

Cela nécessiterait la prise en charge du langage quoi qu'il arrive, car ce dont vous avez besoin, c'est que lorsque le résultat passe à travers la pile, le compilateur injecte une instruction pour enregistrer la trame de pile dans laquelle il passe. Ainsi, le retour d'un résultat serait différent dans les versions de débogage.

"le compilateur injecte une instruction pour enregistrer le frame de pile qu'il traverse" - mais ? est explicite, ce n'est rien comme des exceptions, ou n'aimez-vous pas enregistrer _seulement_ le ? qu'il a traversé?

Quoi qu'il en soit, si vous décompressez manuellement l'erreur puis la remettez dans un Err , comment cet ID serait-il même conservé?

"Et comme le trait d'erreur n'est qu'un trait et n'a pas de stockage, il pourrait être stocké dans le résultat à la place"
Il existe une variante à cela: implémenter le trait Error pourrait être spécial dans le compilateur pour ajouter un champ entier supplémentaire au type, la création du type déclencherait la génération d'un ID, et une copie / suppression incrémenter / décrémenter efficacement le refcount (et finalement l'effacer du TLS "in-flight error set" si Result::unwrap n'est pas utilisé).

Mais cela entrerait en conflit avec le trait Copy . Je veux dire, tout comme votre plan, ajouter un comportement spécial à Result qui n'est _pas_ déclenché par ? ou d'autres actions spécifiques de l'utilisateur peut invalider les invariants Copy .

EDIT : À ce stade, vous pouvez aussi bien y intégrer un Rc<ErrorTrace> .
EDIT2 : Que dis-je même, vous pouvez effacer la trace d'erreur associée sur catch .
EDIT3 : En fait, en baisse, voir ci-dessous une meilleure explication.

"le compilateur injecte une instruction pour enregistrer la trame de pile dans laquelle il tombe" - mais? est explicite, cela n'a rien à voir avec des exceptions, ou n'aimez-vous pas enregistrer uniquement le? il est passé?

Cela ne fonctionne pas car il y a trop de cadres dans lesquels vous pouvez passer et qui n'utilisent pas ? . Sans parler du fait que tout le monde ne va pas gérer les erreurs avec seulement ? .

Quoi qu'il en soit, si vous décompressez manuellement l'erreur puis la remettez dans un Err, comment cet ID serait-il même conservé?

C'est pourquoi cela devrait être le support du compilateur. Le compilateur devrait suivre les variables locales qui sont des résultats et faire de son mieux pour propager l'ID de résultat pour les ré-wraps. Si c'est trop magique, cela pourrait être limité à un sous-ensemble d'opérations.

Cela ne fonctionne pas car il y a trop de cadres dans lesquels vous pouvez passer et qui n'utilisent pas ? . Sans parler du fait que tout le monde ne va pas gérer les erreurs avec seulement ? .

D'accord, je pourrais voir le retour de Result directement casse spéciale dans des fonctions complexes avec plusieurs chemins de retour (dont certains seraient des retours anticipés de ? ).

Si c'est trop magique, cela pourrait être limité à un sous-ensemble d'opérations.

Ou rendu entièrement explicite. Avez-vous des exemples de réemballage non ? qui devraient être suivis comme par magie par le compilateur?

@eddyb Le cas courant de la gestion manuelle des erreurs est une IoError où vous souhaitez gérer un sous-ensemble:

loop {
  match establish_connection() {
    Ok(conn) => { ... },
    Err(err) => {
      if err.kind() == io::ErrorKind::ConnectionRefused {
        continue;
      } else {
        return Err(err);
      }
    }
  }
}

@mitsuhiko Ensuite, garder l'ID dans io::Error fonctionnerait certainement.

Donc, pour récapituler, une Vec<Option<Trace>> "carte d'entiers épars" en TLS, saisie par struct ErrorId(usize) et accessible par 3 éléments de langue:

  • fn trace_new(Location) -> ErrorId , lors de la création d'une valeur d'erreur non- const
  • fn trace_return(ErrorId, Location) , juste avant de revenir d'une fonction _déclarée_ comme -> Result<...> (c'est-à-dire pas une fonction générique sur le retour que _happens_ à utiliser avec un type Result ici)
  • fn trace_destroy(ErrorId) , lors de la suppression d'une valeur d'erreur

Si la transformation est effectuée sur MIR, Location peut être calculé à partir du Span de l'instruction déclenchant soit la construction d'une valeur d'erreur, soit l'écriture dans Lvalue::Return , ce qui est beaucoup plus fiable qu'un pointeur d'instruction IMO (pas de moyen facile d'obtenir cela dans LLVM de toute façon, vous devriez émettre en ligne asm pour chaque plate-forme spécifique).

@eddyb

Cela ne conduira-t-il pas à un ballonnement de taille binaire?

@ arielb1 Vous le feriez en mode débogage uniquement là où le debuginfo gonfle le binaire de toute façon - vous pouvez également réutiliser intelligemment le debuginfo _shrug_.

@eddyb qu'est-ce qu'un emplacement dans ce cas? Je ne sais pas ce qui est difficile dans la lecture de l'adresse IP. Bien sûr, vous avez besoin d'un JS personnalisé pour chaque cible, mais ce n'est pas vraiment difficile.

@mitsuhiko, ce pourrait être la même information que nous utilisons pour les vérifications de débordement et autres paniques émises par le compilateur. Voir core::panicking::panic .

Pourquoi regrouper autant d'informations de pile dans un Error / Result pendant que la pile est déroulée? Pourquoi ne pas simplement exécuter le code alors que la pile est toujours là? Par exemple, que faire si vous êtes intéressé par les variables sur le chemin de la pile? Permettez simplement aux gens d'exécuter du code personnalisé lorsqu'un Err est appelé, par exemple pour appeler un débogueur de leur choix. C'est ce que try! fournit déjà car il s'agit d'une macro (remplaçable). Le moyen le plus simple est de paniquer sur un cas Err qui imprime la pile à condition que l'on ait démarré le programme avec les bons paramètres.

Avec try vous pouvez faire tout ce que vous voulez sur un cas Err , et vous pouvez remplacer la macro crate large, ou très étendue pour ne pas toucher le code critique de performance, par exemple si l'erreur est difficile à reproduire et vous devez exécuter une grande partie du code critique de perf.

Personne n'aurait besoin de conserver une fraction de l'information dans une pile artificielle que l'on accumule parce que la vraie va être détruite. Toutes les méthodes de remplacement de macros pourraient être améliorées avec:

  • ? étant remplaçable de la même manière, le plus simple serait de définir ? comme sucre pour try! - particulièrement nécessaire pour attraper les erreurs non originaires à la frontière de la caisse comme décrit dans mon commentaire ci-dessus.
  • avoir un système de macro plus puissant, comme l'appariement sur le type afin de permettre encore plus de flexibilité. Oui, on pourrait penser à mettre cela dans le système de traits traditionnel, mais la rouille ne permet pas de remplacer les implémentations de traits, donc ça va être un peu compliqué

@ est31 La pile n'est pas _automatiquement_ "déroulée" comme en cas de panique, c'est du sucre syntaxique pour les premiers retours.
Et oui, vous pourriez demander au compilateur d'insérer des appels de certaines fonctions vides avec des noms fixes sur lesquels vous pouvez effectuer des points d'arrêt, c'est assez facile (et aussi avoir des informations qui vous permettent de faire, par exemple call dump(data) - où dump et data sont des arguments que les débogueurs peuvent voir).

@eddyb Je pense que les fonctions vides ne permettraient pas un cas d'utilisation de, par exemple en gardant quelques instances de débogage «canari» sur un grand déploiement pour voir si des erreurs apparaissent dans les journaux, pour ensuite revenir en arrière et corriger les choses. Je comprends qu'il est préférable d'être proactif que réactif, mais tout n'est pas facile à prévoir

@hexsel Oui, c'est pourquoi je préfère la méthode basée sur TLS où Result::unwrap ou une nouvelle méthode ignore (ou même toujours lors de la suppression de l'erreur?) vide la trace dans stderr.

@eddyb Si vous ajoutez le pointeur d'instruction ou quelque chose dérivé de la valeur à une pile comme une structure de données dans TLS, vous reconstruisez essentiellement votre petite version de la pile réelle. Un retour réduit la pile d'une entrée. Donc, si vous faites cela pendant que vous retournez, vous _unwind_ une partie de la pile, tout en construisant une version limitée de celle-ci à un autre endroit de la RAM. Peut-être que "dérouler" est le mauvais terme pour désigner un comportement résultant de retours "légaux", mais si tout le code fait ? ou try! et à la fin vous interceptez le résultat final est le même. C'est génial que rust ne rend pas la propagation des erreurs automatique, j'ai vraiment aimé la façon dont java avait exigé que toutes les exceptions soient répertoriées après le mot clé throws , rust est une bonne amélioration à ce sujet.

@hexsel une approche basée sur la try! ) permettrait cela - vous pouvez exécuter le code que vous souhaitez et vous connecter à n'importe quel système de journalisation. Vous auriez besoin d'une certaine détection pour les "dupes" quand plusieurs try attrapent la même erreur alors qu'elle se propage dans la pile.

@ est31 Remplacer try! ne fonctionne que dans votre propre code (il s'agit littéralement de l'observation des macros importées), il faudrait que ce soit quelque chose de différent, comme nos «éléments lang faibles».

@ est31 Ce n'est pas vraiment correct (à propos du déroulement), les traces et la pile réelle n'ont pas nécessairement de relation, car déplacer Result s autour n'a pas besoin de remonter dans la trace d'origine, cela peut aller sur le côté aussi.
De plus, si le binaire est optimisé, la majeure partie du backtrace a disparu, et si vous n'avez pas d'informations de débogage, alors Location est strictement supérieur. Vous pourriez même exécuter un plugin obscurcissant qui remplace toutes les informations source par des hachages aléatoires qui peuvent être comparés par les développeurs de certains produits à source fermée.

Les débogueurs sont utiles (et en passant, j'adore la sortie plus propre de la trace de lldb ), mais ils ne sont pas une panacée, et nous produisons déjà des informations sur les paniques afin que vous puissiez obtenir des indices sur Que se passe-t-il.

À propos de cela - J'ai eu quelques réflexions sur une astuce au niveau du système de types où {Option,Result}::unwrap aurait un argument de type supplémentaire, par défaut sur un type dépendant de l'emplacement à partir duquel la fonction a été appelée, de sorte que les paniques de ceux-ci auraient informations de localisation plus utiles.

Avec les progrès de la paramétrisation de la valeur, cela pourrait encore être une option, mais certainement pas la seule, et je ne veux pas ignorer purement et simplement Result traces, j'essaie plutôt de trouver un modèle qui est _implementable_.

Remplacer try! n'est pas du tout une solution car il est contenu dans votre propre caisse. C'est inacceptable comme expérience de débogage. J'ai déjà essayé beaucoup de choses en essayant de gérer le try! actuel. Surtout si vous avez de nombreuses caisses impliquées et que les erreurs sont transmutées plusieurs fois sur le chemin de la pile, il est presque impossible de déterminer d'où provient l'erreur et pourquoi. Si un tel pansement est la solution, nous devrons revoir la gestion des erreurs en général pour les grands projets Rust.

@eddyb est-ce que votre suggestion est d'intégrer littéralement le nom de fichier, le nom de la fonction, le numéro de ligne et le numéro de colonne à ce vecteur? Cela semble énormément de gaspillage, en particulier parce que ces informations sont déjà contenues dans un format beaucoup plus traitable dans DWARF. DWARF nous permet également d'utiliser le même processus à un prix raisonnable en production, alors que ce type d'informations de localisation semble être si inutile que personne ne lancerait jamais un binaire de version avec.

En quoi cela représenterait-il beaucoup plus de gaspillage que les informations DWARF? Les noms de fichiers seraient dédupliqués, et sur x64, le tout a la taille de 3 pointeurs.

@mitsuhiko donc fondamentalement, vous êtes d'accord avec la direction générale, mais pas avec les spécificités techniques de celle-ci?

Est-il facile d'exposer des informations DWARF à une API Rust générale?

@eddyb car les informations DWARF ne sont pas contenues dans le binaire de la version mais dans des fichiers séparés. Je peux donc avoir les fichiers de débogage sur des serveurs de symboles et envoyer un binaire dépouillé aux utilisateurs.

@mitsuhiko Oh, c'est une approche totalement différente de ce que je supposais. Rust ne prend actuellement pas en charge cet atm, AFAIK, mais je suis d'accord qu'il devrait.

Pensez-vous que les pointeurs d'instructions sont réellement utiles par rapport aux identificateurs aléatoires générés par votre système de construction aux fins de débogage des binaires de version?

Mon expérience a été qu'avec tous les débogueurs intégrés ont du mal à récupérer une grande partie de la trace de la pile, à l'exception de la récursivité auto / mutuelle explicite et des fonctions très volumineuses.

Oui, try! est contenu dans votre caisse. Si vous passez un pointeur de fonction ou similaire au code de la bibliothèque et qu'il y a une erreur dans votre fonction, l'approche try fonctionnera toujours. Si une caisse que vous utilisez a une erreur interne ou un bogue, vous pourriez avoir besoin de son code source pour déboguer déjà, si les informations retournées par Err ne vous aident pas. Les traces de pile ne sont utiles que si vous avez accès au code, donc les bibliothèques de sources fermées (dont vous ne pouvez pas modifier le code) devront passer par le support d'une manière ou d'une autre.

Qu'en est-il simplement d'activer les deux approches et de laisser le développeur décider de ce qui est le mieux pour eux? Je ne nie pas que l'approche basée sur TLS ne présente aucun avantage.

Le modèle try est implémentable très facilement, il suffit de désugar ? to try , seules des extensions de langage supplémentaires sont nécessaires si catch entre (vous auriez ajouté ce comportement à l'intérieur du comportement codé en dur de ? quand même)

@eddyb "Pensez-vous que les pointeurs d'instructions sont réellement aussi utiles que les identificateurs aléatoires générés par votre système de construction aux fins de débogage des binaires de version?"

C'est ainsi que fonctionne le débogage des binaires natifs en général. Nous (Sentry) l'utilisons presque entièrement pour le support iOS à ce stade. Nous obtenons un vidage sur incident et résolvons les adresses avec llvm-symbolizer en symboles réels.

@mitsuhiko Puisque nous libbacktrace , nous pourrions l'utiliser pour résoudre les pointeurs de code vers les emplacements source, donc je ne suis pas totalement opposé à cela.

@eddyb ouais. Je viens de regarder le code de panique. Ce serait bien s'il s'agissait en fait d'une API que le code Rust pourrait utiliser. Je peux voir que cela est utile dans quelques autres endroits.

À propos de cela - j'ai eu quelques réflexions sur une astuce au niveau du système de types où {Option, Result} :: unwrap aurait un argument de type supplémentaire, par défaut à un type dépendant de l'emplacement à partir duquel la fonction a été appelée, de sorte que les paniques de ceux-ci seraient ont des informations de localisation beaucoup plus utiles.

En parlant de ça ...

@glaebhoerl Hah, ça vaut peut-être la peine d'être poursuivi alors? Au moins comme une expérience instable.

@eddyb Aucune idée. Cela vaut probablement la peine d'en discuter d'abord avec certains GHC impliqués, je n'en ai entendu parler qu'au passage et j'ai recherché un lien sur Google tout à l'heure. Et Rust n'a pas de paramètres implicites réels comme le fait GHC; si les paramètres de type par défaut pourraient fonctionner à la place est une question intéressante (probablement une à laquelle vous avez réfléchi plus que moi).

Juste une pensée: il serait utile d'avoir un commutateur qui amène rustc à construire un cas spécial Err tel qu'il appelle une fonction fn(TypeId, *mut ()) avec la charge utile avant de retourner . Cela devrait suffire pour commencer par des éléments de base comme le filtrage des erreurs en fonction de la charge utile, le piégeage dans un débogueur s'il détecte une erreur d'intérêt ou la capture de traces pour certains types d'erreurs.

PR 33389 ajoute un support expérimental pour le trait Carrier . Comme il ne faisait pas partie de la RFC d'origine, il devrait bénéficier d'une période d'examen et de discussion particulièrement serrée avant de passer au FCP (qui devrait probablement être séparé du FCP pour le reste de l'opérateur ? ). Voir ce fil de

Je suis opposé à l'extension de ? à Option s.

Le libellé de la RFC est assez clair sur le fait que l'opérateur ? consiste à propager des erreurs / "exceptions".
Utiliser Option pour signaler une erreur n'est pas le bon outil. Renvoyer None fait partie du flux de travail normal d'un programme réussi, tandis que renvoyer Err indique toujours une erreur.

Si nous voulons améliorer certains domaines de la gestion des erreurs (par exemple en ajoutant des traces de pile), implémenter ? sur Option signifie que nous devrons exclure ? des modifications .

@tomaka pourrions-nous garder la discussion sur le fil de discussion ? (J'ai déjà résumé votre objection ) Personnellement, je trouve que les longues discussions sur GH deviennent assez lourdes, et ce serait également bien de pouvoir séparer la discussion sur ce point particulier des autres points ou questions futurs qui pourraient survenir.

@eddyb Voici la documentation de la version publiée de la fonctionnalité de pile d'appels implicite GHC que j'ai mentionnée plus tôt .

Aucune mise à jour ici depuis un moment. Quelqu'un travaille-t-il pour faire avancer les choses? Les tâches restantes dans le PO sont-elles toujours exactes? Quelque chose ici peut-il être recherché par E-help?

J'ai joué le week-end dernier s'il était possible d'écrire un client Sentry pour Rust qui ait du sens. Étant donné que la plupart de la gestion des erreurs est en fait basée sur les résultats maintenant au lieu de paniques, j'ai remarqué que l'utilité de ceci est sévèrement limitée au point où je viens de décider de l'abandonner complètement.

Je suis allé à la base de code crates.io comme exemple pour essayer d'y intégrer un système de rapport d'erreurs. Cela m'a ramené à cette RFC parce que je pense vraiment qu'à moins que nous puissions enregistrer le pointeur d'instruction d'une manière ou d'une autre lorsque les résultats sont transmis et convertis dans les différents stacktraces, il sera impossible d'obtenir un rapport d'erreur correct. Je vois déjà que c'est une douleur énorme juste de déboguer les échecs de logique complexe locale où je recourt aujourd'hui à ajouter des paniques d'où je pense que l'erreur vient.

Malheureusement, je ne vois actuellement pas comment l'IP pourrait être enregistrée sans des changements massifs dans le fonctionnement des résultats. Quelqu'un d'autre a-t-il déjà joué avec cette idée?

Nous en discutions lors de la réunion @ rust-lang / lang. Certaines choses qui sont survenues:

Premièrement, il y a un intérêt certain à voir ? stabiliser le plus rapidement possible. Cependant, je pense que la plupart d'entre nous aimeraient aussi voir ? fonctionner sur Option et pas seulement Result (mais pas, je pense, bool , comme a également été proposé). Une préoccupation concernant la stabilisation est que si nous nous stabilisons sans offrir aucun type de trait permettant d'utiliser des types autres que Result , il n'est pas rétrocompatible de l'ajouter plus tard.

Par exemple, j'écris moi-même du code comme celui-ci avec une certaine régularité:

let v: Vec<_> = try!(foo.iter().map(|x| x.to_result()).collect());

où je compte sur try! pour informer l'inférence de type que je m'attends collect ce que Result<Vec<_>, _> . Si vous utilisez ? , cette inférence pourrait échouer dans le futur.

Cependant, dans les discussions précédentes, nous avons également décidé qu'un amendement RFC était nécessaire pour discuter des subtilités de toute sorte de caractère «porteur». Il est clair que cette RFC devrait être écrite dès que possible, mais nous préférerions ne pas bloquer la progression sur ? sur cette discussion.

Une pensée que nous avions était que si nous prenions l'implémentation de et que nous rendions le trait instable et ne l'implémentions que pour Result et certains types factices privés, cela devrait supprimer l'inférence tout en ne faisant que ? utilisable avec Result .

Un dernier point: je pense que la plupart d'entre nous préféreraient que si vous utilisez ? sur une Option , cela nécessite que le type de retour de votre fonction soit également Option (pas par exemple Result<T,()> ). Il est intéressant de noter que cela aidera avec les limitations d'inférence, car nous pouvons finalement déduire du type de retour déclaré dans de nombreux cas quel type est requis.

La raison pour laquelle l'inter-conversion ne veut pas est qu'il semble susceptible de conduire à une logique lâche, un peu analogue à la façon dont C permet if x même lorsque x a un type intégral. Autrement dit, si Option<T> désigne une valeur où None fait partie du domaine de cette valeur (comme il se doit), et Result<> représente (généralement) l'échec d'une fonction pour réussir, alors supposer que None signifie que la fonction devrait sortir de l'erreur semble suspecte (et comme une sorte de convention arbitraire). Mais cette discussion peut attendre la RFC, je suppose.

La raison de ne pas vouloir d'inter-conversion est qu'elle semble susceptible de conduire à une logique lâche, un peu analogue à la façon dont C autorise if x même lorsque x a un type intégral. Autrement dit, si Option<T> désigne une valeur où None fait partie du domaine de cette valeur (comme il se doit), et Result<> représente (généralement) l'échec d'une fonction pour réussir, alors supposer que None signifie que la fonction devrait sortir d'une erreur semble suspect (et comme une sorte de convention arbitraire). Mais cette discussion peut attendre la RFC, je suppose.

Je suis entièrement d'accord avec cela.

Une autre question sur laquelle nous avions convenu de stabiliser la porte était de clouer les contrats auxquels impl s du trait From devraient obéir (ou quel que soit le trait que nous finirons par utiliser pour Err -upcasting ).

@glaebhoerl

Une autre question sur laquelle nous avions convenu de stabiliser la porte était de clouer les contrats que les impls du trait From devraient obéir (ou quel que soit le trait que nous finissons par utiliser pour Err-upcasting).

En effet. Pouvez-vous me rafraîchir la mémoire et commencer par quelques exemples de choses qui, selon vous, devraient être interdites? Ou peut-être simplement les lois que vous avez en tête? Je dois admettre que je me méfie des «lois» comme celle-ci. D'une part, ils ont tendance à être ignorés dans la pratique - les gens profitent du comportement réel quand cela convient à leurs objectifs, même s'il va au-delà des limites prévues. Cela conduit donc à une autre question: si nous avions des lois, les utiliserions-nous pour quelque chose? Optimisations? (Cela me semble peu probable, cependant.)

Au fait, quel est l'état des expressions catch ? Sont-ils implémentés?

Malheureusement non :(

Le mar 26 juillet 2016 à 06:41:44 -0700, Alexander Bulaev a écrit:

Au fait, quel est l'état des expressions catch ? Sont-ils implémentés?


Vous recevez ceci parce que vous avez créé le fil.
Répondez directement à cet e-mail ou affichez-le sur GitHub:
https://github.com/rust-lang/rust/issues/31436#issuecomment -235270663

Peut-être devriez-vous planifier sa mise en œuvre? Il n'y a rien de plus déprimant que les RFC acceptées mais non implémentées ...

cc # 35056

Pour info, https://github.com/rust-lang/rfcs/pull/1450 (types pour les variantes d'énumération) ouvrirait des moyens intéressants d'implémenter Carrier . Par exemple, quelque chose comme:

trait Carrier {
    type Success: CarrierSuccess;
    type Error: CarrierError;
}

trait CarrierSuccess {
    type Value;
    fn into_value(self) -> Self::Value;
}

// (could really use some HKT...)
trait CarrierError<Equivalent: CarrierError> {
    fn convert_error(self) -> Equivalent;
}

impl<T, E> Carrier for Result<T, E>
{
    type Success = Result::Ok<T, E>;
    type Error = Result::Err<T, E>;
}

impl<T, E> CarrierSuccess for Result::Ok<T, E> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T, E1, E2> CarrierError<Result::Err<T, E2>> for Result::Err<T, E1>
    where E2: From<E1>,
{
    fn convert_error(self) -> Result::Err<T, E2> {
        Err(self.into())
    }
}

impl<T> Carrier for Option<T>
{
    type Success = Option::Some<T>;
    type Error = None;
}

impl<T> CarrierSuccess for Option::Some<T> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T> CarrierError<Option::None> for Option::None {
    fn convert_error(self) -> Option::None {
        self
    }
}

fn main() {
    let value = match might_be_err() {
        ok @ Carrier::Success => ok.into_value(),
        err @ Carrier::Error => return err.convert_error(),
    }
}

Je voulais juste partager quelques réflexions de https://github.com/rust-lang/rust/pull/35056#issuecomment -240129923. Ce PR introduit un trait Carrier avec un type factice. L'intention était de sauvegarder - en particulier, nous voulions stabiliser ? sans avoir à stabiliser son interaction avec l'inférence de type. Cette combinaison de trait et de type factice semblait être prudente.

L'idée était (je pense) que nous rédigerions ensuite une RFC discutant de Carrier et essayions de modifier le design pour qu'il corresponde, en ne stabilisant que lorsque nous étions satisfaits de la forme globale (ou en supprimant éventuellement Carrier total, si nous ne pouvons pas atteindre un design que nous aimons).

Maintenant, pour parler un peu plus spéculativement, j'anticipe que, si nous adoptons un trait Carrier , nous voudrions interdire l'interconversion entre les transporteurs (alors que ce trait est fondamentalement un moyen de convertir vers / à partir de Result ). Donc intuitivement, si vous appliquez ? à un Option , ce n'est pas grave si le fn renvoie Option ; et si vous appliquez ? à un Result<T,E> , ce n'est pas grave si le fn renvoie Result<U,F>E: Into<F> ; mais si vous appliquez ? à un Option et que fn renvoie Result<..> , ce n'est pas correct.

Cela dit, ce type de règle est difficile à exprimer dans le système de typage actuel. Le point de départ le plus évident serait quelque chose comme HKT (ce que nous n'avons bien sûr pas vraiment, mais ignorons cela pour l'instant). Cependant, ce n'est évidemment pas parfait. Si nous devions l'utiliser, on supposerait que le paramètre Self pour Carrier a le genre type -> type -> type , puisque Result peut implémenter Carrier . Cela nous permettrait d'exprimer des choses comme Self<T,E> -> Self<U,F> . Cependant, cela ne s'appliquerait pas nécessairement à Option , qui a le genre type -> type (tout cela dépendrait bien sûr du type de système HKT que nous avons adopté, mais je ne pense pas que nous '' J'irai jusqu'au "lambdas de type général"). Encore plus extrême pourrait être un type comme bool (bien que je ne veuille pas implémenter Carrier pour bool, je m'attendrais à ce que certaines personnes veuillent implémenter Carrier pour un nouveau type 'd booléen).

Ce que j'avais considéré, c'est que les règles de frappe pour ? pourraient elles-mêmes être spéciales: par exemple, nous pourrions dire que ? ne peut être appliqué qu'à un type nominal Foo<..> de _some_ kind, et qu'il correspondra au trait Carrier contre ce type, mais il faudra que le type de retour du fn englobant soit également Foo<..> . Nous allons donc essentiellement instancier Foo avec de nouveaux paramètres de type. L'inconvénient de cette idée est que si ni le type auquel ? est appliqué, ni le type du fn englobant n'est connu, nous ne pouvons pas appliquer cette contrainte sans ajouter un nouveau type d'obligation de trait. C'est aussi plutôt ad hoc. :) Mais ça marcherait.

Une autre pensée que j'ai eue est que nous pourrions reconsidérer le trait Carrier . L'idée serait d'avoir Expr: Carrier<Return>Expr est le type auquel ? est appliqué et Return est le type d'environnement. Par exemple, cela pourrait ressembler à ceci:

trait Carrier<Target> {
    type Ok;
    fn is_ok(&self) -> bool; // if true, represents the "ok-like" variant
    fn unwrap_into_ok(self) -> Self::Ok; // may panic if not ok
    fn unwrap_into_error(self) -> Target; // may panic if not error
}

Puis expr? désucres pour:

let val = expr;
if Carrier::is_ok(&val) {
    val.unwrap_into_ok()
} else {
    return val.unwrap_into_error();
}

La principale différence ici est que Target ne serait pas le type _error_, mais un nouveau type Result . Ainsi, par exemple, nous pourrions ajouter l'implication suivante:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_ok() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { Err(F::from(self.unwrap_err())) }
}

Et puis nous pourrions ajouter:

impl<T> Carrier<Option<T>> for Option<T> {
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_some() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { debug_assert!(self.is_none()); None }
}

Et enfin, nous pourrions implémenter pour bool comme ceci:

struct MyBool(bool);
impl<T> Carrier<MyBool> for MyBool {
    type Ok = ();
    fn is_ok(&self) -> bool { self.0 }
    fn unwrap_into_ok(self) -> Self::Ok { debug_assert!(self.0); () }
    fn unwrap_into_error(self) -> { debug_assert!(!self.0); self }
}

Maintenant, cette version est plus flexible. Par exemple, nous _pouvons_ autoriser l'interconversion entre les valeurs Option à convertir en Result en ajoutant un impl comme:

impl<T> Carrier<Result<T,()>> for Option<T> { ... }

Mais bien sûr, nous n'avons pas à le faire (et nous ne le ferions pas).

@Stebalien

Pour info, rust-lang / rfcs # 1450 (types pour les variantes enum) ouvrirait des moyens intéressants d'implémenter Carrier

Alors que j'écrivais cette idée que je viens d'écrire, je pensais à avoir des types pour les variantes d'énumération, et comment cela pourrait affecter les choses.

Une chose que j'ai remarquée en écrivant du code qui utilise ? est qu'il est légèrement ennuyeux de ne pas avoir de mot-clé "throw" - en particulier, si vous écrivez Err(foo)? , le compilateur ne le fait pas. t _connaissez_ que cela reviendra, vous devez donc écrire return Err(foo) . C'est correct, mais vous n'obtenez pas les conversions into() sans les écrire vous-même.

Cela se produit dans des cas comme:

let value = if something_or_other() { foo } else { return Err(bar) };

Oh, je devrais ajouter une autre chose. Le fait que nous permettons aux impls d'influencer l'inférence de type _ devrait_ signifier que foo.iter().map().collect()? , dans un contexte où le fn renvoie un Result<..> , je suppose qu'aucune annotation de type ne serait requise, car si nous savons que le Le type de retour fn est Result , un seul impl serait potentiellement applicable (au moins localement).

Oh, et une version légèrement meilleure de mon trait Carrier serait probablement:

trait Carrier<Target> {
    type Ok;
    fn into_carrier(self) -> Result<Self::Ok, Target>;
}

où vous l'implémenteriez comme:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn into_carrier(self) -> Result<T, Result<U,F>> {
        match self { Ok(v) => Ok(v), Err(e) => Err(e.into()) }
    }
}

Et expr? générerait du code comme:

match Carrier::into_carrier(expr) {
    Ok(v) => v,
    Err(e) => return e,
}

Un inconvénient (ou un avantage ...) de ceci est bien sûr que les conversions Into sont poussées dans les impls, ce qui signifie que les gens pourraient ne pas les utiliser lorsque cela a du sens. Mais cela signifie également que vous pouvez les désactiver si (pour votre type particulier) cela n'est pas souhaité.

@nikomatsakis IMO, le trait devrait être IntoCarrier et IntoCarrier::into_carrier devrait retourner un Carrier (une nouvelle énumération) au lieu de réutiliser le résultat comme ceci. C'est:

enum Carrier<C, R> {
    Continue(C),
    Return(R),
}
trait IntoCarrier<Return> {
    type Continue;
    fn into_carrier(self) -> Carrier<Self::Continue, Return>;
}

@Stebalien bien sûr,

Nomination pour discussion (et possible FCP de l'opérateur ? seul) lors de la réunion de l'équipe lang. Je suppose que nous devons obtenir une sorte de trait de transporteur temporaire dans les prochains jours au FCP.

J'ai ouvert rust-lang / rfcs # 1718 pour discuter du trait Carrier.

Écoutez, écoutez! L'opérateur ? entre maintenant spécifiquement dans la période de commentaire finale . Cette discussion dure à peu près pendant ce cycle de publication qui a commencé le 18 août. L'inclinaison est de stabiliser l'opérateur ? .

En ce qui concerne le trait de porteur , une version temporaire a atterri dans le # 35777 qui devrait garantir que nous avons la liberté de décider de l'une ou l'autre manière en empêchant les interactions indésirables avec l'inférence de type.

Membres @ rust-lang / lang, veuillez cocher votre nom pour signaler votre accord. Laissez un commentaire avec des préoccupations ou des objections. Autres, veuillez laisser des commentaires. Merci!

  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @aturon
  • [x] @eddyb
  • [] @pnkfelix (en vacances)

Je me demande si les bibliothèques basées sur tokio finiront par utiliser beaucoup and_then . Ce serait un argument pour laisser foo().?bar() être un raccourci pour foo().and_then(move |v| v.bar()) , de sorte que les résultats et les contrats à terme puissent utiliser la même notation.

Pour être clair, ce FCP concerne la fonction question_mark , pas catch , n'est-ce pas? Le titre de ce numéro implique le suivi de ces deux fonctionnalités dans ce numéro.

@seanmonstar ce dernier n'a même pas été implémenté, alors oui. Vraisemblablement, si le FCP aboutit à une acceptation, cela sera changé pour suivre catch .

Pour être clair, ce FCP concerne la fonctionnalité question_mark, pas catch, n'est-ce pas? Le titre de ce numéro implique le suivi de ces deux fonctionnalités dans ce numéro.

Oui, juste la fonction question_mark .

Suivi sur https://github.com/rust-lang/rfcs/issues/1718#issuecomment -241764523. J'avais pensé que le comportement actuel ? pouvait être généralisé en "bind current continuation", mais la partie map_err(From::from) de ? fait un peu plus que simplement bind: /. Si nous ajoutons une instance From pour le résultat, alors je suppose que la sémantique pourrait être v? => from(v.bind(current-continuation)) .

Il y a eu beaucoup de discussions sur les mérites relatifs de ? dans ce fil de discussion interne:

https://internals.rust-lang.org/t/the-operator-will-be-harmful-to-rust/3882/

Je n'ai pas le temps de faire un résumé détaillé pour le moment. Je me souviens que les commentaires sont axés sur la question de savoir si ? est suffisamment visible pour attirer l'attention sur les erreurs, mais j'oublie probablement d'autres aspects de la discussion. Si quelqu'un d'autre a le temps de résumer, ce serait génial!

Je n'ai pas encore commenté et c'est peut-être bien trop tard, mais je trouve l'opérateur ? déroutant également, s'il est utilisé comme une déclaration de retour cachée, comme @hauleth l'a souligné dans la discussion que vous avez liée @ nikomatsakis.

Avec try! , il était évident qu'il pourrait y avoir un retour quelque part, car une macro peut le faire. Avec le ? comme un return caché, nous aurions 3 façons de renvoyer les valeurs d'une fonction:

  • retour implicite
  • retour explicite
  • ?

J'aime bien ça, comme l' a dit

De cette façon, tout le monde est familier, cela conduit l'erreur jusqu'à la fin, où vous pouvez la gérer, et il n'y a pas de retour implicite. Cela pourrait donc ressembler approximativement à ceci:

laissez a = essayer! (x? .y? .z);

Cela aide à la fois à rendre le code plus concis et à ne pas cacher un retour. Et il est familier avec d'autres langages tels que coffeescript .

Comment la résolution de ? au niveau de l'expression au lieu du niveau de la fonction affecterait-elle les futurs? Pour tous les autres cas d'utilisation, cela me semble correct.

J'aime bien ça, comme l' a dit

De cette façon, tout le monde est familier, cela conduit l'erreur jusqu'à la fin, où vous pouvez la gérer, et il n'y a pas de retour implicite. Cela pourrait donc ressembler approximativement à ceci:

laissez a = essayer! (x? .y? .z);

J'ai postulé cela. Je pense que ce serait une solution parfaite.

Avec try !, il était évident qu'il pourrait y avoir un retour quelque part, car une macro peut le faire.

Cela n'est évident que parce que vous connaissez le fonctionnement des macros dans Rust. Ce qui sera exactement le même une fois que ? sera stable, répandu et expliqué dans chaque introduction à Rust.

@conradkleinespel

nous aurions 3 façons de renvoyer les valeurs d'une fonction:

  • retour implicite

Rust n'a pas de "retours implicites", il a des expressions qui évaluent à une valeur. C'est une différence importante.

if foo {
    5
}

7

Si Rust avait un "retour implicite", ce code se compilerait. Mais ce n'est pas le cas, vous avez besoin de return 5 .

Qu'est-ce qui sera exactement le même une fois? est stable, répandu et expliqué dans chaque introduction à Rust.

Pour un exemple de ce à quoi cela pourrait ressembler, https://github.com/rust-lang/book/pull/134

Avec try !, il était évident qu'il pourrait y avoir un retour quelque part, car une macro peut le faire.
Cela n'est évident que parce que vous connaissez le fonctionnement des macros dans Rust. Qu'est-ce qui sera exactement le même une fois? est stable, répandu et expliqué dans chaque introduction à Rust.

Dans n'importe quelle langue que je connais, «macros» signifie «ici, soyez des dragons» et que là tout peut arriver. Je reformulerais donc cela en "parce que vous connaissez le fonctionnement des macros", sans la partie "in Rust".

@hauleth

laissez a = essayer! (x? .y? .z);

J'ai postulé cela. Je pense que ce serait une solution parfaite.

Je suis fortement en désaccord. Comme vous obtiendrez un symbole magique qui ne fonctionne que dans try! et pas à l'extérieur.

@hauleth

laissez a = essayer! (x? .y? .z);
J'ai postulé cela. Je pense que ce serait une solution parfaite.
Je suis fortement en désaccord. Comme vous obtiendrez un symbole magique qui ne fonctionne qu'en essai! et pas à l'extérieur.

Je n'ai pas dit que ? ne devrait fonctionner que dans try! . Ce que je disais, c'est que ? devrait fonctionner comme un opérateur de canal qui pousserait les données dans le flux et renverrait l'erreur dès qu'elle se produirait. try! ne serait pas nécessaire dans ce cas, mais pourrait être utilisé dans le même contexte que celui utilisé actuellement.

@steveklabnik Je pense à Rust comme un langage ayant un retour implicite.Dans votre exemple, 5 n'a pas été retourné implicitement, mais prenons ceci:

fn test() -> i32 {
    5
}

Ici, 5 est retourné implicitement, n'est-ce pas? Contrairement à return 5; vous auriez besoin dans votre exemple. Cela fait 2 façons différentes de renvoyer une valeur. Ce que je trouve un peu déroutant à propos de Rust. Ajouter un troisième n'aiderait pas l'OMI.

Ce n'est pas. C'est le résultat d'une expression, plus précisément, le corps de la fonction. "retour implicite" implique que vous pouvez d'une manière ou d'une autre revenir implicitement de n'importe où, mais ce n'est pas vrai. Aucun autre langage basé sur des expressions n'appelle cela un «retour implicite», car ce serait mon exemple de code ci-dessus.

@steveklabnik Très bien, merci d'avoir pris le temps de l'expliquer: +1:

C'est parfait! Je peux tout à fait voir d'où vous venez, ce sont juste deux choses différentes que les gens utilisent souvent d'une mauvaise manière. J'ai vu des gens supposer que "retour implicite" signifie que vous pouvez simplement laisser le ; off n'importe où dans la source pour retourner .... que _would_ serait très mauvais: sourire:

@hauleth Le? l'opérateur serait juste un sucre syntaxique pour and_then dans ce cas. De cette façon, vous pourriez l'utiliser dans beaucoup plus de cas et ce ne devrait pas être un retour difficile à manquer. C'est aussi ce que font toutes les autres langues qui ont un? opérateur. Rust's? L'opérateur dans l'implémentation actuelle serait l'opposé exact de ce que font tous les autres langages. L'approche fonctionnelle est également and_then et est encouragée de toute façon, car elle a un flux de contrôle clair. Alors juste faire? sucre syntaxique pour and_then, puis en gardant l'essai actuel! pour explicitement "déballer et retourner", semble être la situation beaucoup plus propre, en rendant les retours plus visibles et le? opérateur plus flexible (en pouvant l'utiliser dans des cas de non-retour comme le filtrage de motifs).

Exactement.

Łukasz Niemier
[email protected]

Wiadomość napisana przez Christopher Serr [email protected] w dniu 02.09.2016, o godz. 21h05:

@hauleth https://github.com/hauleth Le? l'opérateur serait juste un sucre syntaxique pour and_then dans ce cas. De cette façon, vous pourriez l'utiliser dans beaucoup plus de cas et ce ne devrait pas être un retour difficile à manquer. C'est aussi ce que font toutes les autres langues qui ont un? opérateur. Rust's? L'opérateur dans l'implémentation actuelle serait l'opposé exact de ce que font tous les autres langages. L'approche fonctionnelle est également and_then et est encouragée de toute façon, car elle a un flux de contrôle clair. Alors juste faire? sucre syntaxique pour and_then, puis en gardant l'essai actuel! pour explicitement "déballer et retourner", semble être la situation beaucoup plus propre, en rendant les retours plus visibles et le? opérateur plus flexible (en pouvant l'utiliser dans des cas de non-retour comme le filtrage de motifs).

-
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/31436#issuecomment -244461722, ou désactivez le fil https://github.com/notifications/unsubscribe-auth/ AARzN5-w4EO9_FwNMDpvtYkGUuQKGt-Kks5qmHOHgaJpZM4HUm_-.

Et lorsque je travaillais sur une Pull Request pour le repo Rust, je devais en fait travailler avec du code utilisant le? opérateur et en fait cela a vraiment nui à la lisibilité pour moi, comme c'est le cas; était super caché (mentalement, car ce n'est que du bruit qui est filtré dans le cerveau) et je l'ai beaucoup négligé. Et je trouve cela assez effrayant.

@steveklabnik nous l' appelons « retour implicite », parce que nous ne sommes pas les seuls ceux .

@hauleth huh, au cours de toutes mes années de Ruby, je n'ai jamais entendu personne appeler cela un retour implicite. Je maintiens toujours que ce n'est pas la bonne façon d'y penser.

J'ai utilisé ? dans quelques projets et je l'ai préféré à try! , principalement parce qu'il est en position de suffixe. En général, le «vrai» code Rust entre à peu près dans la Result 'monade' à main et ne le quitte jamais sauf aux nœuds feuilles occasionnels; et un tel code devrait toujours propager les erreurs. Pour la plupart, peu importe quelles expressions génèrent des erreurs - elles sont toutes simplement renvoyées dans la pile, et je ne veux pas voir cela lorsque je lis la logique principale du code.

Ma principale préoccupation avec ? est que je pourrais obtenir le même avantage - position postfixe - avec des macros de méthode, si elles existaient. J'ai d'autres craintes qu'en stabilisant la formulation actuelle, nous limitons l'expressivité future de la gestion des erreurs - la conversion actuelle Result n'est pas suffisante pour rendre la gestion des erreurs dans Rust aussi ergonomique que je le souhaiterais; nous avons déjà fait plusieurs erreurs de conception avec la gestion des erreurs Rust qui semblent difficiles à corriger, et cela peut nous creuser plus profondément; bien que je n'ai aucune preuve concrète.

J'ai écrit cela plusieurs fois auparavant, mais je suis absolument amoureux de ? et des possibilités du trait porteur. J'ai entièrement converti un projet en utilisant ? et cela a rendu beaucoup de choses possibles (en particulier en ce qui concerne le chaînage) qui était trop complexe avec try! . Pour le plaisir, j'ai aussi passé en revue d'autres projets pour voir comment ils se débrouilleraient avec ? et dans l'ensemble, je n'ai rencontré aucun problème avec cela.

En tant que tel, je donne un énorme +1 à la stabilisation de ? sur la base d'un trait Carrier mieux nommé qui couvre idéalement également certains des autres cas que j'ai soulevés dans l'autre discussion à ce sujet.

Ma principale préoccupation avec? est que je pourrais obtenir le même avantage - position postfix - avec des macros de méthode, si elles existaient.

Peut-être avons-nous besoin d'une RFC pour cela? La plupart des gens semblent apprécier la fonctionnalité de?, Mais pas le? lui-même.

Peut-être avons-nous besoin d'une RFC pour cela? La plupart des gens semblent apprécier la fonctionnalité de?, Mais pas le? lui-même.

Il _is_ une RFC avec beaucoup de discussion à ce sujet. De plus, je ne sais pas d'où vous tirez cette «plupart des gens». Si cela vient des participants à ce numéro, bien sûr, vous verrez plus de gens se disputer parce que la stabilisation est déjà l'action par défaut de l'équipe.
Le ? a été énormément discuté avant la fusion de la RFC, et en tant que partisan, il est assez fatigant de devoir faire la même chose lorsque la stabilisation est discutée.

Quoi qu'il en soit, je vais mettre mon +1 pour les sentiments de @mitsuhiko ici.

Il y a une RFC avec beaucoup de discussion à ce sujet. De plus, je ne sais pas d'où vous tirez cette «plupart des gens». Si cela vient des participants à ce numéro, bien sûr, vous verrez plus de gens se disputer car la stabilisation est déjà l'action par défaut de l'équipe.

Désolé, mon commentaire était trop bref. Je faisais référence à la création d'un RFC pour une sorte de "macros de méthode", par exemple func1().try!().func2().try!() (pour autant que je sache, ce n'est actuellement pas possible).

Personnellement, j'aime le? opérateur, mais je partage les mêmes préoccupations que @brson , et je pense qu'il serait bon d'explorer des alternatives avant de stabiliser cette fonctionnalité. Y compris la conversion RFC, ce thread et le thread interne que @nikomatsakis a lié, il y a certainement encore des controverses sur cette fonctionnalité, même si ce sont les mêmes arguments encore et encore. Cependant, s'il n'y a pas d'alternatives viables, la stabilisation est la plus logique.

Il semble prématuré de stabiliser une fonctionnalité sans l'avoir entièrement implémentée - dans ce cas, l'expression catch {..}.

J'ai déjà exprimé mes inquiétudes sur cette fonctionnalité et je pense toujours que c'est une mauvaise idée. Je pense qu'avoir un opérateur de retour conditionnel postfix ne ressemble à rien dans aucun autre langage de programmation et pousse Rust au-delà de son budget de complexité déjà étiré.

@mcpherrinm D'autres langages ont à la place des chemins de déroulement cachés à chaque appel pour la gestion des erreurs, appelleriez-vous operator() un "opérateur de retour conditionnel"?

Quant au budget de complexité, il n'est syntaxiquement différent que de try! , du moins la partie dont vous vous plaignez.
Est-ce que l'argument contre try! -code lourd, que ? rend seulement plus lisible?
Si tel est le cas, je serais d'accord s'il existe une alternative sérieuse autre que "ne pas avoir d'automatisation de propagation d'erreur _à tous_".

Suggérer un compromis: https://github.com/rust-lang/rfcs/pull/1737

Il n'a peut-être aucune chance d'être accepté, mais j'essaye quand même.

J'aime l'idée de @keeperofdakeys sur les "macros de méthode". Je ne pense pas que la syntaxe ? devrait être acceptée pour la même raison que l'opérateur ternaire n'est pas dans la lisibilité Rust. Le ? lui-même ne dit rien. Au lieu de cela, je préférerais de beaucoup voir la possibilité de généraliser le comportement de ? avec les "macros de méthode".

a.some_macro!(b);
// could be syntax sugar for
some_macro!(a, b); 
a.try!();
// could be syntax sugar for
try!(a); 

De cette façon, il serait clair quel est le comportement et cela permet un chaînage facile.

Une macro de méthode comme result.try!() semble être une amélioration plus générique de l'ergonomie du langage et semble moins ad hoc qu'un nouvel opérateur ? .

@brson

J'ai d'autres préoccupations qu'en stabilisant la formulation actuelle, nous limitons l'expressivité future dans la gestion des erreurs - la conversion actuelle de Result n'est pas suffisante pour rendre la gestion des erreurs dans Rust aussi ergonomique que je le souhaiterais

C'est un point intéressant. Cela vaudrait la peine de consacrer du temps à cela (peut-être que vous et moi pouvons discuter un peu). Je conviens que nous pourrions faire mieux ici. La conception proposée pour un trait Carrier (voir https://github.com/rust-lang/rfcs/issues/1718) peut aider ici, en particulier si elle est combinée avec une spécialisation, car elle rend les choses plus flexibles.

Je doute vraiment que les macros de méthode soient une bonne extension du langage.

macro_rules! macros

Ce n'est pas ainsi que fonctionnent les méthodes. Les méthodes ont ces propriétés:

  1. Ne peut pas être déclaré dans une portée de module, mais doit être déclaré dans un bloc impl .
  2. Sont importés avec le type / trait auquel le bloc impl est associé, plutôt qu'importé directement.
  3. Sont envoyés sur la base de leur type de récepteur, plutôt que d'être envoyés sur la base du fait qu'il s'agit d'un symbole unique et sans ambiguïté dans cette portée.

Étant donné que les macros sont développées avant la vérification de type, aucune de ces propriétés ne peut être vraie pour les macros utilisant la syntaxe de la méthode pour autant que je sache. Bien sûr, nous pourrions simplement avoir des macros qui utilisent la syntaxe de méthode mais qui sont distribuées et importées de la même manière que les macros «gratuites», mais je pense que la disparité en ferait une fonctionnalité très déroutante.

Pour ces raisons, je ne pense pas que ce soit un bon choix de retarder ? sur la croyance que des "macros de méthode" peuvent apparaître un jour.

De plus, je pense qu'il y a une ligne à laquelle une certaine construction est si largement utilisée et si importante qu'elle devrait être promue des macros au sucre. for boucles ? fait partie intégrante de l'histoire de la gestion des erreurs de Rust, et je pense qu'il est approprié que ce soit du sucre de première classe au lieu d'une macro.

@hauleth , @CryZe

Pour répondre à ceux qui suggèrent que ? devrait être un opérateur and_then , cela fonctionne bien dans des langages comme Kotlin (je ne suis pas familier avec coffeescript) en raison de leur utilisation intensive des fonctions d'extension mais ce n'est pas si simple dans la rouille. Fondamentalement, la plupart des utilisations de and_then ne sont pas maybe_i.and_then(|i| i.foo()) , elles sont maybe_i.and_then(|i| Foo::foo(i)) Le premier pourrait être exprimé comme maybe_i?.foo() mais le second ne le peut pas. On pourrait dire que Foo::foo(maybe_i?, maybe_j?) se transforme en maybe_i.and_then(|i| maybe_j.and_then(|j| Foo::foo(i, j))) mais cela semble encore plus déroutant que de simplement dire que la rouille revient tôt en frappant le premier ? qui évalue une erreur. Cependant, ce serait sans doute plus puissant.

@Stebalien Dans la RFC acceptée, catch { Foo::foo(maybe_i?, maybe_j?) } fait ce que vous voulez.

@eddyb Bon point. Je suppose que je peux laisser de côté le "Cependant, ce serait sans doute plus puissant". Cela revient à catch implicite / essai explicite versus catch explicite / essai implicite:

let x: i32 = try Foo::foo(a?.b?.c()?));
let y: Result<i32, _> = Foo::foo(a?.b?.c()?);

Contre:

let x: i32 = Foo::foo(a?.b?.c()?);
let y: Result<i32, _> = catch  Foo::foo(a?.b?.c()?);

(syntaxe modulo)

@Stebalien Un autre exemple: si je voulais passer Foo à une fonction bar , avec votre proposition, je devrais:

bar(Foo::foo(a?.b?.c()?)?)

Est-ce cela que vous avez en tête? Notez que le ? supplémentaire, sans lui bar obtiendrait un Result au lieu d'un Foo .

@eddyb Probablement. Remarque: je ne propose pas vraiment cela! Je soutiens que l'utilisation de ? comme opérateur de tube n'est pas particulièrement utile dans rust sans un moyen de gérer le cas Foo::foo(bar?) .

Juste pour noter que je déteste l'idée des macros de méthode et que je ne peux pas penser à une fonctionnalité de langage à laquelle je m'opposerais plus fortement. Ils brouillent le phasage du compilateur, et à moins que nous n'apportions des changements vraiment assez fondamentaux au langage, il n'y a aucun moyen qu'ils puissent exister et avoir un comportement sans surprise. Ils sont également difficiles à analyser raisonnablement et presque certainement pas rétrocompatibles.

@Stebalien , avec ? comme opérateur pipe Foo::foo(bar?) ressemblerait à ceci: Foo::foo(try!(bar)) et bar(Foo::foo(a?.b?.c()?)?) (en supposant que Foo::foo : fn(Result<_, _>) -> Result<_, _> ): bar(try!(Foo::foo(a?.b?.c()?))) .

@hauleth mon point était que Foo::foo(bar?)? est _beaucoup_ plus commun que bar?.foo()? dans la rouille. Par conséquent, pour être utile, ? devrait prendre en charge ce cas (ou une autre fonctionnalité devrait être introduite). Je postulais un moyen de le faire et je montrais que cela au moins serait compliqué. Tout l'intérêt de ? est de pouvoir éviter d'écrire try!(foo(try!(bar(try!(baz()))))) (2x les parenthèses!); il n'est généralement pas possible de réécrire ceci comme try!(baz()?.bar()?.foo()) .

Mais vous pouvez toujours faire:

try!(baz().and_then(bar).and_then(foo))

Łukasz Niemier
[email protected]

Wiadomość napisana przez Steven Allen [email protected] w dniu 05.09.2016, o godz. 15h39:

@hauleth https://github.com/hauleth mon point était que Foo :: foo (bar?)? est beaucoup plus courant que bar? .foo ()? dans la rouille. Par conséquent, pour être utile,? devrait prendre en charge ce cas (ou une autre fonctionnalité devrait être introduite). Je postulais un moyen de le faire et je montrais que cela au moins serait compliqué. Le point entier de? est de pouvoir éviter d'écrire try! (foo (try! (bar (try! (baz ())))))) (2x les parenthèses!); il n'est généralement pas possible de réécrire ceci comme try! (baz () ?. bar () ?. foo ()).

-
Vous recevez cela parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244749275, ou désactivez le fil https://github.com/notifications/unsubscribe-auth/ AARzN1Hdk6uk5-SoYawtgAbJUDf_8MsMks5qnBumgaJpZM4HUm_-.

Sur une note légèrement liée, il semble que la fonction? -Feature soit principalement utilisée par les constructeurs, nous pourrions donc éviter le besoin de la fonction? -Feature en fournissant un moyen facile de construire des générateurs qui enveloppent un résultat. J'ai proposé quelque chose ici, mais il faudra peut-être un peu plus de travail.

https://github.com/colin-kiegel/rust-derive-builder/issues/25

Merci pour vos réflexions sur l'idée de macro de méthode @nrc et @withoutboats , il est bon d'entendre quelques raisons concrètes pour lesquelles elles ne fonctionneraient pas.

@nielsle Je ne pense pas qu'il soit exact de dire que ? est "principalement" utilisé par les constructeurs. Alors que les constructeurs sont un exemple où je pense que l'avantage d'un opérateur postfix léger transparaît vraiment, je préfère ? à try! dans tous les contextes.

@nielsle concernant les futurs, j'étais à l'origine préoccupé pour des raisons similaires. Mais, après y avoir réfléchi, je pense que async / await remplacerait tout besoin de ? dans ce contexte. Ils sont en fait assez orthogonaux: vous pouvez faire des choses comme (await future)?.bar() .

(Ce serait peut-être bien d'avoir un opérateur de suffixe au lieu du mot-clé await pour que les parenthèses ne soient pas nécessaires. Ou peut-être qu'une priorité soigneusement réglée serait suffisante.)

J'aimerais vraiment voir une documentation écrite avant de nous stabiliser. J'ai regardé dans la référence et je n'ai trouvé aucune mention. Où devons-nous documenter cette fonctionnalité?

@cbreeden Je sais que @steveklabnik évite généralement de documenter les fonctionnalités instables, car il y a une chance que ce soit une perte de temps si elles ne sont jamais stabilisées. Je ne sais pas si nous avons déjà bloqué la stabilisation sur l'écriture de la documentation auparavant.

@solson Vous avez raison, ce n'était probablement pas l'endroit pour en

Je pense que la partie importante de cette RFC est d'avoir quelque chose du côté droit d'une expression qui agit comme try! , car cela rend la lecture des utilisations séquentielles / chaînées de "try" _much_ plus lisible et d'avoir "catch" . À l'origine, j'étais un partisan à 100% de l'utilisation de ? comme syntaxe, mais récemment, je suis tombé sur un code (propre!) Utilisant déjà ? ce qui m'a fait prendre conscience qu'en dehors d'exemples simples ? est extrêmement facile à négliger . Ce qui me fait maintenant croire que l'utilisation de ? comme syntaxe pour "le nouvel essai" pourrait être une grosse erreur.

Par conséquent, je propose que ce soit une bonne idée de _mettre un sondage_ avant de le finaliser (avec notification à ce sujet sur les forums) pour obtenir un retour d'information sur l'utilisation de ? ou d'autres symboles comme syntaxe . De manière optimale avec un exemple d'utilisation dans une fonction plus longue. Notez que je considère seulement que cela pourrait être une bonne idée de renommer ? pour ne rien changer d'autre. Le sondage pourrait lister d'autres noms possibles qui sont apparus dans le passé comme ?! (ou ?? ) ou simplement quelque chose comme "utiliser? Vs.

Btw. ne pas utiliser ? pourrait également satisfaire les personnes qui ne l'aiment pas car c'est la même syntaxe que les autres langages de type facultatif. Et ceux qui veulent en faire une syntaxe optionnelle pour les types d'options rust. (A travers cela ne sont pas des préoccupations que je partage).

De plus, je pense qu'avec un tel sondage, les personnes qui ne participent normalement pas au processus RFC pourraient être atteintes. Normalement, cela peut ne pas être nécessaire mais try! => ? est un très gros changement pour quiconque écrit et / ou lit du code rust.

PS:
J'ai mis en place l' essentiel de la fonction aimée ci-dessus dans différentes variantes ("?", "?!", "??") à travers je ne sais pas s'il y en avait eu plus. Il y avait aussi un RFC pour renommer ? en ?! qui a été redirigé vers cette discussion.

Désolé, possibilité de redémarrer une discussion déjà longue en cours: smiley_cat:.

(Notez que ?? est mauvais si vous voulez toujours introduire ? pour Option car expr??? serait ambigu)

? est extrêmement facile à négliger

De quoi parlons-nous ici? Code complètement non mis en évidence? Navigation régulière de code en surbrillance?
Ou vous recherchez activement ? dans une fonction?

Si je sélectionne un ? dans mon éditeur, _tous_ les autres ? du fichier sont mis en surbrillance avec un fond jaune vif, et la fonction de recherche fonctionne également, donc je ne vois pas ce dernier comme posant des difficultés _any_ pour moi.

Comme pour les autres cas, je préfère résoudre ce problème en mettant mieux en évidence que de finir avec ?? ou ?! .

@dathinab Je pense que .try! () ou quelque chose serait encore mieux, mais cela nécessiterait UFCS pour les macros.

let namespace = namespace_opt.ok_or(Error::NoEntry).try!();

De cette façon, c'est difficile à rater, mais tout aussi facile, voire plus facile à taper que .unwrap() .

@eddyb : il s'agit de code normal en surbrillance, par exemple sur github. Par exemple, lors de la lecture d'une base de code. De mon point de vue, cela me semble un peu faux si j'ai besoin d'une mise en évidence forte pour ne pas oublier facilement un ? qui introduirait à la fois un autre chemin de retour (/ chemin à attraper) et changerait éventuellement le type d'une variable de Result<T> à T

@CryZe : Je suis d'accord avec vous, mais je ne pense pas que nous puissions obtenir cela dans un proche avenir. Et avoir quelque chose qui est un peu plus court que .try!() n'est pas si mal non plus.

@CryZe J'aime aussi cette syntaxe, mais @withoutboats a mentionné des raisons solides pour lesquelles les macros de méthode pourraient nuire au langage.
D'un autre côté, je crains que si des macros de méthode apparaissent, je ne pense pas qu'elles fonctionneraient bien avec ? .

Je ne suis pas intrinsèquement contre l'utilisation de deux caractères pour ce sigil, mais j'ai regardé les exemples et je n'ai pas trouvé que le changement rendait ? plus visible pour moi.

Ouais, pareil. Je pense que cela doit être une sorte de mot-clé, car les symboles en général sont davantage utilisés pour la structuration, à la fois dans le langage normal et les langages de programmation.

@CryZe : Peut-être que quelque chose comme ?try serait une sorte de mot-clé. Mais pour être honnête, je n'aime pas ça ( ?try ).

@eddyb

et la fonction de recherche fonctionne également

La recherche de ? produira de faux positifs, contrairement aux versions plus longues.

À la recherche de ? produira de faux positifs, alors que les versions plus longues ne le feront probablement pas.

J'espère que les surligneurs de syntaxe s'en chargeront en interne (en évitant les faux positifs). En fait, ils peuvent même insérer une forme de marqueur «pourrait retourner» dans la frange (à côté des numéros de ligne).

Par exemple,
screen-2016-09-15-175131

Wow, cela aide vraiment beaucoup. Mais alors tu dois vraiment t'assurer
faites configurer correctement votre éditeur, ce que vous ne pouvez pas faire tout le temps (comme
sur GitHub par exemple).

2016-09-15 23:52 GMT + 02: 00 Steven Allen [email protected] :

Par exemple,
[image: screen-2016-09-15-175131]
https://cloud.githubusercontent.com/assets/310393/18568833/1deed796-7b6d-11e6-99af-75f0d7ddd778.png

-
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/31436#issuecomment -247465972,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/ABYmbjyrt07NXKMUdmlBfaciRZq7uBVEks5qqb4sgaJpZM4HUm_-
.

@CryZe Je suis presque sûr que GitHub utilise simplement un surligneur de syntaxe open source; nous pouvons le patcher :-).

Je pense qu'en général, c'est une bonne idée pour le projet Rust de recommander que les surligneurs de syntaxe pour Rust utilisent un style très visible sur ? , afin d'équilibrer les problèmes de visibilité.

Je pense que ? est le meilleur choix que nous puissions faire ici, et nous l'avons beaucoup utilisé dans Miri .

Revenant à la motivation originale, try!(...) est intrusif et rend difficile d'ignorer le flux d'erreurs et de simplement lire le chemin heureux. C'est un inconvénient par rapport aux exceptions invisibles des langues traditionnelles. L'extension de ? à un mot clé plus impliqué aurait le même inconvénient.

D'un autre côté, avec ? , quand je ne me soucie pas du flux d'erreurs, je peux l'ignorer et le laisser disparaître en arrière-plan. Et quand je me soucie vraiment du flux d'erreurs, je peux toujours voir ? très bien. Mettre en évidence ? brillamment n'est même pas nécessaire pour moi, mais si cela aide d'autres personnes, c'est génial. Il s'agit d'une amélioration par rapport aux exceptions invisibles et à try! .

Passer à un sigil trivialement plus grand comme ?! ne m'aiderait en aucun cas, mais rendrait la lecture et l'écriture du code de gestion des erreurs légèrement pire.

Merci à tous pour une période de commentaires finale chaleureuse (ainsi qu'un fil de discussion interne préalable ). Étant donné que nous parlons de la RFC la

Je vais commencer par aller droit au but: l'équipe @ rust-lang / lang a décidé de stabiliser l'opérateur ? lorsqu'il est appliqué à des valeurs de type Result . Notez que la fonctionnalité catch n'est pas stabilisée (et, en fait, n'a pas encore été implémentée); de même, le soi-disant «trait porteur», qui est un moyen d'étendre ? à des types comme Option , est toujours dans la phase de discussion pré-RFC . Nous avons cependant pris des mesures dans l'implémentation actuelle pour nous assurer que nous pouvons ajouter le trait Carrier plus tard (ce qui répond à certaines de mes préoccupations précédentes concernant une interaction potentielle avec l'inférence).

J'aimerais prendre un peu de temps pour résumer la discussion qui a eu lieu depuis le début fil RFC original . Si vous souhaitez lire le fil de discussion, le commentaire FCP et le commentaire récapitulatif de ce fil tentent de couvrir la conversation en profondeur. Dans certains cas, je vais créer un lien vers les commentaires de ce fil d'origine s'ils sont plus détaillés que ceux correspondants de ce fil.

La portée de l'opérateur ? doit être l'expression courante et non la fonction courante.

Lorsqu'une erreur se produit, la macro try! propage cette erreur inconditionnellement à la fonction appelante (en d'autres termes, elle exécute un return avec l'erreur). Tel qu'il est actuellement conçu, l'opérateur ? suit ce précédent, mais avec l'intention de prendre en charge un mot clé catch qui permet à l'utilisateur de spécifier une portée plus limitée. Cela signifie, par exemple, que x.and_then(|b| foo(b)) peut être écrit comme catch { foo(x?) } . En revanche, plusieurs langages récents utilisent l'opérateur ? pour signifier quelque chose de plus analogue à and_then , et on craint que cela puisse prêter à confusion pour les nouveaux utilisateurs.

En fin de compte, une fois que catch est implémenté, c'est une question de défaut . Et il y a plusieurs raisons pour lesquelles nous pensons que la valeur par défaut "break out of function" (avec l'option de personnalisation) est plus appropriée pour ? dans Rust :

Le ? obscurcit le flux de contrôle car il est difficile à repérer.

Une préoccupation courante est que l'opérateur ? est trop facile à ignorer . C'est clairement un exercice d'équilibre. Avoir un opérateur léger permet de se concentrer facilement sur le "chemin heureux" quand vous le souhaitez - mais il est important d'avoir une indication sur l'endroit où les erreurs peuvent survenir (contrairement aux exceptions, qui introduisent un flux de contrôle implicite). De plus, il est facile de rendre ? plus facile à repérer grâce à la coloration syntaxique (par exemple, 1 , 2 ).

Pourquoi pas des macros de méthode?

L'un des grands avantages de ? est qu'il peut être utilisé en position post-correction, mais
nous pourrions obtenir des avantages similaires à partir de "macros de méthode" comme foo.try! . Bien que cela soit vrai, les macros de méthodes ouvrent elles - méthodes (par exemple, distribuées en fonction du type de récepteur et n'utilisant pas de portée lexicale). De plus, l'utilisation d'une macro de méthode comme foo.try! a une sensation de poids nettement plus lourde que foo? (voir le point précédent).

Quels contrats From devrait-il offrir?

Dans la discussion RFC originale, nous avons décidé de reporter la question de [s'il devrait y avoir des "contrats" pour From ] ((https://github.com/rust-lang/rust/issues/31436#issuecomment -180558025). Le consensus général de l'équipe lang est que l' on devrait voir l'opérateur ? comme invoquant le trait From , et que le From impls peut naturellement faire tout est autorisé par leurs signatures de type. Notez que le rôle du trait From est ici de toute façon assez limité: il sert simplement à convertir d'une sorte d'erreur en une autre (mais toujours dans le contexte d'un Result ).

Cependant , je voudrais noter que la discussion concernant un caractère "porteur" est en cours et que l' adoption de conventions fortes est plus importante dans ce scénario . En particulier, le trait porteur définit ce qui constitue «succès» et «échec» pour un type, ainsi que si un type de valeur (par exemple, un Option ) peut être converti en un autre (par exemple, un Result ). Beaucoup ont soutenu que nous ne voulons pas prendre en charge les interconversions arbitraires entre les types "de type erreur" (par exemple, ? ne devrait pas pouvoir convertir Option en Result ). Évidemment, puisque les utilisateurs finaux peuvent implémenter le trait Carrier pour leurs propres types comme ils le souhaitent, il s'agit en fin de compte d'une ligne directrice, mais je pense que c'est une question importante.

essayez de port! utiliser ?

Je ne pense pas que nous puissions faire cela de manière rétrocompatible et nous devrions laisser l'implémentation de try! seule. Devrions-nous abandonner try! ?

@nrc Pourquoi n'est-il pas rétrocompatible?

@withoutboats try!(x) est (x : Result<_, _>)? et nous pourrions probablement l'implémenter de cette façon si nous _ voulions_, mais en général x? pourrions déduire tout ce qui supporte le Carrier trait (dans le futur), un exemple est iter.collect()? qui avec try! ne serait que Result mais peut de manière réaliste être Option .

Ça a du sens. Je pensais que nous acceptions que l'ajout d'impls à std pouvait provoquer des ambiguïtés d'inférence; pourquoi ne pas l'accepter aussi ici?

Quoi qu'il en soit, je pense que try! devrait être obsolète.

? est plus utile dans un modèle de générateur comme context, tandis que try est plus utile dans une méthode imbriquée comme context. Je pense qu'il ne devrait pas être obsolète, quoi que cela signifie.

@ est31 Ils font exactement la même chose pour le moment, sauf pour l'inférence. Je ne sais pas exactement ce que vous voulez dire, mais ? est généralement strictement plus propre (encore une fois par inférence modulo). Pouvez-vous donner des exemples?

@eddyb foo()?.bar()?.baz() est mieux que try!(try!(foo()).bar()).baz() , et try!(bar(try!(foo()))) est plus beau que bar(foo()?)?

Je trouve ? plus lisible dans les deux cas. Cela réduit l'encombrement inutile des parenthèses.

Quand cela at-il atterri sur stable?

@ofek c'est pour le tout, qui n'est pas encore terminé, donc c'est difficile à dire. https://github.com/rust-lang/rust/pull/36995 a stabilisé la syntaxe de base ? , qui devrait être stable en 1.14.

N'oubliez pas d'ajouter le? opérateur à la référence maintenant que sa stabilité: https://doc.rust-lang.org/nightly/reference.html#unary -operator-expressions

Et @bluss a souligné que le livre est également obsolète: https://doc.rust-lang.org/nightly/book/syntax-index.html

@tomaka

Je suis opposé à l'extension? aux Options.

Il ne doit pas être utilisé à des fins d'erreur. Par exemple, si j'utilise une méthode wrapper pour prendre get et le mapper par une fonction, alors j'aimerais pouvoir propager le cas None vers le haut.

? a été présenté comme étant juste pour des erreurs; la notation do plus générale pour une propagation générale comme celle-ci n'était pas un but.

Le 29 octobre 2016, 11:08 -0400, ticki [email protected] , a écrit:

@tomaka (https://github.com/tomaka)

Je suis opposé à l'extension? aux Options.

Il ne doit pas être utilisé à des fins d'erreur. Par exemple, si je propose une méthode wrapper pour prendre get et le mapper par une fonction, alors j'aimerais pouvoir propager le cas None.

-
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/31436#issuecomment-257096575) ou désactivez le fil de discussion (https://github.com/notifications/unsubscribe -auth / AABsipGIpTF1-7enk-z_5JRYYtl46FLPks5q42DCgaJpZM4HUm_-).

Ceci (= ? lui-même) est maintenant une fonctionnalité stable! (À partir de Rust 1.13)

Deux problèmes de documentation:

  • [x] Mettre à jour le chapitre sur la gestion des erreurs dans le livre n ° 37750
  • [x] Mettre Carrier jour la documentation sur les traits

Notez que ni catch ni le trait Carrier n'ont encore été correctement implémentés, juste la seule fonctionnalité ? .

Carrier existe et les messages d'erreur y font référence lors de l'utilisation de ? , il serait donc préférable que le problème du transporteur ait été résolu plutôt que rejeté. Ses documents doivent également être mis à jour, car les documents font référence à sa mise en œuvre pour Option. (Ce qui est faux).

35946 devrait supprimer toute mention de Carrier des messages d'erreur. Il semble que nous devrions au moins supprimer la mention Option de la documentation Carrier .

J'ajoute T-libs à ce problème en raison de l'interaction avec le trait Carrier

Salut; dans # 31954, la conversion ascendante pour le type d'erreur est effectuée en utilisant From (et similaire dans la tête actuelle ), mais la RFC 243 indique clairement que Into doit être utilisé pour la conversion.

Y a-t-il une raison pour laquelle From été utilisé à la place? Lors d'une tentative de conversion ascendante vers un type d'erreur générique (c'est- io::Error dire From ne peut pas être implémenté pour les types d'erreur locaux.

(FWIW, en tant qu'auteur de la RFC 243, je ne me suis pas demandé très sérieusement si From ou Into était préférable, et j'ai peut-être fait le bon choix ou non. Ce qui veut simplement dire que la question doit être décidée en fonction des mérites (qui à ce stade peuvent inclure la compatibilité ascendante), plutôt que ce qui est écrit dans la RFC.)

Malheureusement, il y aurait une régression. Si aucune instance (non triviale) From<...> pour un type d'erreur n'est implémentée, le compilateur peut déduire le type dans certains cas, où il ne peut pas le déduire lors de l'utilisation de Into (l'ensemble de From instances Into n'est pas connu lors de la compilation d'un crate).

Voir https://play.rust-lang.org/?gist=6d3ee9f93c8b40094a80d3481b12dd00 ("simplifié" à partir d'un problème du monde réel impliquant fmt::Error dans src / librustc / util / ppaux.rs # L81 )

Il y a un nouveau RFC qui remplacera l'ancien en termes de description de ? : rust-lang / rfcs / pull / 1859

Je suis -1 sur avoir du tout des blocs catch , quand ils sont déjà beaucoup plus concis avec les fermetures.

Les fermetures interfèrent avec les instructions de contrôle de flux. ( break , continue , return )

Maintenant que https://github.com/rust-lang/rfcs/pull/1859 a été fusionné et prétend que nous réutilisons ce problème comme problème de suivi, je voudrais proposer que nous réévaluions le catch partie de la RFC 243.

Je ne propose pas que nous nous en débarrassions carrément, mais l'enthousiasme pour catch n'a jamais été aussi grand qu'il ne l'était pour ? et je pense qu'il vaut la peine de s'assurer que cela a toujours du sens à la lumière. des idiomes qui ont émergé pour ? , et qui devraient apparaître à la lumière de Try .

Je suis toujours impatient d'avoir des prises; plus tôt dans la journée, j'ai dû contorsionner du code d'une manière qui aurait pu être évitée si catch était stable.

La fonctionnalité a été implémentée tous les soirs avec la syntaxe do catch , n'est-ce pas?

@withoutboats Oui, c'est actuellement do catch , puisque catch { ... } conflit avec les littéraux de structure ( struct catch { }; catch { } ).
.
@archshift Comme @SimonSapin l'a souligné ci-dessus, les fermetures interfèrent avec break , continue et return .

@bstrie Je trouve que je veux assez fréquemment catch ces jours-ci; cela revient souvent lors du refactoring.

Je n'avais pas réalisé que nous avions l'intention d'exiger do catch comme syntaxe. Étant donné que le risque de rupture dans le monde réel semble extrêmement faible (les deux enfreignent les directives de dénomination des structures et devraient avoir le constructeur comme première expression dans l'instruction (ce qui est rare en dehors de la position de retour)), pourrions-nous peut-être tirer parti de rustfmt pour réécrire les identifiants incriminés en catch_ ? Si cela nécessite Rust 2.0, alors, eh bien, j'ai toujours été du genre à dire que Rust 2.0 ne devrait de toute façon contenir que des changements de rupture triviaux ...

@bstrie Nous ne voulons absolument pas exiger do catch long terme. La discussion qui a conduit à l'utilisation de cette syntaxe pour le moment est ici: https://github.com/rust-lang/rust/pull/39921

Excellent, merci pour le contexte.

Je viens juste de venir ici parce que j'espérais que catch était stable, et j'ai dû apprendre que ce n'est pas le cas - alors oui, absolument, maintenant que ? est stable, ce serait bien d'avoir aussi catch .

Je voulais voir jusqu'où nous étions arrivés sur le reste et j'ai vu la discussion sur la capture.

J'ai envisagé une idée probablement stupide qui pourrait aider dans des cas comme celui-ci: autoriser éventuellement le préfixe des mots-clés avec @ ou un autre sigil, puis faire en sorte que tous les nouveaux mots-clés utilisent uniquement le sigil. Nous avons également eu un problème similaire avec la discussion coroutine. Je ne me souviens pas si j'y suis allé comme solution là-bas - je l'ai peut-être - mais il semble que cela puisse continuer à apparaître.

FWIW, C # prend en charge le contraire: @ pour l'utilisation de mots-clés comme identifiants. Cela se voit couramment dans Razor, où vous transmettez des éléments tels que new { <strong i="6">@class</strong> = "errorbox" } pour définir les propriétés des nœuds HTML.

@scottmcm
C'est intéressant. Je n'en savais rien. Mais pour Rust, nous devons aller dans l'autre sens à cause de la compatibilité.

Bonne idée de leur part, cependant. L'écosystème .net comprend de nombreux langages, tous avec des mots-clés disparates et tous capables de s'appeler.

Une autre possibilité pour l'avenir des mots-clés: les mettre derrière un attribut, comme une sorte de #[feature] stable.

Je pense que ce problème nécessitera une solution plus générale à long terme.

@camlorn Ce fil de discussion peut vous intéresser, en particulier l'idée d'Aaron sur les «époques» de Rust: https://internals.rust-lang.org/t/pre-rfc-stable-features-for-breaking-changes/5002

Je pense que nous devrions lier https://github.com/rust-lang/rust/issues/42327 à partir de la description du problème ici. (Aussi peut-être que le texte RFC devrait être mis à jour pour y lier plutôt qu'ici.)

(EDIT: j'ai posté un commentaire là-bas, je ne sais pas qui y est ou n'est pas déjà abonné!)

@camlorn Cela pourrait cependant ouvrir le chemin "rustfmt fait une réécriture triviale pendant un moment, puis il devient un mot-clé" chemin. AKA profite du "ça ne casse pas s'il y a une annotation supplémentaire qui aurait pu être écrite qui le ferait fonctionner dans les deux" trappe d'échappement dans la garantie de stabilité. Et, semblable à l'UFCS pour les changements d'inférence, un modèle hypothétique de «stockage sous une forme entièrement élaborée» pourrait permettre aux vieilles caisses de fonctionner.

Cela ne me dérangerait pas si la syntaxe du bloc catch était juste do { … } ou même ?{ … }

do a la belle propriété d'être déjà un mot-clé. Il a la propriété douteuse (selon votre point de vue) d'invoquer la notation do Haskell, bien que cela n'ait jamais arrêté ses utilisations passées, et celle-ci est un peu plus proche dans le cas d'utilisation.

il ressemble aussi, mais se comporte différemment des javascripts proposés do expressions

Cette proposition n'est pas vraiment pertinente pour Rust, qui utilise déjà des blocs nus comme expressions.

Je signalais simplement la confusion possible puisque les deux se ressembleraient mais feraient des choses complètement différentes.

Il a la propriété douteuse (selon votre point de vue) d'invoquer la notation de type Haskell

@rpjohnst Result et Option sont des monades, donc c'est au moins conceptuellement similaire. Il devrait également être compatible avec les versions antérieures pour l'étendre à l'avenir pour prendre en charge toutes les monades.

À vrai dire, dans le passé, l'objection était que si nous ajoutions do pour quelque chose de conceptuellement similaire aux monades, mais sans les soutenir pleinement, cela rendrait les gens tristes.

En même temps, alors que nous pourrions probablement le rendre compatible avec l'avant avec la notation do complète, nous ne devrions probablement if / while / for / loop ou break / continue / return , que nous devons pouvoir utiliser à l'intérieur et à travers les blocs catch (ou dans ce cas do ). (Ceci est dû au fait que la notation do complète est définie en termes de fonctions d'ordre supérieur, et si nous remplissons le contenu d'un bloc catch dans une série de fermetures imbriquées, contrôler soudainement le flux de toutes les ruptures.)

Donc, en fin de compte, cet inconvénient de do est qu'il ressemble à une notation do sans être réellement devenir do-notation non plus. Personnellement, je suis tout à fait d' do avec ça parce que Rust n'obtiendra pas do - mais c'est la confusion.

@nikomatsakis , # 42526 est fusionné, vous pouvez le marquer comme terminé dans la liste de suivi :)

Est-il possible de rendre le mot clé catch contextuel mais de le désactiver si un struct catch est dans la portée et d'émettre un avertissement de dépréciation?

Je ne sais pas si cela est approprié, mais j'ai rencontré un problème que cela doit peut-être résoudre dans la mesure où vous souhaitez parfois annuler une valeur ok plutôt qu'une valeur d'erreur, ce qui est actuellement possible lorsque vous utilisez return dans la mesure où vous le souhaitez souvent abandonner une erreur _inner_ via un _outer_ ok. Comme quand une fonction say renvoie Option<Result<_,_>> ce qui est assez courant avec un itérateur par exemple.

En particulier j'ai une macro dans un projet que j'utilise copieusement en ce moment:

macro-rules! option_try {
    ( $expr:expr ) => {
        match $expr {
            Ok(x)  => x,
            Err(e) => return Some(Err(e.into())),
        }
    }
}

Il est très courant qu'une fonction appelée dans une implémentation Iterator::next doit immédiatement abandonner avec Some(Err(e)) en cas d'échec. Cette macro fonctionne à l'intérieur d'un corps de fonction normal mais pas à l'intérieur d'un bloc catch car catch ne saisit pas catégoriquement return mais juste la syntaxe spéciale ? .

Bien qu'à la fin, les retours étiquetés rendraient toute l'idée du bloc catch redondante, n'est-ce pas?

Il semble que # 41414 est terminé. Quelqu'un pourrait-il mettre à jour le PO?

Mise à jour: RFC rust-lang / rfcs # 2388 est maintenant fusionné et donc catch { .. } doit être remplacé par try { .. } .
Consultez le problème de suivi juste au-dessus de ce commentaire.

Qu'est-ce que cela signifie pour l'édition 2015, la syntaxe do catch { .. } toujours sur la voie de la stabilisation, ou sera-t-elle abandonnée et prise en charge uniquement via try { .. } dans l'édition 2018+?

@ Nemo157 Ce dernier.

La proposition actuelle comporte-t-elle deux déclarations try ... catch ... ? Si c'est le cas, je ne comprends pas la sémantique.

Quoi qu'il en soit, si cette proposition ne concerne que le désucrement, je suis cool avec elle. ie Est-ce qu'un bloc catch change simplement où l'opérateur ? termine?

Comme toutes les cases à cocher sont cochées dans le premier article, quand irions-nous de l'avant? S'il y a toujours des problèmes non résolus, nous devons ajouter de nouvelles cases à cocher.

Il y a probablement beaucoup de questions non résolues qui n'ont pas été enregistrées.
Par exemple, le comportement ok-wrapping n'est pas réglé au sein de l'équipe lang, la conception du Try n'est pas finalisée, et ainsi de suite. Nous devrions probablement diviser ce problème en plusieurs autres plus ciblés car il a probablement survécu à son utilité.

Hmm ... ça me dérange que ces questions n'aient pas été enregistrées.

@ mark-im Donc, pour clarifier, je pense qu'ils ont été quelque part; mais pas au même endroit; c'est un atm un peu dispersé dans divers RFC et problèmes, etc., donc ce que nous devons faire est de les enregistrer aux bons endroits.

La conception du trait de support est suivie dans https://github.com/rust-lang/rust/issues/42327; il y a là une discussion approfondie sur les faiblesses de l'actuel et une possible nouvelle direction. (Je prévois de faire un pré-RFC pour un changement une fois que 2018 sera un peu réglé.)

Je pense donc qu'il ne reste que try{} ici, et le seul désaccord que je connaisse à ce sujet concerne des choses qui ont été réglées dans la RFC et reconfirmées dans l'un des problèmes mentionnés ci-dessus. Il pourrait cependant être bon d'avoir un problème de suivi distinct.

Je vais ajouter une case à cocher pour la tâche de mise en œuvre en attente dont je sais qu'il reste encore à faire ...

@scottmcm Je sais que @joshtriplett avait des inquiétudes concernant le wrapping OK (noté dans le try RFC) et j'aimerais personnellement limiter break dans la stabilisation initiale de try { .. } donc que vous ne pouvez pas faire loop { try { break } } et autres.

@Centril

pour que vous ne puissiez pas faire loop { try { break } }

Pour le moment, vous ne pouvez pas utiliser break dans un bloc sans boucle, et c'est correct: break ne doit être utilisé que dans les boucles. Pour quitter tôt un bloc try , la méthode standard consiste à écrire Err(e)? . et cela oblige les premières feuilles à se trouver toujours dans le chemin de contrôle «anormal».

Donc ma proposition est que le code que vous avez montré devrait être autorisé, et il devrait casser le loop , pas seulement en laissant le try .

L'avantage immédiat, c'est que lorsque vous voyez break vous savez qu'il va casser une boucle, et vous pouvez toujours le remplacer par un continue . En outre, cela supprime le besoin d'étiqueter le point de rupture lorsque vous utilisez des blocs try dans un loop et que vous souhaitez quitter la boucle.

@Centril Merci d'avoir soulevé ces questions.

En ce qui concerne break , personnellement, je serais bien de dire simplement que try ne se soucie pas de break et qu'il passe par la boucle contenant. Je ne veux tout simplement pas que break interagisse avec try .

En ce qui concerne Ok -wrapping, oui, j'aimerais aborder cela avant de stabiliser try .

@centril Oui, je suis au courant. Mais il est important de se rappeler que c'est re re soulever la question. La RFC a décidé de l'avoir , elle a été implémentée sans elle, mais l'intention d'origine a été reprise _de nouveau_ , et l'implémentation a été modifiée pour suivre la RFC. Ma grande question est donc de savoir si des faits matériels ont changé, d'autant plus que c'est l'un des sujets les plus bruyants que j'ai jamais vu discutés sur RFC + IRLO.

@scottmcm Bien sûr, comme vous le savez, je suis d'accord pour conserver Ok -wrapping;) et je suis d'accord que le problème doit être considéré comme réglé.

Je voulais juste faire un commentaire à ce sujet, je ne sais pas si c'est la bonne chose:

Essentiellement, une situation que j'ai est celle des rappels dans un framework GUI - au lieu de renvoyer un Option ou Result , ils doivent retourner un UpdateScreen , pour dire au framework si l'écran doit être mis à jour ou non. Souvent, je n'ai pas du tout besoin de journalisation (il n'est tout simplement pas pratique de me connecter à chaque erreur mineure) et je renvoie simplement un UpdateScreen::DontRedraw lorsqu'une erreur s'est produite. Cependant, avec l'opérateur actuel ? , je dois écrire ceci tout le temps:

let thing = match fs::read(path) {
    Ok(o) => o,
    Err(_) => return UpdateScreen::DontRedraw,
};

Comme je ne peux pas convertir un Result::Err en un UpdateScreen::DontRedraw via l'opérateur Try, cela devient très fastidieux - souvent j'ai des recherches simples dans les cartes de hachage qui peuvent échouer (ce qui n'est pas une erreur ) - si souvent dans un rappel, j'ai 5 à 10 utilisations de l'opérateur ? . Parce que ce qui précède est très détaillé à écrire, ma solution actuelle est de impl From<Result<T>> for UpdateScreen comme ceci , puis d'utiliser une fonction interne dans le rappel comme ceci:

fn callback(data: &mut State) -> UpdateScreen {
     fn callback_inner(data: &mut State) -> Option<()> {
         let file_contents = fs::read_to_string(data.path).ok()?;
         data.loaded_file = Some(file_contents);
         Some(())
     }

    callback_inner(data).into()
}

Puisque le rappel est utilisé comme un pointeur de fonction, je ne peux pas utiliser un -> impl Into<UpdateScreen> (pour une raison quelconque, renvoyer un impl n'est actuellement pas autorisé pour les pointeurs de fonction). Donc, la seule façon pour moi d'utiliser l'opérateur Try est de faire le tour de la fonction interne. Ce serait bien si je pouvais simplement faire quelque chose comme ça:

impl<T> Try<Result<T>> for UpdateScreen {
    fn try(original: Result<T>) -> Try<T, UpdateScreen> {
        match original {
             Ok(o) => Try::DontReturn(o),
             Err(_) => Try::Return(UpdateScreen::DontRedraw),
        }
    }
}

fn callback(data: &mut State) -> UpdateScreen {
     // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
     let file_contents = fs::read_to_string(data.path)?;
     data.loaded_file = Some(file_contents);
     UpdateScreen::Redraw
}

Je ne sais pas si cela serait possible avec la proposition actuelle et je voulais simplement ajouter mon cas d'utilisation pour examen. Ce serait formidable si un opérateur Try personnalisé pouvait prendre en charge quelque chose comme ça.

ÉDITER:
J'ai fait une erreur.


Ignorer ce message


Cela pourrait-il mieux jouer avec l'inférence de type, cela échoue même dans les cas simples.

fn test_try(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x // : Option<_> // why is this type annotation necessary
    = try { div? + 1 };

    println!("{:?}", x);
}

Si cela est réécrit pour utiliser une fermeture au lieu du bloc try (et dans le processus un emballage automatique lâche), alors nous obtenons

fn test_closure(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x =  (|| (div? + 1).into())();

    println!("{:?}", x);
}

Ce qui ne nécessite pas d'annotation de type, mais cela nécessite que nous enveloppions le résultat.

terrain de jeux

@KrishnaSannasi votre exemple basé sur la fermeture a également un échec d'inférence de type ( terrain de jeu ) car Into ne contraint pas la sortie et vous ne l'utilisez nulle part plus tard.

Cela semble être principalement un problème avec le trait Try plutôt que les blocs try , similaire à Into il ne propage aucune information de type des entrées vers la sortie, donc le type de sortie doit pouvoir être déterminé par son utilisation ultérieure. Il y a un _lot_ de discussion sur https://github.com/rust-lang/rust/issues/42327 à propos du trait, je ne l'ai pas lu, donc je ne suis pas sûr que l'une des propositions puisse résoudre ce problème.

@ Nemo157

Oui, j'ai modifié mon code de dernière minute pour l'utiliser, et je ne l'ai pas testé. Ma faute.

À quelle distance sommes-nous de stabiliser les blocs d'essai? C'est la seule fonctionnalité dont j'ai besoin de nuit: D

@Arignir

Je crois qu'une fois que cela est fait, cela peut être stabilisé.

block try {} catch (ou d'autres identifiants suivants) pour laisser l'espace de conception ouvert pour l'avenir, et indiquer aux utilisateurs comment faire ce qu'ils veulent avec match à la place

N'y a-t-il pas une conception intermédiaire pour autoriser la fonctionnalité maintenant tout en laissant la possibilité de laisser l'espace de conception ouvert pour l'avenir (et donc un éventuel bloc catch )?

Le PR que j'ai fait devrait quand même cocher cette case, CC @nikomatsakis

J'ai essayé de l'utiliser pour la première fois hier, et j'ai été un peu surpris que ceci:

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    }?;
    Ok(x)
}

ne compile pas en raison de

error[E0284]: type annotations required: cannot resolve `<_ as std::ops::Try>::Ok == _`

Je devais plutôt faire

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: Result<(), ()> = try {
        Err(())?
    };
    let x = x?;
    Ok(x)
}

au lieu.

C'était déroutant au début, alors peut-être vaut-il la peine de changer le message d'erreur ou de le mentionner dans --explain ?

Si vous déplacez un peu le point d'interrogation dans votre premier exemple vers

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    };
    Ok(x?)
}

Vous obtenez un meilleur message d'erreur. L'erreur survient parce que Rust ne peut pas décider à quel type résoudre le try { ... } , en raison de sa généralité. Comme il ne peut pas résoudre ce type, il ne peut pas savoir quel est le type <_ as Try>::Ok , c'est pourquoi vous avez l'erreur que vous avez faite. (car l'opérateur ? déballe le type Try et rend le type Try::Ok ). Rust ne peut pas fonctionner seul avec le type Try::Ok , il doit être résolu via le trait Try et le type qui implémente ce trait. (qui est une limitation du fonctionnement actuel de la vérification de type)

Tout pour cette fonctionnalité est implémenté, n'est-ce pas? Dans l'affirmative, combien de temps voulons-nous envisager de nous asseoir dessus avant de nous stabiliser?

Je pensais que la question restait ouverte de savoir si nous voulions cela ou non. En particulier, il y a eu une discussion pour savoir si nous voulons utiliser le langage des exceptions ici (essayer, attraper).

Personnellement, je suis fermement contre le fait d'essayer de donner l'impression que Rust a quelque chose comme des exceptions. Je pense que l'utilisation du mot catch en particulier est une mauvaise idée parce que quiconque vient d'une langue avec des exceptions supposera que cela se déroule, et ce n'est pas le cas. Je m'attendrais à ce que ce soit déroutant et douloureux à enseigner.

En particulier, il y a eu une discussion pour savoir si nous voulons utiliser le langage des exceptions ici (essayer, attraper).

Je pense que https://github.com/rust-lang/rfcs/pull/2388 a définitivement réglé si try tant que nom est acceptable. Ce n'est pas une question ouverte. Mais la définition du trait Try ainsi que le Ok -wrapping semblent l'être.

Ok -wrapping a déjà été décidé dans la RFC d'origine, puis supprimé lors de l'implémentation, et finalement ré-ajouté plus tard. Je ne vois pas en quoi c'est une question ouverte.

@rpjohnst Eh bien, c'est pour moi . Voir https://github.com/rust-lang/rust/issues/31436#issuecomment -427096703, https://github.com/rust-lang/rust/issues/31436#issuecomment -427252202 et https: // github.com/rust-lang/rust/issues/31436#issuecomment -437129491. Quoi qu'il en soit ... le point de mon commentaire était que try tant que "langage des exceptions" est une question réglée.

Woah, quand est-ce arrivé? La dernière chose dont je me souviens, ce sont les discussions sur les éléments internes. Je suis aussi très contre Ok-wrapping :(

Eww. Je ne peux pas croire que c'est arrivé. Ok -wrapping est si horrible (cela brise l'intuition très sensible que toutes les expressions de retour dans une fonction devraient être du type de retour de la fonction). Alors oui, définitivement avec @ mark-im là-dessus. Le désaccord de Josh est-il suffisant pour garder cette question ouverte et en discuter davantage? Je serais heureux de lui apporter son soutien dans la lutte contre cela, non pas que cela signifie quelque chose en tant que non-membre de l'équipe.

Ok -wrapping tel qu'accepté dans la RFC 243 (littéralement celui qui a défini l'opérateur ? , si vous vous demandez quand cela s'est produit) ne change rien aux types d'expressions de retour de fonction. Voici comment RFC 243 l'a défini: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#catch -expressions

Cette RFC introduit également une forme d'expression catch {..} , qui sert à "étendre" l'opérateur ? . L'opérateur catch exécute son bloc associé. Si aucune exception n'est levée, alors le résultat est Ok(v)v est la valeur du bloc. Sinon, si une exception est levée, le résultat est Err(e) .

Notez que catch { foo()? } équivaut essentiellement à foo() .

Autrement dit, il prend un bloc de type T et l'encapsule inconditionnellement pour produire une valeur de type Result<T, _> . Toute instruction return dans le bloc n'est pas du tout affectée; si le bloc est l'expression de queue d'une fonction, la fonction doit renvoyer un Result<T, _> .

Il a été implémenté de cette façon tous les soirs depuis des lustres: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=88379a1607d952d4eae1d06394b50959. Cela a été fait après de nombreuses discussions par l'équipe lang dans, et lié à partir de ce fil: rust-lang / rust # 41414 (et ceci est également lié en haut de ce numéro).

Le 28 mai 2019 17:48:27 PDT, Alexander Regueiro [email protected] a écrit:

Eww. Je ne peux pas croire que c'est arrivé. Ok emballage est si horrible (il
rompt l'intuition très sensible que toutes les expressions retournent dans un
doit être du type de retour de la fonction). Alors oui, définitivement
avec @ mark-im à ce sujet. Le désaccord de Josh est-il suffisant pour garder cela
question ouverte, et obtenir plus de discussion à ce sujet? Je lui apporterais volontiers son soutien
en combattant cela, non pas que cela signifie quelque chose en tant que non-membre de l'équipe.

Je vous remercie. Je ne suis pas seulement en désaccord avec cela uniquement pour moi-même; Je représente également les nombreuses personnes que j'ai vues exprimer la même position que moi.

@joshtriplett @ mark-im @alexreg

L'un de vous peut-il expliquer pourquoi vous trouvez que l'emballage de Ok est si désagréable ou fournir un lien vers un endroit qui a déjà été expliqué? Je suis allé chercher, mais dans une vue rapide, je n'ai rien vu. Je n'ai pas de cheval là-dedans (je n'ai littéralement commenté cela que parce que j'ai vu toutes les cases cocher et aucune discussion pendant un mois), mais maintenant que j'ai donné un coup de pied à ce nid de frelons, je veux mieux comprendre les arguments.

Le mar 28 mai 2019 à 15:40:47 -0700, Russell Johnston a écrit:

Ok -wrapping a déjà été décidé dans la RFC d'origine, puis supprimé lors de l'implémentation, et finalement ré-ajouté plus tard. Je ne vois pas en quoi c'est une question ouverte.

Je pense que vous avez en partie répondu à votre propre question. Je ne pense pas que tout le monde
impliqué dans la discussion RFC originale était sur la même page; try était
absolument quelque chose que beaucoup de gens voulaient, mais il n'y avait pas de consensus
pour Ok-wrapping.

Le mar 28 mai 2019 à 15:44:46 -0700, Mazdak Farrokhzad a écrit:

Quoi qu'il en soit ... le point de mon commentaire était que try tant que "langage des exceptions" est une question réglée.

Pour clarifier, je ne trouve pas la métaphore des "exceptions" attrayante,
et la plupart des tentatives de choses comme try-fn et Ok-wrapping semblent
essayez de rendre le langage faux avec un mécanisme semblable à une exception.
Mais try lui-même, comme moyen d'attraper ? à autre chose que le
limite de fonction, a du sens en tant que construction de flux de contrôle.

Le mar 28 mai 2019 à 23:37:33 -0700, Gabriel Smith a écrit:

L'un de vous peut-il expliquer pourquoi vous trouvez que Ok emballage est si désagréable

Comme l'une des rares raisons:

Le mar 28 mai 2019 à 17:48:27 -0700, Alexander Regueiro a écrit:

cela brise l'intuition très sensible que toutes les expressions de retour dans une fonction doivent être du type de retour de la fonction

Cela rompt les diverses approches utilisées par les gens pour le raisonnement dirigé
sur les fonctions et la structure du code.

J'ai certainement mes propres pensées ici, mais pourrions-nous s'il vous plaît ne pas rouvrir ce sujet maintenant? Nous venons d'avoir une discussion controversée de plus de 500 messages sur la syntaxe, alors j'aimerais éviter les mines terrestres pendant un certain temps.

Si cela est bloqué dans l'équipe de langue qui en discute, la case à cocher "Résoudre si les blocs catch doivent" encapsuler "la valeur de résultat (# 41414)" est décochée à nouveau (peut-être avec un commentaire indiquant qu'elle est bloquée sur l'équipe de langue) afin que les personnes qui regardent ce problème de suivi connaît-il le statut?

Excusez-moi, je n'essaye pas de rouvrir quoi que ce soit - répétez simplement ce qui est marqué comme décidé dans le problème de suivi et quand + comment cela s'est-il passé.

@rpjohnst Merci pour l'info!

@yodaldevoid Josh a assez bien résumé mes pensées.

Je suis légèrement moins opposé au ok-wrapping confiné à un bloc (au lieu d'affecter le type d'une fonction), mais je pense que cela crée toujours un mauvais précédent: comme Josh l'a dit: «Je ne trouve pas la métaphore des exceptions attrayante»

@joshtriplett a essentiellement résumé mon point de vue aussi: les problèmes sont la pertinence de la métaphore "exception" (on peut dire que panique + catch_unwind est beaucoup plus analageuse) et le raisonnement basé sur le type. Je suis en effet d'accord avec les blocs try comme mécanisme de portée et de flux de contrôle aussi, mais pas les points les plus radicaux.

D'accord, assez juste, n'ayons pas tout le débat ici ... peut-être simplement décocher la case comme suggéré, et le remettre au débat lang-team (à leur propre temps), en utilisant une partie de la justification mentionnée dans ce fil? Tant que la stabilisation n'est pas précipitée, cela semble raisonnable, je suppose.

Une syntaxe pour les annotations de type a-t-elle été convenue? J'espérais quelques try { foo()?; bar()?; }.with_context(|_| failure::err_msg("foon' n' barn'")?; , qui ne sont même pas intéressés à distance par la compilation: error[E0282]: type annotations needed .

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4e60d44a8f960cf03307a809e1a3b5f2

J'ai lu les commentaires il y a quelque temps (et charger à nouveau 300 commentaires sur github est bien trop fastidieux), mais je me souviens que la plupart (sinon tous) des exemples concernant le débat autour de Try::Ok wrapping utilisé Ok dans l'exemple. Considérant que Option implémente également Try , j'aimerais savoir comment cela influe sur la position de l'équipe de quel côté du débat se trouver.

Chaque fois que j'utilise Rust, je continue à penser "mec, j'aimerais vraiment pouvoir utiliser un bloc try ici", mais environ 30% du temps c'est parce que j'aimerais vraiment pouvoir utiliser try pour Option s (comme J'avais l'habitude d'utiliser Scala, qui utilisait la syntaxe for pour s'appliquer aux monades en général, mais est très similaire à try ici).

Juste aujourd'hui, j'utilisais la caisse json et elle expose les méthodes as_* qui renvoient des options.

En utilisant les deux syntaxes, mon exemple aurait été:

match s {
  "^=" => |a, b| try { a.as_str()?.starts_with(b.as_str()?) }.unwrap_or(false),
  "$=" => |a, b| try { Some(a.as_str()?.ends_with(b.as_str()?)) }.unwrap_or(false),
  // original
  "$=" => |a, b| {
    a.as_str()
      .and_then(|a| b.as_str().map(|b| (a, b)))
      .map(|(a, b)| a.starts_with(b))
      .unwrap_or(false)
    },
}

Je pense que, contextuellement, que le type de retour soit ou non Option ou Result est assez clair, et de plus, cela n'a pas vraiment d'importance (en ce qui concerne la compréhension du code). En toute transparence, le sens est clair: "Je dois vérifier si ces deux choses sont valides et faire une opération dessus." Si je devais en choisir un, j'irais avec le premier, car je ne pense pas qu'il y ait de perte de compréhension quand on considère que cette fonction est intégrée dans un contexte plus large, comme le fera try toujours être.

Quand j'ai commencé à regarder ce fil, j'étais contre Ok wrapping parce que je pensais qu'il valait mieux être explicite, mais depuis, j'ai commencé à prêter attention aux moments où j'ai dit "Je souhaite que je pourrait utiliser un bloc try ici »et j'en suis venu à la conclusion que Ok -wrapping est bon.

Je pensais à l'origine que le wrapping non Ok serait mieux dans le cas où votre dernière instruction est une fonction qui renvoie le type qui implémente Try , mais la différence de syntaxe serait

try {
  fallible_fn()
}

try {
  fallible_fn()?
}

Et dans ce cas, je pense à nouveau que Ok -wrapping est meilleur car il indique clairement que fallible_fn est une fonction de retour Try , donc c'est en fait plus explicite.

Je veux savoir ce que l'opposition en pense et, comme je n'en vois pas beaucoup d'autres dans ce fil, @joshtriplett.

EDIT: Je devrais mentionner que je ne regardais cela que d'un point de vue ergonomie / compréhension de la lecture. Je n'ai aucune idée si l'un a plus de mérites techniques que l'autre en termes de mise en œuvre, comme une inférence plus facile.

Je voulais aussi donner à try une chance pour une analyse imbriquée Option :

#![feature(try_blocks)]

struct Config {
    log: Option<LogConfig>,
}

struct LogConfig {
    level: Option<String>,
}

fn example(config: &Config) {
    let x: &str = try { config.log?.level? }.unwrap_or("foo");
}

Cela échoue avec

error[E0282]: type annotations needed
  --> src/lib.rs:12:19
   |
12 |     let x: &str = try { config.log?.level? }.unwrap_or("foo");
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: type must be known at this point

Le plus proche que j'ai eu était

fn example(config: &Config) {
    let x: Option<&str> = try { &**config.log.as_ref()?.level.as_ref()? };
    let x = x.unwrap_or("foo");
}

Le as_ref est assez malheureux. Je sais que Option::deref aidera certains ici, mais pas assez. Cela donne l'impression que l'ergonomie (ou l'idée connexe) devrait entrer en jeu.

Les multiples lignes sont également malheureuses.

try pourrait-il utiliser une inférence de secours de Result comme des littéraux entiers? Cela permettrait- il à la première tentative de Result<&str, NoneError> ? Quels problèmes restants y aurait-il - trouver probablement un type d'erreur courant pour ? s à convertir? (Ai-je manqué la discussion à ce sujet quelque part?)

@shepmaster Je suis d'accord avec l'inférence de type. Curieusement, cependant, j'ai essayé votre code exact avec une implémentation quelque peu naïve try_ et cela fonctionne bien: https://github.com/norcalli/koption_macros/blob/4362fba8fa9b6c62fdaef4df30060234381141e7/src/lib.rs#L23

    let x = try_! { config.log?.level? }.unwrap_or("foo".to_owned());
    assert_eq!(x, "debug");

fonctionne très bien.

une implémentation try_ quelque peu naïve

Oui, mais votre appel de macro renvoie un String , pas un &str , nécessitant la propriété. Vous n'affichez pas le code environnant, mais cela échouera car nous ne sommes pas propriétaires du Config :

fn example(config: &Config) {
    let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
}
error[E0507]: cannot move out of captured variable in an `Fn` closure
  --> src/lib.rs:20:21
   |
19 | fn example(config: &Config) {
   |            ------ captured outer variable
20 |     let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
   |                     ^^^^^^^^^^ cannot move out of captured variable in an `Fn` closure

Il alloue également inconditionnellement un String ; J'ai utilisé unwrap_or_else dans cet exemple pour éviter cette inefficacité.

Il est dommage que cette fonctionnalité n'ait pas été stabilisée avant les blocs async/await . OMI, il aurait été plus cohérent d'avoir

let fut = async try {
    fut1().await?;
    fut2().await?;
    Ok(())
};

au lieu de lui permettre de fonctionner sans try . Mais je suppose que ce navire a navigué depuis longtemps.

Re: auto-wrapping, je ne pense pas qu'il soit possible d'être cohérent avec les blocs async maintenant. async blocs Future . Mais cela est vrai même avec des retours anticipés, ce qui ne serait pas possible avec des blocs try .

Cela pourrait devenir doublement déroutant si jamais nous avons un bloc hypothétique async try . Cela devrait

Je ne pense pas que nous ayons perdu des chances de cohérence dans le sens de l’emballage automatique. Les blocs et les fonctions async peuvent utiliser ? , et les deux doivent faire leur propre emballage manuel Ok -wrapping, pour les retours anticipés et finaux.

Un bloc try , d'autre part, pourrait utiliser ? avec Ok emballage automatique y compris pour les "retours anticipés" en supposant une fonction de retour anticipé - peut - être une rupture d' fonction try hypothétique pourrait facilement effectuer Ok emballage automatique

Un bloc hypothétique async try pourrait simplement combiner la fonctionnalité des deux-auto- Ok -wrap, puis auto- Future -wrap. (L'inverse est impossible à implémenter, et serait sans doute écrit try async toute façon.)

L'incohérence que je vois est que nous avons confondu les blocs async avec des fonctions. (Cela s'est produit à la dernière minute contrairement à la RFC, rien de moins.) Cela signifie que return en async blocs sort du bloc, tandis que return en try blocks quitte la fonction contenant. Cependant, ceux-ci ont au moins un sens isolément, et les blocs async sans retour anticipé ou valeur de rupture d'étiquette seraient beaucoup plus difficiles à utiliser.

Y a-t-il quelque chose qui empêche la stabilisation de cela, ou tout simplement personne n'a encore pris le temps de le faire? Je suis intéressé par la création des PR nécessaires autrement 🙂

Le 18 novembre 2019 02:03:36 PST, Kampfkarren [email protected] a écrit:

Tout ce qui arrête la stabilisation de cela, ou tout simplement personne n'a pris le
le temps de le faire encore? Je suis intéressé par la création des PR nécessaires
sinon 🙂>
>
->
Vous recevez ceci parce que vous avez été mentionné.>
Répondez directement à cet e-mail ou affichez-le sur GitHub:>
https://github.com/rust-lang/rust/issues/31436#issuecomment -554944079

Oui, le bloqueur de la stabilisation travaille par le biais de la décision sur le wrapping Ok. Cela ne devrait pas être stabilisé tant que nous n’aurons pas obtenu un consensus sur la manière dont il devrait se comporter.

Je suis personnellement contre Ok-wrapping, mais je me demande combien il serait difficile d'ajouter après le fait. Le no-ok-wrapping forward est-il compatible avec le ok-wrapping?

Je peux imaginer des cas délicats tels que l'ambiguïté de Result<Result<T,E>> , mais dans des cas aussi ambigus, nous pourrions simplement revenir au no-wrapping. L'utilisateur pourrait alors explicitement Ok-wrap pour lever l'ambiguïté. Cela ne semble pas trop mal, car je ne m'attends pas à ce que cette sorte d'ambiguïté revienne trop souvent ...

Il ne devrait y avoir aucune ambiguïté, car ce n'est pas Ok -coercion mais Ok -wrapping. try { ...; x } donnerait Ok(x) tout aussi clairement que Ok({ ...; x }) .

@joshtriplett Est-ce que cela n'est pas résolu? Le problème de suivi a resolve whether catch blocks should "wrap" result value comme coché, en citant https://github.com/rust-lang/rust/issues/41414

@rpjohnst Désolé, j'aurais dû être plus clair. Ce que je veux dire, c'est que si nous stabilisons try maintenant sans Ok-wrapping, je crois qu'il pourrait être ajouté plus tard de manière rétrocompatible.

Autrement dit, je pense que la plupart des gens conviennent que nous devrions avoir des blocs try , mais tout le monde n'est pas d'accord sur catch ou ok-wrapping. Mais je ne pense pas que ces discussions doivent bloquer try ...

@Kampfkarren Oui. La conversation ci-dessus détaille la progression de cette affaire. Il a été coché prématurément sans consulter tout le monde. @joshtriplett en particulier avait des préoccupations, que plusieurs autres (dont moi-même) partageaient.

@ mark-im Comment voyez-vous exactement l'ajout d'Ok-wrapping à l'avenir? J'essaie de comprendre comment cela pourrait être fait, et je ne peux pas vraiment le voir.

Je vais donc commencer par dire que je ne sais pas si c'est une bonne idée ou non ...

Nous stabiliserions le bloc try sans ok-wrapping. Par exemple:

let x: Result<usize, E> = try { 3 }; // Error: expected Result, found usize
let x: Result<usize, E> = try { Ok(3) }; // Ok (no pun intended)

Plus tard, supposons que nous soyons parvenus à un consensus sur le fait que nous devrions avoir Ok-wrapping, alors nous pourrions autoriser certains cas qui ne fonctionnaient pas auparavant:

let x: Result<usize, E> = try { 3 }; // Ok
let x: Result<usize, E> = try { Ok(3) }; // Also Ok for backwards compat
let x: Result<Result<usize, E1>, E2> = try { Ok(3) }; // Ok(Ok(3))
let x: Result<Result<usize, E1>, E2> = try { Ok(Ok(3)) }; // Ok(Ok(3))

La question est de savoir si cela peut rendre quelque chose d'ambigu qui ne l'était pas auparavant. Par exemple:

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Cependant, peut-être que Ok-wrapping doit déjà s'attaquer à ce problème?

Quoi qu'il en soit, mon intuition est que ces cas étranges ne se présentent pas souvent, donc cela n'a peut-être pas tant d'importance.

Qu'en est-il d'utiliser Ok-wrapping sauf lorsque le type renvoyé est Result ou Option ? Cela permettrait un code plus simple dans la plupart des cas, mais permettrait de spécifier la valeur exacte si nécessaire.

// Ok-wrapped
let v: Result<i32, _> = try { 1 };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Ok(1) };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Err("error") };

// Ok-wrapped
let v: Option<i32> = try { 1 };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { Some(1) };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { None };

Ajouter Ok -coercion ou une sorte de syntaxe dépendant de Ok -wrapping (ce qui devrait arriver pour prendre en charge la stabilisation sans elle et l'introduire plus tard) serait très mauvais pour la lisibilité, et a été largement contesté à plusieurs reprises sur i.rl.o (généralement par des gens qui ne comprennent pas le simple Ok -wrapping qui est implémenté).

Personnellement, je suis fortement en faveur du Ok -wrapping tel qu'implémenté, mais je serais encore plus fortement contre toute forme de coercition ou de dépendance syntaxique qui rendrait la compréhension des situations qui vont être difficiles Ok(...) est partout sans avoir à essayer de déterminer si cela a forcé ou non).

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Cependant, peut-être que Ok-wrapping doit déjà s'attaquer à ce problème?

Non, c'est sans ambiguïté Ok(Err(3)) , Ok -wrapping est indépendant de la syntaxe ou des types, il enveloppe juste quelle que soit la sortie du bloc dans la variante Try::Ok .

@ mark-im Je ne pense pas que nous puissions raisonnablement passer de l'un à l'autre après la stabilisation. Aussi mauvais que je considère un emballage Ok, un emballage Ok incohérent qui essaie de deviner si vous le voulez ou non serait encore pire.

Dans ma base de code, je traite beaucoup de valeurs optionnelles, j'ai donc introduit mon propre bloc try comme une macro il y a longtemps. Et à l'époque où je l'ai présenté, j'avais différentes variantes avec et sans emballage Ok, et la version Ok-wrapping s'est avérée tellement plus ergonomique, que c'est la seule macro que j'ai fini par utiliser.

J'ai une tonne de valeurs optionnelles avec lesquelles je dois travailler, et elles sont principalement numériques, donc j'ai des tonnes de situations comme celle-ci:

let c = try { 2 * a? + b? };

Sans Ok-wrapping, ce serait beaucoup moins ergonomique au point que je resterais probablement sur ma propre macro que d'utiliser les vrais blocs d'essai.

Compte tenu de l'histoire vénérable de ce problème de suivi, de sa confusion originale et regrettable avec l'opérateur ? , et du barrage routier sur le problème de Ok -wrapping, je suggérerais de fermer ce problème purement et simplement et d'envoyer try retour au début du processus RFC, où cette discussion peut obtenir la visibilité qu'elle mérite et (espérons-le) aboutir à une sorte de conclusion.

Sans Ok-wrapping, ce serait beaucoup moins ergonomique

Pourriez-vous s'il vous plaît préciser quels éléments exactement non ergonomiques cela introduirait?

Sans Ok-wrapping, votre exemple ressemblerait à ceci:

let c = try { Ok(2 * a? + b?) };

ce qui est assez bon à mon avis.

Je veux dire, avec un petit exemple comme celui-ci, cela peut sembler excessif, mais plus le bloc try contient de code, moins l'impact de ce wrapper Ok(...) est important.

Suite au commentaire de Ok -wrapping si le bloc try ne le fait pas (et quelqu'un va sûrement créer une caisse standard pour cette petite macro), mais l'inverse n'est pas le cas.

Cette macro n'est pas possible tant que le trait Try est instable.

Pourquoi? Quoi qu'il en soit, quand il se stabilisera (hypothétiquement pas trop loin dans le futur), ce sera tout à fait possible.

Les blocs @ Nemo157 try sont également activés que tous les soirs en ce moment, et ils ne seront probablement pas stabilisés dans le cas improbable où nous déciderions de retirer Try . Cela signifie qu'ils ne seront probablement pas stabilisés avant Try . Donc, dire que la macro n'est pas possible n'a pas de sens.

@KrishnaSannasi Je suis curieux de savoir pourquoi Try pourrait être arraché?

@ mark-im Je ne pense pas que ce sera le cas, j'explique simplement pourquoi s'inquiéter du fait que Try soit allumé la nuit pour essayer des blocs n'est pas une préoccupation réaliste. J'ai hâte de recevoir Try sur stable.

Étant donné que ? a déjà été stabilisé, et les blocs try ont une conception claire englobant à la fois Result et Option de la même manière que ? fait, il n'y a aucune raison que je puisse voir pour bloquer leur stabilisation lors de la stabilisation de Try . Je ne l'ai pas surveillé de près, mais mes impressions étaient qu'il y avait beaucoup moins de consensus sur la conception de Try que pour try blocs, donc j'ai pu voir try bloque la stabilisation des années avant le trait Try (comme cela s'est produit pour ? ). Et même si le trait Try est abandonné, je ne vois aucune raison qui devrait bloquer les blocs try stabilisés car ils ne fonctionnent qu'avec Result et Option comme ? serait alors.

(Pour _pourquoi_ vous ne pouviez pas écrire cette macro try blocs Try instable, la macro aurait été étendue à try { Try::from_ok($expr) } ; vous pourriez créer des macros par type pour seulement Result et Option , mais IMO qui ne répondrait pas au point "très facile à [...] émuler").

Étant donné que ? est déjà stable dans une casse spéciale même si le trait Try ne peut pas être utilisé sur stable, je ne vois pas pourquoi Try être instable bloquerait les blocs try. implémenté sur stable, car si le trait Try est supprimé, nous avons toujours Option et Result prenant en charge ? sur stable, juste sans l'ergonomie.

Je suggérerais le concept suivant pour essayer la capture sémantique ...

Considérez le code suivant:

union SomeFunctionMultipleError {
    err0: Error1,
    err1: Error2,
}

struct SomeFunctionFnError {
    index: u32,
    errors: SomeFunctionMultipleError,
}

fn some_function() -> Result<i32, SomeFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(SomeFunctionFnError{ index: 0, errors: SomeFunctionMultipleError {err1: Error2 {id0: 0, id1: 0, id3: 0}}})
    }
}

union OtherFunctionMultipleError {
    err0: Error1,
    err1: Error2,
    err2: Error3,
}

struct OtherFunctionFnError {
    id: u32,
    errors: OtherFunctionMultipleError,
}

fn other_function() -> Result<i32, OtherFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(OtherFunctionFnError {id: 0, errors: OtherFunctionMultipleError {err0: Error1 {id0: 0, id1: 0}}})
    }
}

C'est le code qui pourrait être généré par des exceptions Zero-Overhead dans Rust avec la fonction de syntaxe suivante:

fn some_function() -> i32 throws Error1, Error2 {
    if 0 == 0 {
        2
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function() -> i32 throws Error1, Error2, Error3 {
    if 0 == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

ou même ces erreurs pourraient être déduites implicitement par le compilateur:

fn some_function(i: i32) -> i32 throws { // Implicitly throws Error1, Error2
    if i == 0 {
        2
    } else if i == 1 {
        Error1 {id0: 0, id1: 0, id3: 0}.throw
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function(i: i32) -> i32 throws { // Implicitly throws Error1
    if i == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

Ce rien d'autre sucre syntaxique !! Le comportement est le même !!

Salut à tous,

Quelqu'un a-t-il vu ma proposition concernant les exceptions à zéro frais généraux ci-dessus?

@redradist Un des principaux points du bloc try est que nous pourrions l'utiliser à l' intérieur d'une fonction , sans avoir besoin de créer une fonction pour chaque bloc. Votre proposition n'a aucun rapport avec ce que je vois.

Aujourd'hui encore, j'ai ressenti le besoin try blocs ? . Je voulais ajouter du contexte aux erreurs, mais le faire pour chaque ? aurait eu besoin d'une énorme quantité de passe-partout. Attraper les erreurs avec try et ajouter le contexte à un seul endroit aurait empêché cela.

Btw. encapsuler les opérations dans une fonction interne aurait été difficile dans ce cas, car le contexte n'a pas de durée de vie évidente, et la division des éléments en plusieurs fonctions interrompt NLL.

Je voudrais mentionner que dans l'implémentation actuelle, un bloc try n'est pas une expression. Pensez que c'est un oubli.

Je voudrais mentionner que dans l'implémentation actuelle, un bloc try n'est pas une expression. Pensez que c'est un oubli.

Pourriez-vous publier le code qui ne fonctionne pas pour vous? Je peux l'utiliser dans un contexte d'expression ici: ( Rust Playground )

#![feature(try_blocks)]

fn main() {
    let s: Result<(), ()> = try { () };
}

Bien sûr, la voici .

Et en voici un autre qui montre que l'inférence de type sur les blocs try n'est pas encore terminée. Ce qui est particulièrement ennuyeux car les attributions de type ne sont pas prises en charge dans les blocs if let .

@ Nokel81 le problème avec votre exemple précédent est que l'expression dans if let $pat = $expr n'est pas un contexte d'expression régulière, mais plutôt un contexte spécial "sans parenthèses". Pour un exemple de la façon dont cela fonctionne avec les expressions struct, consultez cet exemple où il est syntaxiquement clair qu'il y a une expression struct là, et cet exemple où ce n'est pas. Donc, l'erreur n'est pas que try n'est pas une expression, mais que l'erreur est fausse, et devrait dire " try expression n'est pas autorisée ici; essayez de l'entourer de parenthèses" (et le avertissement incorrect concernant les parenthèses inutiles supprimées).

Votre dernier exemple est en fait ambigu pour l'inférence de type. Le type de e est _: From<usize> dans ce cas, ce qui n'est pas assez d'informations pour lui donner un type concret. Vous devrez l' utiliser d'une manière ou d'une autre pour lui donner un type concret pour permettre à l'inférence de type de réussir. Ce n'est pas un problème spécifique à try ; c'est ainsi que fonctionne l'inférence de type dans Rust.

Maintenant, si vous essayez immédiatement de faire correspondre un Ok et de supprimer le cas Err , vous avez un cas pour un message d'erreur sous-optimal sans véritable moyen simple de le résoudre .

Ah merci beaucoup pour l'explication approfondie. Je suppose que je ne comprends toujours pas pourquoi le dernier est ambigu pour l'inférence de type. Pourquoi le type de l'expression n'est-il pas Result<isize, usize> ?

L'opérateur ? peut effectuer des conversions de type entre différents types d'erreur en utilisant le trait From . Il se développe à peu près au code de rouille suivant (en ignorant le trait Try ):

match expr {
    Ok(v) => v,
    Err(e) => return From::from(e),
}

L'appel From utilise le type de l'expression finale renvoyée pour déterminer les conversions de type d'erreur à effectuer, et n'utilisera pas automatiquement le type de la valeur transmise par défaut.

Toutes mes excuses si cela a déjà été abordé, mais il me semble étrange que:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result = try { // no type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

ne parvient pas à compiler avec:

error[E0282]: type annotations needed

mais:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result : Result<_, _> = try { // partial type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

est correct.

Si cela posait un problème parce que les arguments de type de Result ne pouvaient pas être déduits, je comprendrais, mais, comme indiqué ci-dessus, ce n'est pas le cas et rustc est capable d'effectuer une inférence une fois qu'on lui dit que le résultat de une expression try est une sorte de Result , qu'elle devrait pouvoir déduire de core::ops::Try::into_result .

Pensées?

@nwsharp c'est parce que try / ? est générique sur les types Try . Si vous aviez un autre type qui était impl Try<Ok=_, Error=()> , le bloc try pourrait évaluer ce type ainsi que Result . Désugare, votre exemple est à peu près

#![feature(try_trait, label_break_value)]

use std::ops::Try;

fn main() -> Result<(), ()> {
    let result /*: Result<_, _>*/ = 'block: {
        match Try::into_result(Err(())) {
            Ok(ok) => Try::from_ok(ok),
            Err(err) => {
                break 'block Try::from_error(From::from(err));
            }
        }
    };
    result.map_err(|err| err)
}

@ CAD97 Merci pour l'explication.

Cela dit, je ne m'attendais pas à ce que try soit effectivement capable de provoquer une sorte de conversion entre différents impls Try .

Je m'attendrais à un dé-surgaring où le même Try impl est sélectionné pour into_result , from_ok et from_error .

À mon avis, la perte ergonomique de l'impossibilité d'effectuer l'inférence de type (d'autant plus qu'aucune alternative impl Try n'existe même) ne l'emporte sur l'avantage de permettre cette conversion.

Nous pourrions permettre l'inférence en supprimant l'ambiguïté et conserver la possibilité d'accepter la conversion via quelque chose comme:

try { ... }.into()

Avec la couverture correspondante impl:

impl<T: Try, E: Into<T::Err>> From<Result<T::Ok, E>> for T {
    fn from(result: Result<T::Ok, E>) -> Self {
        match result {
            Ok(ok) => T::from_ok(ok),
            Err(err) => T::from_err(err.into()),
        }
    }
}

(Ce qui honnêtement, je suppose, a tout de même du sens, bien que je doute personnellement de la conversion automatique des types d'erreur ici. Si cela est souhaité, l'utilisateur devrait .map_err() sur le Result .)

En général, je pense que ce désucrement est "trop ​​intelligent". Il cache trop et sa sémantique actuelle risque de dérouter les gens. (Surtout compte tenu du fait que l'implémentation actuelle demande des annotations de type sur quelque chose qui ne les prend pas directement en charge!)

Ou, aller encore plus loin avec la couverture impl, je suppose.

impl <T: Try, U: Try> From<U> for T 
    where U::Ok : Into<T::Ok>, U::Err : Into<T::Err>
{
    fn from(other: U) -> Self {
        match other.into_result() {
            Ok(ok) => Self::from_ok(ok.into()),
            Err(err) => Self::from_err(err.into()),
        }
    }
}

Ou peu importe...

Cela dit, je ne m'attendais pas à ce que try soit effectivement capable de provoquer une sorte de conversion entre différents impls Try .

Je m'attendrais à un dé-surgaring où le même Try impl est sélectionné pour into_result , from_ok et from_error .

À mon avis, la perte ergonomique de l'impossibilité d'effectuer l'inférence de type (d'autant plus qu'aucune alternative impl Try n'existe même) ne l'emporte sur l'avantage d'autoriser cette conversion.

Il existe quatre types stables Try : Option<T> , Result<T, E> , Poll<Result<T, E>> et Poll<Option<Result<T, E>> .

NoneError est instable, donc Option<T> est bloqué en essayant dans Option<T> tandis que NoneError est instable. (Notez cependant que la documentation appelle explicitement From<NoneError> comme "activer option? à votre type d'erreur.")

Cependant, les impls Poll définissent leur type d'erreur sur E . Pour cette raison, le "morphing de type" de Try est stable, car vous pouvez ? a Poll<Result<T, E>> dans un -> Result<_, E> pour obtenir un Poll<T> et retourne tôt le cas E .

En fait, cela alimente un petit assistant "mignon" :

fn lift_err<T, E>(x: Poll<Result<T, E>>) -> Result<Poll<T>, E> { Ok(x?) }

@ CAD97 Merci de m'avoir fait plaisir. Ce sera une chose délicate à enseigner aux nouveaux arrivants et nécessitera un peu d'amour en termes de messages d'erreur.

A-t-on pensé à permettre la spécification du impl Try souhaité pour atténuer le comportement non intuitif ici?

Par exemple, faire du vélo un peu, try<T> { ... } . Ou y a-t-il encore quelque chose sur quoi trébucher, même cela?

Pour peut-être ajouter un peu plus de couleur ici, le fait que try { } sur un tas de Result ne produise pas "seulement" un Result est inattendu et me rend triste . Je comprends pourquoi , mais je n'aime pas ça.

Oui, il y a eu une discussion sur la combinaison de "l'attribution de type généralisée" (il y a votre terme à rechercher) et try . Je pense que, la dernière fois que j'ai entendu, try: Result<_, _> { .. } était destiné à fonctionner éventuellement.

Mais je suis d'accord avec vous: les blocs try méritent des diagnostics ciblés pour s'assurer que leur type de sortie est spécifié.

Veuillez consulter ce problème séparé pour une question précise et étroite afin de résoudre le consensus de l'équipe linguistique sur la question de Ok -wrapping.

Veuillez lire le commentaire d'ouverture de ce fil avant de commenter, et en particulier, veuillez noter que ce fil ne concerne que cette seule question, pas tout autre problème lié à try ou ? ou Try .

Je ne vois pas pourquoi le bloc try est nécessaire. Cette syntaxe

fn main() -> Result<(), ()> {
    try {
        if foo() {
            Err(())?
        }
        ()
    }
}

peut être remplacé par ceci:

fn main() -> Result<(), ()> {
    Ok({
        if foo() {
            Err(())?
        }
        ()
    })
}

Ils utilisent tous les deux le même nombre de caractères, mais le second est déjà stable.

Lors de l'affectation du résultat à une variable, cela peut indiquer qu'une fonction d'assistance doit être créée pour renvoyer le résultat. Si ce n'est pas possible, une fermeture peut être utilisée à la place.

Les blocs @dylni try sont particulièrement utiles lorsqu'ils ne contiennent pas le corps entier d'une fonction. L'opérateur ? en cas d'erreur fait aller le contrôle de flux à la fin du bloc le plus interne try , sans revenir de la fonction.

`` rouille
fn main () / * aucun résultat ici * / {
laissez résultat = essayez {
toto ()?. bar () ?. baz ()?
};
résultat du match {
//…
}
}

@SimonSapin Cela

fn main() /* no result here */ {
    let result  = foo()
        .and_then(|x| x.bar())
        .and_then(|x| x.baz());
    match result {
        // …
    }
}

C'est plus verbeux, mais je pense qu'une solution plus simple serait une syntaxe de fermeture de méthode:

fn main() /* no result here */ {
    let result  = foo()
        .and_then(::bar)
        .and_then(::baz);
    match result {
        // …
    }
}

Le type est également correctement déduit avec and_then , où vous avez besoin d'annotations de type pour try . J'ai eu cela assez rarement pour que je ne pense pas qu'une syntaxe terser vaudrait le tort à la lisibilité.

La RFC acceptée a un raisonnement supplémentaire: https://rust-lang.github.io/rfcs/0243-trait-based-exception-handling.html

Quoi qu'il en soit, les arguments en faveur des constructions de langage pour le flux de contrôle avec l'opérateur ? (et .await ) sur des méthodes de chaînage comme and_then ont déjà été largement discutés.

Quoi qu'il en soit, les arguments en faveur des constructions de langage pour le flux de contrôle avec l'opérateur ? (et .await ) sur des méthodes de chaînage comme and_then ont déjà été largement discutés.

@SimonSapin Merci. Ceci et la relecture de la RFC m'ont convaincu que cela peut être utile.

Je pensais que je pourrais utiliser des blocs try pour ajouter facilement du contexte aux erreurs, mais pas de chance pour l'instant.

J'ai écrit une petite fonction qui fonctionne très bien. Notez que File::open()? échoue avec un std::io::Error tandis que la ligne suivante échoue avec un anyhow::Error . Malgré les différents types, le compilateur trouve comment convertir les deux en Result<_, anyhow::Error> .

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> Result<(usize, usize), anyhow::Error> {
    let path = path.as_ref();
    let mut file = BufReader::new(File::open(path)?);
    Ok(config.root_store.add_pem_file(&mut file)
        .map_err(|_| anyhow!("Bad PEM file"))?)
}

Je voulais ajouter un contexte d'erreur alors j'ai essayé d'utiliser un bloc try et de toute façon with_context() :

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> anyhow::Result<(usize, usize)> {
    let path = path.as_ref();
    try {
        let mut file = BufReader::new(File::open(path)?);
        Ok(config.root_store.add_pem_file(&mut file)
            .map_err(|_| anyhow!("Bad PEM file"))?)
    }
    .with_context(|| format!("Error adding certificate {}", path.display()))
}

Mais maintenant, l'inférence de type échoue:

error[E0282]: type annotations needed
  --> src/net.rs:29:5
   |
29 | /     try {
30 | |         let mut file = BufReader::new(File::open(path)?);
31 | |         Ok(config.root_store.add_pem_file(&mut file)
32 | |             .map_err(|_| anyhow!("Bad PEM file"))?)
33 | |     }
   | |_____^ cannot infer type
   |
   = note: type must be known at this point
   ```

I don't understand why a type annotation is needed here but not in the first case. Nor do I see any easy way to add one, as opposed to using an [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) which does let me add an annotation:

```rust
(|| -> Result<_, anyhow::Error> {
    let domain = DNSNameRef::try_from_ascii_str(host)?;
    let tcp = TcpStream::connect(&(host, port)).await?;

    Ok(tls.connect(domain, tcp).await?)
})()
.with_context(|| format!("Error connecting to {}:{}", host, port))

@jkugelman

Encore,

c'est parce que try / ? est générique sur les types Try . Si vous aviez un autre type qui était [ impl Try<Ok=_, Error=anyhow::Error> ], le bloc try pourrait évaluer ce type ainsi que Result .

(De plus, vous n'avez pas besoin de Ok votre expression de fin dans un bloc try (# 70941).)

Je pense que le fait que cela continue à apparaître signifie que

  • Avant la stabilisation, try doit prendre en charge une attribution de type ( try: Result<_,_> { ou autre) ou atténuer ce problème,
  • Cela nécessite définitivement des diagnostics ciblés lorsque l'inférence de type d'un bloc try échoue, et
  • Nous devrions fortement envisager de donner à try un type de secours à Result<_,_> quand il n'est pas contraint autrement. Oui, c'est difficile, sous-spécifié et potentiellement problématique, mais cela _ résoudrait_ le cas à 80% des blocs try nécessitant une annotation de type car $12: Try<Ok=$5, Error=$8> n'étant pas assez spécifique.

De plus, étant donné que # 70941 semble se résoudre vers "oui, nous voulons (une forme de) ' Try::from_ok wrapping'", nous voulons probablement _aussi_ un diagnostic ciblé pour l'expression de queue d'un try block renvoie Ok(x) quand x fonctionnerait.

Je soupçonne que le bon comportement pour essayer est

  • étendre la syntaxe pour permettre une attribution manuelle comme try: Result<_, _> { .. } , try as Result<> , ou autre (je pense que try: Result est probablement bien? cela semble être la syntaxe préférée)
  • examinez le "type attendu" qui vient du contexte - s'il y en a un, préférez-le comme type de résultat d'un try
  • sinon, par défaut à Result<_, _> - ce n'est pas un repli d'inférence de type comme avec i32 , cela se produirait plus tôt, mais cela signifierait que des choses comme try { }.with_context(...) compilent.

Cependant, je crains que nous puissions obtenir des erreurs autour de ? et de la coercition into , du moins tant que le type d'erreur n'est pas spécifié. En particulier si vous écrivez du code où vous ? le résultat d'un bloc try , comme ceci:

#![feature(try_blocks)]

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x: Result<_, _> = try {
        std::fs::File::open("foo")?;
    };

    x?;

    Ok(())
}

fn main() { 
}

Vous obtenez toujours des erreurs ( terrain de jeu ) et à juste titre, car on ne sait pas sur quel ? la coercition "into" doit se déclencher.

Je ne sais pas quelle est la meilleure solution ici, mais cela implique probablement un repli d'inférence de type qui me rendra nerveux.

implique probablement un repli d'inférence de type qui me rendra nerveux.

Le plus simple: si toutes les utilisations de ? dans un bloc Try contiennent le même type Try::Error , utilisez ce type d'erreur pour le bloc try contenant (sauf si autrement lié).

Le "(sauf indication contraire)" est, bien sûr, la partie subtile et effrayante.

J'espère que je ne suis pas trop peu constructif avec ce post. Cependant, je voulais comparer l' exemple ? ne fait pas de conversion forcée, et il n'y a pas de wrapping automatique du résultat du bloc try :

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x = try {
        std::fs::File::open("foo").err_convert()?;
        Ok(())
    };

    x?;

    Ok(())
}

Dans ce monde:

  • Il est facile de voir que la portée try et la fn elle-même aboutissent à un succès sans aucune valeur. Il est également facile de voir sans même essayer qu'ils produisent des Result s.
  • Il est évident où se produit la conversion d'erreur.
  • La conversion d'erreur pourrait être déplacée vers l'expression x? , rendant la portée try spécifique aux opérations std::fs::File .
  • Tous les indices de type s'enchaînent couramment à partir de la signature de type. Tant pour la machine que pour nous les humains.
  • L'indication de type par l'utilisateur n'est requise que dans les cas où nous voulons réellement replier les erreurs en une autre, indépendante.

Je serais très heureux dans cet univers parallèle.

@phaylon Bien que j'apprécie la manière prudente dont vous avez rédigé ce commentaire, je crains qu'il ne soit plutôt peu constructif. La conversion d'erreur fait partie de ? et cela ne changera pas, et, à la lumière de cela, ok-wrapping est fondamentalement orthogonal par rapport au reste de cette discussion.

Si les fonctions try (avec les types return et throw) doivent être prises en compte, alors peut-être vaut-il la peine de considérer également la syntaxe d'attribution d'un bloc try comme quelque chose de similaire.

par exemple

try fn foo() -> u32 throw String {
  let result = try: u32 throw String {
    123
  };
  result?
}

Désolé si cela a été discuté mais quels sont les avantages de l'utilisation

try fn foo() -> u32 throw String { ... }

ou similaire par opposition à

fn foo() -> Result<u32, String> { ... }

?
Cela ressemble à une syntaxe en double

@gorilskij Si je comprends bien, le principal avantage est d'obtenir Ok -wrapping. Sinon, vous devez écrire:

fn foo() -> Result<u32, String> {
    try {
        // function body
    }
}

Certaines personnes préfèrent également throws car ils trouvent que la terminologie des exceptions est pertinente.

Personnellement, je souhaite rester le plus loin possible de l'apparition d'exceptions.

Ce n'est pas le fil de discussion pour discuter de try fn , donc veuillez ne pas pousser cette tangente plus loin. Ce fil est pour la fonctionnalité acceptée des blocs try , pas pour la fonctionnalité potentielle (et pour l'instant, pas pour RFCd) try fn .

Je viens de remarquer que la RFC originale pour ? proposait d'utiliser Into , et non From . Il est dit:

La présente RFC utilise le trait std::convert::Into à cette fin (qui a une couverture implicite de transfert de From ).

Bien que laisse la méthode de remontée exacte comme une question non résolue . Into était (vraisemblablement) préféré sur la base des indications de From :

Préférez utiliser Into plutôt que From lorsque vous spécifiez des limites de trait sur une fonction générique. De cette façon, les types qui implémentent directement Into peuvent également être utilisés comme arguments.

Cependant, dans le RFC Try trait , Into n'est plus mentionné et la conversion est effectuée en utilisant From place. C'est aussi ce que le code utilise maintenant, même pour ?
https://github.com/rust-lang/rust/blob/b613c989594f1cbf0d4af1a7a153786cca7792c8/src/librustc_ast_lowering/expr.rs#L1232

Cela semble malheureux, car il n'y a pas d'implémentation générale allant de Into à From . Cela signifie que les erreurs qui implémentent Into (par opposition à From ) soit par erreur, pour des raisons héritées, ou pour un autre besoin, ne peuvent pas être utilisées avec ? (ou Try ). Cela signifie également que toute implémentation qui suit la recommandation de la bibliothèque standard d'utiliser Into dans les limites ne peut pas utiliser ? . Avec un exemple, la bibliothèque standard recommande d'écrire:

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where E: Into<MyError>

mais si je le fais, je ne peux pas utiliser ? dans le corps de la fonction. Si je veux faire ça, je dois écrire

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where MyError: From<E>

mais si je le fais, les utilisateurs avec des types d'erreur qui implémentent Into au lieu de From ne peuvent pas utiliser cette fonction. Notez que l'inverse n'est pas vrai, en raison de l'implication générale de Into basée sur From .

Il est probablement (?) Trop tard pour réparer ? maintenant (ce qui est _très_ malheureux - peut-être dans la prochaine édition?), Mais nous devrions au moins nous assurer de ne pas creuser plus profondément ce chemin dans le Try trait.

@jonhoo @cuviper a essayé de changer le désugarage de From à Into dans # 60796 pour vérifier # 38751 et cela a entraîné une grande quantité de bris d'inférence exactement à cause du From -> Into blanket impl a rendu plus difficile pour rustc de gérer le cas courant de la conversion d'identité. Il a été décidé que cela ne valait pas la peine de déduire les coûts.

@jonhoo, vous pourriez également trouver ce commentaire de niko informatif:

Il existe également une limite codée en dur dans le système de caractères. Si vous devez résoudre un objectif comme ?X: Into<ReturnType> , nous ne parviendrons pas à le résoudre, mais si vous devez résoudre un objectif comme ReturnType: From<?X> , nous réussirons potentiellement et en inférerons une valeur pour ?X .

Edit: Ici, ?X fait référence à une variable d'inférence inconnue. La limite codée en dur dans le système de traits actuel est que le type Self doit être au moins partiellement déduit pour que nous puissions explorer cette option.

Le TL; DR est que déduire avec Into est plus difficile, et d'une manière inhérente à la façon dont le solveur de traits fonctionne.

@KrishnaSannasi @ CAD97 Merci, c'est utile! Je crains toujours que nous nous stabilisions trop sur la base de From étant donné que nous laissons définitivement les implémenteurs de Into de côté. Est-ce que l'on s'attend à ce que l'inférence s'améliore éventuellement? Est-ce que les directives pour préférer Into dans les limites devraient être modifiées? Attendons-nous qu'avec les nouvelles règles de cohérence des traits en 1.41 (je pense que c'était le cas), il n'y ait plus de raison d'implémenter seulement Into , et de considérer tous ces bogues impls?

Si l'inférence est assez bonne, elle devrait être compatible en aval pour passer à Into plus tard. Le pire que nous puissions casser est l'inférence étant donné que From implique Into

Est-ce que cela (le trait Try ) permet à ? de travailler avec les traits Into / From avec des limites génériques pour les types qui implémentent Try (par exemple Result lui-même)?

ie ? dans une fermeture ou une fonction qui retourne par exemple impl Into<Result>

(Ça ne semble pas quand j'essaye ça tous les soirs?)

J'ai hâte de voir cela stabilisé. Après avoir lu ce fil, et # 70941, je pense que le résumé devrait être mis à jour comme suit:

  1. "résoudre si les blocs catch doivent" envelopper "la valeur du résultat" doit être coché, "résolu comme oui "

  2. Une nouvelle inquiétude s'est ajoutée concernant ces problèmes d'inférence. Peut-être quelque chose comme:

    • [] Difficultés ergonomiques dues à des problèmes d'inférence de type.

ISTM que cette dernière préoccupation pourrait être traitée, entre autres par:

  • Ajoutez une syntaxe de chiffrement de type sur mesure à try , avant la stabilisation
  • Décidez que https://github.com/rust-lang/rfcs/pull/803 # 23416 (Attribution de type) résoudra ce problème et devrait d'abord être stabilisé
  • Ajoutez une sorte de secours automatique (par exemple à Result , peut-être comme suggéré dans https://github.com/rust-lang/rust/issues/31436#issuecomment -614735806
  • Décidez de stabiliser try tel quel, maintenant, en laissant l'espace syntaxique / sémantique pour une amélioration future

(Certaines de ces options ne sont pas mutuellement exclusives.)

Merci de votre attention et j'espère que vous trouverez ce message utile.

L'attribution de type a un certain nombre de problèmes (syntaxiques et autres) et semble peu susceptible d'être implémentée bientôt, encore moins stabilisée; bloquer les blocs try sur la syntaxe d'attribution de type ne semble pas approprié.

Un retour à Result pourrait aider, mais ne résout pas les problèmes d'inférence de type avec les types d'erreur: try { expr? }? (ou en pratique des équivalents plus complexes) ont effectivement deux appels à .into() , ce qui donne au compilateur trop de flexibilité sur le type intermédiaire.

@ijackson merci d'avoir pris l'initiative de résumer l'état actuel. Je pense que vous avez raison de dire qu'il existe différentes façons d'améliorer les blocs d'essai, mais l'un des problèmes est que nous ne savons pas laquelle faire, en partie parce que chaque solution a ses propres inconvénients.

En ce qui concerne l'attribution de type, cependant, je pense que les défis de mise en œuvre ne sont pas si difficiles. Cela pourrait être un bon candidat sur lequel accorder une certaine attention et essayer de le pousser au-delà de la ligne d'arrivée malgré tout. Je ne me souviens pas s'il y avait eu beaucoup de controverse sur la syntaxe ou quoi que ce soit du genre.

Le mercredi 05 août 2020 à 14:29:06 -0700, Niko Matsakis a écrit:

En ce qui concerne l'attribution de type, cependant, je pense que les défis de mise en œuvre ne sont pas si difficiles. Cela pourrait être un bon candidat sur lequel accorder une certaine attention et essayer de le pousser au-delà de la ligne d'arrivée malgré tout. Je ne me souviens pas s'il y avait eu beaucoup de controverse sur la syntaxe ou quoi que ce soit du genre.

Si je me souviens bien, la principale préoccupation était que l'autorisation de l'attribution
partout serait un changement de grammaire substantiel, et potentiellement un
limiter un. Je ne me souviens pas de tous les détails, juste que la préoccupation était
soulevé.

Personnellement, je pense que les problèmes d'ergonomie ne sont pas si graves qu'il ne vaut pas la peine de stabiliser cette fonctionnalité maintenant. Même sans attribution de type d'expression, l'introduction d'une liaison let n'est pas une solution de contournement si moche.

De plus, les blocs try peuvent être utiles dans les macros. En particulier, je me demande si @withoutboats l' excellente bibliothèque fehler souffrirait de moins de problèmes avec les déficiences de notre système de macros, si elle pouvait envelopper les corps des procs dans try .

Je rencontre des endroits où j'aimerais beaucoup utiliser des blocs d'essai. Ce serait bien de passer outre la ligne. Personnellement, je sacrifierais absolument à 100% l'attribution de type s'il était nécessaire d'obtenir des blocs d'essai sur la ligne. Je ne me suis pas encore retrouvé dans une situation où j'ai dit "dang j'aimerais avoir une attribution de type ici", mais j'ai fini par faire un IIFE pour simuler beaucoup de blocs d'essai. Laisser une fonction utile à long terme instable parce qu'elle entre en conflit avec une autre caractéristique instable à long terme est une situation vraiment malheureuse.

Pour être un peu plus précis, je me retrouve à faire cela lorsque je suis à l'intérieur d'une fonction qui renvoie Result, mais je veux faire une sorte de traitement sur les choses qui retournent une option. Cela dit, si Try en général était stable, je préférerais probablement encore les blocs try, car je ne veux pas revenir de la fonction principale pour le faire, mais plutôt donner une sorte de valeur par défaut si quelque chose dans la chaîne est Aucun. Cela a tendance à se produire pour moi dans le code de style de sérialisation.

Personnellement, je voulais l'attribution de type beaucoup plus souvent que d'essayer des blocs (même si j'ai parfois voulu les deux). En particulier, j'ai souvent eu du mal avec le "débogage de type" où le compilateur déduit un type différent de ce à quoi je m'attendais. Habituellement, vous devez ajouter une nouvelle liaison let quelque part, ce qui est vraiment perturbateur et amène rustfmt à interrompre l'historique des annulations. De plus, il existe de nombreux endroits où l'attribution de type permettrait d'éviter un poisson turbo supplémentaire.

Par contraste, je peux simplement utiliser and_then ou d'autres combinateurs pour terminer tôt sans sortir. Peut-être pas aussi propre qu'un bloc d'essai, mais pas si mal non plus.

@steveklabnik @ mark-im Les blocs Try et l'attribution de type ne sont en aucun cas en conflit, il ne s'agit pas d'une fonctionnalité ou d'une autre. C'est juste que les blocs try ont des échecs d'inférence de type non ergonomique, et l'attribution de type généralisée pourrait être un moyen de résoudre ce problème, mais comme l'attribution de type généralisée n'est pas une fonctionnalité à court terme ou même une chose sûre, @joshtriplett (et je d'accord) ne veut pas que cette fonctionnalité se bloque lors de l'attribution de type généralisée.

Cela ne signifie même pas que nous ne ferions pas de l'attribution de type généralisée la solution au problème; une option qui mérite d'être étudiée est "stabiliser l'essai tel quel, en espérant qu'un jour l'attribution de type généralisée résoudra ce problème." Tout ce qui a été dit, c'est de ne pas bloquer la stabilisation lors de l'attribution de type.


@ rust-lang / lang Je dois admettre que c'est un peu difficile de comprendre la nuance de l'échec de l'inférence de type à partir de ce thread, à cause des limitations de GitHub et des nombreux autres sujets abordés ici. Étant donné que prendre une décision sur la façon de gérer les échecs d'inférence est la seule chose qui empêche les tentatives de stabilisation de se stabiliser, je pense qu'il serait bénéfique que nous ayons une réunion pour en discuter, et quelqu'un pourrait prendre position pour obtenir une compréhension approfondie du problème de l'inférence. .

Une question qui me vient à l'esprit, par exemple: est-ce que ce problème d'inférence est spécifiquement dû à la flexibilité de conversion que nous avons autorisée dans Try ? Je sais que cette décision a été discutée à mort, mais si c'est le cas, cela semble être de nouvelles informations pertinentes qui pourraient justifier de changer la définition du trait Try .

@withoutboats Je suis d'accord avec la nécessité de rassembler toutes les informations en un seul endroit et le désir de pousser cette fonctionnalité au-delà de la ligne d'arrivée. Cela dit, je pense que la dernière fois que nous avons enquêté ici, il est également devenu clair que les modifications apportées à Try pourraient être difficiles en raison de la compatibilité ascendante - @cramertj a mentionné des Pin , IIRC.

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