Rust: Problème de suivi pour la promotion de `!` vers un type (RFC 1216)

Créé le 29 juil. 2016  ·  259Commentaires  ·  Source: rust-lang/rust

Problème de suivi pour rust-lang/rfcs#1216, qui promeut ! en un type.

Problèmes en attente à résoudre

Événements et liens intéressants

A-typesystem B-RFC-approved B-unstable C-tracking-issue F-never_type Libs-Tracked T-lang T-libs finished-final-comment-period

Commentaire le plus utile

@petrochenkov Oubliez ! et regardez simplement les énumérations.

Si j'ai une énumération avec deux variantes, je peux y faire correspondre deux cas :

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

Cela fonctionne pour n'importe quel n, pas seulement deux. Donc, si j'ai une énumération avec zéro variante, je peux y faire correspondre zéro cas.

enum Void {
}

let void: Void = ...;
match void {
}

Jusqu'ici tout va bien. Mais regardez ce qui se passe, nous essayons de faire correspondre les modèles imbriqués. Voici une correspondance sur une énumération à deux variantes dans un Result .

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

Nous pouvons étendre le motif intérieur à l'intérieur de l'extérieur. Il existe deux variantes Foo , il y a donc deux cas pour Err . Nous n'avons pas besoin d'instructions de correspondance distinctes pour faire correspondre le Result et le Foo . Cela fonctionne pour les énumérations avec n'importe quel nombre de variantes... _sauf zéro_.

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

Pourquoi cela ne fonctionnerait-il pas ? Je n'appellerais pas la correction de cet ajout d'un "support spécial" pour les types inhabités, j'appellerais cela la correction d'une incohérence.

Tous les 259 commentaires

Huzzah !

Il y a une implémentation WIP de ceci ici : https://github.com/canndrew/rust/tree/bang_type_coerced

Son statut actuel est le suivant : il est construit avec old-trans et est utilisable mais a quelques tests qui échouent. Certains tests échouent en raison d'un bogue qui fait planter du code comme if (return) {} pendant la trans. Les autres tests concernent l'optimisation du temps de liaison et ont toujours été bogués pour moi, donc je ne sais pas s'ils ont quelque chose à voir avec mes modifications.

Ma feuille de route actuelle est :

  • Faites-le fonctionner avec MIR. J'espère que cela ne sera pas trop difficile car c'est ainsi que j'ai commencé à l'implémenter, mais j'ai eu un problème où MIR génère une erreur de segmentation lors de la compilation.
  • Purger les éléments de divergence obsolètes du compilateur ( FnOutput , FnDiverging et associés).
  • Cachez le nouveau type derrière une porte de fonctionnalité. Cela signifierait, lorsque la fonctionnalité est désactivée :

    • ! ne peut être analysé que comme un type dans la position de retour.

    • Les variables de type divergent sont par défaut () .

  • Découvrez comment nous pouvons déclencher des avertissements de compatibilité lorsqu'un () par défaut est utilisé pour résoudre un trait. Une façon de le faire pourrait être d'ajouter un nouveau type à l'AST appelé DefaultedUnit . Ce type se comporte comme () et se transformera en () dans certaines circonstances mais lève un avertissement lorsqu'il résout un trait (comme () ). Le problème avec cette approche est que je pense qu'il sera difficile d'attraper et de corriger tous les bogues avec l'implémentation - nous finirions par casser le code des gens afin d'éviter que leur code ne soit cassé.

Y a-t-il quelque chose à ajouter à cette liste ? Est-ce que ce sera juste moi qui travaille là-dessus ? Et cette branche doit-elle être déplacée vers le référentiel principal ?

Découvrez comment nous pouvons déclencher des avertissements de compatibilité lorsqu'un () par défaut est utilisé pour résoudre un trait. Une façon de le faire pourrait être d'ajouter un nouveau type à l'AST appelé DefaultedUnit . Ce type se comporte comme () et se transformera en () dans certaines circonstances mais lève un avertissement lorsqu'il résout un trait (comme () ). Le problème avec cette approche est que je pense qu'il sera difficile d'attraper et de corriger tous les bogues avec l'implémentation - nous finirions par casser le code des gens afin d'éviter que leur code ne soit cassé.

@eddyb , @arielb1 , @anyone_else : Des réflexions sur cette approche ? J'en suis à peu près à ce stade (sans quelques tests ratés que j'essaie (très lentement) de corriger).

Pour quels traits devons-nous implémenter !? Le PR initial #35162 comprend Ord et quelques autres.

! ne devrait-il pas automatiquement implémenter _tous_ les traits ?

Ce genre de code est assez courant :

trait Baz { ... }

trait Foo {
    type Bar: Baz;

    fn do_something(&self) -> Self::Bar;
}

Je m'attendrais à ce que ! soit utilisable pour Foo::Bar afin d'indiquer qu'un Bar ne peut jamais réellement exister :

impl Foo for MyStruct {
    type Bar = !;
    fn do_something(&self) -> ! { panic!() }
}

Mais cela n'est possible que si ! implémente tous les traits.

@tomaka Il y a RFC à ce sujet : https://github.com/rust-lang/rfcs/pull/1637

Le problème est que si ! implémente Trait il devrait également implémenter !Trait ...

Le problème c'est que si ! implémente Trait il devrait aussi implémenter !Trait...

Puis-cas spécial ! afin qu'il ignore toute exigence de trait ?

@tomaka ! ne peut pas implémenter automatiquement _all_ traits car les traits peuvent avoir des méthodes statiques et des types/consts associés. Il peut implémenter automatiquement des traits qui n'ont que des méthodes non statiques (c'est-à-dire des méthodes qui prennent un Self ).

Quant à !Trait , quelqu'un a suggéré que ! pourrait implémenter automatiquement à la fois Trait _and_ !Trait . Je ne suis pas sûr que ce soit le bon, mais je soupçonne que les traits négatifs ne le sont pas du tout.

Mais oui, ce serait bien si ! pouvait implémenter automatiquement Baz dans l'exemple que vous avez donné pour précisément ce genre de cas.

Quand exactement les variables de type divergentes par défaut sont-elles () / ! et quand renvoyons-nous une erreur indiquant de ne pas pouvoir déduire suffisamment d'informations de type ? Est-ce spécifié quelque part ? J'aimerais pouvoir compiler le code suivant :

let Ok(x) = Ok("hello");

Mais la première erreur que j'obtiens est " unable to infer enough type information about _ ". Dans ce cas, je pense qu'il serait logique que _ par défaut ! . Cependant, lorsque j'écrivais des tests sur le comportement par défaut, j'ai trouvé étonnamment difficile de définir une variable de type par défaut. C'est pourquoi ces tests sont si compliqués.

J'aimerais avoir une idée claire de la raison exacte pour laquelle nous avons ce comportement par défaut et quand il est censé être invoqué.

Mais la première erreur que j'obtiens est "impossible de déduire suffisamment d'informations de type sur _". Dans ce cas, je pense qu'il serait logique que _ soit par défaut !. Cependant, lorsque j'écrivais des tests sur le comportement par défaut, j'ai trouvé étonnamment difficile de définir une variable de type par défaut. C'est pourquoi ces tests sont si compliqués.

C'est une très bonne idée à mon avis. Idem pour None qui serait par défaut Option<!> par exemple.

@carllerche

Le test unit_fallback est certainement une façon étrange de le démontrer. Une version moins macroscopique est

trait Balls: Default {}
impl Balls for () {}

struct Flah;

impl Flah {
    fn flah<T: Balls>(&self) -> T {
        Default::default()
    }
}

fn doit(cond: bool) {
    let _ = if cond {
        Flah.flah()
    } else {
        return
    };
}

fn main() {
    let _ = doit(true);
}

Seule la variable de type créée par return / break / panic!() défaut n'importe quoi.

Quand exactement les variables de type divergent par défaut sont-elles ()/! et quand renvoyons-nous une erreur indiquant de ne pas pouvoir déduire suffisamment d'informations de type ? Est-ce spécifié quelque part ?

Définir « spécifié ». :) La réponse est que certaines opérations, qui ne sont pas écrites à ma connaissance en dehors du code, nécessitent que le type soit connu à ce stade. Le cas le plus courant est l'accès au champ ( .f ) et la répartition de la méthode ( .f() ), mais un autre exemple est deref ( *x ), et il y en a probablement un ou deux de plus. Il y a pour la plupart des raisons décentes pour que cela soit nécessaire - de manière générale, il existe plusieurs façons divergentes de procéder, et nous ne pouvons pas progresser sans savoir laquelle prendre. (Il serait possible, potentiellement, de refactoriser le code pour que ce besoin puisse être enregistré comme une sorte d'"obligation en attente", mais c'est compliqué à faire.)

Si vous allez jusqu'à la fin de la fn, alors nous exécutons toutes les opérations de sélection de traits en attente jusqu'à ce qu'un état stable soit atteint. C'est le point où les valeurs par défaut (par exemple, i32, etc.) sont appliquées. Cette dernière partie est décrite dans la RFC et parle des paramètres de type par défaut spécifiés par l'utilisateur (bien que cette RFC en général ait besoin de travail).

@canndrew ceux-ci ressemblent un peu à https://github.com/rust-lang/rust/issues/12609

Mec, c'est un vieux bug ! Mais oui, je dirais que mon #36038 est dupe de ça (je pensais l'avoir déjà vu quelque part). Je ne pense pas que ! puisse vraiment être pris en compte pour les heures de grande écoute tant que ce n'est pas réglé.

Est-il prévu que ! affecte l'exhaustivité de la recherche de motifs ? Exemple de comportement actuel, peut-être erroné :

#![feature(never_type)]

fn main() {
    let result: Result<_, !> = Ok(1);
    match result {
//        ^^^^^^ pattern `Err(_)` not covered
        Ok(i) => println!("{}", i),
    }
}

@tikue oui, c'est l'un des bugs listés ci-dessus.

@lfairy whoops, je ne l'ai pas vu car il n'était pas répertorié dans les cases à cocher en haut. Merci!

Est-il prévu d'implémenter From<!> for T et Add<T> for ! (avec un type de sortie de ! ) ? Je sais que c'est une demande vraiment étrangement spécifique - j'essaie d'utiliser les deux dans ce PR .

From<!> for T définitivement. C'est probablement à l'équipe des bibliothèques de décider de Add<T> for ! mais je pense personnellement que ! devrait implémenter chaque trait pour lequel il a une implémentation logique et canonique.

@canndrew Merci ! Je suis habitué au trait Nothing de scala qui est un sous-type de chaque type, et peut donc être utilisé à peu près partout où une valeur peut apparaître. Cependant, je sympathise définitivement avec le désir de comprendre les effets que impl All for ! ou similaire auraient sur le système de types de rouille, en particulier concernant les limites négatives des traits et autres.

Par https://github.com/rust-lang/rfcs/issues/1723#issuecomment -241595070 From<!> for T a des problèmes de cohérence.

Ah d'accord, oui. Nous devons faire quelque chose à ce sujet.

Ce serait bien si les impls de trait pouvaient indiquer explicitement qu'ils sont remplacés par d'autres impls de trait. Quelque chose comme:

impl<T> From<T> for T
    overridden_by<T> From<!> for T
{ ... }

impl<T> From<!> for T { ... }

N'est-ce pas couvert par la spécialisation? Edit : je crois que c'est la règle de l'impl du réseau.

Est-ce une alternative viable pour éviter autant que possible un soutien spécial pour les types inhabités ?

Toutes ces match (res: Res<A, !>) { Ok(a) /* No Err */ } , méthodes spéciales pour Result semblent très artificielles, comme des fonctionnalités pour des fonctionnalités, et ne semblent pas valoir l'effort et la complexité.
Je comprends que ! est une fonctionnalité favorite de

@petrochenkov #12609 n'est pas une fonction spéciale pour le type Jamais. C'est juste une correction de bogue pour détecter un code clairement inaccessible.

@petrochenkov Oubliez ! et regardez simplement les énumérations.

Si j'ai une énumération avec deux variantes, je peux y faire correspondre deux cas :

enum Foo {
    Flim,
    Flam,
}

let foo: Foo = ...;
match foo {
    Foo::Flim => ...,
    Foo::Flam => ...,
}

Cela fonctionne pour n'importe quel n, pas seulement deux. Donc, si j'ai une énumération avec zéro variante, je peux y faire correspondre zéro cas.

enum Void {
}

let void: Void = ...;
match void {
}

Jusqu'ici tout va bien. Mais regardez ce qui se passe, nous essayons de faire correspondre les modèles imbriqués. Voici une correspondance sur une énumération à deux variantes dans un Result .

enum Foo {
    Flim,
    Flam,
}

let result_foo: Result<T, Foo> = ...;
match result_foo {
    Ok(t) => ...,
    Err(Flim) => ...,
    Err(Flam) => ...,
}

