Rust: Tracking-Problem beim Hochstufen von `!` zu einem Typ (RFC 1216)

Erstellt am 29. Juli 2016  ·  259Kommentare  ·  Quelle: rust-lang/rust

Tracking-Problem für rust-lang/rfcs#1216, wodurch ! zu einem Typ hochgestuft wird.

Ausstehende Probleme zu lösen

Interessante Veranstaltungen und Links

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

Hilfreichster Kommentar

@petrochenkov Vergessen Sie ! und schauen Sie sich einfach Aufzählungen an.

Wenn ich eine Aufzählung mit zwei Varianten habe, kann ich mit zwei Fällen darauf abgleichen:

enum Foo {
    Flim,
    Flam,
}

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

Dies funktioniert für jedes n, nicht nur für zwei. Wenn ich also eine Aufzählung mit null Varianten habe, kann ich darauf mit null Fällen abgleichen.

enum Void {
}

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

So weit, ist es gut. Aber schauen Sie, was passiert, wenn wir versuchen, verschachtelte Muster abzugleichen. Hier ist ein Abgleich mit einer Aufzählung mit zwei Varianten in einem Result .

enum Foo {
    Flim,
    Flam,
}

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

Wir können das innere Muster innerhalb des äußeren erweitern. Es gibt zwei Foo Varianten, also gibt es zwei Fälle für Err . Wir brauchen keine separaten Match-Anweisungen, um auf Result und auf Foo . Dies funktioniert für Enumerationen mit beliebig vielen Varianten... _außer null_.

enum Void {
}

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

Warum sollte das nicht funktionieren? Ich würde dies nicht als "besondere Unterstützung" für unbewohnte Typen beheben, ich würde es als Inkonsistenz beheben.

Alle 259 Kommentare

Huzza!

Hier gibt es eine WIP-Implementierung: https://github.com/canndrew/rust/tree/bang_type_coerced

Der aktuelle Status ist: Es baut mit old-trans auf und ist verwendbar, hat aber ein paar fehlgeschlagene Tests. Einige Tests schlagen aufgrund eines Fehlers fehl, der dazu führt, dass Code wie if (return) {} während der Übertragung abstürzt. Die anderen Tests haben mit Link-Time-Optimierung zu tun und waren bei mir immer fehlerhaft, daher weiß ich nicht, ob sie etwas mit meinen Änderungen zu tun haben.

Meine aktuelle Roadmap lautet:

  • Bringen Sie es mit MIR zum Laufen. Dies wird hoffentlich nicht allzu schwer sein, da ich auf diese Weise mit der Implementierung begonnen habe, aber ich hatte ein Problem, bei dem MIR während der Kompilierung Segfault erstellt.
  • Entfernen Sie das veraltete Divergenzmaterial aus dem Compiler ( FnOutput , FnDiverging und ähnliche).
  • Blenden Sie den neuen Typ hinter einem Feature-Gate aus. Dies würde bedeuten, wenn die Funktion deaktiviert ist:

    • ! kann nur als Typ in der Rückgabeposition geparst werden.

    • Divergierende Typvariablen sind standardmäßig () .

  • Finden Sie heraus, wie wir Kompatibilitätswarnungen ausgeben können, wenn ein Standardwert von () verwendet wird, um eine Eigenschaft aufzulösen. Eine Möglichkeit hierfür könnte darin bestehen, dem AST einen neuen Typ namens DefaultedUnit hinzuzufügen. Dieser Typ verhält sich wie () und wird unter bestimmten Umständen zu () aber eine Warnung aus, wenn er eine Eigenschaft auflöst (als () ). Das Problem bei diesem Ansatz ist, dass ich denke, dass es schwierig sein wird, alle Fehler mit der Implementierung zu finden und zu beheben - wir würden am Ende den Code der Leute knacken, um ihren Code vor dem Brechen zu bewahren.

Muss dieser Liste noch etwas hinzugefügt werden? Arbeite nur ich daran? Und sollte dieser Zweig in das Haupt-Repository verschoben werden?

Finden Sie heraus, wie wir Kompatibilitätswarnungen ausgeben können, wenn ein standardmäßiges () verwendet wird, um ein Merkmal aufzulösen. Eine Möglichkeit hierfür könnte darin bestehen, dem AST einen neuen Typ namens DefaultedUnit hinzuzufügen. Dieser Typ verhält sich wie () und wird unter bestimmten Umständen zu () aber eine Warnung aus, wenn er eine Eigenschaft auflöst (als () ). Das Problem bei diesem Ansatz ist, dass ich denke, dass es schwierig sein wird, alle Fehler mit der Implementierung zu finden und zu beheben - wir würden am Ende den Code der Leute knacken, um ihren Code vor dem Brechen zu bewahren.

@eddyb , @arielb1 , @anyone_else : Gedanken zu diesem Ansatz? Ich bin so ziemlich bis zu diesem Stadium (ohne ein paar fehlgeschlagene Tests, die ich (sehr langsam) zu beheben versuche).

Für welche Eigenschaften sollten wir implementieren!? Die ursprüngliche PR #35162 enthält Ord und einige andere.

Sollten ! automatisch _alle_ Eigenschaften implementieren?

Diese Art von Code ist ziemlich häufig:

trait Baz { ... }

trait Foo {
    type Bar: Baz;

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

Ich würde erwarten, dass ! für Foo::Bar verwendbar ist, um anzuzeigen, dass ein Bar niemals wirklich existieren kann:

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

Dies ist jedoch nur möglich, wenn ! alle Eigenschaften implementiert.

@tomaka Es gibt RFC darüber: https://github.com/rust-lang/rfcs/pull/1637

Das Problem ist, dass, wenn ! Trait implementiert, es auch !Trait implementieren sollte ...

Das Problem ist, dass wenn ! implementiert Trait es sollte auch !Trait implementieren...

Dann ! Sonderfall

@tomaka ! kann _all_ Traits nicht automatisch implementieren, da Traits statische Methoden und zugehörige Typen/Konstanten haben können. Es kann jedoch automatisch Merkmale implementieren, die nur nicht-statische Methoden haben (dh Methoden, die ein Self annehmen).

Was !Trait betrifft, hat jemand vorgeschlagen, dass ! sowohl Trait _und_ !Trait automatisch implementieren könnte. Ich bin mir nicht sicher, ob das gut ist, aber ich vermute, dass negative Eigenschaften überhaupt nicht gut sind.

Aber ja, es wäre schön, wenn ! Baz in Ihrem Beispiel für genau diese Fälle automatisch implementieren könnte.

Wann genau setzen wir divergierende Typvariablen standardmäßig auf () / ! und wann geben wir einen Fehler aus, weil wir nicht genügend Typinformationen ableiten können? Ist das irgendwo angegeben? Ich möchte folgenden Code kompilieren können:

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

Aber der erste Fehler, den ich erhalte, ist " unable to infer enough type information about _ ". In diesem Fall wäre es meiner Meinung nach sinnvoll, dass _ standardmäßig auf ! . Als ich jedoch Tests zum Standardverhalten schrieb, fand ich es überraschend schwierig, eine Typvariable als Standard festzulegen. Deshalb sind diese Tests so kompliziert.

Ich hätte gerne eine klare Vorstellung davon, warum wir dieses Standardverhalten haben und wann es aufgerufen werden soll.

Aber der erste Fehler, den ich erhalte, ist "nicht in der Lage, genügend Typinformationen über _ abzuleiten". In diesem Fall halte ich es für sinnvoll, dass _ standardmäßig auf ! gesetzt wird. Als ich jedoch Tests zum Standardverhalten schrieb, fand ich es überraschend schwierig, eine Typvariable als Standard festzulegen. Deshalb sind diese Tests so kompliziert.

Das ist meiner Meinung nach eine sehr gute Idee. Dasselbe gilt für None das zum Beispiel standardmäßig Option<!> .

@carllerche

Der unit_fallback Test ist sicherlich eine seltsame Art, dies zu demonstrieren. Eine weniger makro-ey Version ist

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

Nur die Typvariable, die von return / break / panic!() standardmäßig alles.

Wann genau setzen wir divergierende Typvariablen standardmäßig auf ()/! und wann geben wir einen Fehler aus, weil wir nicht genügend Typinformationen ableiten können? Ist das irgendwo angegeben?

Definiere "spezifiziert". :) Die Antwort ist, dass bestimmte Operationen, die nirgendwo außerhalb des Codes aufgeschrieben werden, erfordern, dass der Typ zu diesem Zeitpunkt bekannt ist. Der häufigste Fall ist der Feldzugriff ( .f ) und der Methodenversand ( .f() ), aber ein anderes Beispiel ist deref ( *x ), und wahrscheinlich gibt es noch ein oder zwei weitere. Dafür gibt es meist vernünftige Gründe – im Allgemeinen gibt es mehrere unterschiedliche Vorgehensweisen, und wir können keine Fortschritte machen, ohne zu wissen, welchen wir nehmen sollen. (Möglicherweise wäre es möglich, den Code so umzugestalten, dass dieser Bedarf als eine Art "ausstehende Verpflichtung" registriert werden kann, aber dies ist kompliziert.)

Wenn Sie es bis zum Ende des Fn schaffen, führen wir alle ausstehenden Merkmalsauswahloperationen durch, bis ein stationärer Zustand erreicht ist. Dies ist der Punkt, an dem Standardwerte (zB i32 usw.) angewendet werden. Dieser letzte Teil wird im RFC beschrieben, in dem es um benutzerdefinierte Standardtypparameter geht (obwohl RFC im Allgemeinen Arbeit benötigt).

https://github.com/rust-lang/rust/issues/36011
https://github.com/rust-lang/rust/issues/36038
https://github.com/rust-lang/rust/issues/35940

Gibt es einige Fehler, die der Liste der ausstehenden Probleme hinzugefügt werden müssen.

@canndrew die sehen ein bisschen ähnlich aus wie https://github.com/rust-lang/rust/issues/12609

Junge, das ist ein alter Fehler! Aber ja, ich würde sagen, meine #36038 ist ein Duplikat davon (ich dachte, ich hätte sie schon einmal irgendwo gesehen). Ich glaube nicht, dass ! wirklich für die Hauptsendezeit in Betracht gezogen werden kann, bis das behoben ist.

Ist geplant, dass ! die Vollständigkeit des Mustervergleichs beeinflusst? Beispiel für aktuelles, möglicherweise falsches Verhalten:

#![feature(never_type)]

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

@tikue ja, es ist einer der oben aufgeführten Fehler.

@lfairy Whoops, habe es nicht gesehen, weil es nicht in den Kontrollkästchen oben aufgeführt war. Vielen Dank!

Gibt es Pläne, From<!> for T und Add<T> for ! (mit einem Ausgabetyp von ! ) zu implementieren? Ich weiß, dass das eine wirklich seltsam spezifische Anfrage ist – ich versuche, beides in dieser PR zu verwenden .

From<!> for T definitiv. Add<T> for ! ist wahrscheinlich Sache des Libs-Teams, aber ich persönlich denke, ! sollte jede Eigenschaft implementieren, für die es eine logische, kanonische Implementierung hat.

@canandrew Danke! Ich bin an die Eigenschaft Nothing von scala gewöhnt, die ein Untertyp jedes Typs ist und daher so ziemlich überall verwendet werden kann, wo ein Wert vorkommen kann. Ich sympathisiere jedoch definitiv mit dem Wunsch, die Auswirkungen zu verstehen, die impl All for ! oder ähnliches auf das Typensystem von Rost haben würde, insbesondere in Bezug auf negative Eigenschaftsgrenzen und dergleichen.

Laut https://github.com/rust-lang/rfcs/issues/1723#issuecomment -241595070 hat From<!> for T Kohärenzprobleme.

Ach richtig, ja. Wir müssen etwas dagegen tun.

Es wäre gut, wenn Trait-Impls explizit angeben könnten, dass sie von anderen Trait-Impls überschrieben werden. Etwas wie:

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

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

Ist das nicht durch Spezialisierung abgedeckt? Bearbeiten: Ich glaube, dies ist die Gitter-Impl-Regel.

Ist es eine praktikable Alternative, die Sonderförderung für unbewohnte Typen so weit wie möglich zu vermeiden?

All diese match (res: Res<A, !>) { Ok(a) /* No Err */ } , speziellen Methoden für Result sehen sehr konstruiert aus, wie Features um der Features willen, und scheinen den Aufwand und die Komplexität nicht wert zu sein.
Ich verstehe, dass ! ein Haustier-Feature von @canndrew ist und er es weiterentwickeln möchte, aber vielleicht ist es von Anfang an die falsche Richtung und #12609 ist nicht einmal ein Fehler?

@petrochenkov #12609 ist keine Besonderheit für den Nie-Typ. Es ist nur eine Fehlerbehebung, um eindeutig nicht erreichbaren Code zu erkennen.

@petrochenkov Vergessen Sie ! und schauen Sie sich einfach Aufzählungen an.

Wenn ich eine Aufzählung mit zwei Varianten habe, kann ich mit zwei Fällen darauf abgleichen:

enum Foo {
    Flim,
    Flam,
}

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

Dies funktioniert für jedes n, nicht nur für zwei. Wenn ich also eine Aufzählung mit null Varianten habe, kann ich darauf mit null Fällen abgleichen.

enum Void {
}

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

So weit, ist es gut. Aber schauen Sie, was passiert, wenn wir versuchen, verschachtelte Muster abzugleichen. Hier ist ein Abgleich mit einer Aufzählung mit zwei Varianten in einem Result .

enum Foo {
    Flim,
    Flam,
}

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

Wir können das innere Muster innerhalb des äußeren erweitern. Es gibt zwei Foo Varianten, also gibt es zwei Fälle für Err . Wir brauchen keine separaten Match-Anweisungen, um auf Result und auf Foo . Dies funktioniert für Enumerationen mit beliebig vielen Varianten... _außer null_.

