Rust: Problème de suivi pour les traits TryFrom/TryInto

Créé le 5 mai 2016  ·  240Commentaires  ·  Source: rust-lang/rust

Problème de suivi pour https://github.com/rust-lang/rfcs/pull/1542


Faire:

B-unstable C-tracking-issue T-libs

Commentaire le plus utile

J'aimerais que ! devienne un type de vocabulaire suffisant pour que Result<_, !> soit intuitivement lu comme "résultat infaillible", ou "résultat qui ne se trompe (littéralement) jamais". L'utilisation d'un alias (peu importe un type séparé !) me semble un peu redondant et je peux le voir me causer au moins une pause momentanée lors de la lecture des signatures de type - "attendez, en quoi était-ce différent de ! à nouveau ?" YMMV, bien sûr.

Tous les 240 commentaires

Existe-t-il un moyen d'imprimer de manière générique une erreur avec la valeur d'origine si la conversion échoue sans nécessiter Clone afin qu'une méthode connexe qui panique en cas d'échec puisse avoir de bons messages d'erreur?

Une discussion pour savoir si cela devrait aller dans le prélude est en cours pour quand cela deviendra stable .

Toutes mes excuses si cela est couvert ailleurs, mais que voudrions-nous voir avant de marquer cela comme stable ? Je suis presque sûr d'avoir réimplémenté cette fonctionnalité plusieurs fois dans divers projets, donc un trait réutilisable commun me ferait 😄

Nous pouvons en discuter pour le prochain cycle.

🔔 Ce problème entre maintenant dans une période de commentaires finaux d'un cycle pour stabilisation 🔔

Comme point de stabilisation, l'équipe libs aimerait également ajouter ces traits au prélude dans le cadre de leur stabilisation. Cela nécessitera qu'une course de cratère soit 100% propre _au minimum_, mais nous sommes relativement confiants que l'état actuel de résolution le rend rétrocompatible pour ajouter des traits au prélude.

J'ai quelques questions sur le but de ces traits.

  1. Pour quels types de std seront-ils implémentés ?
  2. Quels types devraient obtenir des implémentations comme impl TryFrom<T> for T ?
  3. Quels types devraient obtenir des implémentations comme impl TryFrom<U> for T s'ils ont déjà impl From<U> for T ?

cc @sfackler , pourriez-vous développer l'ensemble actuel d'impls et certaines des justifications également ?

Je pense qu'en général l'intuition devrait être exactement la même pour celle de From / Into , sauf lorsque la conversion peut échouer. Tout comme nous ajoutons progressivement des implémentations de From et Into aux types de bibliothèques standard, je pense que nous ferons de même pour TryFrom et TryInto . La directive la plus importante ici est que la conversion doit être "canoniquement évidente" - s'il existe plusieurs façons raisonnables de convertir un type en un autre, TryFrom ou From peut ne pas être le bon choses à utiliser.

_En général_, je ne pense pas qu'il faille s'attendre à ce que tous les types doivent impl TryFrom<T> for T ou lever manuellement un impl From<U> for T . En particulier, il n'est souvent pas clair quel type d'erreur choisir. Cependant, ces types d'implémentations sont en grande partie celles qui peuvent et doivent être définies pour faire fonctionner une API particulière. Par exemple, nous avons ces deux types d'implémentations pour diverses combinaisons de types d'entiers primitifs pour les raisons décrites dans la RFC. Comme autre exemple, un trait ad hoc que TryFrom / TryInto remplacera est postgres::IntoConnectParams , qui a une implémentation réflexive pour convertir un ConnectParams en lui-même.

En général, je ne pense pas qu'il faille s'attendre à ce que tous les types impl TryFrom<T> for T ou soulèvent manuellement un impl From<U> for T .

Lorsqu'une conversion TryFrom est infaillible, le type d'erreur doit être enum Void {} , n'est-ce pas ? (Ou une énumération similaire avec un autre nom.) Ce qui, soit dit en passant, me semble être une bonne raison d'avoir un type à usage général Void dans std .

@SimonSapin qui casserait une API de style IntoConnectParams ainsi que le cas d'utilisation de la conversion d'entiers décrit dans la RFC puisque les types d'erreur des conversions infaillibles et faillibles ne correspondraient pas.

@sfackler Pas si vous utilisez simplement From pour les types d'erreur (par exemple try! )! impl<T> From<!> for T peut et doit exister à cette fin.

Tout comme nous ajoutons progressivement des implémentations de From et Into aux types de bibliothèques standard, je pense que nous ferons de même pour TryFrom et TryInto .

Cela ne semble pas encore s'être produit, donc je ne sais pas si c'est parce que personne ne l'a fait ou s'il n'y a pas de types applicables dans std . S'il existe des types applicables dans std , ce serait bien de voir quelques implémentations pour eux. Par exemple, parmi les bonnes implémentations suivantes, existe-t-il ?

impl TryFrom<u32> for char
impl TryFrom<char> for u8
impl TryFrom<Vec<u8>> for String
impl TryFrom<&[u8]> for &str

_En général_, je ne pense pas qu'il faille s'attendre à ce que tous les types doivent impl TryFrom<T> for T ou lever manuellement un impl From<U> for T .

Le problème est que si vous voulez utiliser TryInto<T> et que T n'est pas dans votre caisse, vous devez simplement espérer que impl TryFrom<T> for T a été implémenté. C'est une limitation malheureuse, qui n'existe pas pour From / Into .

@SimonSapin qui casserait une API de style IntoConnectParams ainsi que le cas d'utilisation de la conversion d'entiers décrit dans la RFC puisque les types d'erreur des conversions infaillibles et faillibles ne correspondraient pas.

Cela signifie-t-il que le type d'erreur est en quelque sorte corrigé en fonction du type vers lequel vous convertissez ?

Pourquoi TryFrom::Err et TryInto::Err ne sont pas délimités par std::error::Error ?

non limité par std :: error :: Error ?

Cela empêcherait Err d'être () , qui est un type d'erreur viable pour les conversions infaillibles.

Si () est le type d'erreur, la fonction peut renvoyer Err(()) . Cela ne rend pas la fonction infaillible. Il ne parvient tout simplement pas à fournir une erreur utile lorsqu'il échoue. Lors de l'écriture de code qui utilise des conversions qui peuvent ou non être faillibles, je pense que le meilleur moyen est de se limiter à Into et d'écrire une spécialisation qui utilise TryInto .

Une fois la RFC 1216 implémentée , ! sera le type d'erreur approprié pour les conversions infaillibles. Je _pense_ que cela signifierait qu'un Error lié à TryFrom::Err / TryInto::Err serait OK.

J'aimerais certainement que l'implémentation From<T> produise une implémentation de TryFrom<T, Err = !> (et idem pour Into , bien sûr).

Je _pense_ que cela signifierait qu'un Error lié à TryFrom::Err / TryInto::Err serait OK.

Oui, nous aurons certainement impl Error for ! { ... } pour précisément ce genre de cas.

Je suis un peu inquiet des différences de type dans le cas d'utilisation des conversions d'entiers, mais il semble que nous devrions probablement essayer de stabiliser cela jusqu'à ce que ! -as-a-type atterrisse pour avoir une chance d'expérimenter.

Dans mon POV, tous les types associés qui représentent des erreurs doivent être limités par Error . Malheureusement, nous avons déjà laissé un type sans : FromStr::Err (Les seuls autres types publics sont TryInto et TryFrom , cochés avec ack 'type Err;' et ack 'type Error;' ). Ne refaisons pas ça.

Discuté récemment, l'équipe libs a décidé de stabiliser ces traits jusqu'à ce que le type ! soit élaboré.

Je suppose que maintenant ce n'est plus bloqué, non ?

Supprimer la nomination car @sfackler va enquêter sur les types ! et ces traits.

Cela aura-t-il un changement pour se stabiliser dans un avenir prévisible ?

Pourquoi TryFrom::Err et TryInto::Err ne sont pas limités par std::error::Error ?

std::err::Error n'existe pas dans les builds #![no_std] . De plus, dans de nombreux cas, il n'y a aucun avantage à implémenter std::err::Error pour un type d'erreur même lorsqu'il est disponible. Ainsi, je préférerais ne pas avoir une telle limite.

J'ai expérimenté l'implémentation moi-même dans ma propre bibliothèque. Je voudrais réitérer l'inquiétude exprimée par @SimonSapin dans https://github.com/rust-lang/rfcs/pull/1542#issuecomment -206804137 : try!(x.try_into()); prête à confusion car le mot "essayer" est utilisé deux manières différentes dans la même déclaration.

Je comprends que beaucoup de gens pensent que de telles choses devraient être écrites x.try_into()?; , cependant je fais partie d'un nombre important de personnes (basé sur tous les débats) qui préfèrent fortement ne pas utiliser la syntaxe ? à cause de... toutes les raisons évoquées dans tous les débats.

Personnellement, je pense que nous devrions toujours essayer de trouver un modèle qui ne nécessite pas le préfixe try_ sur les noms.

Je ne suis pas particulièrement attaché à la dénomination, mais je ne peux personnellement penser à rien de mieux.

Il est déjà devenu semi-standard d'utiliser try_ pour des variantes de fonctions qui renvoient un Result .

cependant, je fais partie d'un nombre important de personnes (sur la base de tous les débats) qui préfèrent fortement ne pas utiliser la syntaxe ? cause de... toutes les raisons mentionnées dans tous les débats.

Ce débat est terminé maintenant, n'est-ce pas ? Je veux dire, je sympathise avec ce côté du débat : je ne sais pas pourquoi les gens ont dit qu'ils trouvaient try!() si ennuyeux, ? est moins visible, encourage la gestion des erreurs paresseuse et il semble que un gaspillage de syntaxe pour quelque chose pour lequel nous avions déjà une macro parfaitement bonne (à moins qu'elle ne soit étendue pour devenir quelque chose de beaucoup plus général à l'avenir).

Mais c'est du passé maintenant. ? est stable et ne s'en va pas. Donc, nous pourrions tous y passer tous afin que nous utilisions tous la même chose et que nous puissions cesser de nous soucier des conflits de noms avec try! .

Je voudrais réitérer la préoccupation exprimée par @SimonSapin dans rust-lang/rfcs#1542 (commentaire)

Puisqu'on me cite nommément, permettez-moi de dire que je n'ai plus vraiment ce souci. Au moment où j'ai fait ce commentaire, l'opérateur ? était une proposition dont l'avenir était incertain, mais maintenant il est là pour rester.

De plus, je pense que stabiliser le plus tôt possible est plus important qu'une autre série de noms de bikeshedding. Cela fait des mois que le RFC a été accepté et cela a été implémenté #[unstable] .

Il est déjà devenu semi-standard d'utiliser try_ pour des variantes de fonctions qui renvoient un résultat.

C'est ce que je trouve le plus étrange à propos de cette fonctionnalité. La plupart des fonctions que j'écris renvoient un Result mais je n'ai jamais nommé aucune de ces fonctions avec un préfixe try_ sauf lorsque j'essaie d'expérimenter avec ce trait.

De plus, je n'ai trouvé aucun avantage pratique à écrire ceci:

impl TryInto<X> for Y {
    type Err = MyErrorType;

   fn try_into(self) -> Result<X, Self::Err> { ... }
}

Au lieu de cela, je peux toujours écrire ceci, beaucoup moins de surcharge syntaxique :

    fn into_x(self) -> Result<X, MyErrorType> { ... }

Je n'ai jamais eu à écrire de code générique paramétré par TryInto ou TryFrom malgré de nombreuses conversions, donc cette dernière forme est suffisante pour toutes mes utilisations dans les types que je définis. Je pense qu'avoir des paramètres TryInto<...> ou TryFrom<...> semble être une forme discutable.

De plus, j'ai trouvé que nommer le type d'erreur associé Err au lieu de Error était sujet aux erreurs car j'ai toujours tapé Error . J'ai remarqué que cette erreur avait été commise même lors de la rédaction de la RFC elle-même : https://github.com/rust-lang/rfcs/pull/1542#r60139383. De plus, le code qui utilise Result utilise déjà largement le nom Err puisqu'il s'agit d'un constructeur Result .

Il y avait une autre proposition qui se concentrait spécifiquement sur les types entiers et utilisait des termes comme « élargir » et « rétrécir », par exemple x = try!(x.narrow()); que j'avais également implémenté. J'ai trouvé que la proposition était suffisante pour mes utilisations de la fonctionnalité proposée ici dans mon utilisation réelle car je n'ai fini par faire de telles conversions que sur des types entiers intégrés. Il est également plus ergonomique et plus clair (IMO) pour les cas d'utilisation pour lesquels il suffit.

De plus, je n'ai trouvé aucun avantage pratique à écrire ceci...

Je suis plutôt d'accord. Ce trait est là où une chose peut être utilisée pour créer une autre chose mais parfois ce processus peut échouer - mais cela ressemble à presque toutes les fonctions. Je veux dire, devrions-nous avoir ces impls ? :