Nous pouvons étendre le motif intérieur à l'intérieur de l'extérieur. Il existe deux variantes Foo , il y a donc deux cas pour Err . Nous n'avons pas besoin d'instructions de correspondance distinctes pour faire correspondre le Result et le Foo . Cela fonctionne pour les énumérations avec n'importe quel nombre de variantes... _sauf zéro_.

enum Void {
}

let result_void: Result<T, Void> = ...;
match result_void {
    Ok(t) => ...,
    // ERROR!
}

Pourquoi cela ne fonctionnerait-il pas ? Je n'appellerais pas la correction de cet ajout d'un "support spécial" pour les types inhabités, j'appellerais cela la correction d'une incohérence.

@petrochenkov J'ai peut-être mal compris ce que vous disiez. Il y a deux questions abordées dans le fil #12609 :

(0) Faut-il autoriser la compilation de ce code ?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
}

(1) Faut-il autoriser la compilation de ce code ?

let res: Result<u32, !> = ...;
match res {
    Ok(x) => ...,
    Err(_) => ...,
}

Telles qu'elles sont actuellement mises en œuvre, les réponses sont respectivement « non » et « oui ». #12609 parle de (1) spécifiquement dans le problème lui-même mais je pensais à (0) quand j'ai répondu. Quant à ce que les réponses devraient être, je pense que (0) devrait certainement être "oui", mais pour (1) je ne suis pas sûr non plus.

@canndrew
Il peut être raisonnable de faire (1), c'est-à-dire des motifs inaccessibles, une peluche et non une erreur matérielle indépendamment des types inhabités, la RFC 1445 contient plus d'exemples de pourquoi cela peut être utile.

Concernant (0) je suis plus ou moins convaincu par votre explication. Je serai totalement heureux si ce schéma repose naturellement sur l'implémentation de la vérification des modèles dans le compilateur et supprime plus de code spécial qu'il n'en ajoute.

Au fait, j'ai fait un PR pour essayer de réparer (0) ici : https://github.com/rust-lang/rust/pull/36476

Je serai totalement heureux si ce schéma repose naturellement sur l'implémentation de la vérification des modèles dans le compilateur et supprime plus de code spécial qu'il n'en ajoute.

Ce n'est pas le cas, bizarrement. Mais c'est probablement plus un artefact de la façon dont je l'ai piraté dans le code existant plutôt qu'il n'y a pas de moyen élégant de le faire.

Je pense que pour les macros, il est utile que (1) ne soit pas une erreur grave.

Je pense que (1) devrait compiler par défaut mais donner un avertissement à partir du même lint qu'ici :

fn a() -> u32 {
    return 4;
    5
}
warning: unreachable expression, #[warn(unreachable_code)] on by default

Tant qu'on y est, est-ce que ça a du sens de rendre certains schémas irréfutables face à ! ?

let res: Result<u32, !> = ...;
let Ok(value) = res;

Je suis d'accord que le fait de ne pas être exhaustif correspond à une erreur, mais inaccessible, c'est-à-dire des modèles redondants, juste un avertissement semble avoir du sens.

J'ai eu des PR assis pendant un moment à ramasser de la pourriture. Y a-t-il quelque chose que je puisse faire pour aider à les réviser ? Il faut probablement en discuter. Je parle de #36476, #36449 et #36489.

Je suis contre l'idée de penser que le type divergent (ou type « inférieur » en théorie des types) est le même que le type vide enum (ou type « zéro » en théorie des types). Ce sont des créatures différentes, bien que les deux ne puissent avoir aucune valeur ou instance.

À mon avis, un type de fond ne peut apparaître que dans n'importe quel contexte qui représente un type de retour. Par exemple,

fn(A,B)->!
fn(A,fn(B,C)->!)->!

Mais tu ne devrais pas dire

let g:! = panic!("whatever");

ou

fn(x:!) -> !{
     x
}

ou même

type ID=fn(!)->!;

car aucune variable ne doit avoir le type ! , et donc aucune variable d'entrée ne doit avoir le type ! .

Vide enum est différent dans ce cas, vous pouvez dire

enum Empty {}

impl Empty {
    fn new() -> Empty {
         panic!("empty");
    }
}

alors

 match Empty::new() {}

C'est-à-dire qu'il y a une différence fondamentale entre ! et Empty : vous ne pouvez déclarer aucune variable de type ! mais pouvez le faire pour Empty .

@earthengine Quel est l'intérêt d'introduire cette distinction (à mes yeux complètement artificielle) entre deux sortes de types inhabitables ?

De nombreuses raisons pour ne pas avoir une telle distinction ont été évoquées - par exemple, être capable d'écrire Result<!, E> , faire en sorte que cela interagisse bien avec des fonctions divergentes, tout en étant capable d'utiliser les opérations monadiques sur Result , comme map et map_err .

En programmation fonctionnelle, le type vide ( zero ) est fréquemment utilisé pour coder le fait qu'une fonction ne retourne pas ou qu'une valeur n'existe pas. Quand vous dites bottom type, je ne vois pas clairement à quel concept de théorie des types vous faites référence ; habituellement bottom est le nom d'un type qui est un sous-type de tous les types -- mais dans ce sens, ni ! ni une énumération vide ne sont bottom dans Rust. Mais encore une fois, zero n'étant pas un type bottom n'est pas rare dans la théorie des types.

C'est-à-dire qu'il y a une différence fondamentale entre ! et Empty : vous ne pouvez déclarer aucune variable de type ! mais pouvez le faire pour Empty .

C'est ce que cette RFC corrige. ! n'est pas vraiment un "type" si vous ne pouvez pas l'utiliser comme un type.

@RalfJung
Ces concepts sont issus de la logique linéaire https://en.wikipedia.org/wiki/Linear_logic

Comme il y a des erreurs dans le texte précédent de ce post, je les ai supprimées. Une fois que j'aurai bien compris, je le mettrai à jour.

top est une valeur qui peut être utilisée de toutes les manières possibles

Qu'est-ce que cela signifie dans le sous-typage ? Qu'il peut devenir n'importe quel type ? Parce que c'est bottom alors.

@eddyb J'ai fait quelques erreurs, veuillez attendre mes nouvelles mises à jour.

Ce PR - qui a été fusionné en une journée - est en conflit assez grave avec mon PR de correspondance de modèles qui est en révision depuis plus d'un mois. Désolé, mon deuxième modèle de correspondance PR depuis que le premier est devenu impossible à fusionner et a dû être réécrit après avoir été examiné pendant deux mois.

les gars

@canndrew argh, je suis désolé pour ça. =( J'avais l'intention de répondre à vos relations publiques aujourd'hui aussi...

Désolé, je ne veux pas me plaindre, mais ça traîne depuis longtemps. J'ai renoncé à essayer de maintenir plusieurs PR à la fois et maintenant j'essaie d'obtenir ce changement depuis septembre. Je pense qu'une partie du problème est la différence de fuseau horaire - vous êtes tous en ligne pendant que je suis au lit, il est donc difficile de discuter de ce genre de choses.

Étant donné que les exceptions sont un trou logique et nécessaire dans le système de types, je me demandais si quelqu'un avait sérieusement réfléchi à la possibilité de les traiter de manière plus formelle avec !.

En utilisant https://is.gd/4EC1Dk comme exemple et point de référence, et si nous dépassions cela, et.

1) Traiter toute fonction qui peut paniquer mais ne renvoie pas d'erreur ou de résultat, sa signature de type passe implicitement de -> Foo à -> Result<Foo,!> 2) any Résultattypes would have their Error types implicitly be converted to enum AnonMyErrWrapper { Die(!),Error}```
3) Depuis ! est une annonce de taille nulle inhabitable, il n'y aurait aucun coût de conversion entre les types, et une conversion implicite pourrait être ajoutée pour la rendre rétrocompatible.

L'avantage, bien sûr, étant que les exceptions sont effectivement levées dans le système de types, et il serait possible de les raisonner, d'effectuer une analyse statique sur elles, etc.

Je me rends compte que ce serait ... non trivial d'un point de vue communautaire, sinon d'un point de vue technique. :)

En outre, cela chevauche probablement un éventuel système d'effets futurs.

@tupshin c'est un changement radical, du moins sans beaucoup de gymnastique. Je recommande de désactiver le déroulement et d'utiliser "Résultat" manuellement si vous voulez cette clarté. [Et d'ailleurs, ce problème n'est pas vraiment l'endroit pour évoquer une telle chose --- la conception de ! n'est pas quelque chose que vous remettez en question, il s'agit donc d'un travail purement futur.]

Je me rends compte à quel point ce serait cassé, du moins sans gymnastique significative, et il est juste que ce soit un travail totalement futur. Et oui, je suis assez content de ce qui est prévu ! jusqu'à présent, dans la mesure où il va. :)

@nikomatsakis Concernant les questions en suspens, ces deux peuvent être cochées

  • Nettoyage du code à partir de # 35162, réorganisez un peu typeck pour les types de thread via la position de retour au lieu d'appeler expr_ty
  • Résoudre le traitement des types inhabités dans les matchs

Je vais commencer une autre fissure à celui-ci:

  • Comment mettre en œuvre des avertissements pour les personnes s'appuyant sur (): Trait fallback où ce comportement pourrait changer à l'avenir ?

Je prévois d'ajouter un indicateur à TyTuple pour indiquer qu'il a été créé en définissant par défaut une variable de type divergent, puis en vérifiant cet indicateur dans la sélection des traits.

@canndrew

Je prévois d'ajouter un indicateur à TyTuple pour indiquer qu'il a été créé en définissant par défaut une variable de type divergent, puis en vérifiant cet indicateur dans la sélection des traits.

D'accord, super!

Eh bien, peut-être génial. =) Cela semble un peu complexe d'avoir deux types sémantiquement équivalents ( () vs () ) qui sont distincts de cette façon, mais je ne peux pas penser à une meilleure façon. =( Et une grande partie du code doit être préparée pour cela de toute façon grâce aux régions.

Ce que je veux dire, c'est que j'ajoute un bool à TyTuple

TyTuple(&'tcx Slice<Ty<'tcx>>, bool),

ce qui indique que nous devrions lever des avertissements sur ce tuple (unité) si nous essayons de sélectionner certains traits sur celui-ci. C'est plus sûr que mon approche initiale consistant à ajouter un autre TyDefaultedUnit .

Espérons que nous n'avons besoin de garder ce bool pendant un cycle d'avertissement.

En ce qui concerne le problème uninitialized / transmute / MaybeUninitialized , je pense qu'un plan d'action raisonnable serait :

  • Ajoutez MaybeUninitialized à la bibliothèque standard, sous mem
  • Ajoutez une coche sur uninitialized que son type est connu pour être habité. Sinon, lancez un avertissement avec une note indiquant que ce sera une erreur grave à l'avenir. Suggérez d'utiliser MaybeUninitialized place.
  • Ajoutez une vérification sur transmute que son type « à » n'est inhabité que s'il est également de type « de ». De même, déclenchez un avertissement qui deviendra une erreur matérielle.

Les pensées?

Il semble y avoir un certain désaccord sur la sémantique de &! . Après #39151, avec la fonction de porte never_type activée, il est traité comme inhabité dans les matchs.

Si les implémentations automatiques de traits pour ! ne sont pas là, au moins la mise en œuvre des traits appropriés à partir de std serait très utile. Une énorme limitation de void::Void est qu'il vit en dehors de std et cela signifie que la couverture impl<T> From<Void> for T est impossible et cela limite la gestion des erreurs.

Je pense qu'au moins From<!> devraient être implémentés pour tous les types et Error , Display et Debug devraient être implémentés pour ! .

Je pense qu'au moins From<!> devraient être implémentés pour tous les types

Cela entre malheureusement en conflit avec le From<T> for T impl.

Cela entre malheureusement en conflit avec le From<T> for T impl.

Au moins jusqu'à ce que la spécialisation en treillis impl soit implémentée.

Bons points. J'espère le voir réalisé un jour.

[T; 0] devrait-il sous-typer un [!; 0] et &[T] sous-typer un &[!] ? Il me semble intuitif que la réponse devrait être « oui », mais la mise en œuvre actuelle pense le contraire.

[!; 0] est habité, tout comme &[!] donc je dirais non. De plus, nous n'utilisons pas de sous-typage cette fois-ci.

[!; 0] et &[!] ne doivent pas être inhabités, les deux types peuvent accepter la valeur [] (ou &[] ).

Personne n'a dit qu'ils sont ou devraient être inhabités.

let _: u32 = unsafe { mem::transmute(return) };
En principe, cela pourrait être OK, mais le compilateur se plaint de "transmuter entre 0 bits et 32 ​​bits".

La transmutation de ! -> () est cependant autorisée. Je n'ai pas de raison particulière de le souligner; c'est une incohérence, mais je ne peux pas penser à un problème pratique ou théorique avec cela.

Je ne m'attendrais pas à du sous-typage et m'y opposerais pour éviter de compliquer toutes sortes de logiques dans le compilateur. Je ne veux pas de sous-typage sans rapport avec les régions ou la liaison de région, fondamentalement.

Est-il possible d'implémenter Fn , FnMut et FnOnce pour ! d'une manière générique sur tous les types d'entrée et de sortie ?

Dans mon cas, j'ai un constructeur générique qui peut prendre une fonction, qui sera invoquée lors de la construction :

struct Builder<F: FnOnce(Input) -> Output> {
    func: Option<F>,
}

Comme func est un Option , un constructeur utilisant None ne peut pas déduire le type de F . Par conséquent, une implémentation de fn new devrait utiliser Bang :

impl Builder<!> {
    pub fn new() -> Builder<!> {
        Builder {
            func: None,
        }
    }
}

La fonction func devrait ressembler à ceci pour être invoquée à la fois sur Builder<!> et Builder<F> :

impl<F: FnOnce(Input) -> Output> Builder<F> {
    pub fn func<F2: FnOnce(Input) -> Output>(self, func: F) -> Builder<F2> {
        Builder {
            func: func,
        }
    }
}

Maintenant, le problème : actuellement, les traits Fn* ne sont pas implémentés pour ! . De plus, vous ne pouvez pas avoir un type associé générique pour les traits (par exemple Output dans Fn ). Alors, est-il possible d'implémenter la famille de traits Fn* pour ! de manière à ce qu'elle soit néanmoins générique sur tous les types d'entrée et de sortie ?

Cela semble contradictoire. <! as Fn>::Output n'a-t-il pas besoin d'être résolu en un seul type ?

<! as Fn>::Output est ! , n'est-ce pas ?

En théorie, <! as Fn>::Output devrait être ! , mais le vérificateur de type actuel veut que les types associés correspondent exactement : https://is.gd/4Mkxfm

@SimonSapin Il doit se résoudre à un seul type, c'est pourquoi je pose ma question. D'un point de vue linguistique, c'est un comportement tout à fait correct. Mais du point de vue des bibliothèques, l'utilisation de ! dans des contextes de fonctions génériques serait très limitée si le comportement actuel était conservé.

Si le code suivant

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    #[allow(unreachable_code)]
    *with_print(10,&return)
}

imprime 10 ? Pour le moment, il n'imprime rien. Ou toute autre solution de contournement qui retardera le return jusqu'à l'impression ?

Je veux dire, comme je l'ai déjà dit, il existe en fait deux types différents de type ! : l'un est paresseux et l'autre est impatient. La notation habituelle désigne une notation enthousiaste, mais parfois nous pourrions avoir besoin de la notation paresseuse.


Comme @RalfJung s'intéresse à une version précédente qui utilise &return , j'ai à nouveau ajusté le code. Et encore une fois, mon point est que nous devrions pouvoir retarder le return plus tard. Nous pourrions utiliser des fermetures ici, mais je ne peux utiliser que return , pas break ou continue etc.

Par exemple, ce serait bien si nous pouvions écrire

#![feature(never_type)]

fn with_print<T>(i:i32, r:T) -> T {
    println!("{}", i);
    r
}


fn main() {
    for i in 1..10 {
        if i==5 { with_print(i, /* delay */break) }
        with_print(i, i);
    }
}

avec la pause retardée jusqu'à ce que 5 soit imprimé.

@earthengine Non - cela ne devrait certainement rien imprimer. ! est inhabité, ce qui signifie qu'aucune valeur de type ! ne peut être créée. Donc, si vous avez une référence à une valeur de type ! , vous êtes à l'intérieur d'un bloc de code mort.

La raison pour laquelle return a le type ! est qu'il diverge. Le code qui utilise le résultat d'une expression return ne peut jamais être exécuté car return ne "se termine jamais".

oO je ne savais pas que vous pouviez écrire &return . Cela n'a aucun sens, pourquoi devriez-vous être autorisé à prendre l'adresse de return ?^^

Mais, de toute façon, @earthengine dans votre code, les arguments sont évalués avant que quit_with_print soit appelé. L'évaluation du 2e argument exécute return , ce qui termine le programme. C'est comme écrire quelque chose comme foo({ return; 2 }) -- foo n'est jamais exécuté.

@RalfJung return est une expression de type ! , tout comme break ou continue . Il a un type, mais son type est inhabité.

! a été implémenté et fonctionne tous les soirs depuis un an maintenant. J'aimerais savoir ce qu'il faut faire pour l'amener vers la stabilisation.

Une chose qui n'a pas été résolue est ce qu'il faut faire à propos des intrinsèques qui peuvent retourner ! (c'est-à-dire uninitialized , ptr::read et transmute ). Pour uninitialized , la dernière fois que j'ai entendu, il y avait un consensus selon lequel il devrait être déconseillé en faveur d'un type MaybeUninit et d'un futur possible &in , &out , &uninit références. Il n'y a pas de RFC pour cela, bien qu'il y ait une RFC antérieure où j'ai proposé de déprécier uninitialized uniquement pour les types qui n'implémentent pas un nouveau trait Inhabited . Peut-être que ce RFC devrait être supprimé et remplacé par les RFC « deprecate uninitialized » et « add MaybeUninit » ?

Pour ptr::read , je pense que c'est bien de le laisser comme UB. Quand quelqu'un appelle ptr::read il affirme que les données qu'il lit sont valides, et dans le cas de ! ce n'est certainement pas le cas. Peut-être que quelqu'un a une opinion plus nuancée à ce sujet?

La correction de transmute est facile - il suffit de faire une erreur de transmutation vers un type inhabité (au lieu de ICEing comme c'est le cas actuellement). Il y avait un PR pour résoudre ce problème, mais il a été fermé pour la raison que nous avons encore besoin d'une meilleure idée de la façon de traiter les données non initialisées.

Alors, où en sommes-nous avec ces derniers en ce moment ? (/cc @nikomatsakis) ? Serions-nous prêts à aller de l'avant en dépréciant uninitialized et en ajoutant un type MaybeUninit ? Si nous faisions paniquer ces intrinsèques lorsqu'ils étaient appelés avec ! , est-ce que cela serait une mesure palliative appropriée qui nous permettrait de stabiliser ! ?

Les problèmes en suspens sont également répertoriés :

Quels traits devons-nous implémenter pour ! ? Le PR initial #35162 comprend Ord et quelques autres. Il s'agit probablement davantage d'un problème de T-libs, j'ajoute donc cette balise au problème.

Actuellement, il existe une sélection assez basique : PartialEq , Eq , PartialOrd , Ord , Debug , Display , Error . A part Clone , qui devrait certainement être ajouté à cette liste, je ne vois pas d'autres qui soient d'une importance vitale. Avons-nous besoin de bloquer la stabilisation à ce sujet ? Nous pourrions simplement ajouter plus d'impls plus tard si nous le jugeons bon.

Comment implémenter des avertissements pour les personnes s'appuyant sur (): Trait repli où ce comportement pourrait changer à l'avenir ?

L'avertissement resolve_trait_on_defaulted_unit est implémenté et déjà dans la version stable.

Sémantique souhaitée pour ! en coercition (#40800)
Quels types de variables devraient se replier sur ! (#40801)

Ceux-ci semblent être les vrais bloqueurs. @nikomatsakis : Avez-vous des idées concrètes sur ce qu'il faut faire à propos de celles-ci qui ne demandent qu'à être mises en œuvre ?

Est-il logique que ! implémente également Sync et Send ? Il existe plusieurs cas où les types d'erreur ont des limites Sync et/ou Send , où il serait utile d'utiliser ! comme "ne peut pas échouer".

@canndrew Je ne suis pas d'accord pour l'interdire dans un code dangereux. Le unreachable utilise la transmutation pour créer un type inhabité et indique ainsi à l'optimiseur qu'une certaine branche de code ne peut jamais se produire. Ceci est utile pour les optimisations. Je prévois de fabriquer ma propre caisse en utilisant une telle technique de manière intéressante.

@Kixunil ne serait-il pas plus explicite d'utiliser https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html ici ?

@RalfJung ce serait le cas, mais c'est instable. Cela me rappelle qu'interdire la transmutation en types inhabités serait un changement décisif.

@jsgf Désolé, oui, ! implémente également tous les traits du marqueur ( Send , Sync , Copy , Sized ).

@Kixunil Hmm, c'est dommage. Il serait cependant préférable de stabiliser le unreachable intrinsèque.

Pas un bloqueur de stabilisation (parce que perf, pas sémantique), mais il semble que Result<T, !> n'utilise pas l'inhabitabilité de ! dans la mise en page enum ou match codegen : https://github.com /rust-lang/rouille/issues/43278

C'est une idée tellement vague que je me sens presque coupable de l'avoir laissée tomber ici, mais :

La craie pourrait-elle aider à répondre à certaines des questions les plus difficiles concernant les implémentations de !: Trait ?

@ExpHP Je ne pense pas. L'implémentation automatique de !: Trait pour les traits qui n'ont pas de types/consts associés et seules des méthodes non statiques est connue pour être saine. C'est juste une question de savoir si nous voulons le faire ou non. L'implémentation automatique pour d'autres traits n'a pas vraiment de sens car le compilateur devrait insérer des valeurs arbitraires pour les types/consts associés et inventer ses propres impls de méthode statique arbitraires.

Il y a déjà un resolve-trait-on-defaulted-unit peluche dans rustc. Faut-il cocher la rubrique correspondante ?

@canndrew N'est-il pas judicieux de définir les types associés sur ! ?

@taralx ce n'est pas faux, mais cela empêcherait le code valide de fonctionner. Notez que le code suivant se compile aujourd'hui :

trait Foo {
    type Bar;
    fn make_bar() -> Self::Bar;
}

impl Foo for ! {
    type Bar = (i32, bool);
    fn make_bar() -> Self::Bar { (42, true) }
}

@lfairy Cela dépend de votre opinion sur ce qui est un code "valide". Pour moi, j'accepterais volontiers que le code que vous avez donné soit "invalide", car vous ne devriez pas pouvoir obtenir quoi que ce soit à partir de ! .

@earthengine Je ne vois pas le point que vous essayez de faire. Vous pouvez appeler une méthode statique sur un type sans en avoir une instance. Que vous puissiez ou non "obtenir quoi que ce soit" de ! n'a rien à voir avec cela.

Il n'y a aucune raison pour laquelle vous ne devriez pas pouvoir obtenir quoi que ce soit à partir de ! le type . Cependant, vous ne pouvez rien obtenir d' un ! , c'est pourquoi des méthodes non statiques sur ! pourraient être dérivées sans forcer la décision du programmeur.

Je pense que la règle la plus simple à retenir serait d'implémenter n'importe quel trait pour lequel il existe exactement 1 implémentation valide (à l'exclusion de ceux qui ajoutent plus de divergence que ! s dans la portée cause déjà).

Tout ce que nous allons avec devrait être égal ou plus conservateur avec cela (ce qu'aucune implémentation automatique ne convient également).

@ Ericson2314 Je ne comprends pas ce que cela signifie, pourriez-vous donner quelques exemples ?

@SimonSapin La panic!() ou loop { } , mais un v: ! qui est déjà dans la portée est bien. Avec des expressions comme celles-ci exclues, de nombreux traits n'ont qu'un seul impl possible ! ce type-vérifie. (D'autres peuvent avoir un contrat informel qui exclut tout sauf 1 impl, mais nous ne pouvons pas les traiter automatiquement.)

// two implementations: constant functions returning true and false.
// And infinitely more with side effects taken into account.
trait Foo { fn() -> bool }
// Exactly one implementation because the body is unreachable no matter what.
trait Bar { fn(Self) -> Self }

Mathématiquement parlant, ! est un "élément initial", ce qui signifie que pour une signature de type qui a ! dans ses arguments, il y a exactement une implémentation, c'est-à-dire que toutes les implémentations sont égales -- quand observé de l'extérieur. C'est parce qu'ils ne peuvent pas tous être appelés.

Qu'est-ce qui bloque réellement cette stabilisation? Est-ce seulement la situation mem::uninitialized ou autre chose ?

@arielb1 : Je pense que c'est aussi #40800 et #40801. Savez-vous quel est le statut de ceux-ci?

Quels documents ont été écrits à ce sujet, ou les gens prévoient-ils d'écrire pour cela ? Avec les ajouts récents aux pages de type primitif dans la documentation standard (#43529, #43560), le ! tapez obtenir une page là-bas ? La référence devrait probablement aussi être mise à jour, si ce n'est déjà fait.

Existe-t-il des plans pour permettre de spécifier que l'asm diverge afin que nous puissions écrire des choses comme ?

#[naked]
unsafe fn error() -> !{
  asm!("hlt");
}

Sans avoir à utiliser loop{} pour apaiser le vérificateur de type ?

Edit: Bien que je suppose que l'utilisation de core::intrinsics::unreachable puisse être acceptable dans du code de très bas niveau.

@Eroc33 quand ! est un type, vous pouvez faire ceci :

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");
  std::mem::uninitialized()
}

Jusque-là, vous pouvez faire ceci:

#[naked]
unsafe fn error() -> ! {
  asm!("hlt");

  enum Never {}
  let never: Never = std::mem::uninitialized();
  match never {}
}

@Kixunil ! est un type de nuit que vous devez utiliser asm! . Cela se fait généralement comme suit :

#[naked]
unsafe fn error() -> ! {
    asm!("hlt");
    std::intrinsics::unreachable();
}

@Kixunil Cela peut être fait plus facilement en utilisant std::intrinics::unreachable() qui spécifie exactement que le code avant est divergent.

@Kixunil D'après la discussion ci-dessus, std::mem::uninitialized pouvoir retourner ! semble être sujet à changement ? A moins que j'aie mal compris ?

D'après la discussion ci-dessus, std::mem::uninitialized être capable de retourner ! semble être sujet à changement ? A moins que j'aie mal compris ?

Je pense que uninitialized finira par être complètement déprécié et, en guise d'arrêt, paniquera au moment de l'exécution lorsqu'il sera utilisé avec ! (bien que cela n'affecterait pas ce cas).

Je pense aussi que nous devrons stabiliser std::intrinsics::unreachable quelque part dans libcore et le nommer unchecked_unreachable ou quelque chose pour le distinguer de la macro unreachable! . J'avais l'intention d'écrire un RFC pour ça.

@eddyb Ah, ouais, j'ai oublié que asm!() est instable, donc std::intrinsics::unreachable() peut aussi être utilisé.

@tbu- Bien sûr, je préfère fortement celui-là au lieu de créer un type inhabité. Le problème est qu'il est instable, donc s'il était en quelque sorte impossible de marquer de manière non sécurisée une branche de code comme inaccessible, cela casserait non seulement le code existant, mais le rendrait également impossible à réparer. Je pense qu'une telle chose nuirait gravement à la réputation de Rust (surtout si l'on considère les principaux développeurs affirmant fièrement qu'il est stable). Autant j'aime Rust, autant cela me ferait reconsidérer qu'il s'agit d'un langage utile.

@ Eroc33, je crois comprendre que certaines personnes suggèrent de le faire, mais c'est une simple suggestion, pas une décision définitive. Et je m'oppose à une telle suggestion car cela briserait la compatibilité descendante, forçant Rust à devenir 2.0. Je ne pense pas qu'il y ait de mal à le soutenir. Si les gens sont préoccupés par les bugs, les peluches seraient plus appropriées.

@canndrew Pourquoi pensez-vous que uninitialized sera obsolète ? Je ne peux pas croire une telle chose. Je le trouve extrêmement utile, donc à moins qu'il n'existe un très bon remplaçant, je ne vois aucune raison de le faire.

Enfin, je voudrais réitérer que je suis d'accord pour dire que intrinsics::unreachable() est meilleur que les hacks actuels. Cela étant dit, je m'oppose à l'interdiction ou même à la dépréciation de ces hacks jusqu'à ce qu'il y ait un remplacement suffisant.

Le remplacement de non initialisé est union { !, T }. Vous pouvez devenir inaccessible en
ptr::read::<*const !>()ing et de nombreuses autres manières similaires.

Le 8 août 2017, 15h58, notifications "Martin Habovštiak"@github.com
a écrit:

@eddyb https://github.com/eddyb Ah, ouais, j'ai oublié que asm ! () est
instable, donc std::intrinsics::unreachable() peut également être utilisé.

@tbu- https://github.com/tbu- Bien sûr, je préfère fortement celui-là au lieu de
créant un type inhabité. Le problème, c'est que c'est instable, donc si c'était
en quelque sorte impossible de marquer de manière non sécurisée une branche de code comme inaccessible, ce ne serait pas
ne casse que le code existant, mais le rend également impossible à réparer. je pense une telle chose
nuirait gravement à la réputation de Rust (en particulier compte tenu des principaux développeurs
prétendant fièrement qu'il est stable). Autant j'aime Rust, autant ce serait
me faire reconsidérer qu'il s'agit d'un langage utile.

@Eroc33 https://github.com/eroc33 je crois comprendre que certaines personnes
suggérer de le faire, mais c'est une simple suggestion, pas une décision définitive. Et moi
s'opposer à une telle suggestion car cela briserait la compatibilité descendante,
forçant Rust à devenir 2.0. Je ne pense pas qu'il y ait de mal à le soutenir.
Si les gens sont préoccupés par les bugs, les peluches seraient plus appropriées.

@canndrew https://github.com/canndrew Pourquoi pensez-vous que non initialisé
sera obsolète ? Je ne peux pas croire une telle chose. je le trouve extrêmement utile,
donc à moins qu'il n'existe un très bon remplaçant, je ne vois aucune raison pour
Ce faisant.

Enfin, je voudrais réitérer que je suis d'accord que intrinsèques::unreachable()
est meilleur que les hacks actuels. Cela étant dit, je m'oppose à l'interdiction ou même à
déprécier ces hacks jusqu'à ce qu'il y ait un remplacement suffisant.

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

@nagisa Merci ! On dirait que cela résoudrait le problème au niveau technique.

Serait-il possible d'en découper un sous-ensemble afin que ! puisse être utilisé comme paramètre de type dans les types de retour ? En ce moment si vous avez une fonction stable

fn foo() -> ! { ··· }

Et si vous voulez utiliser ? , vous ne pouvez pas effectuer la transformation habituelle pour

fn foo() -> io::Result<!> { ··· }

Les questions par défaut de coercition épineuse et de paramètre de type affectent-elles ce cas ?

40801 Peut être coché.

Nous devrions essayer de corriger le #43061 avant de stabiliser ! .

Je n'ai vu aucun problème I-unsound ouvert mentionnant never_type, j'en ai donc déposé un nouveau pour cela : #47563. Les choses semblent supposer que [!; 0] est inhabité mais je peux en créer un avec un simple [] .

@dtolnay ajouté à l'en-tête du problème

@candrew comment allez-vous pour le temps? J'aimerais voir un sous-ensemble d'utilisation de ! se stabiliser (à l'exclusion des règles autour de ce qui est exhaustif). J'ai un peu perdu le fil d'où nous en sommes, cependant, et je pense qu'il a besoin d'un champion. Avez-vous le temps d'être cette personne ?

@nikomatsakis Je suis sûr que je peux trouver du temps pour travailler sur ce genre de choses. Mais que faut-il faire exactement ? Est-ce juste les bugs liés à ce problème ?

Il semble que @varkor ait déjà ouvert des PR pour résoudre les problèmes restants (génial !). Autant que je sache, les seules choses qui restent à faire sont de décider si nous sommes satisfaits des traits actuellement implémentés et de déplacer/renommer la porte des fonctionnalités afin qu'elle ne couvre que les changements exhaustifs de correspondance de modèles.

Autre chose : voulons-nous faire paniquer mem::uninitialized::<!>() au moment de l'exécution (cela provoque actuellement UB) ? Ou devrions-nous laisser ce genre de changements pour le moment ? Je ne suis pas au courant de ce qui se passe avec les directives de code dangereux.

Je pense que le plan est toujours https://github.com/rust-lang/rfcs/pull/1892 , que je viens de remarquer que vous avez écrit. :)

@RalfJung Y a-t-il quelque chose en particulier qui bloque cela? Je pourrais écrire un PR aujourd'hui qui ajoute MaybeUninit et désapprouve uninitialized .

@canndrew Étant donné que de nombreux projets souhaitent prendre en charge les trois canaux de publication et que uninitialized est disponible sur stable, il est préférable de ne commencer à émettre des avertissements de dépréciation que sur Nightly une fois que le remplacement est disponible sur le canal stable. La dépréciation douce via les commentaires doc est très bien.

J'ai créé un PR pour stabiliser ! : https://github.com/rust-lang/rust/pull/47630. Je ne sais pas si nous sommes encore prêts à le fusionner. Nous devrions au moins attendre de voir si les PR de @varkor résolvent les problèmes ouverts restants.

Je pense également que nous devrions revoir https://github.com/rust-lang/rfcs/pull/1699 afin que les gens puissent commencer à écrire des impls de traits élégants pour ! .

@canndrew : Bien que cette RFC n'ait pas été acceptée, elle ressemble remarquablement à la proposition de https://github.com/rust-lang/rust/issues/20021.

https://github.com/rust-lang/rust/issues/36479 devrait probablement être également ajouté à l'en-tête du problème.

@varkor c'est très similaire. Avec votre travail de code inaccessible, avez-vous remarqué des problèmes avec un code comme celui-ci qui est maintenant signalé comme inaccessible ? :

impl fmt::Debug for ! {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        *self    // unreachable!
    }
}

Parce que cela nécessiterait à peu près d'accepter une sorte de proposition comme celles-ci.

@canndrew quelque chose que j'ai préconisé ces derniers temps - bien que nous n'ayons pas encore développé de modèle formel - est une sorte de "problème récapitulatif" qui tente de délimiter clairement ce que nous stabilisons. Vous pouvez le considérer comme un rapport après le RFC, essayant de résumer sous la forme d'une liste à puces les caractéristiques principales de ce qui se stabilise mais aussi de ce qui ne l'est pas .

Une partie de cela serait des pointeurs vers des cas de test qui démontrent le comportement en question.

Pensez-vous que vous pourriez essayer d'élaborer une telle chose? Nous pouvons discuter un peu ensemble si vous le souhaitez d'une sorte de "contour" -- ou je peux peut-être essayer de l'esquisser.

Question connexe : devrions-nous essayer de résoudre https://github.com/rust-lang/rust/issues/46325 avant de nous stabiliser ? Peut-être que cela n'a pas d'importance.

@nikomatsakis Je vote pour ne pas attendre d'avertissement, seuls les problèmes doivent être résolus. C'est inoffensif. Si aucune autre préoccupation réelle n'apparaissait, je pense que nous devrions aller de l'avant et le stabiliser.

@canndrew : Je ne pense pas avoir vraiment examiné de tels cas, même si je pense vraiment qu'il sera indispensable de pouvoir omettre des implémentations impossibles lorsque ! sera stabilisé.

@nikomatsakis

Pensez-vous que vous pourriez essayer d'élaborer une telle chose? Nous pouvons discuter un peu ensemble si vous le souhaitez d'une sorte de "contour" -- ou je peux peut-être essayer de l'esquisser.

Je pourrais au moins rédiger un brouillon et vous pourriez me dire si c'est ce que vous aviez en tête. Je vais essayer de le faire dans les prochains jours.

@nikomatsakis Quelque chose comme ça ?

Résumé du problème - stabilisation de !

Qu'est-ce qui se stabilise

  • ! est maintenant un type à part entière et peut maintenant être utilisé dans n'importe quelle position de type (par exemple RFC 1216 ). Le type ! peut contraindre à n'importe quel autre type, voir https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_never.rs pour un exemple.
  • L'inférence de type va maintenant par défaut les variables de type sans contrainte à ! au lieu de () . La charpie resolve_trait_on_defaulted_unit a été retirée. Un exemple d'où cela se produit est si vous avez quelque chose comme :

    // We didn't specify the type of `x`. Under some circumstances, type inference
    // will pick a type for us rather than erroring
    let x = Deserialize::deserialize(data);
    

    Sous les anciennes règles, cela désérialiserait un () , alors que sous les nouvelles règles, cela désérialiserait un ! .

  • Le portail de fonctionnalités never_type est stable, bien que certains des comportements qu'il servait auparavant vivent maintenant derrière le nouveau portail de fonctionnalités exhaustive_patterns (voir ci-dessous).

Ce qui n'est pas stabilisé

devrions-nous essayer de résoudre #46325 avant de nous stabiliser ?

Aussi agréable que ce soit de nettoyer chaque bout comme celui-ci, cela ne semble pas vraiment être un bloqueur.

@canndrew

Quelque chose comme ça?

Oui, merci ! C'est génial.

La principale chose qui manque, ce sont des pointeurs vers des cas de test montrant comment se comporte ! . Le public devrait être l'équipe lang ou d'autres personnes qui suivent de près, je pense, cela ne cible pas vraiment les gens "à usage général". Donc, par exemple, j'aimerais quelques exemples d'endroits où les coercitions sont légales, ou où ! peuvent être utilisés. J'aimerais également voir les testicules qui nous montrent que la correspondance de motifs exhaustive (sans fonctionnalité de porte activée) se comporte toujours. Ceux-ci devraient être des pointeurs dans le référentiel.

@canndrew

Aussi agréable que ce soit de nettoyer chaque bout comme celui-ci, cela ne semble pas vraiment être un bloqueur.

Eh bien, cela signifie que nous allons activer un nouveau code qui va changer immédiatement le comportement (par exemple, let x: ! = ... , je pense, se comportera différemment pour certaines expressions). Il semble préférable de résoudre si nous le pouvons. Peut-être que vous pouvez en faire une erreur grave sur le PR que vous avez ouvert et que nous pouvons l'intégrer au cratère existant sur le PR ?

@nikomatsakis J'ai à nouveau mis à jour ce problème récapitulatif avec des liens et des exemples. Aussi, désolé d'avoir mis du temps à en arriver là, j'ai été très occupé la semaine dernière.

Peut-être que vous pouvez en faire une erreur grave sur le PR que vous avez ouvert et que nous pouvons l'intégrer au cratère existant sur le PR ?

Terminé.

@rfcbot fcp fusionner

Je propose que nous stabilisions le type ! -- ou au moins une partie de celui-ci, comme décrit ici . Le PR est là .

Un bit de données que je voudrais est une course de cratère. Je suis en train de rebaser https://github.com/rust-lang/rust/pull/47630 (puisque @canndrew ne répond pas aux pings pour le moment) afin que nous puissions obtenir ces données.

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

  • [x] @Kimundi
  • [x] @alexcrichton
  • [ ] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [ ] @pnkfelix
  • [x] @sfackler
  • [x] @sansbateaux

Aucune préoccupation actuellement répertoriée.

Une fois qu'une majorité de réviseurs approuvera (et aucun ne s'y opposera), cela entrera dans sa dernière période de commentaires. Si vous repérez un problème majeur qui n'a été soulevé à aucun moment de ce processus, veuillez en parler !

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

Oh, deux ou trois choses dont je viens de me souvenir :

  • Nous devrions considérer l'idée de stabiliser ceci seulement dans la nouvelle époque . En particulier, la modification des règles de repli est rétrocompatible : l'exécution du cratère nous donnera une sorte de limite inférieure sur les retombées, mais ce n'est qu'une limite inférieure. Mais peut-être pouvons-nous simplement conserver les anciennes règles de secours à moins que vous ne soyez dans la nouvelle époque.
  • Deuxièmement, je pense qu'une partie du plan ici était également d'avoir des directives pour savoir quand il est approprié d'implémenter un trait pour ! . Le TL; DR est qu'il n'y a pas de problème si les méthodes du trait sont inutilisables sans fournir d'abord une ! -- donc implémenter Clone pour ! est ok, je pense , mais l'implémentation de Default ne l'est pas. En d'autres termes, si la mise en œuvre de l'impl vous oblige à panic! sans discernement, c'est un mauvais signe. =)

@nikomatsakis Pourrions-nous changer la règle de repli dans une nouvelle époque mais toujours rendre ! comme type disponible à l'époque 2015 ?

@nikomatsakis

Nous devrions considérer l'idée de stabiliser ceci seulement dans la nouvelle époque .

La dernière fois que nous avons fait une course de cratère (c'était il y a longtemps), les retombées du changement des règles de secours étaient assez minimes. Nous nous sommes également penchés sur le code qui pourrait être affecté par le changement depuis un certain temps.

Deuxièmement, je pense qu'une partie du plan ici était également d'avoir des directives pour savoir quand il est approprié d'implémenter un trait pour ! .

Ceci est mentionné dans la documentation pour !

@SimonSapin

Pourrions-nous changer la règle de repli dans une nouvelle époque mais encore faire ! comme type disponible à l'époque 2015?

Oui

@canndrew

La dernière fois que nous avons fait une course de cratère (c'était il y a longtemps), les retombées du changement des règles de secours étaient assez minimes. Nous nous sommes également penchés sur le code qui pourrait être affecté par le changement depuis un certain temps.

Ouais. Voyons ce que dit le cratère. Mais, comme je l'ai dit, le cratère ne nous donne jamais qu'une limite inférieure - et c'est une sorte de "changement facultatif". Pourtant, je soupçonne que vous avez raison et que nous pouvons « s'en sortir » en changeant cela sans trop d'effet sur le code dans la nature.

Le TL;DR est que ce n'est pas grave si les méthodes du trait sont inutilisables sans d'abord fournir une !

C'est juste la règle normale - vous ajoutez un impl lorsque vous pouvez l'implémenter de manière sensée, où "l'implémenter de manière sensée" exclut la panique, mais inclut "ex falso" UB en présence de données invalides.

@arielb1 Oui, mais pour une raison quelconque, les gens ont tendance à être confus à propos de choses comme celle-ci en présence de ! , il semble donc utile de l'appeler explicitement.

Peut-être qu'il serait utile d'avoir une méthode sûre fn absurd(x: !) -> ! qui est documentée comme un moyen sûr d'exprimer du code inaccessible ou quelque part dans libstd ? Je pense qu'il y avait un RFC pour ça... ou au moins un problème RFC : https://github.com/rust-lang/rfcs/issues/1385

@RalfJung Cette fonction absurd n'est-elle pas juste identity ? Pourquoi est-ce utile ?

Ce n'est pas la même chose que l'intrinsèque unsafe fn unreachable() -> ! qui ne prend aucun argument et a un comportement très indéfini.

Eh bien, oui, c'est surtout le cas. J'utilisais le type de retour ! dans le sens de "ne se termine pas". (Le type habituel de absurd est fn absurd<T>(x: !) -> T , mais cela ne semble pas non plus utile dans Rust.)

Je pensais que cela aiderait peut-être avec "les gens ont tendance à être confus à propos de choses comme celle-ci en présence de ! " -- avoir de la documentation quelque part que le raisonnement "ex faux" est une chose.

Il semble que je me sois également trompé de problème... Je pensais qu'il y avait quelque part une discussion sur une telle fonction "ex falso". On dirait que je me trompe.

fn absurd<T>(x: !) -> T peut aussi s'écrire match x {} , mais c'est difficile à trouver si vous ne l'avez pas vu auparavant. Cela vaut au moins la peine de le signaler dans rustdoc et dans le livre.

fn absurde(x: !) -> T peut aussi s'écrire match x {}, mais c'est difficile à trouver si vous ne l'avez pas vu auparavant.

C'est pourquoi j'ai suggéré une méthode quelque part dans libstd. Je ne sais pas où se trouve le meilleur endroit.

La bonne chose à propos de absurd est que vous pouvez le transmettre à des fonctions d'ordre supérieur. Je ne suis pas sûr que ce soit jamais nécessaire vu comment ! se comporte wrt. l'unification, cependant.

fn absurd<T>(x: !) -> T peut aussi s'écrire match x {}

Il peut également être écrit simplement comme x (AFAIK) - vous n'avez besoin que de match si vous avez un enum vide.

Une fonction absurd(x: !) -> T serait utile pour passer aux fonctions d'ordre supérieur. J'en ai eu besoin (par exemple) pour enchaîner les contrats à terme, dont l'un a le type d'erreur ! et l'autre le type d'erreur T . Même si une expression de type ! peut forcer en T cela ne veut pas dire que ! et T s'uniront, donc parfois vous devez toujours le faire manuellement convertir le type.

De même, je pense que les types Result -like devraient avoir une méthode .infallible() qui convertit Result<T, !> en Result<T, E> .

Les types de type Result doivent avoir une méthode .infallible() qui convertit Resultrésulter.

Je me serais attendu à ce qu'une telle méthode ait le type Result<T, !> -> T .

Je me serais attendu à ce qu'une telle méthode ait le type Result-> T.

N'est-ce pas la même chose que unwrap ? La panique sera optimisée car le boîtier Err est inaccessible.

@Amanieu Le fait est qu'il n'y a pas de panique pour commencer. J'adorerais avoir une peluche panique, et cela se déclencherait si nous comptions sur l'optimisation. Même sans les peluches, l'utilisation de unwrap ajoute un pistolet lors des refactorisations, moins la panique redevient un code en direct.

unwrap lit comme assert -- "run-time check ahead" (IIRC il y a même eu des propositions pour le renommer mais elles sont arrivées trop tard... malheureusement). Si vous ne voulez pas et avez besoin d'un contrôle d'exécution, infallible le rendrait explicite.

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

@rfcbot fcp annuler

@aturon et @pnkfelix n'ont pas coché leurs cases.

Proposition de @cramertj annulée.

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

  • [x] @alexcrichton
  • [x] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [ ] @pnkfelix
  • [x] @sfackler
  • [x] @sansbateaux

Aucune préoccupation actuellement répertoriée.

Une fois qu'une majorité de réviseurs approuvera (et aucun ne s'y opposera), cela entrera dans sa dernière période de commentaires. Si vous repérez un problème majeur qui n'a été soulevé à aucun moment de ce processus, veuillez en parler !

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

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

Nous en avons discuté lors de la réunion d'aujourd'hui. Je reste déchiré quant à savoir s'il faut changer le repli maintenant ou seulement dans la nouvelle époque. Voici les différents points.

L'impact attendu du changement est faible. Donc, de façon réaliste, cela ne fait pas beaucoup de différence de toute façon. Vous pouvez voir l'analyse complète ici , mais le résumé est :

Et cela ne laisse que les deux régressions réelles : oplog-0.2.0 (en fait c'est sa dépendance bson-0.3.2 qui est cassée) et rspec-1.0.0-beta.4. Les deux s'appuient sur l'ancien comportement de secours, même si heureusement, ils se cassent au moment de la compilation, pas au moment de l'exécution. Je vais soumettre des PR pour réparer ces caisses.

Mais il s'agit toujours d'un changement technique (et volontaire). Il n'y a aucune raison particulière que nous devons changer les solutions de repli pour les variables de type, sauf que () est un choix singulièrement pauvre et il est assez rare pour le code de compter sur lui. Nous avons également mis en garde à ce sujet depuis longtemps maintenant.

Je pense que c'est plus une question de philosophie : dans le passé, nous avons fait des changements mineurs comme celui-ci par nécessité. Mais maintenant que nous avons le mécanisme d'époque, il nous offre une manière plus fondée de faire de telles transitions sans rien casser techniquement. Cela vaut peut-être la peine de l'utiliser, juste par principe. D'un autre côté, cela signifierait parfois attendre des années pour faire un changement comme celui-ci. Et bien sûr, nous devons maintenir les deux versions à perpétuité, ce qui rend la spécification de la langue, etc. beaucoup plus complexe.

Je suis un peu déchiré mais je pense que dans l'ensemble, après avoir vacillé d'avant en arrière, je penche actuellement vers "changeons-le universellement", comme prévu.

Je sais que je suis partial, mais je considère que changer le comportement de secours est davantage une correction de bogue par rapport à des trucs comme dyn Trait et catch .

De plus, si quelqu'un veut sauter dessus et dire : "Ah ha ! La rouille casse la rétrocompatibilité après tout ! Leur promesse de stabilité lorsqu'elle atteint 1.0 était un mensonge !" puis la discussion très réfléchie sur cette question montre que la décision n'a pas été prise à la légère, même étant donné qu'il y avait un impact pratique négligeable.

OK, je dis qu'on change ça.

En guise de compromis, je voudrais donc donner une note utile pour les erreurs qui informent les gens que le comportement a changé -- semble plausible. L'idée serait quelque chose comme ça :

  • Si nous voyons une erreur comme !: Foo pour un trait, et que ce trait est implémenté pour (): Foo , et nous avons fait un repli (nous pouvons dire au code de rapport d'erreur ce fait), alors nous ajoutons un nœud supplémentaire.

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

Ces cases à cocher en attente dans la publication du haut ne sont-elles pas encore cochées ? Y a-t-il quelque chose de défait dans cette liste ?

@earthengine Je ne pense pas que les deux que je vois soient particulièrement pertinents -- en ce qui concerne le premier élément, à propos de l'ensemble d'impls, je suppose que c'est maintenant décidé pour le moment.

Fondamentalement, être un ensemble très minimal.

@nikomatsakis Je viens de créer le numéro de résumé ici : https://github.com/rust-lang/rust/issues/48950

A-t-il été envisagé de faire de ! un modèle qui correspond aux valeurs du type ! ? (J'ai fait un rapide examen de ce problème et du problème RFC d'origine, mais je n'ai rien trouvé de pertinent).

Je trouverais cela utile pour des types tels que StreamFuture qui créent des types d'erreur composites à partir de types d'erreur potentiellement ! . En utilisant un modèle ! dans map_err , si le type d'erreur change à un moment donné dans le futur, l'appel map_err arrêtera la compilation au lieu de potentiellement faire quelque chose de différent.

A titre d'exemple, donné

let foo: Result<String, (!, String)> = Ok("hello".to_owned());

cela permettrait d'écrire le plus explicite

let bar: Result<String, String> = foo.map_err(|(!, _)| unreachable!());

au lieu du potentiellement sujet aux erreurs

let bar: Result<String, String> = foo.map_err(|_| unreachable!());

ou le légèrement moins sujet aux erreurs

let bar: Result<String, String> = foo.map_err(|(e, _)| e);

EDIT : en relisant https://github.com/rust-lang/rfcs/pull/1872, les commentaires mentionnent l'introduction du modèle ! . C'est purement dans le contexte des expressions match , je ne sais pas si cela se généralise aux arguments de fermeture/fonction.

@Nemo157

A-t-il été envisagé de faire ! un modèle qui correspond aux valeurs du ! taper?

Intéressant. Je pense que le plan était juste de vous laisser de côté de telles armes, auquel cas - si le type devait changer à l'avenir - vous obtiendriez toujours une erreur, car votre match ne serait plus exhaustif. Le problème avec un motif ! est que, s'il correspond, cela signifie que le bras est impossible, ce qui est un peu gênant ; nous voudrions expliquer cela, j'imagine, en ne vous obligeant pas à donner une expression à exécuter. Pourtant, il serait peut-être bien d'avoir un moyen de dire explicitement que ce cas ne peut pas se produire.

En fait, étendre la gestion des modèles inaccessibles pourrait être une autre façon de résoudre ce problème. Si le bras de correspondance était impossible à prouver, peut-être que le code à l'intérieur du bras pourrait être ignoré. Ensuite, quand vous avez

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(bar?)
}

cela continuerait de s'étendre comme aujourd'hui (_Je ne suis pas sûr à 100% que ce soit la bonne extension, mais cela semble assez proche_)

fn foo() -> Result<String, String> {
    let bar: Result<String, (!, String)> = Ok("hello".to_owned());
    Ok(match Try::into_result(bar) {
        Result::Ok(e) => e,
        Result::Err(e) => return Try::from_err(From::from(e)),
    })
}

mais la branche Result::Err ne serait pas vérifiée et vous n'obtiendriez pas l'erreur the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied vous obtenez aujourd'hui.

Je ne pense pas que cela soit compatible avec ce que proposait la RFC 1872, car il existe un bras explicite qui effectue une correspondance superficielle, il ne repose pas nécessairement sur la validité de la ! , et pourrait potentiellement exécuter la branche associée "en toute sécurité" tant que cette branche n'utilise pas du tout e.0 .

! est un type positif, comme bool , il est donc un peu impossible de faire correspondre un motif dessus dans une liste d'arguments de fermeture sans étendre la syntaxe de fermeture pour autoriser plusieurs branches. par exemple. peut-être que nous pourrions permettre aux fermetures bool -> u32 d'être écrites quelque chose comme (syntaxe hypothétique laide) [~ |false| 23, |true| 45 ~] -> u32 , alors une fermeture de n'importe quel type vide à u32 pourrait simplement être écrite [~ ~] -> u32 .

par rapport à vos exemples originaux, vous pouvez écrire foo.map_err(|_: (!, _)| unreachable!()) bien que je préfère foo.map_err(|(e, _)| e) car cela évite d'utiliser unreachable! .

! est un type positif

Qu'entends-tu par type positif ?

il est un peu impossible de faire correspondre un modèle dans une liste d'arguments de fermeture sans étendre la syntaxe de fermeture pour autoriser plusieurs branches

Les listes d'arguments prennent déjà en charge les sous-modèles irréfutables, par exemple

let foo: Result<String, ((), String)> = Ok("hello".to_owned());
let bar: Result<String, String> = foo.map_err(|((), s)| s);

Je considérerais simplement le modèle ! comme un modèle irréfutable qui correspond à toutes les valeurs 0 du type ! , de même que () est un modèle irréfutable qui correspond à toutes les valeurs 1 de le type () .

Je considérerais juste le ! pattern comme un pattern irréfutable qui correspond à toutes les valeurs 0 du ! type, le même que () est un modèle irréfutable qui correspond à toutes les valeurs 1 du type ().

Eh bien, mais 0 != 1. ;) C'est un peu inutile d'écrire même du code après une correspondance sur un ! -- donc par exemple si le type d'argument est (!, i32) , alors |(!, x)| code_goes_here est idiot car ce code est mort de toute façon. J'écrirais probablement |(x, _)| match x {} pour rendre très explicite que x est un élément de ! . Ou, en utilisant la fonction absurd j'ai proposée ci - |(x, _)| absurd(x) . Ou peut-être |(x, _)| x.absurd() ?

On pourrait imaginer une syntaxe différente qui ne permette d'écrire aucun code, comme ce que @canndrew a écrit ci-dessus. match a un nombre quelconque de branches, il est donc en particulier possible de n'avoir aucune branche -- mais les fermetures ont exactement un cas, donc les seuls modèles qui ont du sens sont ceux qui ont exactement 1 façon de correspondre. Pas 0 manières.

Dommage que nous ne puissions pas ajouter impl<T> From<!> for T maintenant. (Cela chevauche From<T> for T , à moins qu'il ne s'agisse d'une spécialisation ?) Nous ne pourrons probablement pas l'ajouter plus tard. (Dans les cycles de publication après la stabilisation de ! , certaines caisses peuvent implémenter From<!> for SomeConcreteType .)

@SimonSapin bon point ! C'est un bloc de construction très utile pour beaucoup de choses, et je détesterais le perdre pour toujours. J'envisagerais sérieusement un hack ponctuel pour autoriser les deux instances. Ils coïncident sémantiquement dans le cas de chevauchement, il n'y a donc pas d'« incohérence opérationnelle ».

J'envisagerais sérieusement un hack ponctuel

Ensuite, ce hack doit atterrir dans les prochains jours. (Ou être rétroporté pendant la version bêta.)

@SimonSapin Avec quelle rapidité / facilité un tel hack pourrait-il être ajouté ? Ce serait vraiment nul de ne jamais pouvoir avoir From<!> for T .

Alternativement, nous pourrions rapidement lancer un avertissement contre l'implémentation de From<!> for SomeConcreteType .

Je ne sais pas, ça dépend de quoi il s'agit. Je pense que la spécialisation de trait peut permettre à deux impls qui se chevauchent s'il y en a un troisième pour l'intersection, mais cela nécessite-t-il que le trait soit publiquement «spécialisable»?

Malheureusement, spécialisation :

  • Il faudrait que From::from soit remplacé par un default fn , qui serait « visible » en dehors de std. Je ne sais pas si nous voulons faire cela dans les traits std tant que la spécialisation est instable.
  • Tel qu'accepté dans la RFC 1210, ne prend pas spécifiquement en charge la fonctionnalité de langage à laquelle je pensais (lever deux impls qui se chevauchent où aucun n'est plus spécifique que l'autre en écrivant un troisième impl pour l'intersection).

Ici, les deux impls font exactement la même chose. Si nous désactivons simplement la vérification de cohérence, il sera difficile de déterminer quelle implémentation est utilisée, ce qui est normalement très malsain, mais dans ce cas très bien. Cela pourrait déclencher des paniques lors de la compilation en attendant, par exemple, un résultat, mais nous pouvons contourner cela.

En d'autres termes, je pense que c'est beaucoup plus facile que d'accélérer toute spécialisation, heureusement.

Existe-t-il un RFC existant pour formaliser cette idée « autoriser un chevauchement arbitraire si tous les impls impliqués sont inaccessibles » ? Cela semble être une approche intéressante à considérer.

Pas que je sache de. Pour être pendant, l'impl lui-même n'est pas inaccessible, mais les méthodes à l'intérieur sont toutes non appelables (y compris aucun type ou constante associé car ils sont toujours "appelables"). Je suppose que je pourrais rapidement en écrire un s'il est jugé que le piratage est bloqué sur une telle chose.

Juste en mentionnant le problème #49593 qui pourrait causer ! pour se déstabiliser à nouveau pour une meilleure trouvabilité.

Je me demande quels autres impls en plus de T: From<!> ne peuvent ! . Nous devrions probablement essayer de maîtriser toutes ces impls raisonnables avant de nous stabiliser, à la différence des impls pour ! en général, où il n'y a pas une telle précipitation.

Cross-posting à partir de ce commentaire :

Je me demandais - quel est l'exemple classique de pourquoi le repli vers () "devrait" être modifié? C'est-à-dire que si nous continuions à nous rabattre sur (), mais toujours en ajoutant !, cela éviterait certainement ce genre de problème -- et retomberions sur ! n'est pas optimal, étant donné qu'il existe des cas où le repli se produit sur une variable de type qui finit par influencer le code « live » (l'intention est bien sûr différente, mais il est difficile d'empêcher les fuites, comme nous l'avons constaté).

Il y a eu plusieurs régressions à la suite de ce changement et je dois dire que je ne suis pas tout à fait à l'aise avec ça :

(Nomination pour une discussion en équipe linguistique autour de ce point.)

Au cours de la réunion, nous avons un peu retiré certaines des options ici - je me sens réticent à apporter des modifications, principalement parce que cela change la sémantique du code existant dans certains cas extrêmes, et il n'est pas clair que les nouvelles règles soient meilleures . Mais surtout, je pense que nous avons décidé que ce serait bien d'essayer de rassembler à nouveau les avantages/inconvénients de chaque conception afin que nous puissions avoir une discussion plus complète. J'aimerais voir une liste des cas d'utilisation ainsi que des pièges. @cramertj a mentionné son désir de repli dans des cas variants - par exemple, Ok(33) ayant un type qui se replie sur Result<i32, !> , au lieu d'erreur comme il le ferait aujourd'hui ; Je pense qu'ils disaient alors que garder le repli de return à () serait incompatible avec un tel changement (bien qu'ils soient orthogonaux), et pourrait créer des conflits sur la route. C'est juste.

Le défi avec les commentaires Err (#40801) est qu'il y a aussi des cas limites, comme :

let mut x = Ok(22);
x = Err(Default::default());

où le type de x est finalement déduit de Result<T, !> .

Je me souviens d'un plan distinct que je devais essayer de voir si nous pouvions déterminer si (et peut - être mettre en garde ou erreur?) ! fallback jamais « affecté » le code en direct - c'est avéré assez difficile, mais il semble comme si nous pouvions pelucher de nombreux cas dans la pratique (par exemple, celui-là, et probablement le cas de @SimonSapin ).

Il y a également eu une discussion sur la possibilité de supprimer complètement cette solution de secours dans la nouvelle édition. Je soupçonne que cela va casser beaucoup de macros, mais cela vaudrait peut-être la peine d'essayer?

Pour info, cette fonctionnalité a provoqué une régression en 1.26 : #49932

(Réglage des étiquettes selon https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041.)

L'élément de la liste de contrôle sur la coercition dans ! doit-il pointer vers https://github.com/rust-lang/rust/issues/50350 au lieu de faire référence à ce problème ?

Si je veux que cela se stabilise au plus vite, quel est le meilleur endroit pour diriger mon énergie ?

@remexre Je pense que la meilleure description du problème qui a conduit au retour de la stabilisation est à https://github.com/rust-lang/rust/issues/49593

Une solution viable serait donc de considérer Box comme sous-type de
Boîte? Y a-t-il des traits objectsafe qui ne peuvent pas être gérés de cette façon ?

Le dimanche 8 juillet 2018, 08:12 Ralf Jung [email protected] a écrit :

@remexre https://github.com/remexre Je pense que la meilleure description du
le problème qui conduit au retour de la stabilisation est au n° 49593
https://github.com/rust-lang/rust/issues/49593

-
Vous recevez ceci parce que vous avez été mentionné.

Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

Merci,
Nathan

Cette discussion devrait vraiment aller sur https://github.com/rust-lang/rust/issues/49593 , mais un élément clé est que Box::<T>::new nécessite T: Sized , mais que new appelé est supposé avoir T = Error (un trait DST) en fonction du type de retour.

En dehors de l'inélégance d'un cas spécial, y a-t-il des problèmes pour que le cas spécial Box::new soit :

Box::new : T -> Box<U>
where T <: U,
      T: Sized

ou

Box::new : T -> Box<U>
where Box<T> <: Box<U>,
      T: Sized

Tout d'abord, nous devons réfléchir à la taille de ! . Les mathématiciens proposeront quelque chose comme Inf , mais en réalité ce sera usize::MAX , nous nous assurons donc que toute tentative d'allocation d'espace pour ce type échouera ou au moins panic .

Si ! est Sized alors, rien ne nous empêche de compiler Box::new(x as !) , mais c'est fondamentalement une autre façon de panic! , car aucun modèle de mémoire ne peut réellement allouer usize::MAX octets.

Il ne me semble pas évident que ! devrait avoir une taille infinie/ usize::MAX plutôt que d'être un ZST ?

use std::mem::size_of;
enum Void {}
fn main() { println!("{}", size_of::<Void>()); }

est actuellement 0.

La raison a été expliquée dans le texte rendu.

bool : deux valeurs valides => log(2)/log(2) = 1 bit
() : 1 valeurs valides => log(1)/log(2) = 0 bit
! : 0 valeurs valides => log(0)/log(2) = Inf bits

En tant que personne non versée dans la théorie pertinente, je vois adhérer à la formule log(x)/log(y) jusqu'à chasser l'élégance d'un modèle théorique au détriment de l'utilisation pratique. (AKA étant trop intelligent pour son propre bien)

Intuitivement, il semble que ! devrait également être de taille zéro car :

  • bool : a besoin d'espace pour distinguer deux valeurs valides => log(2)/log(2) = 1 bit en plus du type système
  • () : a besoin d'espace pour distinguer une valeur valide => pas d'espace nécessaire au-delà du système de types
  • ! : a besoin d'espace pour distinguer pas de valeurs valides => pas d'espace nécessaire au-delà du système de types

Et bien c'est -infinity en fait, ce qui a du sens, comme placer le ! comme un champ dans une structure transforme essentiellement la structure en ! ainsi (c + -inf = -inf). Donc, puisque nous avons affaire à usize, 0 est la valeur la plus proche que nous ayons pour représenter cela. (Je pense que l'implémentation réelle dans rustc utilise même -inf).

Intuitivement, il semble que ! devrait également être de taille zéro

! n'a pas du tout besoin de taille, car il n'y aura jamais de valeurs créées avec ce type. C'est une question hors de propos.

0 valeurs valides => log(0)/log(2) = bits Inf

log(0) n'est pas défini ; ce n'est pas infini.

D'un autre côté, avoir une taille de usize::MAX est un moyen technique d'imposer l'inhabitabilité. Se mettre d'accord?

@CryZe @varkor

Cela correspond également au concept dont j'essayais de raisonner la validité, où mon intuition veut voir ! comme étant "ZST à un tout autre niveau". (c'est-à-dire que ! est au type système comme () est à l'allocation de mémoire.)

@CryZe
Votre formule -Inf + c == -Inf sens, mais lorsque vous remplacez -Inf par 0usize elle ne tient plus.

D'un autre côté, si l'arithématique est « capée » : tous les débordements sont calculés à usize::MAX alors usize::MAX correspond parfaitement à la formule.

Le modèle du cerveau : si vous avez un objet de type ! , pour allouer une structure qui le contient, vous avez besoin d'une structure encore plus grande que ! , mais la plus grande taille de structure que vous pouvez imager est usize::MAX . Donc, l'espace dont vous avez besoin est toujours de usize::MAX .

Pourquoi ne pas simplement « clamper » la taille de tout type contenant un type vide ( ! , ou un enum Void {} défini par l'utilisateur ) à size=0,alignment=0 ? Cela me semble beaucoup plus sensé sémantiquement...

@remexre
Parce que cela ne fonctionne pas. Exemple : sizeof(Result<usize,!>) == sizeof(usize) .

Eh bien, Result<usize, !> ne contient pas directement ! ; sizeof(Result::Ok(usize)) == 8 , sizeof(Result::Err(!)) == 0

Non, sizeof(Result::Err(!)) == usize::MAX dans ma proposition, car Result::Err(!) :: Result<!,!> .

La règle devrait être la suivante : dans enum s, la taille de ! est considérée comme inférieure à toutes les autres valeurs de taille, mais dans structs , c'est la taille maximale que l'image puisse jamais

Conclusion:

  • ! n'est pas l'heure d'été. Les DST ne sont pas Sized parce qu'ils n'ont pas de taille, mais parce que les informations de taille sont masquées. Mais pour ! nous savons tout à son sujet.
  • ! doit être dimensionné et donc Box::new(x as !) doit être autorisé au moins à se compiler.
  • struct s contient ! devrait être dimensionné comme s'il était ! , enum s contient ! directement dans une variante devrait être dimensionné comme si cette variante n'existe pas.

Soit nous considérons sizeof(!)==0 soit sizeof(!)==usize::MAX , nous devons introduire une arithmétique spéciale pour permettre ce qui précède :

sizeof(!)==0 : dans les structs, 0 + n = 0 :worried:, dans les énumérations, max(0,n) = n : blush:.
sizeof(!)==usize::MAX : dans les structs, usize::MAX + n =usize::MAX :neutral_face:, dans les enums, max(usize::MAX,n) = n : flushed:.

Il y a cependant un avantage en faveur de usize::MAX : cela empêche techniquement toute tentative, même unsafe , de construire un tel objet, car il n'est possible dans aucun système réel.

Il y a cependant un avantage en faveur d'usize::MAX : cela empêche techniquement toute tentative, même dangereuse, de construire un tel objet, car cela n'est possible dans aucun système réel.

Je veux dire, si les manigances unsafe sont autorisées : *transmute::<Box<usize>, Box<!>>(Box::new(0)) me rapporte un ! quelle que soit sa taille. usize::MAX rend plus probable que le compilateur génère une erreur si quelqu'un essaie de faire std::mem::zeroed<!>() , je suppose, mais je mettrais la responsabilité sur la personne qui écrit ce code dangereux, plutôt que sur les mathématiques bizarrerie ci-dessus.

Notez que ! ayant la taille _actually_ 0 -- pas MAX ni -INF -saturated-to-0 -- est nécessaire pour gérer l'initialisation partielle, comme c'était le cas trouvé dans https://github.com/rust-lang/rust/issues/49298#issuecomment -380844923 (et implémenté dans https://github.com/rust-lang/rust/pull/50622).

Si vous souhaitez modifier la façon dont cela fonctionne, veuillez créer un nouveau numéro ou un nouveau PR pour cela ; ce n'est pas l'endroit.

@remexre

Box::new : T -> Box<U>
where T <: U,
      T: Sized

Box est (principalement) un type de bibliothèque et sa méthode new est définie dans la syntaxe Rust dans src/liballoc/boxed.rs . Votre description suppose un opérateur <: qui n'existe pas dans Rust. En effet, le sous-typage dans Rust n'existe que via les paramètres de durée de vie. Si vous souhaitez lire ou regarder plus :

! peut être forcé dans n'importe quel type, mais c'est plus faible que d' être n'importe quel type. (Par exemple, Vec<&'static Foo> est également Vec<&'a Foo> pour tout 'a , mais il n'y a pas de conversion implicite de Vec<!> à Vec<Foo> : http:/ /play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

Je pense que l'idée est que vous pouvez faire ce que vous pensez être approprié en mode unsafe , mais vous devez faire face à l'UB si vous ne le faites pas judicieusement. Si la taille officielle de ! est usize::MAX , cela devrait alerter suffisamment l'utilisateur pour que ce type ne soit jamais instancié.

Aussi, je pensais à l'alignement. Si les types inhabités ont la taille usize::MAX , il est naturel de définir son alignement également usize::MAX . Cela limite effectivement un pointeur valide au pointeur nul. Mais par définition, un pointeur nul n'est pas valide, nous n'avons donc même pas de point valide pour ce type, ce qui est parfait pour ce cas.

@ScottAbbey
Je me concentre sur le problème pour m'assurer que Box::new(x as !) n'a pas de problème afin que nous puissions continuer à stabiliser cela. La méthode proposée est de faire en sorte que ! ait une taille. Si ZST est la norme, ça devrait aller. Je ne discute plus. Implémentez simplement de manière à ce que sizeof(!)==0 fasse l'affaire.

il est naturel de définir son alignement également en utilisant la taille :: MAX

usize::MAX n'est pas une puissance de deux, ce n'est donc pas un alignement valide. De plus, LLVM ne prend pas en charge les alignements de plus de 1 << 29 . Et ! ne devrait pas avoir un grand alignement de toute façon, car let x: (!, i32); x.1 = 4; ne devrait prendre que 4 octets de pile, pas des gigaoctets ou plus.

Veuillez créer un nouveau fil pour cette discussion, @earthengine , si vous souhaitez la continuer.

Le problème qui se produit ici est que Box::new(!) est un Box<$0> où nous attendons un Box<Error> . Avec des annotations de type suffisantes, nous aurions besoin que Box<$0> soit contraint à Box<Error> , ce qui nous laisserait plus tard avec $0 défaut à ! .

Cependant, $0 est une variable de type, donc la coercition n'est pas faite et nous obtenons alors $0 = Error .

Une option hacky pour fixer les choses serait de trouver que nous avons une contrainte $0: Sized (sous - types suivants?) Et l' insertion d' une contrainte calée sur ce point , de sorte que Box est toujours appelé à ce type.

Nous faisons déjà quelque chose comme ça pour détecter Fn dans les fermetures, donc ce ne sera pas si compliqué. Toujours moche.

Voilà, si pendant la coercition nous rencontrons une obligation de la forme $0: Unsize<Y>Y est définitivement non dimensionné et $0 est définitivement dimensionné, ne traitez pas cela comme une ambiguïté mais plutôt traitez-le comme un « ok » et procédez à la coercition non dimensionnée.

@arielb1

Alors, en quoi est-ce différent de la coercition d'un Box::new(()) à Box<Debug> ? std::error::Error est un trait comme Debug , pas un type comme [u8] . std::io::Error d'autre part est un type, mais c'est Sized .

Nous n'avons jamais de problème avec les éléments suivants :

use std::fmt::Debug;

fn f(x:()) -> Box<Debug> {
    Box::new(x)
}

fn main() {
}

@earthengine, la différence entre ce problème et votre code est :

| Écrit | Box::new(!): Box<Debug> | Box::new(()): Box<Debug> |
| ------ | ------ | ------- |
| Exécuté | Box::new(! as Debug): Debug | (Box::new(()) as Box<Debug>): Debug |

Essentiellement, parce que ! peut contraindre à n'importe quoi, il est contraint à Debug avant l'appel à Box::new . Ce n'est pas une option avec () , donc il n'y a pas de problème dans le second cas -- la coercition se produit après Box::new .

@RalfJung

Mon modèle cérébral est que les DST ne peuvent pas exister dans la nature - ils doivent provenir de types plus concrets. Cela est même vrai lorsque nous autorisons les DST dans la position de paramètre - sinon dans la position de retour. Cela dit, jusqu'à ce que la valeur réelle soit déjà placée derrière un pointeur, il ne devrait pas pouvoir se rapprocher d'un DST, même s'il s'agit de ! .

Par exemple, les éléments suivants ne doivent pas être compilés :

let v: str = !;
let v: [u8] = !;
let v: dyn Debug = !;

Tout simplement parce que vous ne pouvez pas remplacer ! par une expression Rust existante pour la compiler.

Éditer

Ce n'est pas une option avec ()

Alors quelqu'un peut-il expliquer pourquoi ? Si () as dyn Debug ne compile pas, ! as dyn Debug ne le sera pas et vice versa. &() as &Debug compile, donc &! as &Debug , pas de problèmes. Si () as dyn Debug peut un jour compiler, le problème que nous avons aujourd'hui se répétera pour () , donc les implémenteurs de DST RFC devront s'en occuper, et donc cela résoudra le même problème que nous avons pour ! .

! contraint à quoi que ce soit parce qu'il est impossible d'exister. "Ex faux quodlibet". Donc, tous vos exemples doivent être compilés -- il n'y a aucune bonne raison théorique de faire une exception spéciale pour les types non dimensionnés.

Cela ne viole même pas « DTS ne peut pas exister » car ! ne peut pas non plus exister. :)

() as dyn Debug ne compile pas

Je ne sais pas quels sont les plans pour les rvalues ​​non dimensionnées, mais je suppose qu'ils pourraient faire cette compilation?
Le fait est que faire cela pour ! ne nous oblige pas à implémenter des rvalues ​​non dimensionnées car cela ne peut se produire que dans du code mort.

Cependant, vous indiquez peut-être une solution possible ici (et c'est peut-être ce que ! pour qu'elle s'applique uniquement si le type de cible est (connu pour être) dimensionné ? Il n'y a pas de bonne raison théorique pour le faire, mais une raison pratique. :D A savoir, que cela pourrait aider à résoudre ce problème.

Quand j'ai dit "DTS ne peut pas exister", je veux dire en syntaxe. Il n'est pas possible d'avoir une variable locale qui est tapée est str , par exemple.

D'un autre côté, ! ne peut pas exister est sémantique. Tu peux écrire

let v = exit(0);

d'avoir v syntaxiquement tapé ! dans le contexte, mais parce que la liaison ne s'exécutera même pas et donc elle ne sortira pas dans le monde réel.

Voici donc la raison : nous autorisons ! contraindre n'importe quel type, uniquement lorsque vous pouvez écrire une expression qui a le même type. Si un type ne peut même pas exister syntaxiquement, il ne devrait pas être autorisé.

Cela s'applique également aux rvalues ​​non dimensionnées, donc avant d'avoir des rvalues ​​non dimensionnées, forcer ! vers des types non dimensionnés n'est pas autorisé, jusqu'à ce que nous sachions comment gérer les rvalues ​​non dimensionnées. Et ce n'est qu'à ce stade que nous pouvons commencer à réfléchir à ce problème (une solution évidente semble être d'autoriser Box::new recevoir des valeurs non dimensionnées).

Il n'est pas possible d'avoir une variable locale dont le type est str, par exemple.

C'est juste une limitation de l'implémentation actuelle : https://github.com/rust-lang/rust/issues/48055

@RalfJung

Ce n'est pas le problème ici.

Encore une fois, supposons que nous soyons dans la situation où Box::new(!: $0): Box<dyn fmt::Debug>$0 est une variable de type.

Le compilateur peut alors déduire assez facilement que $0: Sized (car $0 est égal au paramètre de type de Box::new , qui a une borne T: Sized ).

Le problème vient du fait que le compilateur doit déterminer quel type de coercition utiliser pour transformer le Box<$0> en Box<dyn fmt::Debug> . A partir d'un POV "local", il y a 2 solutions :

  1. Avoir une CoerceUnsized coercition, nécessitant Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>> . Il s'agit d'un programme valide pour $0 = ! et sa
  2. Avoir une contrainte d'identité, avec $0 = dyn fmt::Debug . Ceci est incompatible avec l'exigence $0: Sized .

Le compilateur ne veut pas avoir trop de choses ouvertes lorsqu'il considère les ambiguïtés, car cela peut causer à la fois des problèmes de performances et des problèmes difficiles à déboguer, il choisit donc la coercition à utiliser d'une manière assez stupide (en particulier, nous ne 't have T: CoerceUnsized<T> , donc si le compilateur choisit l'option 1, il peut être "bloqué" assez facilement), et il finit par choisir l'option 2, qui échoue. J'ai eu une idée pour le rendre un peu plus intelligent et choisir l'option 1.

Alors en y réfléchissant à partir de ce POV, la bonne idée pourrait être de déclencher une coercition non dimensionnée si

  1. La coercition non dimensionnée est cohérente (ce seraient les règles actuelles, sauf que l'ambiguïté est OK).
  2. ne pas déclencher la coercition non dimensionnée est incohérent. Nous devons voir que cela peut être calculé sans ruiner les performances sur des fonctions plus importantes.

Avoir une coercition d'identité, avec $0 = dyn fmt::Debug. Cela n'est pas conforme à l'exigence de 0 $ : taille.

Je vois à peine pourquoi devrions-nous autoriser let v: str=! mais pas let v: str; . Si vous ne pouvez même pas déclarer une variable avec un type spécifique, pourquoi pouvez-vous lui lier une valeur (peut-être impossible) ?

Lorsque la rvalue non dimensionnée n'est pas livrée, la manière cohérente est de ne pas autoriser de coercition sur les types non dimensionnés, car cela créera (peut-être temporairement) des rvalues ​​non dimensionnées, même si cela ne se produit que dans l'imagination.

Ma conclusion est donc que le problème Box::new(!) as Box<Error> est un problème bloquant pour les rvalues ​​non dimensionnées, pas pour le type ! . Les règles de coercition ! devraient être :

Vous pouvez écrire let v: T , si et seulement si vous pouvez écrire let v: T = ! .

Le sens réel sera alors évalué avec la langue. Surtout, après que nous ayons des rvalues ​​non dimensionnées, nous avons Box::new(! as dyn Debug) mais avant cela, je dirais non.

Alors que pouvons-nous faire pour aller de l'avant ?

Ajoutez une porte de fonctionnalité sur le code qui déclenche la coercition non dimensionnée, si rvalue non dimensionnée est activé, essayez cette coercition, sinon, ignorez-la. S'il s'agit de la seule coercition disponible, le message d'erreur devrait être "le trait lié str: std::marker::Sized n'est pas satisfait", comme dans des cas similaires. Donc

Nous devons voir que cela peut être calculé sans ruiner les performances sur des fonctions plus importantes.

est abordé : juste une simple vérification des fonctionnalités.

Pendant ce temps, ajoutez un nouveau problème pour les rvalues ​​non dimensionnées et

Intéressant.

Cette

fn main() {
    let _:str = *"";
}

compile, mais

fn main() {
    let v:str = *"";
}

ne pas. Ce n'est même pas lié au type ! . Dois-je créer un problème pour cela?

Je ne sais pas si ce dernier est vraiment un bug. Le deuxième exemple ne compile pas car, sans support rvalue non dimensionné, le compilateur veut savoir statiquement combien d'espace de pile allouer pour la variable locale v mais ne peut pas car il s'agit d'un DST.

Dans le premier exemple, le modèle _ est spécial en ce qu'il correspond non seulement à n'importe quoi (comme une liaison de variable locale), mais il ne crée même pas de variable du tout. Ce code est donc le même que fn main() { *""; } sans let . Déréférencer une référence (même DST) puis ne rien faire avec le résultat n'est pas utile, mais cela semble être valide et je ne suis pas convaincu que cela devrait être invalide.

Droit. Mais c'est vraiment déroutant, surtout ce qui suit

fn main() {
    let _v:str = *"";
}

ne compile pas non plus. Avec votre théorie sur _ cela devrait être le même, sauf que nous appelons la chose inutilisée _v plutôt que simplement _ .

D'après mes souvenirs, la seule différence entre _v et v est que le trait de soulignement principal supprime les avertissements concernant les valeurs inutilisées.

D'un autre côté, _ signifie spécifiquement "le jeter" et est géré spécialement afin de lui permettre d'apparaître à plus d'un endroit dans un motif (par exemple, décompression de tuples, liste d'arguments, etc.) sans provoquer de une erreur à propos d'une collision de noms.

D'un autre côté, _ signifie spécifiquement "le rejeter" et est géré spécialement afin de lui permettre d'apparaître à plus d'un endroit dans un modèle (par exemple, décompression de tuples, liste d'arguments, etc.) sans provoquer d'erreur sur un nom collision.

Correct. En plus de ce que vous avez dit, let _ = foo() entraîne l'appel immédiat de drop , alors que _v n'est supprimé que lorsqu'il sort de la portée (comme v ferait ).

En effet, c'est ce que je voulais dire par « _ est spécial ».

Donc maintenant, il semble que refuser toutes les valeurs r non dimensionnées (à moins que la fonctionnalité "valeurs non dimensionnées" ne soit activée) dans la nature serait un changement décisif, bien que l'effet soit faible, je pense.

Je suggère alors toujours d'avoir une porte de fonctionnalité pour interdire la coercition de ! aux rvalues ​​non dimensionnées. Cela refuserait un code comme let _:str = return; , mais je ne pense pas que quiconque l'utilisera dans le code.

Tant que le type ! est instable, nous pouvons apporter des modifications importantes là où il peut ou non être forcé.

La question est, si nous le stabilisons sans coercition sur les DST afin de corriger https://github.com/rust-lang/rust/issues/49593 , voudrons-nous restaurer cette coercition plus tard lorsque nous ajouterons des rvalues ​​non dimensionnées et serions-nous pouvoir le faire sans casser à nouveau https://github.com/rust-lang/rust/issues/49593 ?

Ma proposition précédente inclut ceci. #49593 devrait être un problème bloquant pour les rvalues ​​non dimensionnées.

Ma suggestion personnelle pour la solution finale est de laisser Box::new (peut également inclure d'autres méthodes importantes) accepter des paramètres non dimensionnés et permettre des ambiguïtés dans des situations similaires.

FWIW, _ n'a rien à voir avec un nom de variable spécial. C'est une syntaxe de modèle entièrement distincte qui ne fait rien à l' endroit recherché . La correspondance de modèle fonctionne sur des lieux , pas sur des valeurs .
Le plus proche d'une liaison de variable est ref _x , mais même dans ce cas, vous aurez probablement besoin de NLL pour éviter les emprunts conflictuels qui en résultent. Cela fonctionne d'ailleurs :

// The type `str` is the type of the place being matched. `x` has type `&str`.
let ref x: str = *"foo";
// Fully equivalent to this:
let x: &str = &*"foo";

@eddyb

Vous n'avez pas compris mon point. Mon hypothèse est que dans Rust, il n'y a actuellement aucune valeur r non dimensionnée pouvant apparaître dans le code. Cependant, il s'avère qu'ils apparaissent dans let _:str = *"" . Bien qu'une telle valeur puisse vivre peu de temps, au niveau syntaxique elle vit. Quelque chose comme un fantôme...

Vos exemples à la place sont techniquement tout à fait légaux et ce n'est pas le sujet. str n'est pas dimensionné, mais &str est dimensionné.

Cependant, il s'avère qu'ils apparaissent dans let _:str = *"". Bien qu'une telle valeur puisse vivre peu de temps, au niveau syntaxique elle vit. Quelque chose comme un fantôme...

Il y a un "fantôme" similaire dans une expression comme &*foo . D'abord vous déréférencez foo puis prenez l'adresse du résultat sans le déplacer . Ce n'est donc pas la même chose que & { *foo } , par exemple.

@earthengine @SimonSapin Aucune valeur r non dimensionnée ("expression de valeur") n'existe ici. Seules les lvalues ​​("place expressions") le font. *"foo" est un lieu , et la correspondance de modèle ne nécessite pas de valeur, seulement un lieu.

Les noms "lvalue" et "rvalue" sont des reliques et quelque peu trompeurs en C aussi, mais encore pire en Rust, car le RHS de let est une "lvalue" (expression de lieu), malgré le nom.
(Les expressions d'affectation sont le seul endroit où les noms et les règles C ont un sens dans Rust)

Dans vos exemples, let x = *"foo"; , la valeur est requise pour la lier à x .
De même, &*"foo" crée une expression de lieu puis l'emprunte, sans jamais avoir besoin de créer une valeur non dimensionnée, tandis que {*"foo"} est toujours une expression de valeur et n'autorise donc pas les types non dimensionnés.

Dans https://github.com/rust-lang/rust/pull/52420 j'ai frappé ça

let Ok(b): Result<B, !> = ...;
b

ne fonctionne plus. Est-ce intentionnel ?

AFAIK oui -- l'histoire du modèle infaillible est compliquée et a une porte de fonctionnalité distincte de ! lui-même. Voir également https://github.com/rust-lang/rfcs/pull/1872 et https://github.com/rust-lang/rust/issues/48950.

@ Ericson2314 spécifiquement la fonctionnalité que vous auriez besoin d'ajouter à liballoc est exhaustive_patterns .

Merci!!

Une conversation intéressante sur les internes :

Si vous comptez faire ça, pourquoi ne pas vous régaler ! lui-même en tant que variable d'inférence ?

Faites simplement un wrapper autour de ! avec un PhandomData pour lever l'ambiguïté du type d'élément.

https://github.com/rust-lang/rust/issues/49593 a maintenant été corrigé. C'était la raison du précédent retour à la stabilisation. Le rapport de stabilisation précédent est ici . Essayons à nouveau !

@rfcbot fcp fusionner

Je pense que rfcbot peut prendre en charge plus d'un FCP dans le même problème. Ouvrons un nouveau numéro pour ce cycle de stabilisation ?

https://github.com/rust-lang/rust/pull/50121 a non seulement inversé la stabilisation, mais également la sémantique de repli. Est-ce quelque chose que nous voulons revoir?

La case non cochée restante dans la description du problème est :

Quels traits devons-nous implémenter pour ! ? Le PR initial #35162 comprend Ord et quelques autres. Il s'agit probablement davantage d'un problème de T-libs, j'ajoute donc cette balise au problème.

Nous pouvons ajouter des impls plus tard, n'est-ce pas ? Ou est-ce un bloqueur ?

@SimonSapin Ouvert https://github.com/rust-lang/rust/issues/57012. Je m'attendrais à ce que le retour à ! soit à nouveau activé dans le cadre de ce changement, oui (bien que discutons-en sur la question de la stabilisation).

Apparemment, il y a un bug/un trou à ne jamais taper derrière une porte de fonctionnalité, et il est possible de s'y référer sur Stable : https://github.com/rust-lang/rust/issues/33417#issuecomment -467053097

Edit : déposé https://github.com/rust-lang/rust/issues/58733.

Ajout d'une utilisation du type dans l'exemple de code lié ci-dessus :

trait MyTrait {
    type Output;
}

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

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

fn main() {
    let _a: Void;
}

Cela compile en Rust 1.12.0 qui, je pense, est le premier avec https://github.com/rust-lang/rust/pull/35162. En 1.11.0, il se trompe avec :

error: the trait bound `fn() -> !: MyTrait` is not satisfied [--explain E0277]
  --> a.rs:12:13
   |>
12 |>     let _a: Void;
   |>             ^^^^
help: the following implementations were found:
help:   <fn() -> T as MyTrait>

error: aborting due to previous error

Quel est l'état de celui-ci ?

Pour autant que je sache, le statut n'a pas changé de manière significative depuis mon résumé dans https://github.com/rust-lang/rust/issues/57012#issuecomment -467889706.

Quel est l'état de celui-ci ?

@hosunrise : Le est actuellement bloqué sur https://github.com/rust-lang/rust/issues/67225

Je suis peut-être loin d'ici, mais les problèmes spécifiques mentionnés dans la discussion sur les peluches (https://github.com/rust-lang/rust/issues/66173) semblent tous deux résolus si chaque énumération a une branche ! dans le système de types ?

Je noterai que cela ne s'applique pas au problème mentionné dans le PO de https://github.com/rust-lang/rust/issues/67225 , qui serait toujours un problème.

Je suis peut-être loin d'ici, mais les problèmes spécifiques mentionnés dans la discussion sur les peluches (#66173) semblent tous deux résolus si chaque énumération a une branche ! dans le système de types ?

En fait, chaque énumération peut être menacée car il y avait un nombre infini de variantes qui ne peuvent jamais se produire, et donc elles ne valent pas toutes la peine d'être mentionnées.

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