enum Void {
}

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

Warum sollte das nicht funktionieren? Ich würde dies nicht als "besondere Unterstützung" für unbewohnte Typen beheben, ich würde es als Inkonsistenz beheben.

@petrochenkov Vielleicht habe ich das falsch verstanden, was du gesagt hast. Im Thread #12609 werden zwei Fragen diskutiert:

(0) Soll dieser Code kompiliert werden dürfen?

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

(1) Soll dieser Code kompiliert werden dürfen?

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

Wie derzeit implementiert, lauten die Antworten "Nein" bzw. "Ja". #12609 spricht von (1) speziell in der Ausgabe selbst, aber ich dachte an (0), als ich antwortete. Was die Antworten angeht, _sollte_ ich denke, (0) sollte definitiv "ja" sein, aber bei (1) bin ich mir auch nicht sicher.

@canndrew
Es kann sinnvoll sein, (1) zu machen, dh unerreichbare Muster, ein Lint und kein harter Fehler, unabhängig von unbewohnten Typen, RFC 1445 enthält weitere Beispiele dafür, warum dies nützlich sein kann.

Bezüglich (0) bin ich von deiner Erklärung mehr oder weniger überzeugt. Ich bin total glücklich, wenn dieses Schema natürlich auf der Pattern-Checking-Implementierung im Compiler beruht und mehr speziellen Code entfernt als hinzufügt.

Übrigens habe ich hier eine PR gemacht, um (0) zu beheben: https://github.com/rust-lang/rust/pull/36476

Ich bin total glücklich, wenn dieses Schema natürlich auf der Pattern-Checking-Implementierung im Compiler beruht und mehr speziellen Code entfernt als hinzufügt.

Es tut es seltsamerweise nicht. Aber das ist wahrscheinlich eher ein Artefakt davon, wie ich es in den vorhandenen Code gehackt habe, als dass es keinen eleganten Weg gibt, dies zu tun.

Ich denke, für Makros ist es nützlich, dass (1) kein schwerer Fehler ist.

Ich denke, (1) sollte standardmäßig kompilieren, aber eine Warnung aus dem gleichen Lint wie hier ausgeben:

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

Wenn wir schon dabei sind, macht es Sinn, einige Muster angesichts von ! unwiderlegbar zu machen?

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

Ich stimme zu, dass es ein Fehler ist, nicht erschöpfende Übereinstimmungen zu machen, aber unerreichbare, dh redundante Muster, nur eine Warnung, scheint sinnvoll zu sein.

Ich hatte einige PRs, die eine Weile herumsaßen und Fäulnis sammelten. Kann ich etwas tun, damit diese überprüft werden? Es muss wahrscheinlich eine Diskussion darüber geben. Ich spreche von #36476, #36449 und #36489.

Ich bin gegen die Idee, zu denken, dass der divergente Typ (oder der "untere" Typ in der Typentheorie) derselbe ist wie der leere Typ enum (oder der Typ "Null" in der Typentheorie). Sie sind unterschiedliche Kreaturen, obwohl beide keinen Wert oder keine Instanz haben können.

Meiner Meinung nach kann ein Bottom-Typ nur in jedem Kontext vorkommen, der einen Return-Typ darstellt. Zum Beispiel,

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

Aber du solltest nicht sagen

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

oder

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

oder auch

type ID=fn(!)->!;

da keine Variablen vom Typ ! sollten und daher keine Eingabevariablen vom Typ ! .

Leer enum ist in diesem Fall anders, kann man sagen

enum Empty {}

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

dann

 match Empty::new() {}

Das heißt, es gibt einen grundlegenden Unterschied zwischen ! und Empty : Sie können keine Variable vom Typ ! deklarieren, aber dies für Empty .

@earthengine Was ist der Vorteil dieser (in meinen Augen völlig künstlichen) Unterscheidung zwischen zwei Arten unbewohnbarer Typen?

Zahlreiche Gründe für die nicht eine solche Unterscheidung, die gebracht wurden - zum Beispiel, in der Lage zu sein , schreiben Result<!, E> , haben diese interact schön mit divergierenden Funktionen, während immer noch die monadische Operationen verwenden zu können , Result , wie map und map_err .

In der funktionalen Programmierung wird der leere Typ ( zero ) häufig verwendet, um die Tatsache zu verschlüsseln, dass eine Funktion nicht zurückgibt oder ein Wert nicht existiert. Wenn Sie bottom type sagen, ist mir nicht klar, auf welches Konzept der Typtheorie Sie sich beziehen; normalerweise ist bottom der Name für einen Typ, der ein Untertyp aller Typen ist -- aber in diesem Sinne sind in Rust weder ! noch eine leere Aufzählung bottom . Aber auch hier ist es in der Typentheorie nicht ungewöhnlich, dass zero kein bottom Typ ist.

Das heißt, es gibt einen grundlegenden Unterschied zwischen ! und Empty : Sie können keine Variable vom Typ ! deklarieren, aber dies für Empty .

Das ist, was dieser RFC repariert. ! ist nicht wirklich ein "Typ", wenn Sie ihn nicht wie einen Typ verwenden können.

@RalfJung
Diese Konzepte stammen aus der linearen Logik https://en.wikipedia.org/wiki/Linear_logic

Da der vorherige Text in diesem Beitrag einige Fehler enthält, habe ich diese entfernt. Sobald ich es richtig gemacht habe, wird dies aktualisiert.

top ist ein Wert, der auf beliebige Weise verwendet werden kann

Was bedeutet das beim Subtyping? Dass es jede Art werden kann? Denn das ist dann bottom .

@eddyb Ich habe einige Fehler gemacht, bitte warte auf meine neuen Updates.

Diese PR - die an einem Tag zusammengeführt wurde - steht im Widerspruch zu meiner Pattern-Matching-PR, die seit über einem Monat im Review steht. Entschuldigung, mein zweiter PR-Mustervergleich, da der erste nicht mehr zusammengeführt werden konnte und nach zwei Monaten Überprüfung neu geschrieben werden musste.

ffs Jungs