impl TryInto<TcpStream> for SocketAddr {
    type Err = io::Error;
    fn try_into(self) -> Result<TcpStream, io::Error> {
        TcpStream::connect(self)
    }
}

impl<T> TryInto<MutexGuard<T>> for Mutex<T> {
    type Err = TryLockError<MutexGuard<T>>;
    fn try_into(self) -> Result<Mutex<T>, Self::Err> {
        self.try_lock()
    }
}

impl TryInto<process::Output> for process::Child {
    type Err = io::Error;
    fn try_into(self) -> Result<process::Output, io::Error> {
        self.wait_with_output()
    }
}

impl TryInto<String> for Vec<u8> {
    type Err = FromUtf8Error;
    fn try_into(self) -> Result<String, FromUtf8Error> {
        String::from_utf8(self)
    }
}

Ce trait semble presque trop générique. Dans tous les exemples ci-dessus, il serait bien préférable de dire explicitement ce que vous faites réellement plutôt que d'appeler try_into .

Je pense qu'avoir des paramètres TryInto<...> ou TryFrom<...> semble être une forme discutable.

Aussi d'accord. Pourquoi ne feriez-vous pas simplement la conversion et ne géreriez-vous pas l'erreur avant de transmettre la valeur à la fonction ?

std::err::Error n'existe pas dans les builds #![no_std]. De plus, dans de nombreux cas, il n'y a aucun avantage à implémenter std::err::Error pour un type d'erreur même lorsqu'il est disponible. Ainsi, je préférerais ne pas avoir une telle limite.

Le seul avantage d'être limité par std::error::Error est qu'il peut être la valeur de retour d'une autre erreur cause() . Je ne sais vraiment pas pourquoi il n'y a pas de core::error::Error , mais je n'ai pas examiné cela.

De plus, j'ai trouvé que nommer le type d'erreur associé Err au lieu d'Erreur était sujet aux erreurs car j'ai toujours tapé Error. J'ai remarqué que cette erreur avait été commise même lors de la rédaction de la RFC elle-même : rust-lang/rfcs#1542 (commentaire). De plus, le code qui utilise Result utilise déjà largement le nom Err puisqu'il s'agit d'un constructeur Result.

FromStr , qui est stable, utilise également Err pour son type associé. Que ce soit le meilleur nom ou non, je pense qu'il est important de le garder cohérent dans toute la bibliothèque standard.

Que TryFrom et TryInto soient ou non trop généraux, j'aimerais vraiment voir des conversions faillibles, au moins entre les types entiers, dans la bibliothèque standard. J'ai une caisse pour cela, mais je pense que les cas d'utilisation vont assez loin pour le rendre standard. À l'époque où Rust était alpha ou bêta, je me souviens avoir utilisé FromPrimitive et ToPrimitive à cette fin, mais ces traits avaient des problèmes encore plus importants.

Impossible de déplacer Error dans core en raison de problèmes de cohérence.

De plus, en raison de problèmes de cohérence, Box<Error> n'implémente pas Error , une autre raison pour laquelle nous ne lions pas le type Err .

Il n'est vraiment pas nécessaire de le lier à la définition du trait, de toute façon - vous pouvez toujours ajouter vous-même cette limite plus tard :

where T: TryInto<Foo>, T::Err: Error

Je ne commente généralement pas ces discussions, mais j'attends cela depuis un moment et, comme plusieurs l'ont exprimé ci-dessus, je ne sais pas quel est le retard; Je veux toujours un trait qui peut échouer. J'ai du code partout qui s'appelle try_from ... Ce trait résume parfaitement cette idée. S'il vous plaît laissez-moi l'utiliser sur la rouille stable.

J'ai aussi écrit tout un tas d'autres choses, mais je l'ai supprimée depuis car malheureusement, la cohérence des traits empêche ce trait d'être aussi utile qu'il pourrait l'être pour moi. Par exemple, j'ai essentiellement réimplémenté une version spécialisée de ce même trait exact pour les types primitifs pour un analyseur hautement générique de ces types. Ne vous inquiétez pas, je râlerai à ce sujet une autre fois.

Cela étant dit, je pense que str::parse en bénéficiera grandement, car il spécialise également un trait FromStr comme limite - qui est exactement ( TryFrom<str> ) spécialisé à la main.

Alors corrigez-moi si je me trompe, mais je pense que stabiliser et utiliser ceci pour str::parse va :

  1. supprimer la réimplémentation passe-partout, et donc supprimer un trait moins familier et spécialisé
  2. ajouter TryFrom<str> dans la signature, qui se trouve correctement dans le module de conversion et est plus facilement évident ce qu'il fait
  3. permettra aux clients en amont d'implémenter TryFrom<str> pour leurs types de données et d'obtenir str.parse::<YourSweetDataType>() gratuitement, ainsi que d'autres try_from qu'ils ont envie d'implémenter, ce qui permet une meilleure organisation logique du code.

Enfin, abstractions mises à part, réutilisation du code, bla bla, je pense que l'un des avantages sous-estimés de traits comme celui-ci est l'achat sémantique qu'ils offrent aux débutants comme aux vétérans. Essentiellement, ils fournissent (ou commencent à fournir, plus nous nous stabilisons) un paysage uniforme avec un comportement familier et canonisé. Default , From , Clone en sont de très bons exemples. Ils fournissent un paysage fonctionnel mémorable auquel les utilisateurs peuvent accéder lors de l'exécution de certaines opérations, et dont ils ont déjà une bonne compréhension du comportement et de la sémantique (apprendre une fois, appliquer partout). Par exemple:

  1. Je veux une version par défaut ; oh laissez-moi atteindre pour SomeType::default()
  2. Je veux convertir ceci, je me demande si SomeType::from(other) est implémenté (vous pouvez simplement le taper et voir s'il compile, sans chercher de documentation)
  3. Je veux cloner ça, clone()
  4. Je veux essayer d'obtenir ceci à partir de ceci, et puisque les erreurs font partie intégrante de la rouille, cela peut échouer dans la signature, alors laissez-moi try_from - oh attendez :P

Toutes ces méthodes deviennent monnaie courante et font partie de la boîte à outils des utilisateurs de Rust, ce qui, à mon humble avis, réduit la charge logique en nous permettant d'atteindre des concepts familiers (et c'est juste un autre nom pour un trait !) dont nous avons déjà intériorisé la documentation et le comportement sémantique.

Bien sûr, ils ne correspondent pas toujours, auquel cas nous spécialisons nos structures de données, mais des traits comme celui-ci réduisent le fardeau de l'API des utilisateurs et des codeurs en leur donnant accès à des concepts qu'ils ont déjà étudiés, au lieu de lire la documentation/trouver votre from_some_thing , etc. Sans même avoir à lire votre documentation, en utilisant les traits std, les utilisateurs peuvent naviguer dans votre API et vos structures de données de manière logique, robuste et efficace.

Dans un certain sens, cela formalise un accord de gentleman entre nous, et rend plus facile et plus familier pour nous d'effectuer certaines opérations familières.

Et c'est tout ce que j'ai à dire à ce sujet ;)

Cela a été précédemment bloqué lors d'une enquête sur la possibilité d'utiliser ! comme type d'erreur pour les conversions entières infaillibles. Comme la fonctionnalité est actuellement implémentée, cela échoue même dans les cas les plus simples : https://is.gd/Ws3K7V.

Pensons-nous toujours à changer les noms des méthodes, ou devrions-nous mettre cette fonctionnalité dans FCP ?

@sfackler Ce lien de terrain de jeu fonctionne pour moi si je change le type de retour à la ligne 29 de Result<u32, ()> à Result<u32, !> : https://is.gd/A9pWbU Il ne reconnaît pas que let Ok(x) = val; est un modèle irréfutable lorsque val a le type Err !, mais cela ne semble pas être un problème de blocage.

@Ixrec, l'une des principales motivations de ces traits était les conversions vers et depuis les typedefs entiers C. Si j'ai une fonction

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    x.try_into()
}

Cela compilera sur les cibles i686 mais pas sur les cibles x86_64.

De même, disons que je veux remplacer le type IntoConnectParams : https://docs.rs/postgres/0.13.4/postgres/params/trait.IntoConnectParams.html. Comment puis-je faire ça s'il y a une couverture impl<T> TryFrom<T> for T { type Error = ! } ? J'ai besoin de l'implémentation réflexive pour ConnectParams , mais avec un type d'erreur concret différent de ! .

Il ne parvient pas à reconnaître que let Ok(x) = val; est un modèle irréfutable lorsque val a le type Err !

Notez qu'il y a un PR ouvert pour cela .

Si j'ai une fonction...

Cela devrait fonctionner cependant

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    let val = x.try_into()?;
    Ok(val)
}

Au risque d'être un commentaire ennuyeux +1, je veux juste mentionner qu'après l'arrivée des macros 1.1 dans Rust 1.15, try_from sera la dernière fonctionnalité gardant Ruma sur Rust tous les soirs. Try_from stable est attendu avec impatience !

Sur une note plus substantielle...

C'est ce que je trouve le plus étrange à propos de cette fonctionnalité. La plupart des fonctions que j'écris renvoient un résultat mais je n'ai jamais nommé aucune de ces fonctions avec un préfixe try_ sauf lorsque j'essaie d'expérimenter avec ce trait.

C'est une bonne observation, mais je pense que la raison du préfixe try_ n'est pas qu'il est nécessaire d'identifier le type de retour comme un Result , mais de le distinguer de l'équivalent non faillible.

De plus, j'ai trouvé que nommer le type d'erreur associé Err au lieu d'Erreur était sujet aux erreurs car j'ai toujours tapé Error. J'ai remarqué que cette erreur avait été commise même lors de la rédaction de la RFC elle-même : rust-lang/rfcs#1542 (commentaire). De plus, le code qui utilise Result utilise déjà largement le nom Err puisqu'il s'agit d'un constructeur Result.

Je suis d'accord sur celui-ci. La plupart des autres types d'erreurs que j'ai rencontrés dans les bibliothèques sont nommés "Error" et j'aime bien que jusqu'à présent "Err" n'ait jamais signifié que Result::Err . Il semble que le stabiliser car "Err" entraînera (sans jeu de mots) que les gens se trompent constamment de nom.

@canndrew Bien sûr, il est possible de faire fonctionner, mais le but de cette motivation pour cette fonctionnalité est de faciliter la gestion correcte de ces types de différences de plate-forme - nous voulons éviter tout l'espace de "compile sur x86 mais pas ARM" .

Je pense que j'ai choisi Err pour la cohérence avec FromStr mais je serais très heureux de passer à Error avant de stabiliser !

la dernière fonctionnalité gardant Ruma sur Rust tous les soirs

De même, le backend alternatif du terrain de jeu n'a besoin que la nuit pour accéder à TryFrom ; les trucs serde nocturnes étaient trop instables à mon goût, alors je suis passé à la configuration du script de construction. Avec 1.15, je reviendrai à #[derive] . J'attends avec impatience que cette fonctionnalité devienne stable !

@sfackler Désolé je ne suivais pas. Dans le cas des conversions d'entiers, il semble que ce dont nous avons vraiment besoin est de ne pas typedef c_ulong en u32 ou u64 selon la plate-forme, mais d'en faire un nouveau type. De cette façon, un TryFrom<c_ulong> ne peut pas interférer avec un TryFrom<u{32,64}> .
Après tout, nous aurons toujours des problèmes de "compile une plate-forme mais pas l'autre" si nous définissons les types différemment sur différentes plates-formes. C'est dommage d'avoir à sacrifier l'impl TryFrom<T> for U where U: From<T> par ailleurs entièrement logique juste pour que nous puissions soutenir ce qui semble être une pratique discutable.

Je ne suggère pas sérieusement que nous bloquions cette RFC jusqu'à ce que nous obtenions une RFC entière de nouveau type écrite + fusionnée + stabilisée. Mais nous devons garder cela à l'esprit pour l'avenir.

De même, supposons que je souhaite remplacer le type IntoConnectParams :

Quel est le problème ici cependant? Pourquoi n'utiliseriez-vous pas un type d'erreur pour TryFrom<ConnectParams> et un autre pour TryFrom<&'a str> ?

Je ne préconiserais pas que nous cassions littéralement tout le code FFI dans le monde. Après avoir essayé et échoué à récupérer des enveloppes de nouveau type d'entier similaires comme Wrapping , il y a des coûts ergonomiques énormes.

Y a-t-il un impl<T> From<!> for T dans la bibliothèque standard ? Je ne le vois pas dans la documentation, mais cela pourrait être un bogue de Rustdoc. Si ce n'est pas là, alors il n'y a aucun moyen de convertir le TryFrom<ConnectParams> impl ! Error en celui dont j'ai réellement besoin.

Après avoir essayé et échoué à récupérer des wrappers de type entier similaires comme Wrapping, les coûts ergonomiques sont énormes.

Je pensais plutôt à la possibilité de définir vos propres types d'entiers a. la. C++ :

trait IntLiteral: Integer {
    const SUFFIX: &'static str;
    const fn from_bytes(is_negative: bool, bytes: &[u8]) -> Option<Self>; // or whatever
}

impl IntLiteral for c_ulong {
    const SUFFIX: &'static str = "c_ulong";
    ...
}

extern fn foo(x: c_ulong);

foo(123c_ulong); // use a c_ulong literal
foo(123); // infer the type of the integer

Cela résoudrait-il la plupart des problèmes ergonomiques ? Je n'aime pas vraiment les littéraux définis par l'utilisateur C++ - ou les fonctionnalités en général qui donnent aux gens le pouvoir de changer le langage de manière confuse - mais je suppose que cela pourrait finir par être le moindre de deux maux.

Y a-t-il un impl<T> From<!> for T dans la bibliothèque standard ?

Pas actuellement car il est en conflit avec l' From<T> for T . Ma compréhension est que la spécialisation impl devrait éventuellement être capable de gérer cela.

Cela ressemble à quelque chose auquel nous devrions revenir dans quelques années.

Quel est le calendrier de stabilisation de la spécialisation et ! ?

Pour la spécialisation, je ne sais pas. Pour ! lui-même, c'est surtout une question de moment où je peux fusionner mes correctifs.

@canndrew Je suis tout à fait d'accord qu'il ne devrait pas être implémenté pour tout. La documentation indique _essayer de construire Self via une conversion_, mais qu'est-ce qui compte comme une conversion ? Que diriez-vous de…_modifier la même chose d'une représentation à une autre, ou d'ajouter ou de supprimer un wrapper_ ? Cela couvre vos Vec<u8> -> String et Mutex<T> -> MutexGuard<T> , ainsi que des choses comme u32 -> char et &str -> i64 ; tout en excluant SocketAddr -> TcpStream et process::Child -> process::Output .

J'ai l'impression que impl From<T> for U devrait probablement impliquer TryFrom<T, Err=!> for U . Sans cela, les fonctions qui prennent TryFrom<T> s ne peuvent pas aussi prendre From<T> s. (Utiliser try_from() | try_into() pour une conversion concrète et infaillible serait probablement juste un anti-modèle. Vous _seriez_ capable de le faire, mais… ce serait idiot, alors ne le faites pas .)

Je me sens comme implpour U devrait probablement impliquer TryFrompour toi.

D'accord.

Vous seriez capable de le faire, mais… ce serait idiot, alors ne le faites pas.

Cela ressemble à une peluche potentielle.

@BlacklightShining Je pense qu'il devrait être implémenté pour les types où, compte tenu du type de sortie, la "conversion" est évidente. Dès que plusieurs conversions sont possibles (utf8/16/32 ? sérialisation vs casting ? etc...), il faut l'éviter.

A MON HUMBLE AVIS:

À l'heure actuelle, nous pourrions facilement fournir une implémentation globale de TryFrom<&str> pour tout ce qui implémente FromStr :

impl<'a, T: FromStr> TryFrom<&'a str> for T {
    type Err = <T as FromStr>::Err;
    fn try_from(s: &'a s) -> Result<T, Self::Err> {
        T::from_str(s)
    }
}

J'aimerais voir quelque chose comme ça ajouté avant que try_from ne soit stabilisé, ou au moins une référence à cela dans les docs. Sinon, il peut être déroutant d'avoir des implémentations de TryFrom<&'a str> et FromStr qui diffèrent.

Je suis d'accord que ce serait une bonne inclusion, mais cela pourrait entrer en conflit avec la proposition Try -> TryFrom impl?

@sfackler potentiellement. Ce serait parfaitement bien si TryFrom , TryInto et FromStr se référencaient tous dans la documentation, mais ma principale préoccupation est qu'il n'y a pas de norme existante qui indique si str::parse et str::try_into renvoient la même valeur.

Je serais d'accord avec une demande douce dans les docs que les gens devraient les mettre en œuvre pour avoir le même comportement, bien que je puisse certainement voir des cas où quelqu'un pourrait penser qu'ils peuvent être différents.

Par exemple, supposons que quelqu'un crée une structure Password pour un site Web. Ils pourraient supposer que "password".parse() vérifierait la validité du mot de passe, puis le convertirait en un hachage, alors que Password::try_from("1234abcd") pourrait supposer que "1234abcd" est déjà un hachage stocké dans la base de données et essayer pour l'analyser directement dans un hachage qui peut être comparé.

Cela a du sens, compte tenu du fait que le libellé de parse implique qu'un certain niveau d'analyse est effectué, alors que try_from n'est qu'une conversion de type. Cependant, en réalité, nous voudrions peut-être préciser que ces deux fonctions ont l'intention d'effectuer la même chose.

Même si l'équipe de langage a proposé de fermer la RFC pour déprécier les paramètres anonymes , ils semblent tous d'accord sur le fait que, idéalement, nous arrêterions de créer du nouveau code qui utilise des paramètres anonymes. Dans cet esprit, pourrions-nous mettre à jour la signature de try_from / try_into pour donner les noms des paramètres ? Ou serait-il plus important de maintenir la symétrie avec from / into ?

Aussi, serait-il utile d'écrire un résumé des principales questions sans réponse qui subsistent ? J'espère vraiment que nous pourrons décider de stabiliser cela pour le prochain cycle de publication. Comme je l'ai mentionné, c'est la seule fonctionnalité nocturne restante que j'utilise beaucoup. :}

@jimmycuadra certainement ouais ! Vous souhaitez envoyer un PR en ajoutant des noms de paramètres ?

En ce qui concerne l'état actuel des choses, je crois que la seule question sans réponse est de savoir s'il devrait y avoir une

impl<T, U> TryFrom<U> for T
    where T: From<U>
{
    type Error = !;

    fn try_from(u: U) -> Result<T, !> {
        Ok(T::from(u))
    }
}

Cela ajoute une belle symétrie, mais rend les choses un peu plus ennuyeuses à gérer dans de nombreux cas, car vous n'avez plus un seul type d'erreur pour les éléments que vous souhaitez convertir.

tapez Erreur = !;

Je suggère de le faire dans un RFC séparé qui prend en compte le résultat de tout ce qui est décidé à propos de toutes les choses indécises concernant ! .

@sfackler Je pense qu'il serait important de prendre en compte les choses que j'ai mentionnées à propos FromStr aussi. Devrions-nous avoir une implémentation similaire pour les implémenteurs FromStr , ou devraient-ils être autorisés à être distincts, ou devrions-nous simplement documenter qu'ils devraient être identiques mais ne doivent pas l'être ?

Je suis d'avis que TryFrom<str> et FromStr devraient être fonctionnellement identiques, et la documentation devrait indiquer clairement que les implémentations des deux sont censées être identiques. L'implémentation de l'un devrait également vous donner l'autre, au moins en vous permettant d'utiliser str::parse . Si Rust avait eu TryFrom depuis le début, FromStr n'aurait jamais été nécessaire. Pour cette raison, je documenterais également TryFrom<str> comme étant la forme préférée pour le nouveau code.

@jimmycuadra dans ce cas, nous devrions modifier parse pour utiliser TryFrom puis mettre une couverture impl pour FromStr -> TryFrom .

Si nous allons changer str::parse à implémenter en termes de TryFrom , devrions-nous également changer d'autres implémentations de FromStr pour les types concrets de la même manière (c'est-à-dire tous les implémenteurs de cette liste : https://doc.rust-lang.org/stable/std/str/trait.FromStr.html) ? Les documents pour FromStr devraient-ils être mis à jour pour suggérer d'utiliser TryFrom la place ? Les implémentations concrètes actuelles de FromStr devraient-elles avoir leur documentation déplacée vers la version TryFrom ?

Je pense que pour la rétrocompatibilité, nous ne pouvons pas changer FromStr , n'est-ce pas ?

La suppression FromStr en faveur de TryFrom<&str> est certainement quelque chose à garder à l'esprit pour Rust 2.0.

Oui, une fois cette fonctionnalité stabilisée, nous déposerons un problème et le marquerons avec 2.0-breakage-wishlist.

Une chose à considérer est également l'ajout d'une méthode parse_into à String qui utilise TryFrom<String> . Je me retrouve souvent à implémenter TryFrom pour les deux si un type stocke en interne un String mais nécessite toujours une validation.

Si nous allons laisser implTryFrom pour T où T : De pour un futur RFC, cette fonctionnalité est-elle alors prête à se stabiliser maintenant ? Je ne veux vraiment pas manquer un autre cycle de publication, donc j'espère que certaines personnes de l'équipe Rust auront la bande passante pour discuter et prendre une décision.

Je pense que le problème est qu'il serait difficile de stabiliser cette fonctionnalité après qu'elle ait été stabilisée et que des personnes aient fourni des impls pour les deux.

Je m'attendrais à ce que le fait d'avoir déjà un T : From<U> placerait U : TryFrom<T> dans la catégorie de changement " trou d'API évident " lorsque l'implémentation est raisonnable.

Cela implique qu'il devrait y avoir au moins un T : TryFrom<T> avec Error = ! , mais la version pour tout From infaillible est clairement meilleure que cela.

IMO, il n'y a pas vraiment de distinction claire entre si From devrait fournir une implémentation TryFrom ou si TryFrom devrait fournir une implémentation From .

Parce que d'une part, vous pourriez considérer T::from(val) n'est que T::try_from(val).unwrap() , et d'autre part, vous pourriez considérer T::try_from(val) n'est que Ok(T::from(val)) . Ce qui est mieux? Je ne sais pas.

vous pourriez considérer T::from(val) n'est que T::try_from(val).unwrap()

Je ne suis pas d'accord avec celui-là. Les implémentations From ne devraient jamais paniquer. Seul l'inverse a du sens.

@clarcharr Parce que From ne devrait pas paniquer, les options sont From en termes de TryFrom<Error=!> ou l'inverse. Mais je détesterais que le conseil habituel soit "Vous devriez implémenter TryFrom avec type Error = ! " au lieu de "Vous devriez implémenter From ".

Un moyen d'obtenir un mouvement pour stabiliser cela? Nous manquons de temps avant que la version 1.18 n'entre en version bêta. @sfackler ?

@rfcbot fusion fcp

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

  • [x] @BurntSushi
  • [x] @Kimundi
  • [x] @alexcrichton
  • [x] @aturon
  • [x] @brson
  • [x] @sfackler

Aucun souci actuellement répertorié.

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

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

@sfackler : juste pour vérifier, sommes-nous bons sur diverses préoccupations autour ! et des impls de couverture ? Je sais que nous en avons parlé lors de la réunion des bibliothèques, mais il serait utile d'avoir un résumé ici.

@aturon La dernière discussion à ce sujet était @sfackler se demandant si impl From<T> for U devrait fournir impl TryFrom<T> for UTryFrom::Error = ! .

@briansmith a suggéré qu'une décision à ce sujet soit une RFC distincte une fois que les questions non résolues concernant le type jamais sont résolues.

Le principal problème avec la stabilisation de ceci maintenant n'est-il pas qu'un tel changement ne peut pas être fait sans casser la rétrocompatibilité ? Ou est-ce que la solution est simplement de ne pas aller de l'avant avec ce changement ?

Je pense que l'ensemble actuel d'impls est intenable. Je pourrais comprendre l'une ou l'autre de ces positions:

  1. TryFrom est destiné aux conversions faillibles _uniquement_, il n'a donc pas de choses comme u8 -> u128 ou usize -> usize .
  2. TryFrom est destiné à _toutes_ les conversions, dont certaines sont infaillibles et ont donc un type TryFrom::Error inhabité.

Mais en ce moment, les choses sont dans un état hybride étrange où le compilateur insère un code de vérification pour une conversion i32 -> i32 et pourtant vous ne pouvez pas faire une conversion String -> String .

Quelles sont les objections à ! en tant que type d'erreur ? La seule chose que j'ai remarquée dans un rapide survol était "mais rend les choses un peu plus ennuyeuses à gérer dans de nombreux cas puisque vous n'avez plus un seul type d'erreur pour les choses que vous voulez convertir", mais je ne suis pas convaincu d'accord avec cela puisque vous devez supposer que vous avez passé quelque chose de personnalisé avec un type d'erreur personnalisé dans un contexte générique quoi qu'il arrive.

Le principal problème avec la stabilisation de ceci maintenant n'est-il pas qu'un tel changement ne peut pas être fait sans casser la rétrocompatibilité ? Ou est-ce que la solution est simplement de ne pas aller de l'avant avec ce changement ?

Je suis d'avis qu'il est trop zélé d'ajouter une implémentation générique de TryFrom lorsque From est implémenté. Bien qu'il soit sémantiquement vrai que s'il existe une implémentation From , il existe une implémentation TryFrom qui ne peut pas produire d'erreur, je ne vois pas cette implémentation fournie être pratiquement utile du tout, sans parler d'un besoin assez commun pour qu'il soit fourni par défaut. Si quelqu'un a vraiment besoin de ce comportement pour son type pour une raison quelconque, ce n'est qu'une simple implémentation.

S'il y a un exemple de cas où vous utiliseriez jamais try_from au lieu de from pour une conversion infaillible, je pourrais certainement changer d'avis.

Quelles sont les objections à ! comme type d'erreur ?

! est à des mois de la stabilisation. Choisissez l'un de stabiliser TryFrom dans un avenir proche ou d'avoir le impl<T, U> TryFrom<U> for T where T: From<U> .

Avons-nous envisagé d'utiliser des alias de trait (rust-lang/rfcs#1733) ici ? Lorsque cela atterrit, nous pouvons alias From<T> à TryFrom<T, Error=!> , ce qui rend les deux traits identiques.

@lfairy Cela casserait l'utilisateur impl s de From , hélas.

@glaebhoerl Ouais, vous avez raison 😥 La section motivation de cette RFC mentionne l'aliasing impl s mais la proposition actuelle les interdit.

(Même si ce n'est pas le cas, les méthodes ont des noms différents, etc.)

Cela pourrait figurer dans une liste de souhaits 2.0, mais quoi qu'il en soit, cela ne se produira pas sans rien casser.

Tout d'abord, merci @sfackler pour une excellente conversation à ce sujet sur IRC. Après avoir laissé les choses dans ma tête pendant un moment, voici où je me suis retrouvé.

Choisissez l'un de stabiliser TryFrom dans un avenir proche ou d'avoir le impl<T, U> TryFrom<U> for T where T: From<U> .

Je pense que la question centrale ici est de savoir si les conversions infaillibles appartiennent au trait. Je pense que c'est le cas, pour des choses comme les conversions infaillibles dans le RFC et pour une utilisation générique (analogue à la façon dont il y a le T:From<T> apparemment inutile). Étant donné cela, ce que je veux le plus, c'est éviter un monde où chaque implémenteur de type est censé impl TryFrom<MyType> for MyType , et chaque From impl devrait également aboutir à un TryFrom impl. (Ou obtenez des bogues classés plus tard pour ne pas les avoir fournis.)

Pourrions-nous donc avoir la couverture impl sans stabiliser ! ? Je pense qu'il y a un moyen, puisque nous avons déjà des types similaires à $ ! dans la bibliothèque, tels que std::string::ParseError . (" Cette énumération est un peu maladroite : elle n'existera jamais réellement. ")

Un croquis de comment cela pourrait fonctionner:

  • Un nouveau type, core::convert::Infallible , implémenté exactement comme std::string::ParseError . (Peut-être même changer ce dernier en un alias de type pour le premier.)
  • impl<T> From<Infallible> for T pour qu'il soit compatible dans ? avec n'importe quel type d'erreur (voir les trucs c_foo plus tard)
  • Utilisez Infallible comme type Error dans la couverture impl
  • Plus tard, considérez type Infallible = !; dans le cadre de la stabilisation du type jamais

Je me porterai volontaire pour faire un PR faisant ça, si ça peut aider à le concrétiser.

Quant à c_foo : ce qui précède continuerait à autoriser un code comme celui-ci :

fn foo(x: c_int) -> Result<i32, TryFromIntError> { Ok(x.try_into()?) }

Mais cela ferait du code comme celui-ci un "footgun" de portabilité, en raison des différents types d'erreurs

fn foo(x: c_int) -> Result<i32, TryFromIntError> { x.try_into() }

Personnellement, je ne suis pas concerné par cette différence car tant que c_int est un alias de type, il y a un footgun "full-auto":

fn foo(x: c_int) -> i32 { x }

Et en général, le code qui s'attend à ce que le type associé à un trait soit le même pour différents impl me semble être une odeur de code. J'ai lu TryFrom comme "généraliser From " ; si l'objectif était "de meilleures conversions entre des sous-ensembles d'entiers" - ce qui semble également utile - alors le "toujours le même type d'erreur" est logique, mais je m'attendrais à quelque chose ciblé dans std::num la place, probablement comme num::cast::NumCast (ou boost::numeric_cast ).

(A part: avec #[repr(transparent)] dans la fusion FCP, peut-être que les types c_foo peuvent devenir de nouveaux types, auquel cas ces conversions pourraient être plus cohérentes. Les impls From&TryFrom pourraient codifier le C "char <= short < = int <= long", ainsi que les tailles minimales requises par la norme, telles que c_int:From<i16> ou c_long:TryFrom<i64> . La conversion ci-dessus serait alors i32:TryFrom<c_int> sur tous plates-formes, avec toujours le même type Error , et le problème disparaît.)

Concernant "Cette énumération est légèrement gênante : elle n'existera jamais réellement."

Y a-t-il une raison pour laquelle le type d'erreur ne peut pas être simplement unité ? Pourquoi s'embêter avec la structure unique ParseError si la conversation ne peut jamais générer d'erreur ?

@sunjay () est la représentation du système de type de "cela peut arriver, mais il n'y a rien d'intéressant à vous dire quand ça arrive". Les types inhabités (comme ! et std::string::ParseError ) sont à l'opposé, la façon dont le système de type dit "cette situation ne peut jamais arriver, vous n'avez donc pas besoin de vous en occuper".

@jimmycuadra

S'il y a un exemple de cas où vous utiliseriez un jour try_from au lieu de from pour une conversion infaillible, je pourrais certainement changer d'avis.

@scottmcm

Je pense que la question centrale ici est de savoir si les conversions infaillibles appartiennent au trait.

Voici mon cas d'utilisation : j'ai un format de fichier de configuration où les valeurs peuvent être booléennes, numériques ou de chaîne, et une macro pour écrire des valeurs de configuration littérales où les clés peuvent être soit des variantes d'énumération, soit une chaîne. Par example:

let cfg = config![
    BoolOpt::SomeCfgKey => true,
    "SomeOtherCfgKey" => 77,
];

Pour faire court, la macro finit par s'étendre à une liste d'appels ($k, $v).into() . J'aimerais vérifier la conversion des clés de chaîne pour m'assurer qu'elles nomment une option de configuration valide, c'est-à-dire implémenter TryFrom<(String, ???)> et changer la macro pour utiliser ($k, $v).try_into() . Il serait plus difficile de faire tout cela s'il n'y avait pas un seul nom de méthode pour la macro à utiliser pour toutes les conversions.

:bell: Ceci entre maintenant dans sa dernière période de commentaires , conformément à l' examen ci-dessus . :cloche:

En fait, j'aime beaucoup l'idée de :

impl<U: TryFrom<T, Error=!>> From<T> for U {
    fn from(val: T) -> U {
        val.unwrap()
    }
}

Parce que quiconque veut TryFrom<Error=!> peut l'implémenter, mais les gens peuvent toujours implémenter From s'ils le souhaitent. Peut-être pourrions-nous finalement déprécier From , mais nous n'y sommes pas obligés.

Le plan de @scottmcm pour utiliser une énumération vide me semble très bien.

@Ericson2314 vous avez écrit :

Pas si vous utilisez simplement From pour les types d'erreurs (par exemple try! ) ! impl<T> From<!> for T peut et doit exister à cette fin.

Comment cela fonctionnerait-il en pratique ? Supposons que j'essaie d'écrire une fonction comme celle-ci :

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError>

Sauf que cela ne fonctionne bien sûr pas, je dois spécifier Error= sur TryInto . Mais quel type dois-je écrire là-bas? MyError semble évident mais je ne peux pas utiliser MyType avec la couverture TryFrom impl.

Proposez-vous ce qui suit ?

fn myfn<E: Into<MyError>, P: TryInto<MyType, Error=E>>(p: P) -> Result<(), MyError>

Cela semble assez verbeux.

Il y a une question plus générale sur la façon dont cela est censé fonctionner si je veux plusieurs conversions "infaillibles" pour le même type mais avec différents types d'erreurs.

Peut-être que la définition TryFrom devrait être modifiée comme suit :

pub trait TryFrom<T, E>: Sized {
    type Error: Into<E>;

    fn try_from(t: T) -> Result<Self, E>;
}

Vous pouvez contraindre l'erreur à être convertible en MyError sans avoir à lui donner un nom explicite, comme

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError> where MyError: From<P::Error>

c'est encore un peu verbeux, mais énonce vraiment les contraintes pour bien appeler la fonction ( terrain de jeu )

EDIT: Et essayer avec une variante comme P::Error: Into<MyError> ne fonctionne pas réellement avec ? car il n'y a pas d'implémentation globale de From pour Into . En changeant TryFrom comme vous l'avez montré, je m'attendrais à rencontrer le même problème.

La dernière période de commentaires est maintenant terminée.

@jethrogb heh tu m'as cité il y a un an donc j'ai dû réfléchir un peu. @ Nemo157 a tout à fait raison et cela semble raisonnable.

Dans le cas spécifique de ! , nous devrions avoir un impl global, mais je me souviens qu'il en chevauche un autre. C'est un chevauchement ennuyeux car les deux implémentations s'accordent sur l'implémentation --- comportement indéfini / code mort.

Un commentaire à ce sujet d'un autre numéro : https://github.com/rust-lang/rust/pull/41904#issuecomment -300908910

Quelqu'un de l'équipe libs a-t-il un avis sur l'idée de scottmcm ? Cela me semble être une excellente approche, et j'aimerais continuer à faire avancer cette fonctionnalité après avoir raté un autre cycle de publication.

L'équipe libs en a reparlé il y a quelques semaines (désolé pour le retard dans la rédaction) ! Nous sommes arrivés à la conclusion que la source des problèmes liés à cette fonctionnalité était principalement que le cas FFI ne correspondait vraiment à aucun autre cas d'utilisation de ces traits - il est unique en ce sens que vous l'appelez sur des types concrets via des alias qui varient basé sur la cible.

Ainsi, le plan d'action de base consiste à ajouter le impl<T, U> TryFrom<T> for U where U: From<T> et à supprimer les impls explicites pour les conversions d'entiers qui sont infaillibles. Pour gérer le cas d'utilisation FFI, nous créerons une API distincte.

L'utilisation d'un alias de type pour éviter le blocage sur ! est intéressante. Ma seule préoccupation est de savoir si ! est plus "spécial" que les types inhabités normaux, ce qui causerait une rupture lorsque nous échangeons l'alias d'un enum inhabité vers ! .

J'ai ouvert un PR pour "l'API séparée" pour les types intégraux : https://github.com/rust-lang/rust/pull/42456

Je n'ai jamais eu à écrire de code générique paramétré par TryInto ou TryFrom malgré de nombreuses conversions, donc cette dernière forme est suffisante pour toutes mes utilisations dans les types que je définis. Je pense qu'avoir des paramètres TryInto<...> ou TryFrom<...> semble être une forme discutable.

J'ai l'intention d'utiliser TryFrom une fois qu'il est stable dans le cadre d'un trait dérivé, et il serait vraiment étrange d'appeler des méthodes intrinsèques ad hoc sur certains types dans le cadre d'une macro derive .

S'il vous plaît, ne le supprimez pas.

Je n'ai jamais eu à écrire de code générique paramétré par TryInto ou TryFrom

Même si c'est le cas, je ne pense pas que cela rende TryInto et TryFrom beaucoup moins utiles. J'utilise Into et From partout dans des contextes non génériques. L'ajout impl s de traits de bibliothèque standard semble beaucoup plus "normal" et "attendu" qu'un tas de méthodes de conversion inhérentes ad hoc.

Je n'ai jamais eu à écrire de code générique paramétré par TryInto ou TryFrom

D'un de mes projets :

pub fn put_str_lossy<C, S> (&self, s: S)
    where C: TryInto<ascii::Char>,
          S: IntoIterator<Item = C>
{
    for c in s.into_iter() {
        self.put_char(match c.try_into() {
            Ok(c) => c,
            Err(_) => ascii::QUESTION_MARK,
        });
    }
}

S'attend-on à ce que les implémentations de ces traits suivent des lois particulières ? Par exemple, si on peut convertir A en B et B en A, faut-il que la conversion soit inversible lorsqu'elle réussit ? :

#![feature(try_from)]

use std::convert::{TryFrom, TryInto};

fn invertible<'a, A, B, E>(a: &'a A) -> Result<(), E>
    where A: 'a + TryFrom<&'a B>,
          A: PartialEq,
          B: 'a + TryFrom<&'a A>,
          E: From<<A as TryFrom<&'a B>>::Error>,
          E: From<<B as TryFrom<&'a A>>::Error>,
{
    let b = B::try_from(a)?;
    let a2 = A::try_from(&b)?;
    assert!(a == &a2);
    Ok(())
}

edit: s/réflexif/inversible/

@briansmith Étant donné le fonctionnement From , je dirais que ce n'est pas inversible de cette façon.

use std::collections::BinaryHeap;

fn main() {
    let a = vec![1, 2];
    let b = BinaryHeap::from(a.clone());
    let c = Vec::from(b);
    assert_ne!(a, c);
}

Je m'interroge donc sur les implémentations actuelles de ce trait. Comme indiqué dans # 43127 (voir aussi # 43064), je ne sais pas si c'est uniquement parce que l'implémentation utilise i/u128, mais il semble que les appels TryInto ne soient pas en ligne. Ce n'est pas très optimal, et pas vraiment dans l'esprit des abstractions à coût nul. Par exemple, l'utilisation <u32>::try_into<u64>() devrait optimiser jusqu'à une simple extension de signe dans l'assemblage final (à condition que la plate-forme soit 64 bits), mais à la place, cela entraîne un appel de fonction.

Existe-t-il une exigence selon laquelle les implémentations du trait doivent être les mêmes pour tous les types d'entiers ?

Selon # 42456, nous n'aurons probablement pas impl TryFrom directement sur les types entiers, mais à quoi ressemble ce trait "NumCast" (vers lequel # 43127 devrait basculer) est toujours en cours de rédaction.

Qu'elles finissent ou non par passer à un autre trait, ces conversions sont implémentées aujourd'hui dans libcore et peuvent être utilisées dans libcore. Je pense que l'utilisation u128 / i128 pour toutes les conversions d'entiers a été faite pour la simplicité du code source. Nous devrions probablement plutôt avoir un code différent selon que le type de source est plus large ou plus étroit que la destination. (Peut-être avec différents appels de macro basés sur #[cfg(target_pointer_width = "64")] vs #[cfg(target_pointer_width = "32")] vs #[cfg(target_pointer_width = "16")] .)

Rappel : il ne faut pas grand chose pour débloquer celui-ci ! Si vous êtes partant, jetez un œil au résumé de @sfackler et n'hésitez pas à me contacter ou à envoyer un ping à d'autres membres de l'équipe libs pour obtenir des conseils.

Je n'avais pas réalisé que l'équipe des bibliothèques avait accepté que nous puissions utiliser l'idée de @scottmcm comme solution de contournement pour le type de bang instable. Je peux travailler sur un PR pour apporter les modifications mentionnées par @sfackler en utilisant la solution de contournement.

Génial, merci @jimmycuadra !

La majeure partie de cette discussion semble tourner autour de l'implémentation TryFrom pour les types entiers. Que TryFrom se stabilise ou non ne devrait pas être uniquement dû à ces types, à mon avis.

Il existe d'autres conversions intéressantes qui bénéficieraient de ces caractéristiques, telles que TryFrom<&[T]> pour &[T; N] . J'ai récemment soumis un PR pour implémenter exactement ceci : https://github.com/rust-lang/rust/pull/44764.

Des conversions comme celles-ci sont suffisamment importantes pour moi pour que TryFrom soient stabilisés.

Avec # 44174 fusionné, je crois que c'est maintenant débloqué.

Ce PR a supprimé l'implémentation automatique de FromStr pour tout type qui implémente TryFrom<&str> car le système de type ne peut pas actuellement le prendre en charge, même avec une spécialisation. L'intention est que FromStr et parse soient obsolètes au profit de TryFrom<&str> et try_into une fois cette fonctionnalité stabilisée. Il est regrettable de perdre la compatibilité provisoire entre les deux - si quelqu'un a des idées pour un palliatif, veuillez en parler.

S'il n'y a plus de modifications à apporter et que quelqu'un de l'équipe des bibliothèques donne le feu vert à la stabilisation, je peux faire le PR de stabilisation et un PR pour déprécier FromStr / parse .

et un PR pour déprécier FromStr/parse.

Les avertissements de dépréciation ne doivent pas être ajoutés à Nightly tant que le remplacement n'est pas disponible sur Stable (ou jusqu'à ce que https://github.com/rust-lang/rust/issues/30785 soit implémenté), afin qu'il soit possible à tout moment de faire un crate build sans avertissements sur les trois canaux de publication.

J'ai raté l'autre PR puisque les références n'entraînent pas de notifications par e-mail. Je remarque qu'il y a un impl From<Infallible> for TryFromIntError spécifique. Cela ne devrait-il pas être impl<T> From<Infallible> for T comme discuté ?

@jethrogb Malheureusement, cela entre en conflit avec impl<T> From<T> for T et ne peut donc pas être fait (jusqu'à ce que nous obtenions des implémentations d'intersection? - et utiliser ! ne fonctionne pas non plus là-bas).

@scottmcm ah bien sûr.

Je ne pense pas que vous ayez besoin d'impls d'intersection? N'est-ce pas une simple spécialisation ?

Je n'ai pas lu les autres commentaires, mais TryFrom est cassé pour moi en ce moment (cela fonctionnait bien avant).

version rustique :

rustc 1.22.0-nightly (d6d711dd8 2017-10-10)
binary: rustc
commit-hash: d6d711dd8f7ad5885294b8e1f0009a23dc1f8b1f
commit-date: 2017-10-10
host: x86_64-unknown-linux-gnu
release: 1.22.0-nightly
LLVM version: 4.0

La section de code pertinente dont Rust se plaint est ici : https://github.com/fschutt/printpdf/blob/master/src/types/plugins/graphics/two_dimensional/image.rs#L29 -L39 et https://github .com/fschutt/printpdf/blob/master/src/types/plugins/graphics/xobject.rs#L170 -L200 - il s'est bien compilé il y a quelques semaines, c'est pourquoi la bibliothèque a toujours le badge "build pass".

Cependant, sur la dernière version nocturne, TryFrom semble casser :

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::two_dimensional::image::Image`:
  --> src/types/plugins/graphics/two_dimensional/image.rs:29:1
   |
29 | / impl<T: ImageDecoder> TryFrom<T> for Image {
30 | |     type Error = image::ImageError;
31 | |     fn try_from(image: T)
32 | |     -> std::result::Result<Self, Self::Error>
...  |
38 | |     }
39 | | }
   | |_^
   |
   = note: conflicting implementation in crate `core`

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::xobject::ImageXObject`:
   --> src/types/plugins/graphics/xobject.rs:170:1
    |
170 | / impl<T: image::ImageDecoder> TryFrom<T> for ImageXObject {
171 | |     type Error = image::ImageError;
172 | |     fn try_from(mut image: T)
173 | |     -> std::result::Result<Self, Self::Error>
...   |
199 | |     }
200 | | }
    | |_^
    |
    = note: conflicting implementation in crate `core`

error: aborting due to 2 previous errors

error: Could not compile `printpdf`.

Donc, soi-disant, il a une implémentation dupliquée dans crate core . Si quelqu'un pouvait se pencher là-dessus, ce serait super, merci.

@fschutt L'impl en conflit est probablement impl<T, U> TryFrom<T> for U where U: From<T> , ajouté dans https://github.com/rust-lang/rust/pull/44174. Il pourrait exister un T tel que T: ImageDecoder et Image: From<T> .

Y a-t-il quelque chose qui est encore nécessaire pour que cela quitte la porte de la fonctionnalité ?

Si https://github.com/rust-lang/rust/issues/35121 est stabilisé en premier, nous pourrions supprimer le type Infallible introduit dans https://github.com/rust-lang/rust/pull/ 44174 et utilisez ! la place. Je ne sais pas si c'est considéré comme une exigence.

Je pense que le principal bloqueur ici reste les types entiers. Soit nous utilisons un trait Cast séparé pour les types entiers https://github.com/rust-lang/rust/pull/42456#issuecomment -326159595, soit nous faisons en sorte que la peluche de portabilité #41619 se produise en premier.

J'avais donc l'habitude d'avoir une énumération qui implémentait TryFrom pour AsRef<str> , mais cela s'est cassé il y a quelques mois. Je pensais que c'était un bogue introduit dans nightly qui disparaîtrait avec le temps, mais il semble que ce ne soit pas le cas. Ce modèle n'est-il plus pris en charge pour TryFrom ?

impl<S: AsRef<str>> TryFrom<S> for MyEnum {
    type Error = &'static str;

    fn try_from(string: S) -> Result<Self, Self::Error> {
        // Impl here
    }
}
...

Quelles autres options existe-t-il pour convertir à la fois &str et String ?

@kybishop est-ce que MyEnum implémente FromStr ? Cela peut être la source de votre casse.

@nvzqz ce n'est pas le cas, bien qu'il m'ait été recommandé d'utiliser TryFrom via l'IRC Rust car il s'agit d'une solution plus généralisée. TryFrom a d'abord bien fonctionné jusqu'à ce qu'un changement radical se produise la nuit il y a quelques mois.

EDIT : Voulez-vous dire que je devrais passer à l'implémentation FromStr , ou que s'il implémentait FromStr , cela pourrait provoquer une rupture ?

EDIT 2 : Voici l'implémentation complète, plutôt courte et simple pour les curieux : https://gist.github.com/kybishop/2fa9e9d32728167bed5b1bc0b9becd97

@kybishop y a-t-il une raison particulière pour laquelle vous souhaitez une implémentation pour AsRef<str> plutôt que &str ?

@sfackler J'avais l'impression qu'il permettait la conversion à la fois de &str et String , bien que je sois encore un peu un novice de Rust, alors peut-être que je ne comprends pas exactement comment AsRef<str> est utilisé. Je vais essayer de désactiver &str et de voir si AsRef autorise quelque chose que &str ne permet pas.

@kybishop Identique à https://github.com/rust-lang/rust/issues/33417#issuecomment -335815206, et comme le message d'erreur du compilateur l'indique, il s'agit d'un véritable conflit avec l'implémentation impl<T, U> std::convert::TryFrom<U> for T where T: std::convert::From<U> qui était ajouté à libcore.

Il pourrait y avoir un type T (éventuellement dans une caisse en aval) qui implémente à la foisFrom<MyEnum> et AsRef<str>et a MyEnum: From<T> . Dans ce cas, les deux impls s'appliqueraient, donc les deux impls ne sont pas autorisés à exister ensemble.

@SimonSapin a compris. Alors, quelles sont les options pour les personnes souhaitant convertir à la fois &str et &String ? Il suffit d'implémenter TryFrom pour les deux ?

EDIT: Je suppose que je peux juste manger le travail supplémentaire et appeler .as_ref() sur String s. Je peux alors avoir un seul TryFrom impl pour str .

Oui, deux implémentations (éventuellement l'une basée sur l'autre, comme vous l'avez souligné) devraient fonctionner tant que MyEnum n'implémente pas From<&str> ou From<String> .

@kybishop String implémente Deref<str> , donc l'inférence de type devrait permettre &String de contraindre en &str lors de son passage dans l' TryFrom . Il n'est pas toujours nécessaire d'appeler as_ref .

Avec https://github.com/rust-lang/rust/pull/47630 sur le point de se stabiliser ! , y a-t-il un appétit pour un PR pour remplacer Infallible par ! ici ?

Une meilleure voie à suivre serait de créer un alias. Il conserve l'expressivité et utilise la fonctionnalité de langage adaptée.

type Infallible = !;

Je viens de sauter. Je suis avec @scottmcm à ce sujet.

Cependant, cela ajoute la surcharge supplémentaire que cette fonctionnalité ( TryInto / TryFrom ) dépend désormais d'une autre fonctionnalité instable - never_type .

De plus, Infallible a l'avantage de donner plus d'informations/de sémantique sur les raisons pour lesquelles le type ne peut pas être construit. Je suis opiniâtre avec moi-même en ce moment.

J'aimerais que ! devienne un type de vocabulaire suffisant pour que Result<_, !> soit intuitivement lu comme "résultat infaillible", ou "résultat qui ne se trompe (littéralement) jamais". L'utilisation d'un alias (peu importe un type séparé !) me semble un peu redondant et je peux le voir me causer au moins une pause momentanée lors de la lecture des signatures de type - "attendez, en quoi était-ce différent de ! à nouveau ?" YMMV, bien sûr.

@jdahlstrom Je suis totalement d'accord. Nous aurions besoin d'introduire cela dans le livre Rust ou le nomicon pour que ce soit une «vérité de terrain» et conviviale.

Cela fait maintenant deux ans que la RFC pour cette API a été soumise.

~ @briansmith : Il y a un PR de stabilisation en cours.~

EDIT : Ou peut-être que je suis trop fatigué...

Vous avez lié le PR de la stabilisation de type ! .

Depuis que le PR de stabilisation ! vient d'être fusionné, j'ai soumis un PR pour remplacer convert::Infallible par ! : #49038

https://github.com/rust-lang/rust/pull/49038 est fusionné. Je crois que c'était le dernier obstacle à la stabilisation, veuillez me faire savoir si j'ai raté un problème non résolu.

@rfcbot fusion fcp

rfcbot ne répond pas car un autre FCP a déjà été complété auparavant sur https://github.com/rust-lang/rust/issues/33417#issuecomment -302817297.

Uhmm, il y a quelques impls qui devraient être revus. Maintenant que nous avons impl<T, U> TryFrom<U> for T where T: From<U> , les impls restants de TryFrom qui ont type Error = ! devraient soit être remplacés par des impls de From , soit être supprimés, soit rendus faillibles ( changer le type d'erreur en un type non habité).

Ceux que je peux trouver dans ce cas impliquent usize ou isize . Je suppose que les impls From correspondants n'existent pas car leur faillibilité dépend de la taille du pointeur cible. En effet les TryFrom impls sont générés différemment pour différentes cibles : https://github.com/rust-lang/rust/blob/1.24.1/src/libcore/num/mod.rs#L3103 -L3179 ​​This est probablement un danger de portabilité.

généré différemment pour différentes cibles

Pour clarifier : différents corps de méthode pour différents target_pointer_width dans le même impl sont corrects (et probablement nécessaires), différentes API (types d'erreur) ne le sont pas.

PR de stabilisation : #49305. Après quelques discussions, ce PR supprime également certaines implémentations TryFrom qui impliquent usize ou isize parce que nous n'avons pas décidé entre deux façons différentes de les implémenter. (Et nous ne pouvons bien sûr en avoir qu'un.)

Problème de suivi dédié pour ceux-ci : https://github.com/rust-lang/rust/issues/49415

TryFrom a parfaitement fonctionné sur rustc 1.27.0-nightly (ac3c2288f 2018-04-18) sans aucun blocage de fonctionnalité, mais s'est cassé lors de la compilation avec rustc 1.27.0-nightly (66363b288 2018-04-28) .

Y a-t-il eu des régressions vers la stabilisation de cette fonctionnalité au cours des 10 derniers jours ?

@kjetilkjeka Cette fonctionnalité a été récemment déstabilisée : #50121.

(Réouverture puisque la stabilisation a été annulée)

@kjetilkjeka La stabilisation de TryFrom a été annulée dans https://github.com/rust-lang/rust/pull/50121 avec la stabilisation du type ! , à cause du TryFrom<U, Error=!> for T where T: From<U> impl. Le type ! n'était pas stabilisé à cause de https://github.com/rust-lang/rust/issues/49593.

Merci d'avoir expliqué. Cela signifie-t-il que cette fonctionnalité est essentiellement bloquée sur certaines modifications apportées à la coercition du type de compilateur ? Je ne trouve pas de problème expliquant quels changements sont nécessaires, l'ampleur des changements est-elle connue à ce stade ?

Y a-t-il une raison fondamentale pour laquelle nous ne pouvions pas aller de l'avant et stabiliser le trait TryFrom lui-même, et tous les impls qui n'impliquent pas ! , et simplement différer les impls de stabilisation impliquant ! qu'après la possible stabilisation de ! ?

https://github.com/rust-lang/rust/pull/49305#issuecomment -376293243 classe les différentes implémentations de traits possibles.

@joshtriplett Pour autant que je sache, impl<T, U> TryFrom<T> for U where U: From<T> { type Err = !; } en particulier n'est pas rétrocompatible à ajouter si TryFrom a déjà été stable.

@SimonSapin
Pourquoi n'est-ce pas rétrocompatible à ajouter?

(Et avons-nous vraiment besoin de la couverture impl?)

Pourquoi n'est-ce pas rétrocompatible à ajouter?

Je pense que ce n'est pas rétrocompatible puisque TryFrom aurait pu être implémenté manuellement entre TryFrom a été stabilisé et ! a été stabilisé. Lorsque la mise en œuvre générale était ajoutée, elle serait en conflit avec la mise en œuvre manuelle.

(Et avons-nous vraiment besoin de la couverture impl?)

Je pense que la couverture impl a vraiment du sens lors de l'écriture de code générique sur TryFrom . Lorsque vous vous référez à tous les types qui ont une possibilité de conversion en T , vous voudrez la plupart du temps également inclure tous les types qui peuvent être convertis en T à coup sûr. Je suppose qu'une alternative pourrait être d'exiger que tout le monde implémente également TryFrom pour tous les types qui implémentent From et attende la spécialisation avant d'implémenter la couverture. Vous auriez toujours le problème de ce que Err faire Result générique. ! semble être un bon moyen à la fois d'exprimer et d'appliquer par programme l'infaillibilité de la conversion, et j'espère que cela vaut la peine d'attendre.

Nous pourrions toujours attendre que la spécialisation soit disponible pour fournir l'implémentation globale et stabiliser les diverses conversions numériques dans l'intervalle.

J'ai vu diverses demandes à ce sujet dans le contexte de personnes cherchant de l'aide pour savoir comment effectuer des conversions d'entiers de manière idiomatique dans Rust, et aujourd'hui encore, j'ai rencontré quelqu'un qui se demandait spécifiquement comment convertir u64 en u32 avec détection d'erreur.

Je ne suis pas convaincu que la spécialisation permettra comme par magie d'ajouter l'impl de couverture après coup. Ma compréhension des propositions actuelles est qu'un trait doit choisir d'être spécialisable, ce qui peut ne pas être compatible avec les traits existants.

Pour chaque fois que cela est stabilisé : _veuillez_ ajouter TryFrom et TryInto au prélude.

@SergioBenitez Nous l'avons fait sur Nightly pendant un certain temps, et malheureusement, c'était un changement radical pour les caisses qui définissent leur propre trait TryFrom ou TryInto (à utiliser pendant que le std sont instables), suffisamment pour que nous l'ayons annulé.

Je pense que la leçon à apprendre ici pour l'équipe std libs est que lorsqu'il y a un trait que nous pourrions vouloir ajouter au prélude, nous devrions le faire dès qu'il est implémenté et ne pas attendre la stabilisation.

Pour TryFrom et TryInto cependant, une solution serait d'avoir un prélude différent pour l'édition 2018. Cela a déjà été discuté de manière informelle, mais je ne l'ai pas trouvé classé, alors j'ai ouvert https://github.com/rust-lang/rust/issues/51418.

Une solution générique à un problème similaire a été implémentée pour l'extension
méthodes dans https://github.com/rust-lang/rust/issues/48919 , où unstable
les méthodes d'extension émettent des avertissements lorsque du code stable entre en collision.

Il semble que vous pourriez faire quelque chose de similaire avec de nouveaux termes ajoutés au
prélude?

Le jeudi 7 juin 2018 à 11 h 47, Simon Sapin [email protected] a écrit :

@SergioBenitez https://github.com/SergioBenitez Nous l'avons fait sur
Tous les soirs pendant un moment, et malheureusement c'était un changement radical pour les caisses
qui définissent leur propre trait TryFrom ou TryInto (à utiliser pendant que le std
ceux sont instables), suffisamment pour que nous l'ayons inversé.

Je pense que la leçon à apprendre ici pour l'équipe std libs est que lorsque
il y a un trait que nous pourrions vouloir ajouter au prélude, nous devrions le faire
donc dès qu'il est mis en place et ne pas attendre la stabilisation.

Pour TryFrom et TryInto cependant, une solution serait d'avoir un autre
prélude à l'édition 2018. Cela a déjà été discuté de manière informelle, mais
Je ne l'ai pas trouvé classé, alors j'ai ouvert #51418
https://github.com/rust-lang/rust/issues/51418 .


Vous recevez ceci parce que vous êtes abonné à ce fil.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/33417#issuecomment-395525170 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAC2lNbHvgBjWBk48-1UO311-LuUY5lPks5t6XUvgaJpZM4IXpys
.

Dans ce cas, ce qui se heurte, ce ne sont pas les traits eux-mêmes (le nom de l'élément dans le prélude) mais les méthodes mises en évidence par ce trait. Peut-être y a-t-il encore des ajustements similaires que nous pouvons faire, mais c'est plus subtil.

@SimonSapin quel est le plan actuel pour la stabilisation TryFrom/TryInto ? Je n'ai rien trouvé de concret au-delà de son annulation le 29 avril.

@nayato Il est bloqué en stabilisant (encore) le jamais taper https://github.com/rust-lang/rust/issues/35121 , qui lui-même est bloqué sur https://github.com/rust-lang/rust/ numéros/49593.

@SimonSapin Le type jamais n'est utilisé qu'ici . Pourrions-nous stabiliser TryFrom sans cette mise en œuvre et l'atterrir quand jamais n'est stabilisé ? TryFrom est une interface assez importante même en dehors de l'équivalence avec Try .

Malheureusement non. Ce serait un changement radical d'ajouter cette implémentation globale après que le trait a été stabilisé et que les bibliothèques crates.io ont eu la possibilité d'implémenter par exemple TryFrom<Foo> for Bar alors qu'elles ont également From<Foo> for Bar , comme les impls se superposeraient.

Jusqu'à présent, la décision a été que rendre TryFrom "compatible" de cette manière avec From était suffisamment important pour bloquer la stabilisation.

Pensée probablement naïve et idiote :

Et si rustc recevait une erreur permanente lint entre-temps qui se déclenchait pour Error<_, !> , empêchant toute implémentation dans userland, permettant l'ajout de impl s plus tard ?

Ou un impl<T: From<U>, U> TryFrom<U> for T instable avec n'importe quel type d'erreur. Les impls de trait peuvent-ils être instables ?

@jethrogb Non

Je ne suis pas sûr de comprendre entièrement pourquoi cela est bloqué sur le type jamais, mais le fait que ce soit le cas me suggère qu'un mécanisme important manque à Rust. Il semble qu'il devrait y avoir un moyen de réserver l'implémentation souhaitée pour une définition future. Peut-être que ce mécanisme revient à le rendre instable, mais idéalement, ce serait également un mécanisme utilisable par des caisses non standard. Est-ce que quelqu'un connaît une proposition de solution à ce problème?

Il est bloqué à cause de cet impl :

impl<T, U> TryFrom<U> for T where T: From<U> {
    type Error = !;

    fn try_from(value: U) -> Result<Self, Self::Error> {
        Ok(T::from(value))
    }
}

@rust-lang/libs Que pensez-vous de revenir à enum Infallible {} au lieu du type jamais, pour débloquer ?

Personnellement, cela ne me dérange pas d'avoir enum Infalliable {} puis de le changer plus tard en type Infalliable = ! .

Nous en avons discuté lors de la réunion de triage des bibliothèques d'hier, mais nous ne pouvions pas nous souvenir si un tel changement serait acceptable après la stabilisation ou si nous avions déjà rejeté cette idée en raison d'une casse potentielle, ou quelle serait cette casse.

Ce dont nous nous sommes souvenus, c'est que nous ne pouvons effectuer qu'un seul remplacement de ce type : si deux ou plusieurs énumérations vides de bibliothèque standard stables (types séparés) deviennent plus tard le même type, une caisse contenant des impl s pour les deux cassera au fur et à mesure que les impls se chevauchent (ou sont identiques). Par exemple impl From<std::convert::Invallible> for MyError et impl From<std::string::ParseError> for MyError .

Soit dit en passant, nous avons std::string::ParseError qui, pour autant que je sache, est la seule énumération vide de la bibliothèque standard 1.30.0. Donc, si nous pouvons être sûrs qu'il n'y a pas d'autre problème avec ce plan, nous pourrions :

  • Déplacez string::ParseError vers convert::Infallible
  • Réexportez-le à son ancien emplacement avec pub use ou pub type (est-ce que cela fait une différence ?)
  • Utilisez-le dans l'outil de couverture TryFrom
  • Plus tard, dans la même version que lorsque le type jamais est stabilisé, remplacez string::ParseError et convert::Infallible par type _ = ! et utilisez ! directement là où ils ont été utilisés.
  • (Facultatif) Plus tard, émettre des avertissements d'obsolescence pour les anciens alias

Ayant juste et à contrecœur ajouté un trait d'espace réservé TryFrom à ma propre caisse, et certes sans bien comprendre les implications du RFC et des efforts de stabilisation, je suis surpris qu'une couverture TryFrom pour From avec le type d'erreur Infallible / ! est ce qui retient cela ? Ne s'agit-il pas d'objectifs secondaires, après avoir établi des traits stables std TryFrom et généraux TryInto ? Je veux dire, même sans le soulèvement de From à TryFrom (dont je ne comprends pas tout à fait), ne serait-ce pas moins de désabonnement si chaque caisse qui en a besoin n'ajoute pas son propre TryFrom ?

Le problème évident si TryFrom est expédié sans l'implémentation globale pour From est que les caisses peuvent implémenter TryFrom et From pour les mêmes types (peut-être exactement parce que le couverture impl n'est pas là), et ils se cassent lorsque la couverture impl est ajoutée à libstd.

Bien que, à bien y penser, ce ne serait peut-être pas un changement radical avec la spécialisation ?

Hmm, s'il vous plaît, pardonnez-moi encore si je manque l' évidence . C'est presque comme si ces odyssées RFC/suivi d'un an et demi avaient besoin d'une section FAQ en cours d'exécution pour comprendre la progression logique. Et si les conseils consistaient simplement à implémenter From uniquement pour les conversions infaillibles, et TryFrom uniquement pour les conversions faillibles ? Cela ne donne-t-il pas un résultat final pratique similaire, tout en offrant moins d'obstacles à l'expédition dans des caisses standard et d'ajustement ?

Oui, comme @glandium l'a dit, l'ajout de la couverture après que les traits ont déjà été stables est un changement radical. La stabilisation n'est pas encore prête, et il n'est pas clair qu'elle autoriserait ce type d'impl d'intersection de toute façon (plutôt que seulement des impl "strictement plus spécifiques").

Fournir des conseils (documents ?) indiquant qu'il ne faut pas écrire de programmes susceptibles de casser n'est pas suffisant pour justifier des modifications avec rupture. La casse se produirait certainement encore.

Que faut-il pour que le plan décrit dans https://github.com/rust-lang/rust/issues/33417#issuecomment -423073898 commence à se produire ? Que peut-on faire pour aider ?

C'est un peu une tâche ingrate, mais cela aiderait si quelqu'un pouvait passer par ce problème de suivi et https://github.com/rust-lang/rust/issues/35121 pour vérifier s'il y a un problème avec ce plan que nous ' J'ai discuté avant et oublié depuis, en particulier si le remplacement enum Infallible par type Infallible = !; après que l'énumération ait été stable dans une version précédente pourrait être un changement radical.

L'énumération Infallible doit-elle être stabilisée avec le trait ? S'il reste instable, personne ne peut le nommer, et donc l'échanger contre ! plus tard devrait suffire ?

@seanmonstar Non, vous pouvez vous y référer en utilisant <u16 as TryFrom<u8>>::Error et il est considéré comme un nom stable. Témoin:

// src/lib.rs
#![feature(staged_api)]
#![stable(since = "1.0.0", feature = "a")]

#[stable(since = "1.0.0", feature = "a")]
pub trait T1 {
    #[stable(since = "1.0.0", feature = "a")]
    type A;
}

#[unstable(issue = "12345", feature = "b")]
pub struct E;

#[stable(since = "1.0.0", feature = "a")]
impl T1 for u8 {
    type A = E;
}
// src/bin/b.rs
extern crate a;

trait T3 {}

impl T3 for <u8 as a::T1>::A {}
impl T3 for a::E {}

fn main() {}

Le premier T3 impl ne provoque aucune erreur. Seule la deuxième implémentation T3 provoque l'erreur E0658 "Utilisation d'une fonctionnalité de bibliothèque instable".

C'est... Wow, je demande juste à être mordu >_<

J'utilise personnellement l'astuce consistant à rendre un type public dans un module non exporté pour renvoyer des types sans nom, et bien que quelqu'un faisant ce que vous avez dit signifierait une rupture s'il le faisait, mes sentiments sont "honte pour eux", et ne considérez pas ajuster le type sans nom comme cassant.

Personnellement, cela ne me dérange pas de m'assurer que chacune de ces énumérations pas vraiment jamais dans libstd est la même, puis de la changer en un alias de type to ! quand ça devient stable. Cela me semble raisonnable.

Quel est le choix de conception pour une conversion faillible sur un type autre que Copy ? Il convient de veiller à ne pas laisser tomber l'entrée donnée, afin qu'elle puisse être récupérée en cas de panne.

Par exemple, avec String::from_utf8 , on peut voir que le type d'erreur contient l'entrée propriétaire Vec<u8> afin de pouvoir la restituer :

// some invalid bytes, in a vector
let sparkle_heart = vec![0, 159, 146, 150];

match String::from_utf8(sparkle_heart) {
    Ok(string) => {
        // owned String binding in this scope
        let _: String = string;
    },
    Err(err) => {
        let vec: Vec<u8> = err.into_bytes(); // we got the owned vec back !
        assert_eq!(vec, vec![0, 159, 146, 150]);
    },
};

Donc, si nous devions obtenir une implémentation de String: TryFrom<Vec<u8>> , on s'attendrait à ce que <String as TryFrom<Vec<u8>>>::Error soit FromUtf8Error , n'est-ce pas ?

Oui, donner la valeur d'entrée "back" dans le type d'erreur est une option valide. Prendre une référence avec impl<'a> TryFrom<&'a Foo> for Bar en est une autre.

Dans cet exemple spécifique, cependant, je ne suis pas sûr qu'un impl TryFrom soit approprié. UTF-8 n'est qu'un des décodages possibles d'octets en Unicode, et from_utf8 reflète cela dans son nom.

C'est un peu une tâche ingrate, mais cela aiderait si quelqu'un pouvait passer par ce problème de suivi et #35121 pour vérifier s'il y a un problème avec ce plan dont nous avons discuté avant et oublié depuis, en particulier s'il remplace enum Infallible avec type Infallible = !; après que l'énumération ait été stable dans une version précédente pourrait être un changement de rupture.

Aucun problème concret n'a été signalé dans ce numéro ou # 35121. Il y avait une préoccupation autour de la possibilité ! soit spécial d'une certaine manière, les types non inhibés ne le sont pas. Mais aucune inquiétude dans le PR et il ressort clairement des commentaires de révision du code que l'énumération devenant stable était une possibilité (bien que cela ne se soit jamais produit). Voici les liens vers ce que j'ai trouvé.

Conception originale
Une préoccupation abstraite
Feu vert de l'équipe lib

Suivie par:

44174 29 septembre : ajout du type Infaillible

47630 14 mars : stabilisez le type jamais !

49038 22 mars : converti Infaillible au type jamais !

49305 27 mars : TryFrom stabilisé

49518 30 mars : retiré du prélude

50121 21 avril : non stabilisé

Sur la note d'implémentations générales pour quand ! se stabilise,

est-ce que ça marcherait ?

impl<T, U> TryFrom<U> for T
where U: Into<T> {
    type Err = !;

    fn try_from(u: U) -> Result<Self, !> { Ok(u.into()) }
}

impl<T, U> TryInto<U> for T
where U: TryFrom<T> {
    type Err = U::Err;

    fn try_into(self) -> Result<U, !> { U::try_from(self) }
}

De cette façon, toutes les conversions infaillibles (avec From et Into ) obtiennent un TryFrom et TryInto impl où le Err est ! et nous obtenons un TryInto impl pour chaque TryFrom impl comme la façon dont nous obtenons un Into impl pour chaque From impl.

De cette façon, si vous vouliez écrire un impl, vous essaieriez From puis Into puis TryFrom puis TryInto , et l'un d'entre eux fonctionnerait pour votre scénario , si vous avez besoin de conversions infaillibles, vous utiliserez From ou Into (basé sur des règles de cohérence) et si vous avez besoin de conversions faillibles, vous utiliserez TryFrom ou TryInto (sur la base de règles de cohérence). Si vous avez besoin de contraintes, vous pouvez choisir TryInto ou Into selon que vous pouvez gérer les conversions faillibles. TryInto seraient toutes les conversions, et Into seraient toutes les conversions infaillibles.

Ensuite, nous pouvons pelucher contre un impl TryFrom<T, Err = !> ou impl TryInto<T, Err = !> avec un indice pour utiliser impl From<T> ou impl Into<T> .

Ah, je ne les ai pas vus (trop d'autres impls encombraient les docs). Pourrions-nous changer

impl<T, U> TryFrom<U> for T where    T: From<U>,

à

impl<T, U> TryFrom<U> for T where    U: Into<T>,

car cela autorisera strictement plus d'impls, et ne punira pas les gens qui ne peuvent impl que Into pour des raisons de cohérence, de cette façon ils obtiendront également l'impl automatique pour TryFrom , et le From impl serait appliqué de manière transitive en raison de la couverture impl pour Into .

Souhaitez-vous l'essayer, voir s'il compile et envoyer une demande d'extraction ?

Oui je voudrais essayer.

Existe-t-il un cas où From<U> for T ne peut pas être implémenté, mais où Into<T> for U et TryFrom<U> for T peuvent l'être ?

Oui, par exemple

use other_crate::OtherType;

struct MyType;

// this impl will fail to compile
impl From<MyType> for OtherType {
    fn from(my_type: MyType) -> OtherType {
        // impl details that don't matter
    }
}

// this impl will not fail to compile
impl Into<OtherType> for MyType {
    fn into(self) -> OtherType {
        // impl details that don't matter
    }
}

Cela est dû aux règles orphelines.
TryFrom et From sont les mêmes en termes de règles orphelines, et de même TryInto et Into sont les mêmes en termes de règles orphelines

@KrishnaSannasi votre exemple compilera, regardez simplement cet exemple de terrain de jeu

Je pense que la règle de cohérence la plus à jour est décrite dans cette RFC et que les deux implémentations devraient être autorisées.

struct MyType<T>(T);

impl<T> From<MyType<T>> for (T,) {
    fn from(my_type: MyType<T>) -> Self {
        unimplemented!()
    }
}

impl<T> Into<(T,)> for MyType<T> {
    fn into(self) -> (T,) {
        unimplemented!()
    }
}

Cour de récréation
Ah oui, mais dès qu'on ajoute des paramètres génériques ça bascule. Si vous essayez de compiler chaque impl individuellement, la compilation From échouera, tandis que celle Into compilera.


Une autre raison pour laquelle je veux ce changement est qu'actuellement, si vous écrivez une Into , vous ne le faites pas et ne pouvez pas obtenir automatiquement une TryInto . Je considère cela comme une mauvaise conception car elle est incompatible avec From et TryFrom .

Je demande plus s'il y a un cas où vous voudriez que TryFrom<U> for T soit dérivé automatiquement de Into<T> for U , mais où From<U> for T ne peut pas être implémenté. Cela me semble insensé.

Je comprends certainement vouloir une implémentation globale de Into à TryInto , mais malheureusement, le système de type n'autorise pas actuellement une telle implémentation globale car elle entre en conflit avec les autres From à TryFrom et From à Into couverture impls (ou, du moins, c'est ma compréhension).

Je suis à peu près certain que ces cas d'utilisation étaient exactement ce que la RFC @kjetilkjeka liée était censée résoudre. Une fois que cela est implémenté, il ne devrait jamais y avoir de cas où vous pouvez implémenter Into mais pas From .

@scottjmaddox Je ne veux pas nécessairement que Into implique TryFrom autant que je veux que Into implique TryInto . De plus, From et Into sont sémantiquement équivalents, ce n'est pas parce que nous ne pouvons pas exprimer cela dans notre système de traits que cela signifie

TryFrom<U> for T doit être automatiquement dérivé de Into<T> for U , mais where From<U> for T ne peut pas être implémenté.

est absurde. Dans un monde parfait, nous n'aurions pas la distinction et il n'y aurait qu'un seul trait à convertir entre les types, mais en raison des limitations du système, nous nous sommes retrouvés avec deux. Cela ne va pas changer en raison des garanties de stabilité, mais nous pouvons traiter ces deux traits comme le même concept et partir de là.

@clarcharr Même si tel est le cas, il n'y a toujours aucun moyen d'avoir à la fois Into impliquer TryInto et TryFrom impliquer TryInto directement, car les deux couvertures impls serait en conflit. Je voudrais que Into implique TryInto , et que TryFrom implique TryInto . La manière que je propose le fera, bien qu'indirectement.


J'ai fait un commentaire sur ma pull request où j'ai exposé mes principales raisons de ce changement ici


C'est moins important que les raisons ci-dessus, mais j'aime aussi le fait qu'avec cette implémentation, toutes les conversions infaillibles auront désormais une contrepartie faillible, où le type Err de la version faillible est ! .

Juste pour être clair, je veux ces quatre impls

  • From implique Into (pour la stabilité)
  • TryFrom implique TryInto ou TryInto implique TryFrom (car ils sont sémantiquement équivalents)
  • From implique TryFrom
  • Into implique TryInto

Peu m'importe qu'elles soient faites directement ou indirectement.

Ou, nous pourrions faire TryInto un alias :

trait TryInto<T> = where T: TryFrom<Self>;

Je n'ai aucune idée si cela fonctionnerait réellement, mais cela semble assez simple.

Où la méthode into serait-elle définie ?

@clarcharr Comme @SimonSapin l' a dit, où la méthode into serait-elle définie. Nous n'avons pas d'alias de trait. Cela devrait être un autre RFC. Et nous aurions besoin de bloquer celui-ci sur ce RFC, ce qui n'est pas souhaitable.

@KrishnaSannasi Il est actuellement impossible d'avoir les quatre From => Into , From => TryFrom , TryFrom => TryInto et Into => TryInto , car tout ce qui implémente From serait avoir deux impl concurrents pour TryInto (un de From => Into => TryInto et un autre de From => TryFrom => TryInto ).

Étant donné que From => Into et TryFrom => TryInto sont tous les deux critiques, Into => TryInto doit être sacrifié. Ou du moins c'est ma compréhension.

Ouais, je ne pensais pas que l'alias TryInto était correctement alors ignorez-moi ><

Je suis d'accord avec ce que dit @scottjmaddox .

@scottjmaddox

Juste pour dissiper un éventuel malentendu, je supprime From auto impls TryFrom et le remplace par Into auto impls TryFrom .

Il est actuellement impossible d'avoir les quatre From => Into, From => TryFrom, TryFrom => TryInto et Into => TryInto

C'est juste faux, il suffit de regarder l'implémentation proposée.

Celui-là:
From -> Into -> TryFrom -> TryInto

From implémentations automatiques Into
Into impléments automatiques TryFrom
TryFrom implémentations automatiques TryInto

Par extension, en raison des implémentations automatiques transitives, nous obtenons
From implique TryFrom (parce que From auto impls Into et Into auto impls TryFrom )
Into implique TryInto (parce que Into auto impls TryFrom et TryFrom auto impls TryInto )
chacun avec 1 niveau d'indirection (je pense que c'est bien)

Toutes mes conditions sont donc remplies.
Nous pourrions avoir tout

Impl | niveaux d'indirection
-----------------------|----------------------------
From implique Into | Pas d'indirection
TryFrom implique TryInto | Pas d'indirection
From implique TryFrom | 1 niveau d'indirection
Into implique TryInto | 1 niveau d'indirection


From -> Into -> TryInto -> TryFrom
Fonctionnerait aussi, mais je voudrais garder la cohérence et la version originale (vue ci-dessus) est plus cohérente que cette version


Note sur la terminologie : (lorsque je les utilise)

-> signifie mise en œuvre automatique
auto impl fait référence à l'implémentation réelle qui est saisie.
implique signifie que si j'ai cet impl, j'obtiendrai également cet impl


Notez également que j'ai fait une demande d'extraction et que tous les tests ont réussi, ce qui signifie qu'il n'y a pas de trou dans ma logique, c'est possible. Nous avons juste besoin de discuter si ce comportement est voulu. Je pense que oui, car cela préservera la cohérence (ce qui est très important).

demande d'extraction

@KrishnaSannasi Ahhh, maintenant je vois ce que tu veux dire. Oui, cela a du sens et je vois maintenant comment votre modification proposée résout le problème et fournit le comportement souhaité. Merci pour l'explication approfondie!

Edit : d'accord, attendez, cependant... Je ne comprends toujours pas pourquoi les couvertures actuelles ne sont pas suffisantes. Il y a probablement un cas où vous seriez capable d'implémenter Into mais pas From ? Et pourtant, il est possible de dériver automatiquement TryFrom ?

Edit 2 : Ok, je viens de revenir en arrière et de lire votre explication sur la façon dont la règle orpheline peut empêcher les implémentations de From . Tout s'explique maintenant. Je soutiens totalement ce changement. C'est toujours un peu frustrant que la règle des orphelins ait autant de conséquences imprévues, mais nous n'allons pas résoudre ce problème ici, il est donc logique de tirer le meilleur parti des choses.

Est-ce que quelqu'un de @rust-lang/libs serait prêt à démarrer FCP sur ce sujet maintenant que https://github.com/rust-lang/rust/issues/49593 a été corrigé et que never_type est un candidat à la stabilisation encore une fois?

Cette fonctionnalité est déjà passée par FCP pour la stabilisation. ( Proposition , achèvement .) Je pense qu'il n'est pas nécessaire de passer par une autre période de commentaires de 10 jours.

Après qu'un PR de stabilisation pour le type jamais a atterri, nous pouvons faire un (nouveau) PR de stabilisation pour TryFrom / TryInto et utiliser rfcbot là pour s'assurer que les membres de l'équipe le voient.

Serait-il possible de changer les types d'énumération vides existants comme FromStringError en alias ! parallèlement à la stabilisation ?

@clarcharr Oui. En raison de la cohérence trait impl, nous ne pouvons le faire que pour un type. Heureusement, nous n'avons que std::string::ParseError . (Et c'est exactement pourquoi nous l'avons utilisé au lieu d'ajouter un nouveau type pour impl FromString for PathBuf dans https://github.com/rust-lang/rust/pull/55148.)

Cependant, cela n'a aucun rapport avec TryFrom / TryInto . C'est un sujet pour https://github.com/rust-lang/rust/issues/49691 / https://github.com/rust-lang/rust/issues/57012 , car cela doit se produire dans le même cycle de publication comme stabilisation de ! .

@SimonSapin Serait-il possible de fusionner ma pull request (#56796) avant que cela ne se stabilise ?

Bien sûr, je l'ai ajouté à la description du problème pour ne pas perdre de vue.

Merci!

Cela pourrait-il être intégré dans stable? C'est une dépendance pour argdata pour CloudABI.

@mcandre Oui. Ceci est actuellement en attente sur https://github.com/rust-lang/rust/issues/57012 , qui a été récemment débloqué. J'espère que TryFrom arrivera dans Rust 1.33 ou 1.34.

J'en ai marre d'attendre ça et j'ai du temps libre (enfin). S'il y a des éléments de type code ou documentation que je pourrais faire pour faire avancer cela, je me porte volontaire pour aider.

56796 a été fusionné. Nous pouvons donc vérifier cela.

@icefoxen Je suppose que pour le moment, le mieux que vous puissiez faire est de fournir des commentaires (et des modifications) sur les documents de ces traits. En ce moment, je les trouve un peu insuffisants par rapport aux traits From et Into .

Travail sur documentation. Petit accroc : je veux assert_eq!(some_value, std::num::TryFromIntError(())); . Cependant, puisque TryFromIntError n'a ni constructeur ni champs publics, je ne peux pas en faire une instance. Des conseils sur la marche à suivre ? Pour l'instant je fais juste assert!(some_value_result.is_err()); .

Désolé si ce n'est pas le bon endroit, mais il semble qu'il y ait une erreur dans la documentation pour TryFromIntError - il répertorie impl Debug for TryFromIntError , alors qu'en réalité il n'y a pas de code pour cela. Le compilateur génère actuellement une erreur lorsqu'il essaie de déballer un résultat à partir d'une utilisation TryFrom.

Est-il censé y avoir un fmt::Debug impl là-dessus ?

@marco9999

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromIntError(());

Il a un attribut #[derive(Debug)] .

Hmm oui il y a... quelque chose ne fonctionne pas correctement ?

error[E0599]: no method named `unwrap` found for type `std::result::Result<usize, <T as std::convert::TryInto<usize>>::Error>` in the current scope
  --> src\types\b8_memory_mapper.rs:67:51
   |
67 |         let address: usize = T::try_into(address).unwrap();
   |                                                   ^^^^^^
   |
   = note: the method `unwrap` exists but the following trait bounds were not satisfied:
           `<T as std::convert::TryInto<usize>>::Error : std::fmt::Debug`

@ marco9999 Il vous manque probablement une contrainte générique. TryFromIntError n'est utilisé que par certains types, mais votre T peut être n'importe quoi :

fn foo<T: TryInto<u8>>(x: T) -> u8
where
    <T as TryInto<u8>>::Error: Debug,
{
    x.try_into().unwrap()
}

Quoi qu'il en soit, c'est un peu hors sujet, désolé tout le monde. IRC pourrait être un meilleur endroit pour poser ces questions.

Je veux assert_eq!(some_value, std::num::TryFromIntError(()));

@icefoxen Il n'y a pas de valeur utile associée à TryFromIntError donc une telle affirmation ne semble pas avoir beaucoup de valeur. Si vous avez un Result<_, TryFromIntError> et que c'est un Err , il n'y a pas d'autre valeur que cela puisse être.

assert!(some_value_result.is_err());

Cela me semble raisonnable.

Merci @glaebhoerl.

En raison d'un bogue bloquant en cours de correction (https://github.com/rust-lang/rust/issues/49593), j'espérais que le type jamais pourrait être stabilisé bientôt® https://github.com/rust-lang/ rust/issues/57012 et débloquez-le. Cependant, un nouveau problème (https://github.com/rust-lang/rust/issues/57012#issuecomment-460740678) est apparu, et nous n'avons pas non plus de consensus sur un autre (https://github.com /rust-lang/rust/issues/57012#issuecomment-449098855).

Donc, lors d'une réunion de libs la semaine dernière, j'ai évoqué à nouveau l'idée, je crois d'abord proposée par @scottmcm dans https://github.com/rust-lang/rust/issues/33417#issuecomment -299124605, pour stabiliser TryFrom et TryInto sans le type jamais, et ont à la place une énumération vide qui pourrait plus tard devenir un alias pour ! .

La dernière fois que nous en avons discuté (https://github.com/rust-lang/rust/issues/33417#issuecomment-423069246), nous ne pouvions pas nous rappeler pourquoi nous ne l'avions pas fait la fois précédente.

La semaine dernière, @dtolnay nous a rappelé le problème : avant que ! ne devienne un type complet, il peut déjà être utilisé à la place du type de retour d'une fonction pour indiquer qu'elle ne revient jamais. Cela inclut les types de pointeur de fonction. Donc, en supposant que https://github.com/rust-lang/rust/pull/58302 atterrit dans ce cycle, un code comme celui-ci sera valide dans Rust 1.34.0 :

use std::convert::Infallible;
trait MyTrait {}
impl MyTrait for fn() -> ! {}
impl MyTrait for fn() -> Infallible {}

Parce que fn() -> ! et fn() -> Infallible sont deux types (pointeurs) différents, les deux impls ne se chevauchent pas. Mais si nous remplaçons l'énumération vide par un alias de type pub type Infallible = !; (quand ! devient un type complet), alors les deux impl commenceront à se chevaucher et le code comme ci-dessus se cassera.

Donc, changer l'énumération en un alias serait un changement radical. En principe, nous ne l'autoriserions pas dans la bibliothèque standard, mais dans ce cas, nous avons estimé que :

  • Il faut faire tout son possible pour créer du code qui est cassé par ce changement, il semble donc peu probable que cela se produise dans la pratique
  • Nous utiliserons Crater pour obtenir un signal supplémentaire le moment venu
  • Si nous finissons par juger que la rupture est suffisamment importante, avoir à la fois le type jamais et une énumération vide avec le même rôle est une incohérence avec laquelle nous pouvons vivre.

Sur la base de cette discussion, j'ai soumis https://github.com/rust-lang/rust/pull/58302 qui est maintenant dans la période de commentaires finale.

58015 devrait être prêt pour la révision/fusion maintenant.

@kennytm N'est-il pas déjà possible de se référer à ! dans stable? Par exemple, considérez ce qui suit :

trait MyTrait {
    type Output;
}

impl<T> MyTrait for fn() -> T {
    type Output = T;
}

type Void = <fn() -> ! as MyTrait>::Output;

Après cela, Void fait référence au type ! .

Cela ressemble à un bogue, ce qui signifie que les garanties de stabilité ne s'y étendent pas. L'utilisation du type jamais ( ! ) comme type à quelque titre que ce soit est toujours instable, du moins jusqu'à ce que #57012 soit fusionné.

Comment puis-je aider avec la documentation? :-)

Oh, je pensais que https://github.com/rust-lang/rust/pull/58015 avait atterri, mais ce n'est pas encore le cas… Discutons-en là-bas.

Le trait TryFrom pourrait-il avoir une méthode pour vérifier si l'argument peut être converti sans le consommer?

fn check(value: &T) -> bool

Une façon de travailler avec une conversion impossible sans consommation pourrait être de renvoyer la valeur non convertible consommée avec l'erreur associée.

Oups, cela aurait dû être fermé par https://github.com/rust-lang/rust/pull/58302. Fermeture maintenant.


@o01eg La façon typique de faire une conversion sans consommation est d'implémenter par exemple TryFrom<&'_ Foo> au lieu de TryFrom<Foo> .

Attendez... ça ne devrait pas fermer avant d'atterrir le jeudi stable, n'est-ce pas ?

Non, nous fermons les problèmes de suivi lorsque le PR stabilisant la fonctionnalité atterrit sur le maître.

Non, nous fermons généralement le problème de suivi lorsque la stabilisation ou la suppression atterrit dans la branche master . Après cela, il n'y a plus rien à suivre. (À moins qu'un bogue nouvellement signalé n'apparaisse, mais nous le gérons séparément.)

Les problèmes de suivi sont fermés par le PR qui les stabilise. Selon le cycle de publication, cela peut prendre jusqu'à 12 semaines avant la sortie de la version stable.

J'ai compris. Merci pour la clarification, tout le monde! :)

@gregdegruy mettez à jour votre version de Rust vers la version 1.34 ou supérieure.

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