@canndrew argh, das tut mir leid. =( Ich hatte auch vor, heute deine PR zu schreiben...

Entschuldigung, ich möchte nicht jammern, aber das hat sich in die Länge gezogen. Ich habe es aufgegeben, mehrere PRs gleichzeitig zu verwalten, und versuche jetzt seit September, diese eine Änderung vorzunehmen. Ich denke, ein Teil des Problems ist der Zeitzonenunterschied - Sie sind alle online, während ich im Bett liege, daher ist es schwierig, über diese Dinge zu plaudern.

Angesichts der Tatsache, dass Ausnahmen eine logische und notwendige Lücke im Typsystem sind, habe ich mich gefragt, ob jemand ernsthaft darüber nachgedacht hat, sie formeller mit ! zu behandeln.

Verwenden Sie https://is.gd/4EC1Dk als Beispiel und Bezugspunkt, was wäre, wenn wir darüber hinausgingen, und.

1) Behandeln Sie jede Funktion, die in Panik geraten kann, aber keinen Fehler oder kein Ergebnis zurückgibt, lassen Sie ihre Typsignatur implizit von -> Foo zu -> Result<Foo,!> 2) any ändern. Ergebnistypes would have their Error types implicitly be converted to enum AnonMyErrWrapper { Die(!),Error}```
3) Seit ! eine Anzeige in der Größe Null unbewohnbar ist, würde die Konvertierung zwischen den Typen keine Kosten verursachen, und eine implizite Konvertierung könnte hinzugefügt werden, um sie abwärtskompatibel zu machen.

Der Vorteil besteht natürlich darin, dass Ausnahmen effektiv in das Typsystem aufgenommen werden und es möglich wäre, über sie nachzudenken, eine statische Analyse durchzuführen usw.

Mir ist klar, dass dies aus Sicht der Community nicht trivial wäre, wenn nicht aus technischer Sicht. :)

Dies überschneidet sich wahrscheinlich auch mit einem möglichen zukünftigen Wirkungssystem.

@tupshin das ist eine bahnbrechende Veränderung, zumindest ohne viel Gymnastik. Ich empfehle, das Abwickeln zu deaktivieren und "Ergebnis" manuell zu verwenden, wenn Sie diese Klarheit wünschen. [Und übrigens, dieses Thema ist nicht wirklich der Ort, um so etwas anzusprechen --- das Design von ! ist nicht etwas, das Sie in Frage stellen, also ist dies reine Zukunftsarbeit.]

Mir ist klar, wie kaputt es wäre, zumindest ohne nennenswerte Gymnastik, und es ist fair, dass es sich um eine völlig zukünftige Arbeit handelt. Und ja, ich bin sehr zufrieden mit dem, was geplant ist! soweit, soweit es geht. :)

@nikomatsakis In Bezug auf die

  • Code-Bereinigung von #35162, reorganisieren Sie typeck ein wenig, um Typen durch die Return-Position zu führen, anstatt expr_ty aufzurufen
  • Beheben Sie die Behandlung von unbewohnten Typen in Spielen

Ich werde hier einen weiteren Riss starten:

  • Wie kann man Warnungen für Personen implementieren, die sich auf (): Trait-Fallback verlassen, wo sich dieses Verhalten in Zukunft ändern könnte?

Ich plane, TyTuple ein Flag hinzuzufügen, um anzuzeigen, dass es durch Vorgabe einer abweichenden Typvariable erstellt wurde, und dann in der Merkmalsauswahl nach diesem Flag zu suchen.

@canndrew

Ich plane, TyTuple ein Flag hinzuzufügen, um anzuzeigen, dass es durch Vorgabe einer abweichenden Typvariable erstellt wurde, und dann in der Merkmalsauswahl nach diesem Flag zu suchen.

Okay, großartig!

Nun, vielleicht großartig. =) Klingt ein bisschen komplex, zwei semantisch äquivalente Typen ( () vs () ) zu haben, die sich auf diese Weise unterscheiden, aber ich kann mir keinen besseren Weg vorstellen. =( Und ein Großteil des Codes sollte dank Regionen sowieso darauf vorbereitet werden.

Was ich meine ist, dass ich TyTuple einen bool hinzufüge

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

was darauf hinweist, dass wir Warnungen für dieses (Einheiten-)Tupel ausgeben sollten, wenn wir versuchen, bestimmte Eigenschaften darauf auszuwählen. Dies ist sicherer als mein ursprünglicher Ansatz, ein weiteres TyDefaultedUnit hinzuzufügen.

Hoffentlich müssen wir diesen Bool nur für einen Warnzyklus aufbewahren.

Bezüglich des Problems uninitialized / transmute / MaybeUninitialized halte ich folgende Vorgehensweise für sinnvoll:

  • Fügen Sie MaybeUninitialized zur Standardbibliothek hinzu, unter mem
  • Fügen Sie ein Häkchen bei uninitialized dass der Typ als bewohnt bekannt ist. Geben Sie andernfalls eine Warnung mit einem Hinweis aus, dass dies in Zukunft ein schwerer Fehler sein wird. Schlagen Sie vor, stattdessen MaybeUninitialized verwenden.
  • Fügen Sie ein Häkchen bei transmute dass der Typ "bis" nur dann unbewohnt ist, wenn er auch vom Typ "von" ist. Geben Sie auf ähnliche Weise eine Warnung aus, die zu einem harten Fehler wird.

Die Gedanken?

Es scheint einige Meinungsverschiedenheiten über die Semantik von &! . Nach #39151 wird es mit aktiviertem never_type Feature Gate in Matches als unbewohnt behandelt.

Wenn es keine automatischen Trait-Impls für ! wird, wäre es zumindest sehr hilfreich, entsprechende Traits von std zu implementieren. Eine große Einschränkung von void::Void ist, dass es außerhalb von std lebt und das bedeutet, dass pauschale impl<T> From<Void> for T unmöglich sind und dies die Fehlerbehandlung einschränkt.

Ich denke, mindestens From<!> sollte für alle Typen implementiert werden und Error , Display und Debug sollten für ! implementiert werden.

Ich denke, mindestens From<!> sollte für alle Typen implementiert werden

Dies steht leider in Konflikt mit dem From<T> for T Impl.

Dies steht leider in Konflikt mit dem From<T> for T Impl.

Zumindest bis die Gitterimpl-Spezialisierung implementiert ist.

Gute Argumente. Ich hoffe, dass es eines Tages fertig ist.

Sollte [T; 0] ein [!; 0] [T; 0] Untertyp und &[T] ein &[!] Untertyp sein? Es scheint mir intuitiv, dass die Antwort „ja“ lauten sollte, aber die aktuelle Implementierung denkt anders.

[!; 0] ist bewohnt und &[!] also würde ich nein sagen. Außerdem verwenden wir dieses Mal keine Untertypisierung.

Sowohl [!; 0] als auch &[!] sollten nicht unbewohnt sein, beide Typen können den Wert [] (oder &[] ) annehmen.

Niemand sagte, dass sie unbewohnt sind oder sein sollten.

let _: u32 = unsafe { mem::transmute(return) };
Im Prinzip könnte das ok sein, aber der Compiler beschwert sich über "Umwandlung zwischen 0 Bit und 32 Bit".

Das Umwandeln von ! -> () ist jedoch erlaubt. Ich habe keinen besonderen Grund, darauf hinzuweisen; es ist eine Inkonsistenz, aber mir fällt kein praktisches oder theoretisches Problem ein.

Ich würde Subtyping nicht erwarten und würde es ablehnen, um jede Art von Logik im Compiler nicht zu verkomplizieren. Ich möchte keine Untertypisierung, die im Grunde nichts mit Regionen oder Regionsbindung zu tun hat.

Ist es möglich, Fn , FnMut und FnOnce für ! generisch für alle Ein- und Ausgabetypen zu implementieren?

In meinem Fall habe ich einen generischen Builder, der eine Funktion übernehmen kann, die beim Erstellen aufgerufen wird:

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

Da func ein Option , kann ein Konstruktor, der None verwendet, nicht auf den Typ von F . Daher sollte eine fn new Implementierung Bang verwenden:

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

Die func Funktion des Builders sollte ungefähr so ​​aussehen, um sowohl auf Builder<!> als auch auf Builder<F> aufgerufen zu werden:

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

Jetzt das Problem: Derzeit sind die Fn* Merkmale für ! nicht implementiert. Außerdem können Sie keinen generischen zugeordneten Typ für Merkmale haben (z. B. Output in Fn ). Ist es also möglich, die Trait-Familie Fn* für ! dennoch generisch über alle Input- und Output-Typen zu implementieren?

Das klingt widersprüchlich. Muss <! as Fn>::Output in einen einzelnen Typ aufgelöst werden?

<! as Fn>::Output ist ! , nicht wahr?

Theoretisch sollte <! as Fn>::Output ! , aber die aktuelle Typprüfung möchte, dass die zugeordneten Typen genau übereinstimmen: https://is.gd/4Mkxfm

@SimonSapin Es muss zu einem einzigen Typ aufgelöst werden, weshalb ich meine Frage stelle. Aus sprachlicher Sicht ist es völlig korrektes Verhalten. Aber aus Bibliothekssicht wäre die Verwendung von ! in Kontexten von generischen Funktionen sehr eingeschränkt, wenn das aktuelle Verhalten beibehalten wird.

Sollte der folgende Code

#![feature(never_type)]

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


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

druckt 10 ? Im Moment druckt es nichts. Oder irgendwelche anderen Problemumgehungen, die das return bis zum Druck verzögern?

Ich meine, wie ich schon sagte, es gibt eigentlich zwei verschiedene Arten von ! Typen: eine ist faul und eine ist eifrig. Die übliche Notation bezeichnet eine eifrige, aber manchmal brauchen wir die faule.


Da @RalfJung an einer früheren Version interessiert ist, die &return , habe ich den Code noch einmal angepasst. Und noch einmal, mein Punkt ist, dass wir in der Lage sein sollten, die return später zu verzögern. Wir könnten hier Verschlüsse verwenden, aber ich kann nur return , nicht break oder continue usw.

Zum Beispiel wäre es gut, wenn wir schreiben könnten

#![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);
    }
}

wobei die Pause verzögert wird, bis 5 gedruckt wird.

@earthengine Nein - das sollte definitiv nichts drucken. ! ist unbewohnt, dh es können keine Werte vom Typ ! angelegt werden. Wenn Sie also einen Verweis auf einen Wert vom Typ ! , befinden Sie sich in einem toten Codeblock.

Der Grund dafür, dass return den Typ ! ist, dass er divergiert. Code, der das Ergebnis eines return Ausdrucks verwendet, kann nie ausgeführt werden, da return nie "fertigt".

oO Ich wusste nicht, dass man &return schreiben kann. Das macht keinen Sinn, warum solltest du die Adresse von return annehmen dürfen?^^

Aber wie auch immer, @earthengine in Ihrem Code werden Argumente ausgewertet, bevor quit_with_print aufgerufen wird. Die Auswertung des 2. Arguments führt return , wodurch das Programm beendet wird. Das ist, als würde man etwas wie foo({ return; 2 }) schreiben -- foo wird nie ausgeführt.

@RalfJung return ist ein Ausdruck vom Typ ! , genau wie break oder continue . Es hat einen Typ, aber sein Typ ist unbewohnt.

! ist nun seit einem Jahr implementiert und arbeitet jede Nacht. Ich würde gerne wissen, was getan werden muss, um es in Richtung Stabilisierung zu bewegen.

Eine Sache, die noch nicht gelöst wurde, ist, was mit den intrinsischen Eigenschaften zu tun ist, die ! (dh uninitialized , ptr::read und transmute ). Für uninitialized ich zuletzt gehört, dass es einen Konsens gab, dass es zugunsten eines MaybeUninit Typs und möglicher zukünftiger &in , &out , &uninit Referenzen. Dafür gibt es keinen RFC, obwohl es einen früheren RFC gibt, in dem ich vorgeschlagen habe, uninitialized nur für Typen zu verwerfen, die kein neues Inhabited Merkmal implementieren. Vielleicht sollte dieser RFC verschrottet und durch " uninitialized veraltet" und " MaybeUninit hinzufügen" RFCs ersetzt werden?

Für ptr::read denke ich, dass es in Ordnung ist, es als UB zu belassen. Wenn jemand ptr::read anruft, behauptet er, dass die Daten, die er liest, gültig sind, und im Fall von ! dies definitiv nicht der Fall. Vielleicht hat aber jemand eine differenziertere Meinung dazu?

transmute reparieren ist einfach - machen Sie es einfach zu einem Fehler, in einen unbewohnten Typ umzuwandeln (anstelle von ICEing, wie es derzeit der Fall ist). Es gab eine PR , um dies zu beheben, aber sie wurde mit dem Grund geschlossen, dass wir noch eine bessere Vorstellung davon benötigen, wie nicht initialisierte Daten behandelt werden.

Wo stehen wir mit diesen im Moment? (/cc @nikomatsakis)? Wären wir bereit, mit der Einstellung von uninitialized und dem Hinzufügen des Typs MaybeUninit fortzufahren? Wenn wir diese intrinsischen Eigenschaften in Panik versetzen würden, wenn wir mit ! , wäre dies eine geeignete Notlösung, die es uns ermöglichen würde, ! zu stabilisieren?

Es sind auch die ausstehenden Probleme aufgelistet:

Welche Eigenschaften sollten wir für ! implementieren? Die anfängliche PR #35162 enthält Ord und einige andere. Dies ist wahrscheinlich eher ein T-Libs-Problem, daher füge ich dieses Tag dem Problem hinzu.

Derzeit gibt es eine ziemlich einfache Auswahl: PartialEq , Eq , PartialOrd , Ord , Debug , Display , Error . Außer Clone , die auf jeden Fall zu dieser Liste hinzugefügt werden sollten, sehe ich keine anderen, die lebenswichtig sind. Müssen wir hier die Stabilisierung blockieren? Wir könnten später einfach weitere Impls hinzufügen, wenn wir es für richtig halten.

Wie kann man Warnungen für Leute implementieren, die sich auf (): Trait Fallback verlassen, wenn sich dieses Verhalten in Zukunft ändern könnte?

Die Warnung resolve_trait_on_defaulted_unit ist implementiert und bereits in Stable.

Gewünschte Semantik für ! in Zwang (#40800)
Welche Typvariablen sollten auf ! zurückgreifen (#40801)

Diese scheinen die echten Blocker zu sein. @nikomatsakis : Haben Sie konkrete Ideen, was Sie dagegen tun können, die nur darauf warten, umgesetzt zu werden?

Ist es für ! sinnvoll, auch Sync und Send zu implementieren? Es gibt mehrere Fälle, in denen Fehlertypen Sync und/oder Send Grenzen haben, in denen es nützlich wäre, ! als "kann nicht fehlschlagen" zu verwenden.

@canndrew Ich bin nicht einverstanden, es in unsicherem Code zu verbieten. Die unreachable Kiste verwendet Transmute, um einen unbewohnten Typ zu erstellen und so auf den Optimierer hinzuweisen, dass bestimmte Codezweige niemals auftreten können. Dies ist nützlich für Optimierungen. Ich habe vor, meine eigene Kiste mit dieser Technik auf interessante Weise herzustellen.

@Kixunil wäre es nicht expliziter, hier https://doc.rust-lang.org/beta/std/intrinsics/fn.unreachable.html zu verwenden?

@RalfJung wäre es, aber das ist instabil. Erinnert mich daran, dass das Verbot der Verwandlung in unbewohnte Typen eine bahnbrechende Veränderung wäre.

@jsgf Entschuldigung, ja, ! impliziert auch alle Marker-Merkmale ( Send , Sync , Copy , Sized ).

@Kixunil Hmm , das ist schade. Es wäre jedoch viel besser, die intrinsischen unreachable zu stabilisieren.

Kein Stabilisierungsblocker (weil Perf, nicht Semantik), aber es scheint, dass Result<T, !> nicht die Unbewohntheit von ! im Enum-Layout oder Match-Codegen verwendet: https://github.com /rust-lang/rust/issues/43278

Dies ist eine so vage Idee, dass ich mich fast schuldig fühle, sie hier fallen zu lassen, aber:

Könnte Kreide möglicherweise helfen, einige der schwierigeren Fragen in Bezug auf !: Trait Implementierungen zu beantworten?

@ExpHP Ich glaube nicht. Die automatische Implementierung von !: Trait für Merkmale, die keine zugeordneten Typen/Konstanten und nur nicht-statische Methoden haben, ist bekanntermaßen solide. Es ist nur eine Frage, ob wir es tun wollen oder nicht. Die automatische Implementierung für andere Merkmale macht nicht wirklich Sinn, da der Compiler beliebige Werte für die zugehörigen Typen/Konstanten einfügen und seine eigene beliebige statische Methode impls erfinden müsste.

Es gibt bereits einen resolve-trait-on-defaulted-unit Fussel in rustc. Soll der entsprechende Punkt angekreuzt werden?

@canndrew Ist es nicht ! ?

@taralx es ist nicht unsolide, aber es würde verhindern, dass gültiger Code funktioniert. Beachten Sie, dass der folgende Code heute kompiliert wird:

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

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

@lfairy Es hängt von Ihrer Meinung ab, was "gültiger" Code ist. Für mich würde ich gerne akzeptieren, dass der von Ihnen angegebene Code "ungültig" ist, da Sie nichts von ! .

@earthengine Ich verstehe nicht, ! "etwas bekommen" können oder nicht, hat damit nichts zu tun.

Es gibt keinen Grund, warum Sie nichts vom Typ ! . Sie können jedoch nichts aus einem ! herausholen, weshalb nicht-statische Methoden auf ! abgeleitet werden könnten, ohne dem Programmierer irgendwelche Entscheidungen aufzuzwingen.

Ich denke, die am einfachsten zu merkende Regel wäre, jedes Merkmal zu implementieren, für das es genau eine gültige Implementierung gibt (außer denen, die mehr Divergenz hinzufügen, als ! im Umfang bereits verursacht).

Alles, womit wir uns beschäftigen, sollte dem gleich oder konservativer sein (was auch keine automatische Implse passt).

@ Ericson2314 Ich verstehe nicht, was das bedeutet, könnten Sie ein paar Beispiele geben?

@SimonSapin Die Regel "keine Divergenz mehr" bedeutet kein panic!() oder loop { } , aber ein v: ! , das bereits im Geltungsbereich ist, ist in Ordnung. Wenn Ausdrücke wie diese ausgeschlossen sind, haben viele Merkmale nur eine mögliche Impl für ! das Typ-Checks. (Andere haben möglicherweise einen informellen Vertrag, der alle außer 1 Impl ausschließt, aber wir können diese nicht automatisch bearbeiten.)

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

Mathematisch gesehen ist ! ein "initiales Element", was bedeutet, dass es für eine Typsignatur mit ! in ihren Argumenten genau eine Implementierung gibt, dh alle Implementierungen sind gleich – wenn von außen beobachtet. Dies liegt daran, dass sie nicht alle aufgerufen werden können.

Was hindert dies tatsächlich daran, sich zu stabilisieren? Ist es nur die mem::uninitialized Situation oder etwas anderes?

@arielb1 : Ich denke, es ist auch #40800 und #40801. Wissen Sie, wie der Status auf diesen ist?

Welche Dokumente wurden dazu geschrieben oder planen die Leute, dafür zu schreiben? Mit den jüngsten Ergänzungen zu den primitiven Typseiten in den Standarddokumenten (#43529, #43560) sollte die ! Geben Sie dort eine Seite ein? Die Referenz sollte wahrscheinlich auch aktualisiert werden, falls dies noch nicht geschehen ist.

Gibt es Pläne, anzugeben, dass asm divergiert, damit wir solche Dinge schreiben können?

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

Ohne loop{} zu müssen, um die Typprüfung zu beruhigen?

Bearbeiten: Obwohl ich annehme, dass die Verwendung von core::intrinsics::unreachable in sehr niedrigem Code akzeptabel sein kann.

@Eroc33 Wenn ! ein Typ ist, können Sie dies tun:

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

Bis dahin können Sie dies tun:

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

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

@Kixunil ! ist ein Typ auf nightly, den Sie verwenden müssen asm! . Dies geschieht normalerweise wie folgt:

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

@Kixunil Es kann einfacher mit std::intrinics::unreachable() was genau angibt, dass der Code vorher abweicht.

@Kixunil Aus der obigen Diskussion std::mem::uninitialized jedoch ändern zu können, wenn ! werden kann? Es sei denn, ich habe das falsch verstanden?

Aus der obigen Diskussion scheint es jedoch möglich zu sein, dass std::mem::uninitialized ! . Es sei denn, ich habe das falsch verstanden?

Ich denke, uninitialized wird irgendwann völlig veraltet sein und als Lücke zur Laufzeit in Panik geraten, wenn es mit ! (obwohl dies in diesem Fall keine Auswirkungen hätte).

Ich denke auch, dass wir std::intrinsics::unreachable irgendwo in libcore stabilisieren müssen und es unchecked_unreachable oder etwas nennen müssen, um es vom unreachable! Makro zu unterscheiden. Ich wollte dafür einen RFC schreiben.

@eddyb Ah, ja, habe vergessen, dass asm!() instabil ist, also könnte auch std::intrinsics::unreachable() verwendet werden.

@tbu- Sicher, ich bevorzuge das sehr, anstatt einen unbewohnten Typ zu erstellen. Das Problem ist, dass es instabil ist. Wenn es also irgendwie unmöglich wäre, Codezweige unsicher als nicht erreichbar zu markieren, würde dies nicht nur den vorhandenen Code beschädigen, sondern ihn auch nicht reparieren. Ich denke, so etwas würde dem Ruf von Rust ernsthaft schaden (vor allem wenn man bedenkt, dass die Hauptentwickler stolz behaupten, dass es stabil ist). So sehr ich Rust liebe, so etwas würde mich dazu bringen, es als nützliche Sprache zu überdenken.

@ Eroc33 Mein Verständnis ist, dass einige Leute vorschlagen, es zu tun, aber es ist nur ein Vorschlag, keine definitive Entscheidung. Und ich stehe gegen einen solchen Vorschlag, weil er die Abwärtskompatibilität verletzen würde und Rust dazu zwingen würde, 2.0 zu werden. Ich denke nicht, dass es schadet, es zu unterstützen. Wenn die Leute sich Sorgen um Bugs machen, wäre Fussel besser geeignet.

@canndrew Warum glauben Sie, dass uninitialized wird? Ich kann so etwas nicht glauben. Ich finde es äußerst nützlich, daher sehe ich keinen Grund dafür, es sei denn, es gibt einen sehr guten Ersatz.

Abschließend möchte ich wiederholen, dass ich zustimme, dass intrinsics::unreachable() besser ist als aktuelle Hacks. Davon abgesehen bin ich dagegen, diese Hacks zu verbieten oder sogar abzulehnen, bis es einen ausreichenden Ersatz gibt.

Ersatz für nicht initialisiert ist union { !, T }. Du kannst unerreichbar werden durch
ptr::read::<*const !>()ing und viele andere ähnliche Möglichkeiten.

Am 8. August 2017, 15:58 Uhr, "Martin Habovštiak" [email protected]
schrieb:

@eddyb https://github.com/eddyb Ah, ja, habe vergessen, dass asm!() ist
unstable, daher kann auch std::intrinsics::unreachable() verwendet werden.

@tbu- https://github.com/tbu- Klar, ich bevorzuge das lieber als das
unbewohnten Typ zu schaffen. Das Problem ist, es ist instabil, also wenn es so war
irgendwie unmöglich, einen Codezweig unsicher als nicht erreichbar zu markieren, wäre es nicht
nur bestehenden Code brechen, sondern ihn auch unkorrigierbar machen. ich denke sowas
würde dem Ruf von Rust ernsthaft schaden (insbesondere in Anbetracht der Hauptentwickler)
stolz behaupten, dass es stabil ist). So sehr ich Rust liebe, so etwas würde es tun
lassen Sie mich überdenken, dass es eine nützliche Sprache ist.

@Eroc33 https://github.com/eroc33 Mein Verständnis ist, dass einige Leute
schlagen vor, es zu tun, aber es ist nur ein Vorschlag, keine definitive Entscheidung. Und ich
gegen einen solchen Vorschlag stehen, weil dies die Abwärtskompatibilität beeinträchtigen würde,
zwingt Rust, 2.0 zu werden. Ich denke nicht, dass es schadet, es zu unterstützen.
Wenn die Leute sich Sorgen um Bugs machen, wäre Fussel besser geeignet.

@canndrew https://github.com/canndrew Warum denkst du nicht initialisiert ?
wird abgeschafft? Ich kann so etwas nicht glauben. Ich finde es sehr praktisch,
Wenn es also keinen sehr guten Ersatz gibt, sehe ich keinen Grund dafür
dabei.

Abschließend möchte ich noch einmal betonen, dass ich intrinsisch::unreachable()
ist besser als aktuelle Hacks. Davon abgesehen bin ich dagegen, es zu verbieten oder sogar
diese Hacks ablehnen, bis es einen ausreichenden Ersatz gibt.


Sie erhalten dies, weil Sie diesen Thread abonniert haben.
Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/rust-lang/rust/issues/35121#issuecomment-320948013 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AApc0iEK3vInreO03Bt6L3EAByBHQCv9ks5sWFt3gaJpZM4JYi9D
.

@nagisa Danke! Scheint das Problem auf technischer Ebene zu lösen.

Wäre es möglich, eine Teilmenge davon herauszuarbeiten, sodass ! als Typparameter in Rückgabetypen verwendet werden könnte? Gerade jetzt, wenn Sie eine stabile Funktion haben

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

Und wenn Sie ? möchten, können Sie die übliche Transformation nicht in

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

Beeinflussen die Standardfragen für den dornigen Zwang und den Typparameter diesen Fall?

40801 Kann abgehakt werden.

Wir sollten versuchen, #43061 zu reparieren, bevor wir ! stabilisieren.

Ich habe keine offenen I-unsound-Probleme gesehen, die Never_type erwähnen, also habe ich ein neues dafür eingereicht: #47563. Die Dinge scheinen anzunehmen, dass [!; 0] unbewohnt ist, aber ich kann einen mit einem einfachen [] erstellen.

@dtolnay zum Problem-Header hinzugefügt

@canndrew wie geht es dir mit der Zeit? Ich würde gerne sehen, dass eine Teilmenge der ! Nutzung stabilisiert wird (ohne die Regeln zur Vollständigkeit). Ich habe jedoch irgendwie den Faden verloren, wo wir stehen, und ich denke, es braucht einen Champion. Hast du Zeit, diese Person zu sein?

@nikomatsakis Ich bin mir sicher, dass ich etwas Zeit finden kann, um an diesem Zeug zu arbeiten. Was muss aber genau gemacht werden? Sind es nur die Fehler, die zu diesem Thema verlinkt sind?

Es sieht so aus, als hätte @varkor bereits PRs geöffnet, um die verbleibenden Probleme zu beheben (großartig!). Soweit ich das beurteilen kann, müssen wir nur noch entscheiden, ob wir mit den derzeit implementierten Merkmalen zufrieden sind, und das Feature-Gate verschieben/umbenennen, damit es nur die umfassenden Pattern-Matching-Änderungen abdeckt.

Aber noch eine Sache: Wollen wir mem::uninitialized::<!>() zur Laufzeit in Panik versetzen (dies verursacht derzeit UB)? Oder sollten wir diese Art von Änderungen vorerst belassen? Ich bin nicht auf dem Laufenden, was mit den Richtlinien für unsichere Codes vor sich geht.

Ich denke, der Plan ist immer noch https://github.com/rust-lang/rfcs/pull/1892 , den ich gerade bemerkt habe, den du geschrieben hast. :)

@RalfJung Gibt es etwas Besonderes, das das blockiert? Ich könnte eine PR heute schreiben, fügt MaybeUninit und deprecates uninitialized .

@canndrew Da viele Projekte alle drei Veröffentlichungskanäle unterstützen möchten und uninitialized auf Stable verfügbar ist, ist es vorzuziehen, erst bei Nightly mit der Ausgabe von veralteten Warnungen zu beginnen, sobald der Ersatz auf dem Stable-Kanal verfügbar ist. Eine sanfte Einstellung durch doc-Kommentare ist in Ordnung.

Ich habe eine PR zur Stabilisierung von ! : https://github.com/rust-lang/rust/pull/47630. Ich weiß nicht, ob wir schon bereit sind, es zusammenzuführen. Wir sollten zumindest abwarten, ob die PRs von @varkor die verbleibenden offenen Probleme beheben.

Ich denke auch, dass wir https://github.com/rust-lang/rfcs/pull/1699 erneut besuchen sollten, damit die Leute anfangen können, elegante Trait-Impls für ! schreiben.

@canndrew : Obwohl dieser RFC nicht akzeptiert wurde, sieht er dem Vorschlag in https://github.com/rust-lang/rust/issues/20021 bemerkenswert ähnlich

https://github.com/rust-lang/rust/issues/36479 sollte wahrscheinlich auch zum Issue-Header hinzugefügt werden.

@varkor es ist sehr ähnlich. Haben Sie bei Ihrer Arbeit mit unerreichbarem Code irgendwelche Probleme mit Code wie diesem bemerkt, der jetzt als nicht erreichbar gekennzeichnet wird?:

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

Denn das würde es ziemlich notwendig machen, einen solchen Vorschlag anzunehmen.

@canndrew etwas, das ich in letzter Zeit befürworte – obwohl wir noch keine formale Vorlage entwickeln müssen – ist eine Art "Zusammenfassungsthema", das versucht, klar abzugrenzen, was wir stabilisieren. Sie können es sich als einen Bericht nach dem RFC vorstellen, der versucht, in einer Art Aufzählungsliste die hervorstechenden Merkmale dessen zusammenzufassen, was stabilisiert wird, aber auch was nicht .

Ein Teil davon wären Hinweise auf Testfälle, die das fragliche Verhalten demonstrieren.

Glaubst du, du könntest versuchen, so etwas zu entwerfen? Wir können uns ein bisschen unterhalten, wenn Sie möchten, über eine Art "Umriss" - oder ich kann versuchen, ihn zu skizzieren.

Verwandte Frage: sollten wir versuchen, https://github.com/rust-lang/rust/issues/46325 zu lösen, bevor wir uns stabilisieren? Vielleicht ist es egal.

@nikomatsakis Ich Lösung von Warnungsproblemen zu warten. Das ist harmlos. Wenn keine weiteren ernsthaften Bedenken auftauchen, sollten wir meiner Meinung nach weitermachen und es stabilisieren.

@canndrew : Ich glaube nicht, dass ich mir ! stabilisiert ist.

@nikomatsakis

Glaubst du, du könntest versuchen, so etwas zu entwerfen? Wir können uns ein bisschen unterhalten, wenn Sie möchten, über eine Art "Umriss" - oder ich kann versuchen, ihn zu skizzieren.

Ich könnte zumindest einen Entwurf schreiben, und Sie könnten mir sagen, ob Sie das im Sinn hatten. Ich werde versuchen, es in den nächsten Tagen zu erledigen.

@nikomatsakis So etwas?

Zusammenfassung des Problems - Stabilisierung von !

Was wird stabilisiert

  • ! ist jetzt ein vollwertiger Typ und kann nun in jeder Typposition verwendet werden (zB RFC 1216 ). Der Typ ! kann in jeden anderen Typ umgewandelt werden, siehe https://github.com/rust-lang/rust/tree/master/src/test/run-fail/adjust_never.rs für ein Beispiel.
  • Typrückschluss wird jetzt nicht eingeschränkte Typvariablen standardmäßig auf ! anstatt auf () . Der resolve_trait_on_defaulted_unit Fussel wurde eingestellt. Ein Beispiel dafür, wo dies auftaucht, ist, wenn Sie etwas haben wie:

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

    Nach den alten Regeln würde dies ein () deserialisieren, während nach den neuen Regeln ein ! deserialisiert wird.

  • Das Feature-Gate never_type ist stabil, obwohl einige der Verhaltensweisen, die es für das Gate verwendet hat, jetzt hinter dem neuen Feature-Gate exhaustive_patterns (siehe unten) leben.

Was wird nicht stabilisiert

sollten wir versuchen, #46325 zu lösen, bevor wir uns stabilisieren?

So schön es auch wäre, jedes lose Ende so aufzuräumen, es scheint nicht wirklich ein Blocker zu sein.

@canndrew

Etwas wie das?

Ja, danke! Das ist großartig.

Was vor allem fehlt, sind Zeiger auf Testfälle, die zeigen, wie sich ! verhält. Das Publikum sollte das Lang-Team oder andere Leute sein, die genau folgen, ich denke, es zielt nicht wirklich auf "Allzweck"-Leute ab. Also zB hätte ich gerne einige Beispiele für Orte, an denen Zwang legal ist oder wo ! verwendet werden kann. Ich würde auch gerne die Hoden sehen, die uns zeigen, dass sich der umfassende Mustervergleich (ohne aktiviertes Feature-Gate) immer noch verhält. Dies sollten Zeiger auf das Repository sein.

@canndrew

So schön es auch wäre, jedes lose Ende so aufzuräumen, es scheint nicht wirklich ein Blocker zu sein.

Nun, das bedeutet, dass wir neuen Code aktivieren werden, der das Verhalten sofort ändert (zB let x: ! = ... wird sich meiner Meinung nach für einige Ausdrücke anders verhalten). Scheint am besten zu lösen, wenn wir können. Vielleicht können Sie es zu einem harten Fehler auf dem geöffneten PR machen und wir können ihn mit dem bestehenden Kraterlauf auf dem PR in einen Topf werfen?

@nikomatsakis Ich habe dieses zusammenfassende Problem erneut mit einigen Links und Beispielen aktualisiert. Außerdem tut es mir leid, dass es eine Weile gedauert hat, bis ich dazu gekommen bin, ich war in der letzten Woche sehr beschäftigt.

Vielleicht können Sie es zu einem harten Fehler auf dem geöffneten PR machen und wir können ihn mit dem bestehenden Kraterlauf auf dem PR in einen Topf werfen?

Fertig.

@rfcbot fcp zusammenführen

Ich schlage vor, dass wir den Typ ! stabilisieren – oder zumindest einen Teil davon, wie hier beschrieben . Die PR ist da .

Ein Datenbit, das ich gerne hätte, ist ein Kraterlauf. Ich bin dabei, https://github.com/rust-lang/rust/pull/47630 umzubasieren (da @canndrew gerade nicht auf Pings antwortet), damit wir diese Daten

Teammitglied @nikomatsakis hat vorgeschlagen, dies zusammenzuführen. Der nächste Schritt ist die Überprüfung durch den Rest der markierten Teams:

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

Derzeit keine Bedenken aufgeführt.

Sobald eine Mehrheit der Gutachter zustimmt (und keine Einwände erheben), wird dies in die letzte Kommentarfrist eintreten. Wenn Sie ein wichtiges Problem entdecken, das zu keinem Zeitpunkt in diesem Prozess angesprochen wurde, melden Sie sich bitte!

In diesem Dokument finden Sie Informationen darüber, welche Befehle mir markierte Teammitglieder geben können.

Oh, ein paar Dinge sind mir gerade eingefallen:

  • Wir sollten die Idee, dies zu stabilisieren, erst in der neuen Epoche in Betracht ziehen. Insbesondere die Änderung der Fallback-Regeln ist nicht rückwärtskompatibel – der Kraterlauf wird uns eine Art untere Grenze für den Fallout geben, aber es ist nur eine untere Grenze. Aber vielleicht können wir einfach die alten Fallback-Regeln beibehalten, es sei denn, Sie befinden sich in der neuen Epoche.
  • Zweitens glaube ich, dass ein Teil des Plans hier auch darin bestand, einige Richtlinien zu haben, wann es angebracht ist, eine Eigenschaft für ! zu implementieren. Die TL; DR ist, dass es in Ordnung ist, wenn die Methoden in der Eigenschaft unbrauchbar sind, ohne zuerst einen ! -Wert anzugeben – also ist die Implementierung von Clone für ! in Ordnung, denke ich , aber die Implementierung von Default ist es nicht. Anders ausgedrückt, wenn Sie beim Implementieren des Impls wahllos panic! ausführen müssen, ist das ein schlechtes Zeichen. =)

@nikomatsakis Könnten wir die Fallback-Regel in einer neuen Epoche ändern, aber trotzdem ! als Typ in der Epoche 2015 verfügbar machen?

@nikomatsakis

Wir sollten die Idee, dies zu stabilisieren, erst in der neuen Epoche in Betracht ziehen.

Das letzte Mal, als wir einen Kraterlauf gemacht haben (was schon lange her ist), waren die Auswirkungen der Änderung der Fallback-Regeln ziemlich gering. Wir wehren uns auch schon seit einiger Zeit gegen Code, der durch die Änderung beeinträchtigt werden könnte.

Zweitens glaube ich, dass ein Teil des Plans hier auch darin bestand, einige Richtlinien zu haben, wann es angebracht ist, eine Eigenschaft für ! zu implementieren.

Dies wird in den Dokumenten für !

@SimonSapin

Könnten wir die Fallback-Regel in einer neuen Epoche aber noch ändern! als Typ in der Epoche 2015 verfügbar?

Jawohl

@canndrew

Das letzte Mal, als wir einen Kraterlauf gemacht haben (was schon lange her ist), waren die Auswirkungen der Änderung der Fallback-Regeln ziemlich gering. Wir wehren uns auch schon seit einiger Zeit gegen Code, der durch die Änderung beeinträchtigt werden könnte.

Ja. Mal sehen, was Krater sagt. Aber, wie gesagt, Krater gibt uns immer nur eine untere Grenze – und dies ist eine Art "wahlweise Änderung". Dennoch vermute ich, dass Sie Recht haben und wir können dies ohne große Auswirkungen auf den Code in freier Wildbahn ändern.

Der TL;DR ist, dass es in Ordnung ist, wenn die Methoden in der Eigenschaft unbrauchbar sind, ohne zuerst einen ! Wert anzugeben

Das ist nur die normale Regel - Sie fügen ein Impl hinzu, wenn Sie es vernünftig implementieren können, wobei "Implement it in vernünftiger Weise" Panik ausschließt, aber "ex falso" UB einschließt, wenn ungültige Daten vorhanden sind.

@arielb1 Ja, aber aus irgendeinem Grund neigen Leute dazu, sich in Gegenwart von ! über solche Dinge zu verwirren, daher scheint es sich zu lohnen, explizit darauf hinzuweisen.

Vielleicht wäre es hilfreich, eine sichere Methode fn absurd(x: !) -> ! , die dokumentiert ist, um einen sicheren Weg zu sein, unerreichbaren Code oder so irgendwo in libstd auszudrücken? Ich glaube, dafür gab es einen RFC... oder zumindest ein RFC-Problem: https://github.com/rust-lang/rfcs/issues/1385

@RalfJung Ist diese absurd Funktion nicht nur identity ? Warum ist es nützlich?

Es ist nicht dasselbe wie das intrinsische unsafe fn unreachable() -> ! das keine Argumente akzeptiert und ein sehr undefiniertes Verhalten hat.

Nun ja, das ist es meistens. Ich habe den Rückgabetyp ! im Sinne von "beendet nicht" verwendet. (Der übliche Typ von absurd ist fn absurd<T>(x: !) -> T , aber das scheint auch in Rust nicht nützlich zu sein.)

Ich dachte, vielleicht würde dies helfen bei "Menschen neigen dazu, in Anwesenheit von ! über solche Dinge verwirrt zu werden" - irgendwo eine Dokumentation zu haben, die "ex falso"-Argumentation eine Sache ist.

Anscheinend habe ich auch das falsche Problem... Ich dachte, irgendwo gibt es eine Diskussion über eine solche "ex falso"-Funktion. Scheint, als ob ich falsch liege.

fn absurd<T>(x: !) -> T kann auch match x {} , aber das ist schwer zu finden, wenn Sie es noch nicht gesehen haben. Es lohnt sich zumindest, in rustdoc und im Buch darauf hinzuweisen.

fn absurd(x: !) -> T kann auch match x {} geschrieben werden, aber das ist schwer zu finden, wenn Sie es noch nicht gesehen haben.

Richtig, deshalb habe ich irgendwo in libstd eine Methode vorgeschlagen. Ich bin mir jedoch nicht sicher, wo der beste Ort ist.

Das Schöne an absurd ist, dass Sie es an Funktionen höherer Ordnung übergeben können. Ich bin mir nicht sicher, ob dies jemals notwendig ist, wenn man bedenkt, wie! verhält sich bzgl. jedoch die Vereinigung.

fn absurd<T>(x: !) -> T kann auch geschrieben werden match x {}

Es kann auch nur als x (AFAIK) geschrieben werden - Sie brauchen nur match wenn Sie ein leeres enum .

Eine absurd(x: !) -> T Funktion wäre für die Übergabe an Funktionen höherer Ordnung nützlich. Ich habe dies (zum Beispiel) beim Verketten von Futures benötigt, von denen einer den Fehlertyp ! und der andere den Fehlertyp T . Auch wenn ein Ausdruck vom Typ ! in T , bedeutet dies nicht, dass ! und T vereint werden den Typ umwandeln.

Ebenso denke ich, dass Result -ähnliche Typen eine .infallible() -Methode haben sollten, die Result<T, !> in Result<T, E> umwandelt.

Ergebnisähnliche Typen sollten eine .infallible()-Methode haben, die Result . konvertiertresultieren.

Ich hätte erwartet, dass eine solche Methode den Typ Result<T, !> -> T .

Ich hätte erwartet, dass eine solche Methode den Typ Ergebnis hat-> T.

Ist das nicht dasselbe wie unwrap ? Die Panik wird optimiert, da der Fall Err nicht erreichbar ist.

@Amanieu Der Punkt ist, dass es von unwrap während der Refaktorisierung eine Schusswaffe hinzu, damit die Panik wieder zu Live-Code wird.

unwrap liest sich wie assert -- "Laufzeitprüfung voraus" (IIRC es gab sogar Vorschläge, es umzubenennen, aber sie kamen zu spät ... leider). Wenn Sie keine Laufzeitprüfung wünschen und benötigen, würde infallible dies explizit machen.

:bell: Dies geht jetzt in seine letzte Kommentarperiode , wie in der obigen Überprüfung beschrieben . :Klingel:

@rfcbot fcp abbrechen

@aturon und @pnkfelix haben ihre Kästchen nicht

@cramertj- Vorschlag storniert.

Teammitglied @cramertj hat vorgeschlagen, dies zusammenzuführen. Der nächste Schritt ist die Überprüfung durch den Rest der markierten Teams:

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

Derzeit keine Bedenken aufgeführt.

Sobald eine Mehrheit der Gutachter zustimmt (und keine Einwände erheben), wird dies in die letzte Kommentarfrist eintreten. Wenn Sie ein wichtiges Problem entdecken, das zu keinem Zeitpunkt in diesem Prozess angesprochen wurde, melden Sie sich bitte!

In diesem Dokument finden Sie Informationen darüber, welche Befehle mir markierte Teammitglieder geben können.

:bell: Dies geht jetzt in seine letzte Kommentarperiode , wie in der obigen Überprüfung beschrieben . :Klingel:

Das haben wir heute in der Sitzung besprochen. Ich bleibe hin- und hergerissen, ob ich den Fallback jetzt ändern soll oder erst in der neuen Epoche. Hier die verschiedenen Punkte.

Die erwarteten Auswirkungen der Änderung sind gering. Realistisch macht dies also keinen großen Unterschied. Sie können die vollständige Analyse hier sehen , aber die Zusammenfassung lautet:

Und das bleiben nur die beiden tatsächlichen Regressionen: oplog-0.2.0 (eigentlich ist es seine Abhängigkeit bson-0.3.2, die kaputt ist) und rspec-1.0.0-beta.4. Beide basieren auf dem alten Fallback-Verhalten, obwohl sie glücklicherweise zur Kompilierzeit und nicht zur Laufzeit brechen. Ich werde PRs einreichen, um diese Kisten zu reparieren.

Aber es ist technisch immer noch eine bahnbrechende Änderung (und eine freiwillige). Es gibt keinen besonderen Grund , dass wir den Rückfall für Typ - Variablen ändern, mit der Ausnahme , dass () ist eine außerordentlich schlechte Wahl und es ist ziemlich selten , Code auf sie verlassen kann . Auch davor warnen wir schon lange.

Ich denke, das ist eher eine Frage der Philosophie: In der Vergangenheit haben wir solche kleinen Breaking Changes aus der Not heraus gemacht. Aber jetzt, da wir den epochalen Mechanismus haben, bietet er uns einen prinzipielleren Weg, solche Übergänge zu machen, ohne technisch etwas zu zerstören. Es kann sich lohnen, es zu verwenden, nur aus Prinzip. Auf der anderen Seite würde das manchmal bedeuten, jahrelang auf eine solche Veränderung zu warten. Und natürlich müssen wir beide Versionen dauerhaft pflegen, was die Sprachspezifikation usw. viel komplexer macht.

Ich bin ein bisschen hin- und hergerissen, aber ich denke per Saldo, nach dem Hin und Her tendiere ich derzeit wie geplant in Richtung "Lass es uns einfach universell ändern".

Ich weiß, dass ich voreingenommen bin, aber ich sehe das Ändern des Fallback-Verhaltens eher als Bugfix im Vergleich zu Dingen wie dyn Trait und catch .

Außerdem, wenn jemand darauf springen will und sagt: "Ah ha! Rust bricht doch die Abwärtskompatibilität! Ihr Stabilitätsversprechen bei 1.0 war eine Lüge!" dann zeigt die sehr nachdenkliche Diskussion zu diesem Thema, dass die Entscheidung nicht leichtfertig getroffen wurde, auch wenn die praktischen Auswirkungen vernachlässigbar waren.

OK, ich sage, lass es uns einfach ändern.

Als Kompromiss möchte ich daher einen hilfreichen Hinweis für Fehler geben, der die Leute darüber informiert, dass sich das Verhalten geändert hat - scheint plausibel. Die Idee wäre ungefähr so:

  • Wenn wir einen Fehler wie !: Foo für ein Merkmal sehen und dieses Merkmal für (): Foo implementiert ist und wir einen Fallback durchgeführt haben (wir können dem Fehlerberichtscode diese Tatsache mitteilen), dann fügen wir hinzu ein zusätzlicher Knoten.

Die letzte Kommentarfrist ist nun abgeschlossen.

Sind diese ausstehenden Kontrollkästchen im oberen Beitrag noch nicht aktiviert? Gibt es in dieser Liste etwas rückgängig zu machen?

@earthengine Ich glaube nicht, dass die beiden, die ich sehe, besonders relevant sind - was den obersten Punkt betrifft, über den Satz von Impls, denke ich, dass dies jetzt vorerst entschieden ist.

Im Grunde ein sehr minimales Set.

@nikomatsakis Ich habe gerade die Zusammenfassung hier erstellt: https://github.com/rust-lang/rust/issues/48950

Wurde in Erwägung gezogen, ! einem Muster zu machen, das Werten des Typs ! ? (Ich habe dieses Problem und das ursprüngliche RFC-Problem schnell durchsucht, aber nichts Relevantes gefunden).

Ich würde dies für Typen wie StreamFuture nützlich finden, ! -Musters in map_err würde der Aufruf von map_err die Kompilierung stoppen, wenn sich der Fehlertyp irgendwann in der Zukunft ändert, anstatt möglicherweise etwas anderes zu tun.

Als Beispiel gegeben

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

Dies würde es ermöglichen, expliziter zu schreiben

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

statt der potenziell fehleranfälligen

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

oder die etwas weniger fehleranfällige

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

BEARBEITEN: Beim erneuten Lesen von https://github.com/rust-lang/rfcs/pull/1872 erwähnen die Kommentare die Einführung des ! Musters. Das ist jedoch nur im Kontext von match Ausdrücken, ich bin mir nicht sicher, ob es auf Closure-/Funktionsargumente verallgemeinert wird.

@Nemo157

Gab es eine Überlegung zu machen! ein Muster, das den Werten der ! Typ?

Interessant. Ich denke, der Plan war, Sie auf solche Waffen einfach verzichten zu lassen, dann würden Sie - wenn sich der Typ in Zukunft ändern würde - immer noch einen Fehler bekommen, weil Ihr Match jetzt nicht mehr erschöpfend wäre. Das Problem mit einem ! Muster besteht darin, dass, wenn es übereinstimmt, der Arm unmöglich ist, was etwas umständlich ist; Wir möchten dem, denke ich, Rechnung tragen, indem wir nicht von Ihnen verlangen, einen Ausdruck zu geben, um sie auszuführen. Dennoch könnte es schön sein, eine Möglichkeit zu haben, explizit zu sagen, dass dieser Fall nicht eintreten kann.

Tatsächlich könnte die Erweiterung der unerreichbaren Musterbehandlung ein alternativer Weg sein, dieses Problem zu lösen. Wenn der Streichholzarm nachweislich unmöglich war, könnte der Code im Arm vielleicht ignoriert werden. Dann, wenn Sie haben

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

dies würde immer noch so erweitert wie heute (_Ich bin nicht 100% sicher, dass dies die richtige Erweiterung ist, scheint aber nahe genug zu sein_)

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

aber der Result::Err Zweig würde nicht typgeprüft und Sie würden nicht den the trait bound `std::string::String: std::convert::From<(!, std::string::String)>` is not satisfied Fehler erhalten, den Sie heute erhalten.

Ich glaube jedoch nicht, dass dies mit dem vereinbar ist, was RFC 1872 vorgeschlagen hat, da es einen expliziten Arm gibt, der ein flaches Match ausführt, es nicht unbedingt auf die Gültigkeit des ! Werts angewiesen ist und möglicherweise Führen Sie den zugehörigen Zweig "sicher" aus, solange dieser Zweig e.0 überhaupt nicht verwendet.

! ist ein positiver Typ, wie bool , daher ist es irgendwie unmöglich, in einer Closure-Argument-Liste einen Mustervergleich durchzuführen, ohne die Closure-Syntax zu erweitern, um mehrere Verzweigungen zuzulassen. z.B. vielleicht könnten wir zulassen, dass Schließungen bool -> u32 so geschrieben werden (hässliche hypothetische Syntax) [~ |false| 23, |true| 45 ~] -> u32 , dann könnte eine Schließung von einem beliebigen leeren Typ in u32 einfach geschrieben werden [~ ~] -> u32 .

In Bezug auf Ihre ursprünglichen Beispiele können Sie foo.map_err(|_: (!, _)| unreachable!()) schreiben, obwohl ich foo.map_err(|(e, _)| e) bevorzuge, da es die Verwendung von unreachable! vermeidet.

! ist ein positiver Typ

Was meinst du mit positivem Typ?

Es ist irgendwie unmöglich, in einer Closure-Argument-Liste ein Muster zu finden, ohne die Closure-Syntax zu erweitern, um mehrere Verzweigungen zuzulassen

Argumentlisten unterstützen beispielsweise bereits unwiderlegbare Untermuster

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

Ich würde das Muster ! einfach als unwiderlegbares Muster betrachten, das mit allen 0-Werten des Typs ! übereinstimmt, genauso wie () ein unwiderlegbares Muster ist, das mit allen 1-Werten von übereinstimmt der Typ () .

Ich würde es einfach in Erwägung ziehen! Muster als unwiderlegbares Muster, das allen 0-Werten des ! type, das gleiche wie () ist ein unwiderlegbares Muster, das allen 1-Werten des ()-Typs entspricht.

Nun, aber 0 != 1. ;) Es ist irgendwie sinnlos, auch nur Code zu schreiben, nachdem ein ! -- also zB wenn der Argumenttyp (!, i32) , dann |(!, x)| code_goes_here ist albern, da dieser Code sowieso tot ist. Ich würde wahrscheinlich |(x, _)| match x {} schreiben, um deutlich zu machen, dass x ein Element von ! . Oder verwenden Sie die Funktion absurd , die ich oben vorgeschlagen habe , |(x, _)| absurd(x) . Oder vielleicht |(x, _)| x.absurd() ?

Man könnte sich eine andere Syntax vorstellen, bei der man keinen Code schreiben kann, wie es @canndrew oben geschrieben hat. match hat eine beliebige Anzahl von Verzweigungen, daher ist es insbesondere möglich, keine Verzweigung zu haben -- aber Closures haben genau einen Fall, so dass die einzigen Muster, die Sinn machen, diejenigen sind, die genau eine Übereinstimmungsmöglichkeit haben. Nicht 0 Wege.

Schade, dass wir impl<T> From<!> for T jetzt nicht hinzufügen können. (Es überschneidet sich mit From<T> for T , es sei denn, es handelt sich um etwas Spezialisiertes?) Wir können es wahrscheinlich später nicht hinzufügen. (In Release-Zyklen, nachdem ! stabilisiert wurde, könnten einige Crate From<!> for SomeConcreteType implementieren.)

@SimonSapin guter Punkt! Dies ist ein sehr nützlicher Baustein für viele Dinge, und ich würde es hassen, ihn für immer zu verlieren. Ich würde ernsthaft einen einmaligen Hack in Betracht ziehen, um beide Instanzen zuzulassen. Sie fallen im Überlappungsfall semantisch zusammen, es gibt also keine "operative Inkohärenz".

Ich würde ernsthaft über einen einmaligen Hack nachdenken

Dann muss dieser Hack in den nächsten Tagen landen. (Oder während der Beta zurückportiert werden.)

@SimonSapin Wie schnell/einfach könnte ein solcher Hack hinzugefügt werden? Es wäre wirklich scheiße, nie From<!> for T .

Alternativ könnten wir schnell davor warnen, From<!> for SomeConcreteType implementieren.

Ich weiß nicht, hängt davon ab, was der Hack ist. Ich denke, die Eigenschaftsspezialisierung kann zwei überlappende Impls ermöglichen, wenn es eine dritte für die Schnittmenge gibt, aber erfordert das, dass die Eigenschaft öffentlich „spezialisierbar“ ist?

Leider Spezialisierung:

  • Würde erfordern, dass From::from in ein default fn geändert wird, das außerhalb von std "sichtbar" wäre. Ich weiß nicht, ob wir das in Standardmerkmalen tun wollen, solange die Spezialisierung instabil ist.
  • Wie in RFC 1210 akzeptiert, unterstützt es speziell nicht das Sprachfeature, an das ich gedacht habe (zwei überlappende Impls, von denen keines spezifischer ist als das andere, durch Schreiben eines dritten Impls für die Schnittmenge eindeutig zu machen).

Hier machen die beiden Impls genau das Gleiche. Wenn wir nur die Kohärenzprüfung hacken, wird es schwierig zu bestimmen, welches Impl verwendet wird, was normalerweise sehr unsolide ist, aber in diesem Fall in Ordnung ist. Könnte beim Kompilieren einige Panik auslösen, wenn man zB ein Ergebnis erwartet, aber wir können das umgehen.

Mit anderen Worten, ich denke, dies ist zum Glück viel einfacher, als eine Spezialisierung zu beschleunigen.

Gibt es einen bestehenden RFC zur Formalisierung dieser Idee "willkürliche Überschneidung zulassen, wenn alle beteiligten Impls unerreichbar sind"? Scheint ein interessanter Ansatz zu sein.

Nicht, dass ich davon Wüste. Im Gegenzug ist das Impl selbst nicht unerreichbar, sondern die darin enthaltenen Methoden sind alle nicht aufrufbar (einschließlich keiner zugeordneten Typen oder Konstanten, da diese immer "aufrufbar" sind). Ich nehme an, ich könnte schnell einen schreiben, wenn man davon ausgeht, dass der Hack für so etwas blockiert ist.

Ich erwähne nur das Problem Nr. 49593, das verursacht werden könnte! zur besseren Auffindbarkeit wieder unstabilisiert werden.

Ich frage mich, welche anderen Impls außer T: From<!> nicht hinzugefügt werden können, nachdem ! stabilisiert ist. Wir sollten wahrscheinlich versuchen, all diese vernünftigen Impls in den Griff zu bekommen, bevor wir uns stabilisieren, im Gegensatz zu Impls für ! im Allgemeinen, wo es keine solche Eile gibt.

Cross-Posting von diesem Kommentar :

Ich habe mich gefragt - was ist das klassische Beispiel dafür, warum Fallback auf () geändert werden "muss"? Das heißt, wenn wir weiterhin auf () zurückgreifen würden, aber immer noch ! hinzufügen würden, würde dies sicherlich diese Art von Problem vermeiden - und auf ! zurückgreifen! ist nicht optimal, da es Fälle gibt, in denen der Fallback auf eine Typvariable auftritt, die den "Live" -Code beeinflusst (die Absicht ist natürlich anders, aber es ist schwer, Lecks zu verhindern, wie wir festgestellt haben).

Als Ergebnis dieser Änderung gab es mehrere Rückschritte, und ich muss sagen, dass ich damit nicht ganz zufrieden bin:

(Nominierung für die lang-Team-Diskussion zu diesem Punkt.)

Während des Meetings haben wir einige der Optionen hier ein wenig zurückgenommen - ich zögere, Änderungen vorzunehmen, hauptsächlich weil es die Semantik des vorhandenen Codes in einigen Eckfällen ändert und es nicht klar ist, ob die neuen Regeln besser sind . Aber meistens denke ich, dass wir beschlossen haben, dass es großartig wäre, die Vor- und Nachteile jedes Designs noch einmal zu sammeln, damit wir eine umfassendere Diskussion führen können. Ich würde gerne eine Liste mit Anwendungsfällen und Fallstricken sehen. @cramertj erwähnte ihren Wunsch nach Fallback in Variantenfällen -- zB Ok(33) hat einen Typ, der auf Result<i32, !> zurückfällt, anstatt Fehler zu machen, wie es heute der Fall wäre; Ich glaube, sie sagten damals, dass das Beibehalten des Fallbacks von return als () mit einer solchen Änderung nicht vereinbar wäre (obwohl sie orthogonal sind) und später zu Konflikten führen könnte. Das ist fair.

Die Herausforderung bei Err Feedback (#40801) besteht darin, dass es auch hier Randfälle gibt, wie zum Beispiel:

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

wobei der Typ von x letztendlich auf Result<T, !> .

Ich werde an einen separaten Plan erinnert, den ich ausprobieren musste, um festzustellen (und vielleicht warnen? oder Fehler?), ob ! Fallback jemals Live-Code "beeinflusst" - dies erwies sich als ziemlich schwierig, obwohl es so scheint wie wir in der Praxis viele Fälle linten könnten (z. B. dieser und wahrscheinlich der Fall von

Diskutiert wurde auch, ob wir diesen Fallback in der Neuauflage ganz entfernen können. Ich vermute, es wird viele Makros zerstören, aber es wäre vielleicht einen Versuch wert?

Zu Ihrer Information, diese Funktion verursachte eine Regression in 1.26: #49932

(Festlegen von Labels gemäß https://github.com/rust-lang/rust/issues/35121#issuecomment-368669041.)

Sollte der Checklistenpunkt über Nötigung in ! auf https://github.com/rust-lang/rust/issues/50350 verweisen, anstatt auf dieses Problem zu verweisen?

Wenn ich möchte, dass sich dies so schnell wie möglich stabilisiert, wohin lenke ich meine Energie am besten?

@remexre Ich denke, die beste Beschreibung des Problems, das zur Wiederherstellung der Stabilisierung führte, ist unter https://github.com/rust-lang/rust/issues/49593

Wäre eine praktikable Lösung also, Box als Untertyp von
Kasten? Gibt es objektsichere Eigenschaften, die auf diese Weise nicht behandelt werden können?

Am So, 08.07.2018, 08:12 schrieb Ralf Jung [email protected] :

@remexre https://github.com/remexre Ich denke die beste Beschreibung des
Problem, das zur Umkehr der Stabilisierung führte, ist bei #49593
https://github.com/rust-lang/rust/issues/49593


Sie erhalten dies, weil Sie erwähnt wurden.

Antworten Sie direkt auf diese E-Mail und zeigen Sie sie auf GitHub an
https://github.com/rust-lang/rust/issues/35121#issuecomment-403286892 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AEAJtcnsEaFmHrrlHhuQeVOkR8Djzt50ks5uEgVLgaJpZM4JYi9D
.

>

Vielen Dank,
Nathan

Diese Diskussion sollte wirklich in https://github.com/rust-lang/rust/issues/49593 aufgenommen werden , aber ein wichtiger Teil davon ist, dass Box::<T>::new T: Sized erfordert, aber dass new aufgerufen wird, hat T = Error (ein Merkmal DST) basierend auf dem Rückgabetyp.

Abgesehen von der Uneleganz eines Sonderfalls, gibt es irgendwelche Probleme beim Sonderfall Box::new :

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

oder

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

Zuerst sollten wir uns überlegen, wie groß ! sein sollte. Mathematiker werden etwas wie Inf vorschlagen, aber in Wirklichkeit wird es usize::MAX panic .

Wenn ! ist Sized dann gibt es nichts , uns zu stoppen kompilieren Box::new(x as !) , aber das ist im Grunde eine andere Art und Weise zu panic! , da kein Modellspeicher kann tatsächlich ordnen usize::MAX Bytes zu.

Es scheint mir nicht offensichtlich, dass ! eine unendliche / usize::MAX Größe im Vergleich zu einem ZST haben sollte?

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

aktuell ist 0.

Die Begründung wurde im gerenderten Text erklärt.

bool : zwei gültige Werte => log(2)/log(2) = 1 Bit
() : 1 gültige Werte => log(1)/log(2) = 0 Bit
! : 0 gültige Werte => log(0)/log(2) = Inf bits

Als jemand, der sich mit der entsprechenden Theorie nicht auskennt, sehe ich das Festhalten an der log(x)/log(y) Formel so weit, dass die Eleganz eines theoretischen Modells zu Lasten der praktischen Anwendung gejagt wird. (AKA ist zu schlau für das eigene Wohl)

Intuitiv scheint es, als ob ! auch die Größe Null haben sollte, weil:

  • bool : braucht Platz um zwischen zwei gültigen Werten zu unterscheiden => log(2)/log(2) = 1 Bit zusätzlich zum Typsystem
  • () : braucht Platz, um einen gültigen Wert zu unterscheiden => kein Platz außerhalb des Typsystems erforderlich
  • ! : braucht Platz, um keine gültigen Werte zu unterscheiden => kein Platz außerhalb des Typsystems erforderlich

Nun, es ist tatsächlich -unendlich, was Sinn macht, da das Platzieren der ! als Feld in eine Struktur verwandelt die Struktur im Wesentlichen in ! auch (c + -inf = -inf). Da wir es also mit usize zu tun haben, ist 0 der Wert, der diesem am nächsten kommt. (Ich denke, die tatsächliche Implementierung in rustc verwendet sogar -inf).

Intuitiv scheint es, als ob ! auch die Größe Null haben sollte

! braucht überhaupt keine Größe, da nie Werte mit diesem Typ erstellt werden. Es ist eine irrelevante Frage.

0 gültige Werte => log(0)/log(2) = Inf-Bits

log(0) ist undefiniert; es ist nicht unendlich.

Auf der anderen Seite ist eine Größe von usize::MAX eine Methode, um die Unbewohntheit zu erzwingen. Zustimmen?

@CryZe @varkor

Das passt auch zu dem Konzept, das ich versucht habe, die Gültigkeit zu ergründen, wobei meine Intuition ! als "ZST auf einer ganz anderen Ebene" sehen möchte. (dh ! ist für das Typsystem wie () für die Speicherzuweisung.)

@CryZe
Ihre Formel -Inf + c == -Inf macht Sinn, aber wenn -Inf durch 0usize , gilt sie nicht mehr.

Auf der anderen Seite, wenn die Arithematik "gekapert" ist: alle Überläufe berechnen sich zu usize::MAX dann passt usize::MAX perfekt in die Formel.

Das Gehirnmodell: Wenn Sie ein Objekt vom Typ ! , benötigen Sie zum Zuweisen einer Struktur, die es enthält, eine noch größere Struktur als ! , aber die größte Strukturgröße, die Sie abbilden können, ist usize::MAX . Der benötigte Platz beträgt also immer noch usize::MAX .

Warum nicht einfach die Größe eines Typs, der einen void-Typ enthält ( ! oder ein benutzerdefiniertes enum Void {} ) auf size=0,alignment=0 "begrenzen"? Das erscheint mir semantisch sinnvoller...

@remexre
Denn das funktioniert nicht. Beispiel: sizeof(Result<usize,!>) == sizeof(usize) .

Nun, Result<usize, !> enthält ! direkt; sizeof(Result::Ok(usize)) == 8 , sizeof(Result::Err(!)) == 0

Nein, sizeof(Result::Err(!)) == usize::MAX in meinem Vorschlag, weil Result::Err(!) :: Result<!,!> .

Die Regel sollte lauten: In enum s wird die Größe von ! als kleiner angesehen als alle anderen Größenwerte, aber in structs ist dies die maximale Größe, die je ein Bild darstellen kann.

Abschluss:

  • ! ist keine Sommerzeit. DSTs sind nicht Sized nicht weil sie keine Größe haben, sondern weil die Größeninformationen verborgen sind. Aber für ! wissen wir alles darüber.
  • ! sollte so dimensioniert sein, dass Box::new(x as !) zumindest kompilieren darf.
  • struct s enthält ! sollte so bemessen werden, als ob es ! , enum s enthält ! direkt in einer Variante sollte so bemessen werden wie wenn diese Variante nicht existiert.

Entweder betrachten wir sizeof(!)==0 oder sizeof(!)==usize::MAX , wir müssen einige spezielle Arithematiken einführen, um das oben Gesagte zu ermöglichen:

sizeof(!)==0 : in Strukturen, 0 + n = 0 :besorgt:, in Aufzählungen, max(0,n) = n : erröten:.
sizeof(!)==usize::MAX : in structs, usize::MAX + n =usize::MAX :neutral_face:, in enums, max(usize::MAX,n) = n : flushed:.

Es gibt jedoch einen Vorteil für usize::MAX : Es verhindert technisch gesehen alle Versuche, auch unsafe , ein solches Objekt zu konstruieren, da dies in keinem realen System möglich ist.

Usize::MAX hat jedoch einen Vorteil: Es verhindert technisch gesehen jegliche Versuche, auch unsichere, ein solches Objekt zu konstruieren, da dies in keinem realen System möglich ist.

Ich meine, wenn unsafe Spielereien erlaubt sind: *transmute::<Box<usize>, Box<!>>(Box::new(0)) bringt mir ein ! egal wie groß es ist. usize::MAX macht es wahrscheinlicher, dass der Compiler Fehler macht, wenn jemand versucht, std::mem::zeroed<!>() zu tun, nehme ich an, aber ich würde die Last auf die Person legen, die diesen unsicheren Code schreibt, anstatt auf die Mathematik Seltsamkeit oben.

Beachten Sie, dass ! mit der Größe _actually_ 0 -- nicht MAX noch -INF -saturated-to-0 -- benötigt wird, um die teilweise Initialisierung wie bisher zu verarbeiten gefunden in https://github.com/rust-lang/rust/issues/49298#issuecomment -380844923 (und implementiert in https://github.com/rust-lang/rust/pull/50622).

Wenn Sie die Funktionsweise ändern möchten, erstellen Sie bitte eine neue Ausgabe oder PR dafür; dies ist nicht der richtige Ort.

@remexre

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

Box ist (meistens) ein Bibliothekstyp und seine Methode new ist in der Rust-Syntax in src/liballoc/boxed.rs . Ihre Beschreibung geht von einem <: Operator aus, der in Rust nicht existiert. Dies liegt daran, dass die Subtypisierung in Rust nur über Lebensdauerparameter existiert. Wenn Sie mehr lesen oder sehen möchten:

! kann in jede Art dazu gezwungen werden, dies ist jedoch schwächer als jede Art zu sein. (Zum Beispiel ist Vec<&'static Foo> auch Vec<&'a Foo> für 'a , aber es gibt keine implizite Konvertierung von Vec<!> in Vec<Foo> : http:/ /play.rust-lang.org/?gist=82d1c1e1fc707d804a57c483a3e0198f&version=nightly&mode=debug&edition=2015)

Ich denke, die Idee ist, dass Sie im unsafe Modus tun können, was Sie für angemessen halten, aber Sie müssen sich der UB stellen, wenn Sie es nicht mit Bedacht tun. Wenn die offizielle Größe von ! usize::MAX , sollte dies den Benutzer darauf aufmerksam machen, dass dieser Typ niemals instanziiert werden sollte.

Außerdem habe ich über die Ausrichtung nachgedacht. Wenn unbewohnte Typen die Größe usize::MAX , ist es natürlich, ihre Ausrichtung auch usize::MAX . Dies begrenzt effektiv einen gültigen Zeiger auf den Nullzeiger. Aber per Definition ist ein Nullzeiger ungültig, also haben wir nicht einmal einen gültigen Punkt für diesen Typ, was für diesen Fall perfekt ist.

@ScottAbbey
Ich konzentriere mich auf das Problem, um sicherzustellen, dass Box::new(x as !) keine Probleme hat, damit wir das Ganze stabilisieren können. Der vorgeschlagene Weg ist, ! eine Größe zu geben. Wenn ZST der Standard ist, sollte es in Ordnung sein. Ich streite nicht mehr. Implementieren Sie einfach so, dass sizeof(!)==0 ausreicht.

Es ist selbstverständlich, seine Ausrichtung auch zu verwenden::MAX

usize::MAX ist keine Zweierpotenz, also keine gültige Ausrichtung. Außerdem unterstützt LLVM keine Ausrichtungen von mehr als 1 << 29 . Und ! sollte sowieso kein großes Alignment haben, weil let x: (!, i32); x.1 = 4; nur 4 Byte Stack belegen sollte, nicht Gigabyte oder mehr.

Bitte erstellen Sie einen neuen Thread für diese Diskussion,

Das Problem, das hier auftritt, ist, dass Box::new(!) ein Box<$0> wo wir ein Box<Error> erwarten. Bei ausreichenden Typanmerkungen müssten wir Box<$0> zu Box<Error> erzwingen, was uns später bei $0 Standardwert ! .

$0 ist jedoch eine Typvariable, daher wird kein Zwang ausgeübt und wir erhalten dann $0 = Error .

Eine Hacky Option Dinge für die Festsetzung würde zu finden , dass wir eine Einschränkung $0: Sized (folgende Subtypen?) Und Einfügen eines Zwang auf das verkeilt, so dass Box noch in der Art genannt wird.

Wir machen bereits so etwas, um Fn in Schließungen zu erkennen, also wird dies nicht so langwierig sein. Immer noch häßlich.

Das ist es, wenn wir während des Zwanges auf eine Verpflichtung der Form $0: Unsize<Y> stoßen, wobei Y definitiv nicht bemessen ist und $0 definitiv bemessen ist, behandle das nicht als Mehrdeutigkeit, sondern Behandeln Sie es als "OK" und fahren Sie mit dem nicht bemessenen Zwang fort.

@arielb1

Inwiefern unterscheidet sich das also von Zwang zu Box::new(()) zu Box<Debug> ? std::error::Error ist ein Merkmal wie Debug , kein Typ wie [u8] . std::io::Error hingegen ist ein Typ, aber es ist Sized .

Wir haben nie Probleme mit den folgenden:

use std::fmt::Debug;

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

fn main() {
}

@earthengine der Unterschied zwischen diesem Problem und Ihrem Code ist:

| Geschrieben | Box::new(!): Box<Debug> | Box::new(()): Box<Debug> |
| ------ | ------ | -------- |
| Durchgeführt | Box::new(! as Debug): Debug | (Box::new(()) as Box<Debug>): Debug |

Da ! zu allem erzwungen werden kann, wird es im Wesentlichen zu Debug gezwungen, bevor Box::new aufgerufen wird. Das ist bei () keine Option, also gibt es im zweiten Fall kein Problem - der Zwang erfolgt dort nach Box::new .

@RalfJung

Mein Gehirnmodell ist, dass DSTs nicht in freier Wildbahn existieren können – sie müssen von einigen konkreteren Typen stammen. Dies gilt sogar, wenn wir DSTs in der Parameterposition zulassen - wenn nicht in der Rückgabeposition. Das heißt, bis der reale Wert bereits hinter einem Zeiger platziert ist, sollte er nicht in der Lage sein, sich an eine DST anzupassen, auch wenn er ! .

Folgendes sollte beispielsweise nicht kompiliert werden:

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

Ganz einfach, weil Sie ! durch einen vorhandenen Rust-Ausdruck ersetzen können, um ihn zu kompilieren.

Bearbeiten

Das ist keine Option mit ()

Kann also jemand erklären warum? Wenn () as dyn Debug nicht kompiliert wird, soll ! as dyn Debug nicht kompiliert werden und umgekehrt. &() as &Debug kompiliert, also auch &! as &Debug , keine Probleme. Wenn () as dyn Debug eines Tages kompiliert werden kann, wird das Problem, das wir heute haben, für () wiederholt werden, sodass die DST-RFC-Implementierer damit rechnen müssen, und es wird dasselbe Problem lösen, das wir haben für ! .

! erzwingt alles, weil es unmöglich ist zu existieren. "Ex-falo Quodlibet". Alle Ihre Beispiele sollten also kompiliert werden -- es gibt keinen guten theoretischen Grund, eine spezielle Ausnahme für Typen ohne Größe zu machen.

Dies verstößt nicht einmal gegen "DTS kann nicht existieren", da ! auch nicht existieren kann. :)

() as dyn Debug kompiliert nicht

Ich weiß nicht, was die Pläne für Rvalues ​​ohne Größe sind, aber ich denke, sie könnten dies kompilieren?
Der Punkt ist, dass wir für ! keine Rvalues ​​ohne Größe implementieren müssen, da dies nur in totem Code passieren kann.

Vielleicht weisen Sie hier jedoch auf eine mögliche Lösung hin (und vielleicht hat @arielb1 das auch oben vorgeschlagen): Ist es möglich, den ! Zwang so einzuschränken, dass er nur angewendet wird , wenn der theoretischen Grund, aber einen praktischen. :D Nämlich, dass es helfen könnte, dieses Problem zu lösen.

Wenn ich sagte "DTS kann nicht existieren", meine ich syntaktisch. Es ist beispielsweise nicht möglich, eine lokale Variable vom Typ str zu haben.

Andererseits kann ! nicht existieren, ist semantisch. Du kannst schreiben

let v = exit(0);

v im Kontext syntaktisch typisiert ! zu haben, aber die Bindung wird natürlich nicht einmal ausgeführt und daher in der realen Welt nicht beendet.

Hier ist der Grund: Wir erlauben ! Erzwingung für jeden Typ, nur wenn Sie einen Ausdruck schreiben können, der denselben Typ hat. Wenn ein Typ nicht einmal syntaktisch existieren kann, sollte er nicht zugelassen werden.

Dies gilt auch für Rvalues ​​ohne Größe. Bevor wir also Rvalues ​​ohne Größe haben, ist es nicht erlaubt, ! in Typen ohne Größe zu erzwingen, bis wir wissen, wie man mit Rvalues ​​ohne Größe umgeht. Und erst an diesem Punkt können wir anfangen, über dieses Problem nachzudenken (eine offensichtliche Lösung scheint darin zu bestehen, dass Box::new Werte ohne Größe erhalten).

Es ist beispielsweise nicht möglich, eine lokale Variable vom Typ str zu haben.

Das ist nur eine Einschränkung der aktuellen Implementierung: https://github.com/rust-lang/rust/issues/48055

@RalfJung

Das ist hier nicht das Problem.

Angenommen, wir befinden uns in der Situation, in der Box::new(!: $0): Box<dyn fmt::Debug> $0 eine Typvariable ist.

Dann kann der Compiler ziemlich leicht herleiten, dass $0: Sized (da $0 gleich dem Typparameter von Box::new , der eine T: Sized Grenze hat).

Das Problem tritt dort auf, wo der Compiler herausfinden muss, welche Art von Zwang verwendet werden muss, um Box<$0> in ein Box<dyn fmt::Debug> umzuwandeln. Aus einem "lokalen" POV gibt es 2 Lösungen:

  1. Habe einen CoerceUnsized Zwang, der Box<$0>: CoerceUnsized<Box<dyn fmt::Debug>> erfordert. Dies ist ein gültiges Programm für $0 = ! und die Standardeinstellung führt dazu, dass es entsprechend kompiliert wird.
  2. Habe einen Identitätszwang mit $0 = dyn fmt::Debug . Dies steht im Widerspruch zu der Anforderung von $0: Sized .

Der Compiler möchte bei der Berücksichtigung von Mehrdeutigkeiten nicht zu viel offen haben, da dies sowohl Leistungsprobleme als auch schwer zu debuggen verursachen kann nicht T: CoerceUnsized<T> , also wenn der Compiler Option 1 auswählt, kann er ziemlich leicht "hängenbleiben") und er wählt am Ende Option 2 aus, was fehlschlägt. Ich hatte eine Idee, es etwas intelligenter zu machen und Option 1 zu wählen.

Wenn man also von diesem Standpunkt aus darüber nachdenkt, könnte die richtige Idee sein, einen nicht bemessenen Zwang auszulösen, wenn

  1. Der nicht bemessene Zwang ist in sich konsistent (das wären die aktuellen Regeln, außer dass Mehrdeutigkeit in Ordnung ist).
  2. den nicht bemessenen Zwang

Habe einen Identitätszwang mit $0 = dyn fmt::Debug. Dies stimmt nicht mit der Anforderung von $0: Size überein.

Ich kann kaum verstehen, warum wir let v: str=! zulassen sollten, aber nicht let v: str; . Wenn Sie nicht einmal eine Variable mit einem bestimmten Typ deklarieren können, warum können Sie dann einen (vielleicht unmöglichen) Wert daran binden?

Wenn rvalue ohne Größe nicht ausgeliefert wird, ist es konsequent, keinen Zwang zu Typen ohne Größe zuzulassen, da dies (möglicherweise vorübergehend) rvalues ​​ohne Größe erzeugt, selbst wenn dies nur in der Vorstellung geschieht.

Meine Schlussfolgerung ist also, dass das Problem Box::new(!) as Box<Error> ein Blockierungsproblem für Rvalues ​​ohne Größe ist, nicht für den Typ ! . Die ! Zwangsregeln sollten lauten:

Sie können let v: T schreiben, wenn und nur wenn Sie let v: T = ! schreiben können.

Die tatsächliche Bedeutung wird dann mit der Sprache bewertet. Vor allem, nachdem wir rvalues ​​ohne Größe haben, haben wir Box::new(! as dyn Debug) aber davor würde ich nein sagen.

Was können wir also tun, um voranzukommen?

Fügen Sie dem Code ein Feature-Gate hinzu, der die Unsize-Erzwingung auslöst. Wenn der rvalue ohne Größe aktiviert ist, versuchen Sie diese Erzwingung, andernfalls überspringen Sie sie. Wenn dies der einzige verfügbare Zwang ist, sollte die Fehlermeldung wie in ähnlichen Fällen lauten: "Das Merkmal str: std::marker::Sized ist nicht erfüllt". So

Wir müssen sehen, dass dies berechnet werden kann, ohne die Leistung bei größeren Funktionen zu beeinträchtigen.

angesprochen: nur ein einfacher Feature-Check.

Fügen Sie in der Zwischenzeit ein neues Problem für behoben wird.

Interessant.

Dies

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

kompiliert, aber

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

nicht. Dies hat nicht einmal mit dem Typ ! tun. Soll ich dafür ein Thema erstellen?

Ich weiß nicht, ob letzteres wirklich ein Bug ist. Das zweite Beispiel lässt sich nicht kompilieren, da der Compiler ohne Rvalue-Unterstützung ohne Größe statisch wissen möchte, wie viel Stack-Speicherplatz für die lokale Variable v reserviert werden soll, dies jedoch nicht kann, da es sich um eine Sommerzeit handelt.

Im ersten Beispiel ist das _ Muster insofern besonders, als es nicht nur auf alles passt (wie eine lokale Variablenbindung), sondern auch überhaupt keine Variable erstellt. Dieser Code ist also derselbe wie fn main() { *""; } ohne let . Eine Referenz zu dereferenzieren (sogar DST) und dann nichts mit dem Ergebnis zu tun, ist nicht sinnvoll, aber es scheint gültig zu sein und ich bin nicht überzeugt, dass es ungültig sein sollte.

Rechts. Aber es ist wirklich verwirrend, vor allem das Folgende

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

kompiliert auch nicht. Mit Ihrer Theorie zu _ sollte dies dasselbe sein, außer dass wir das unbenutzte Ding _v und nicht nur _ .

Soweit ich mich erinnere, besteht der einzige Unterschied zwischen _v und v darin, dass der führende Unterstrich Warnungen über nicht verwendete Werte unterdrückt.

Auf der anderen Seite bedeutet _ speziell "verwerfen" und wird speziell behandelt, damit es an mehr als einer Stelle in einem Muster (z. B. Tupel entpacken, Argumentliste usw.) ein Fehler über eine Namenskollision.

Auf der anderen Seite bedeutet _ speziell "verwerfen" und wird speziell behandelt, damit es an mehr als einer Stelle in einem Muster (z Kollision.

Richtig. Zusätzlich zu dem, was Sie gesagt haben, führt let _ = foo() dazu, dass drop sofort aufgerufen wird, während _v nur gelöscht wird, wenn es den Gültigkeitsbereich verlässt (wie es v würde) ).

Tatsächlich meinte ich das mit „ _ ist etwas Besonderes“.

Jetzt sieht es so aus, als ob das Verweigern aller Rvalues ​​ohne Größe (es sei denn, die Funktion "unsized rvalues" ist aktiviert) in freier Wildbahn eine bahnbrechende Änderung wäre, obwohl der Effekt meiner Meinung nach gering wäre.

Ich schlage dann immer noch vor, ein Feature-Gate zu haben, um die Coersion von ! auf Rvalues ​​ohne Größe zu unterbinden. Dies würde Code wie let _:str = return; verweigern, aber ich glaube nicht, dass jemand ihn in Code verwenden wird.

Solange der Typ ! instabil ist, können wir grundlegende Änderungen daran vornehmen, wo er erzwungen oder nicht erzwungen werden kann.

Die Frage ist, wenn wir es ohne Zwang zu DSTs stabilisieren, um https://github.com/rust-lang/rust/issues/49593 zu beheben, wollen wir diesen Zwang später wiederherstellen, wenn wir rvalues ​​ohne Größe hinzufügen, und würden wir? in der Lage sein, dies zu tun, ohne https://github.com/rust-lang/rust/issues/49593 erneut zu

Mein vorheriger Vorschlag beinhaltet dies. #49593 sollte ein blockierendes Problem für rvalues ​​ohne Größe sein.

Mein persönlicher Vorschlag für die endgültige Lösung ist, Box::new (kann auch einige andere wichtige Methoden einschließen) Parameter ohne Größe akzeptieren zu lassen und Mehrdeutigkeiten in ähnlichen Situationen zuzulassen.

FWIW, _ ist nichts wie ein spezieller Variablenname. Es handelt sich um eine völlig separate Mustersyntax, die nichts an der übereinstimmenden Stelle ändert . Pattern-Matching funktioniert an Orten , nicht an Werten .
Am nächsten kommt einer Variablenbindung ref _x , aber selbst dann benötigen Sie wahrscheinlich NLL, um eine daraus resultierende widersprüchliche Ausleihe zu vermeiden. Das funktioniert übrigens:

// 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

Du hast meinen Punkt nicht verstanden. Ich gehe davon aus, dass in Rust derzeit keine Rvalues ​​ohne Größe im Code vorkommen können. Es stellt sich jedoch heraus, dass sie in let _:str = *"" . Obwohl ein solcher Wert für keine Zeit leben kann, lebt er auf syntaktischer Ebene. So etwas wie ein Gespenst...

Ihre Beispiele sind stattdessen technisch völlig legal und es geht nicht darum. str hat keine Größe, aber &str hat keine Größe.

Es stellt sich jedoch heraus, dass sie in let _:str = *"" erscheinen. Obwohl ein solcher Wert für keine Zeit leben kann, lebt er auf syntaktischer Ebene. So etwas wie ein Gespenst...

Es gibt einen ähnlichen "Geist" in einem Ausdruck wie &*foo . Zuerst dereferenzieren Sie foo und nehmen dann die Adresse des Ergebnisses, ohne es zu verschieben . Es ist also beispielsweise nicht dasselbe wie & { *foo } .

@earthengine @SimonSapin Es existieren dort keine *"foo" ist ein Platz , und für den Mustervergleich ist kein Wert erforderlich, sondern nur ein Platz.

Die Namen "lvalue" und "rvalue" sind Relikte und auch in C etwas irreführend, aber noch schlimmer in Rust, weil let 's RHS trotz des Namens ein "lvalue" (Ortsausdruck) ist.
(Zuweisungsausdrücke sind der einzige Ort, an dem die C-Namen und -Regeln in Rust einen Sinn ergeben)

In Ihren Beispielen ist let x = *"foo"; der Wert erforderlich, um ihn an x zu binden.
In ähnlicher Weise erstellt &*"foo" einen Ortsausdruck und übernimmt ihn dann, ohne jemals einen Wert ohne Größe erstellen zu müssen, während {*"foo"} immer ein Wertausdruck ist und daher keine Typen ohne Größe zulässt.

In https://github.com/rust-lang/rust/pull/52420 habe ich das getroffen

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

funktioniert nicht mehr. Ist das beabsichtigt?

AFAIK ja – die Geschichte des unfehlbaren Musters ist kompliziert und hat ein separates Feature-Gate von ! selbst. Siehe auch https://github.com/rust-lang/rfcs/pull/1872 und https://github.com/rust-lang/rust/issues/48950.

@Ericson2314 speziell die Funktion, die Sie zu liballoc hinzufügen müssen, ist exhaustive_patterns .

Vielen Dank!!

Ein interessantes Gespräch zum Thema Interna:

Wenn Sie das tun, warum nicht behandeln! selbst als Inferenzvariable?

Erstellen Sie einfach einen Wrapper um ! mit einem PhandomData , um den Elementtyp zu unterscheiden.

https://github.com/rust-lang/rust/issues/49593 wurde jetzt behoben. Dies war der Grund für die vorherige Rückkehr der Stabilisierung. Vorheriger Stabilisierungsbericht ist hier . Versuchen wir es noch einmal!

@rfcbot fcp zusammenführen

Ich denke, rfcbot könnte mehr als einen FCP im selben Problem unterstützen. Lassen Sie uns eine neue Ausgabe für diese Stabilisierungsrunde eröffnen?

https://github.com/rust-lang/rust/pull/50121 nicht nur die Stabilisierung zurückgesetzt, sondern auch die Fallback-Semantik. Wollen wir das noch einmal aufgreifen?

Das verbleibende nicht markierte Kontrollkästchen in der Problembeschreibung ist:

Welche Eigenschaften sollten wir für ! implementieren? Die anfängliche PR #35162 enthält Ord und einige andere. Dies ist wahrscheinlich eher ein T-Libs-Problem, daher füge ich dieses Tag dem Problem hinzu.

Wir können Impls später hinzufügen, oder? Oder ist das ein Blocker?

@SimonSapin Geöffnet https://github.com/rust-lang/rust/issues/57012. Ich würde erwarten, dass der Fallback auf ! als Teil dieser Änderung wieder aktiviert wird, ja (obwohl wir das zum Thema Stabilisierung diskutieren).

Anscheinend gibt es einen Fehler / ein Loch, wenn man den Never-Typ hinter einem Feature-Gate hat, und es ist möglich, auf Stable darauf zu verweisen: https://github.com/rust-lang/rust/issues/33417#issuecomment -467053097

Bearbeiten: hinterlegt https://github.com/rust-lang/rust/issues/58733.

Hinzufügen einer Verwendung des Typs im oben verlinkten Codebeispiel:

trait MyTrait {
    type Output;
}

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

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

fn main() {
    let _a: Void;
}

Dies wird in Rust 1.12.0 kompiliert, was meiner Meinung nach das erste mit https://github.com/rust-lang/rust/pull/35162 ist. In 1.11.0 Fehler mit:

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

Wie ist der Zustand von diesem?

Soweit ich weiß hat sich der Status seit meiner Zusammenfassung in https://github.com/rust-lang/rust/issues/57012#issuecomment -467889706 nicht wesentlich geändert.

Wie ist der Zustand von diesem?

@hosunrise : Das ist derzeit auf https://github.com/rust-lang/rust/issues/67225 gesperrt

Ich mag hier weit weg sein, aber die spezifischen Probleme, die in der Lint-Diskussion (https://github.com/rust-lang/rust/issues/66173) erwähnt werden, scheinen beide lösbar zu sein, wenn jede Aufzählung einen ! Zweig hat das Typensystem?

Ich werde beachten, dass dies nicht für das im OP von https://github.com/rust-lang/rust/issues/67225 erwähnte Problem gilt, das immer noch ein Problem darstellen würde.

Ich mag hier weit weg sein, aber die spezifischen Probleme, die in der Lint-Diskussion (#66173) erwähnt werden, scheinen beide lösbar zu sein, wenn jede Aufzählung einen ! Zweig im Typsystem hat?

Tatsächlich kann jede Aufzählung gefährdet werden, da es unendlich viele Varianten gibt, die niemals vorkommen können, und daher sind sie alle nicht der Rede wert.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen