Rust: const fn Tracking-Problem (RFC 911)

Erstellt am 6. Apr. 2015  ·  274Kommentare  ·  Quelle: rust-lang/rust

https://github.com/rust-lang/rust/issues/57563 | neues Meta-Tracking-Problem

Alter Inhalt

Tracking-Problem für rust-lang/rfcs#911.

Dieses Problem wurde zugunsten gezielterer Probleme geschlossen:

Dinge, die vor der Stabilisierung zu tun sind:

CTFE = https://en.wikipedia.org/wiki/Compile_time_function_execution

A-const-eval A-const-fn B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue T-lang

Hilfreichster Kommentar

Bitte ignorieren Sie dies, wenn ich völlig daneben liege.

Das Problem, das ich bei diesem RFC sehe, ist, dass Sie als Benutzer so viele Funktionen const fn wie möglich markieren müssen, da dies wahrscheinlich die beste Vorgehensweise ist. Dasselbe passiert derzeit in C++ mit contexpr. Ich denke, das ist nur unnötige Ausführlichkeit.

D hat kein const fn , aber es erlaubt, dass jede Funktion zur Kompilierzeit aufgerufen wird (mit einigen Ausnahmen).

zum Beispiel

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

Beachten Sie, dass ich nicht wirklich ein Rust-Benutzer bin und den RFC erst vor ein paar Minuten gelesen habe, daher ist es möglich, dass ich etwas falsch verstanden habe.

Alle 274 Kommentare

Ist dies durch #25609 geschlossen?

@Munksgaard Das fügt dem Compiler AFAIK nur Unterstützung hinzu. Es gibt viele Funktionen in der stdlib, die in const fn geändert und auf Fehler getestet werden müssen. Ich weiß nicht, wie der Fortschritt diesbezüglich ist.

Ich hoffe, dass dies auf std::ptr::null() und null_mut() implementiert wird, damit wir sie verwenden können, um static mut *MyTypeWithDrop zu initialisieren, ohne auf 0usize as *mut _ zurückgreifen zu müssen

EDIT: Entfernt, da es außerhalb des Themas war

Um es klarzustellen, hier geht es nicht in erster Linie um die Nützlichkeit des Features, sondern eher um die beste Art, es zu formulieren (oder den besten Rahmen, um es zu formulieren). Siehe die RFC-Diskussion.

Dies ist nun das Verfolgungsproblem für eine eventuelle Stabilisierung.

https://github.com/rust-lang/rust/issues/29107 wurde geschlossen.

Ich bin nicht der Meinung, dass "Integration mit Mustern" oder Änderungen an der Standardbibliothek dies blockieren sollten. Dies ist auch ohne diese Änderungen sehr nützlich, und diese Änderungen können später vorgenommen werden. Insbesondere möchte ich bald anfangen, const fn in meinem eigenen Code zu verwenden.

Könnte der Stabilisierungsstatus dementsprechend neu bewertet werden?

Ich bezweifle nicht, dass const fn selbst in seiner derzeitigen eingeschränkten Form eine nützliche Funktionalität wäre, aber was ich wirklich gerne hätte, idealerweise bevor ich diesen Weg weitergehe, wäre für diejenigen, die "das const fn -Ansatz", um über ihr bevorzugtes Endspiel nachzudenken und es zu artikulieren. Wenn wir nur weiterhin auf die offensichtlichste Weise nützlich erscheinende Funktionalität hinzufügen, scheint es mir sehr wahrscheinlich, dass wir am Ende mehr oder weniger das gesamte constexpr -Design von C++ kopieren werden. Ist das etwas, womit wir uns wohlfühlen? Auch wenn wir ja sagen, wäre es mir viel lieber, wenn wir diesen Weg mit klarem Blick wählen, anstatt ihn mit kleinen Schritten im Laufe der Zeit als den Weg des geringsten Widerstands zu gehen, bis er unvermeidlich geworden ist.

(Angesichts der Tatsache, dass die Semantik von sicherem Rust-Code vollständig definierbar sein sollte, scheint es wahrscheinlich, dass irgendwann zumindest jede Funktion, die nicht (transitiv) von unsafe abhängt, als const markiert werden kann unsafe ein Implementierungsdetail sein soll, wette ich, dass die Leute darauf drängen werden, diese Beschränkung ebenfalls irgendwie zu lockern. Mir wäre viel lieber, wir würden uns im Ausland umsehen und versuchen, ein kohärenteres, leistungsfähigeres und zu finden gut integrierte Geschichte für die Inszenierung und Berechnung auf Typebene.)

@glaebhörl

Ich bezweifle nicht, dass const fn selbst in seiner derzeitigen eingeschränkten Form eine nützliche Funktionalität wäre, aber was ich wirklich gerne hätte, idealerweise bevor ich diesen Weg weiter gehe, wäre für diejenigen, die den "const fn-Ansatz" bevorzugen Denken Sie über ihr bevorzugtes Endspiel nach und artikulieren Sie es ... es scheint mir sehr wahrscheinlich, dass wir am Ende mehr oder weniger das gesamte constexpr-Design von C++ kopieren werden.

Was ich persönlich noch mehr möchte, ist, dass wir eine ziemlich klare Vorstellung davon haben, wie wir es implementieren werden und welchen Teil der Sprache wir abdecken werden. Allerdings hängt dies meiner Meinung nach sehr eng mit der Unterstützung von zugehörigen Konstanten oder Generika über Ganzzahlen zusammen.

@eddyb und ich haben kürzlich ein Schema skizziert, das eine konstante Bewertung eines sehr breiten Codestreifens ermöglichen könnte. Grundsätzlich alle Konstanten auf MIR senken und interpretieren (in einigen Fällen
abstraktes Interpretieren, ob es Generika gibt, die Sie noch nicht bewerten können, da wird es für mich am interessantesten).

Während es jedoch so aussieht, als wäre es ziemlich einfach, einen sehr großen Teil der "eingebauten Sprache" zu unterstützen, stößt echter Code in der Praxis auf die Notwendigkeit, Speicher sehr schnell zuzuweisen. Mit anderen Worten, Sie möchten Vec oder einen anderen Container verwenden. Und das ist der Punkt, an dem dieses ganze Interpretationsschema meiner Meinung nach komplizierter wird.

Abgesehen davon, @glaebhörl , würde ich auch gerne hören, wie Sie Ihr bevorzugtes alternatives Endspiel artikulieren. Ich glaube, Sie haben einige solcher Gedanken im const fn RFC skizziert, aber ich denke, es wäre gut, sie noch einmal zu hören, und zwar in diesem Zusammenhang. :)

Das Problem bei der Zuordnung besteht darin, dass es in die Laufzeit entweicht.
Wenn wir es irgendwie verbieten können, diese Kompilierzeit-/Laufzeitbarriere zu überschreiten, dann könnten wir, glaube ich, ein funktionierendes liballoc mit const fn haben.
Es wäre nicht schwieriger, diese Art von Zuweisungen zu verwalten, als mit Byte-adressierbaren Werten auf einem interpretierten Stack umzugehen.

Alternativ könnten wir Laufzeitcode generieren, um die Werte jedes Mal zuzuweisen und auszufüllen, wenn diese Barriere passiert werden muss, obwohl ich nicht sicher bin, welche Art von Anwendungsfällen das hat.

Denken Sie daran, dass selbst bei einer vollwertigen constexpr -ähnlichen Auswertung const fn immer noch_ rein wäre: Die zweimalige Ausführung mit 'static -Daten würde zu genau demselben Ergebnis führen und nein Nebenwirkungen.

@nikomatsakis Wenn ich einen hätte, hätte ich ihn erwähnt. :) Ich sehe hauptsächlich nur bekannte Unbekannte. Das Ganze mit const s als Teil des Generics-Systems war natürlich Teil dessen, was ich unter dem C++-Design verstanden habe. Was die Zuordnung const s und const generischen Parametern betrifft, wenn man bedenkt, dass wir bereits Arrays mit fester Größe mit const s als Teil ihres Typs haben und darüber abstrahieren möchten Ich wäre überrascht, wenn es einen viel besseren – im Gegensatz zu einem allgemeineren – Weg gäbe, dies zu tun. Der const fn Teil der Dinge fühlt sich trennbarer und variabler an. Es ist leicht vorstellbar, die Dinge weiter zu führen und Dinge wie const impl s und const Trait Grenzen in Generika zu haben, aber ich bin _sicher_, dass es für diese Art von allgemeinen Dingen Stand der Technik gibt, der die Dinge bereits herausgefunden hat und wir sollten versuchen, es zu finden.

Von den Hauptanwendungsfällen für die Rust-Sprache scheinen diejenigen, die in erster Linie eine Kontrolle auf niedriger Ebene benötigen, wie Kernel, bereits einigermaßen gut bedient zu sein, aber ein weiterer Bereich, in dem Rust viel Potenzial haben könnte, sind Dinge, die in erster Linie eine hohe Leistung erfordern Diese platzstarke Unterstützung (in irgendeiner Form) für gestufte Berechnungen (von denen const fn bereits eine sehr begrenzte Instanz ist) scheint ein Wendepunkt zu sein. (Erst in den letzten Wochen bin ich auf zwei separate Tweets von Leuten gestoßen, die beschlossen haben, von Rust auf eine Sprache mit besseren Staging-Fähigkeiten umzusteigen.) Ich bin mir nicht sicher, ob eine der vorhandenen Lösungen in Sprachen "uns nahe" ist -- constexpr C++, Ad-hoc-CTFE von D, unsere prozeduralen Makros – fühlen sich für solche Dinge wirklich inspirierend und leistungsfähig/vollständig genug an. (Prozedurale Makros scheinen eine gute Sache zu sein, aber eher für Abstraktion und DSLs, nicht so sehr für die leistungsorientierte Codegenerierung.)

Was anregend und gut genug _wäre_, habe ich noch nicht gesehen, und ich bin mit dem ganzen Raum nicht vertraut genug, um genau zu wissen, wo ich suchen muss. Natürlich sollten wir nach dem oben Gesagten zumindest einen Blick auf Julia und Terra werfen, auch wenn sie in vielerlei Hinsicht ganz andere Sprachen als Rust zu sein scheinen. Ich weiß, dass Oleg Kiselyov auf diesem Gebiet eine Menge interessanter Arbeit geleistet hat. Tiark Rompfs Arbeit an Lancet und Lightweight Modular Staging für Scala scheint definitiv einen Blick wert. Ich erinnere mich, dass ich irgendwann eine Präsentation von @kmcallister darüber gesehen habe, wie ein abhängig getippter Rust aussehen könnte (was zumindest allgemeiner sein könnte, als überall const zu kleben), und ich erinnere mich auch, etwas von Oleg gesehen zu haben dass Typen selbst eine Form der Inszenierung sind (was sich natürlich anfühlt, wenn man bedenkt, dass die Phasentrennung zwischen Kompilier- und Laufzeit sehr ähnlich wie Stadien ist) ... viele aufregende potenzielle Verbindungen in viele verschiedene Richtungen, weshalb es sich wie ein Versäumtes anfühlen würde Gelegenheit, wenn wir uns einfach auf die erstbeste Lösung festlegen würden, die uns einfällt. :)

(Dies war nur ein Braindump und ich habe mit ziemlicher Sicherheit viele Dinge unvollkommen charakterisiert.)

Während es jedoch so aussieht, als wäre es ziemlich einfach, einen sehr großen Teil der "eingebauten Sprache" zu unterstützen, stößt echter Code in der Praxis auf die Notwendigkeit, Speicher sehr schnell zuzuweisen. Mit anderen Worten, Sie möchten Vec oder einen anderen Container verwenden. Und das ist der Punkt, an dem dieses ganze Interpretationsschema meiner Meinung nach komplizierter wird.

Ich stimme dieser Charakterisierung von "echtem Code in der Praxis" nicht zu. Ich denke, es besteht großes Interesse an Rust, weil es hilft, den Bedarf an Heap-Speicherzuweisung zu reduzieren. Insbesondere mein Code unternimmt konkrete Anstrengungen, um die Heap-Zuweisung nach Möglichkeit zu vermeiden.

In der Lage zu sein, mehr als das zu tun, wäre _nett_, aber in der Lage zu sein, statische Instanzen von nicht-trivialen Typen mit vom Compiler erzwungenen Invarianten zu konstruieren, ist wesentlich. Der C++ constexpr -Ansatz ist extrem einschränkend, aber er ist mehr als das, was ich für meine Anwendungsfälle benötige: Ich muss eine Funktion bereitstellen, die eine Instanz vom Typ T mit den Parametern x kann y und z so, dass die Funktion garantiert, dass x , y und z gültig sind (z. B. x < y && z > 0 ), sodass das Ergebnis eine static -Variable sein kann, ohne dass der Initialisierungscode verwendet wird, der beim Start ausgeführt wird.

@briansmith FWIW Ein anderer Ansatz, der die Möglichkeit hat, dieselben Anwendungsfälle zu lösen, wäre, wenn Makros Datenschutzhygiene hätten, was wir meiner Meinung nach (hoffentlich) planen.

@briansmith FWIW Ein anderer Ansatz, der die Möglichkeit hat, dieselben Anwendungsfälle zu lösen, wäre, wenn Makros Datenschutzhygiene hätten, was wir meiner Meinung nach (hoffentlich) planen.

Ich denke, wenn Sie die prozeduralen Makros verwenden, können Sie x < y && z > 0 zur Kompilierzeit auswerten. Aber es scheint, als würde es viele, viele Monate dauern, bis prozedurale Makros in stabilem Rust verwendet werden könnten, falls dies jemals der Fall ist. const fn ist interessant, weil es _jetzt_ für Stable Rust aktiviert werden kann, soweit ich den Stand der Dinge verstehe.

@glaebhörl Ich würde wegen strenger Hygiene nicht den Atem anhalten, es ist durchaus möglich, dass wir einen Fluchtmechanismus haben werden (genau wie echte LISPs), also möchten Sie ihn vielleicht nicht aus Sicherheitsgründen.

@glaebhoerl gibt es auch https://anydsl.github.io/, das sogar verwendet
Rust-ähnliche Syntax ;) Sie zielen im Grunde genommen auf gestufte Berechnungen ab
besondere Teilauswertung.

Am Sa, 16. Januar 2016 um 18:29 Uhr, Eduard-Mihai Burtescu <
[email protected]> schrieb:

@glaebhoerl https://github.com/glaebhoerl Ich würde nicht die Luft anhalten
strenge Hygiene, es ist durchaus möglich, dass wir einen Fluchtmechanismus haben werden
(genau wie echte LISPs), also möchten Sie es vielleicht nicht aus Sicherheitsgründen
Zwecke.


Antworten Sie direkt auf diese E-Mail oder zeigen Sie sie auf GitHub an
https://github.com/rust-lang/rust/issues/24111#issuecomment -172271960.

29525 sollte vor der Stabilisierung behoben werden

Da die Semantik von sicherem Rust-Code vollständig definierbar sein sollte, scheint es wahrscheinlich, dass irgendwann zumindest jede Funktion, die nicht (transitiv) von unsafe abhängt, als const markiert werden kann . Und da unsafe ein Implementierungsdetail sein soll, wette ich, dass die Leute auch darauf drängen werden, diese Beschränkung irgendwie zu lockern.

Nur ein Gedanke: Wenn wir jemals das Speichermodell von Rust formell definieren, dann könnte sogar unsafe -Code zur Kompilierzeit sicher und sinnvoll ausgewertet werden, indem er abstrakt/symbolisch interpretiert wird -- das heißt, die Verwendung von rohen Zeigern würde ' Es werden keine direkten Speicherzugriffe wie zur Laufzeit, sondern etwas (nur als Beispiel zur Veranschaulichung) wie ein Nachschlagen in einer Hashmap von zugewiesenen Adressen, zusammen mit ihren Typen und Werten, oder ähnliches, wobei jeder Schritt auf Gültigkeit geprüft wird - so dass jede Ausführung, deren Verhalten undefiniert ist, _ausschließlich_ ein vom Compiler gemeldeter Fehler wäre und nicht eine Sicherheitslücke in rustc . (Dies könnte auch mit der Situation zusammenhängen, in der isize und usize zur Kompilierzeit symbolisch oder plattformabhängig behandelt werden.)

Ich bin mir nicht sicher, wo uns das in Bezug auf const fn verlässt. Einerseits würde dies wahrscheinlich viel nützlicheren Code eröffnen, der zur Kompilierungszeit verfügbar wäre -- Box , Vec , Rc , alles, was unsafe verwendet. const fn sein könnte“ nun im Wesentlichen nach außen verschoben zu _allem, was nicht das FFI betrifft_. Was wir also _eigentlich_ im Typensystem unter dem Deckmantel der const ness verfolgen würden, ist, welche Dinge (transitiv) auf dem FFI beruhen und welche Dinge nicht. Und ob etwas den FFI verwendet oder nicht, ist _immer noch_ etwas, das die Leute zu Recht als internes Implementierungsdetail betrachten, und diese Einschränkung (im Gegensatz zu unsafe ) _wirklich_ nicht machbar zu sein scheint. Und unter diesem Szenario hätten Sie wahrscheinlich viel mehr fn s, die für const ns berechtigt sind, als diejenigen, die dies nicht tun würden.

Sie hätten also immer noch const Probleme, die sich um eine willkürliche, die Implementierung aufdeckende Einschränkung drehen, und Sie müssten am Ende fast überall const schreiben. Das klingt auch nicht gerade verlockend...

Das heißt, die Verwendung von Rohzeigern würde nicht zu direkten Speicherzugriffen wie zur Laufzeit führen, sondern zu etwas ... wie einem Nachschlagen in einer Hashmap zugewiesener Adressen.

@glaebhörl Nun, das ist so ziemlich das Modell, das ich beschrieben habe und das miri von @tsion implementiert.

Ich denke, die FFI-Unterscheidung ist wegen der Reinheit sehr wichtig, die für die Kohärenz _erforderlich_ ist.
Sie _könnten_ nicht einmal GHC für Rust const fn s verwenden, weil es unsafePerformIO hat.

Ich selbst mag das Schlüsselwort const nicht so sehr, weshalb ich mit const fn foo<T: Trait> statt mit const fn foo<T: const Trait> einverstanden bin (da const impl Trait for T erforderlich ist).

Genau wie Sized haben wir wahrscheinlich die falschen Standardeinstellungen, aber ich habe keine anderen Vorschläge gesehen, die realistisch funktionieren könnten.

@eddyb Ich denke, Sie wollten auf https://internals.rust-lang.org/t/mir-constant-evaluation/3143/31 verlinken (Kommentar 31, nicht 11).

@tsion Behoben , danke!

Bitte ignorieren Sie dies, wenn ich völlig daneben liege.

Das Problem, das ich bei diesem RFC sehe, ist, dass Sie als Benutzer so viele Funktionen const fn wie möglich markieren müssen, da dies wahrscheinlich die beste Vorgehensweise ist. Dasselbe passiert derzeit in C++ mit contexpr. Ich denke, das ist nur unnötige Ausführlichkeit.

D hat kein const fn , aber es erlaubt, dass jede Funktion zur Kompilierzeit aufgerufen wird (mit einigen Ausnahmen).

zum Beispiel

// Standalone example.
struct Point { x: i32, y: i32 }

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x: x, y: y }
    }

    fn add(self, other: Point) -> Point {
        Point::new(self.x + other.x, self.y + other.y)
    }
}

const ORIGIN: Point = Point::new(0, 0); // works because 0, 0 are both known at compile time 
const ORIGIN2: Point = Point::new(0, 0); // ditto

const ANOTHER: Point = ORIGIN.add(ORIGIN2); // works because ORIGIN and ORIGIN2 are both const.
{
    let x: i32 = 42;
    let y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Error: x and y are not known at compile time
}
{
    const x: i32 = 42;
    const y: i32 = 24;
    const SOME_POINT: Point = Point::new(x, y); // Works x and y are both known at compile time.
}

Beachten Sie, dass ich nicht wirklich ein Rust-Benutzer bin und den RFC erst vor ein paar Minuten gelesen habe, daher ist es möglich, dass ich etwas falsch verstanden habe.

@MaikKlein in der RFC-Diskussion wurde viel über CTFE diskutiert

Ich sehe hier keine neueren Kommentare, die die Blocker erklären, und die Operation ist nicht sehr aufschlussreich. Wie ist der Stand. Wie können wir das über die Ziellinie bringen?

Dies wird von Rocket verwendet: https://github.com/SergioBenitez/Rocket/issues/19#issuecomment -269052006

Siehe https://github.com/rust-lang/rust/issues/29646#issuecomment -271759986. Außerdem müssen wir unsere Position zur Explizitheit überdenken, da miri die Grenze zu "globalen Nebenwirkungen" überschreitet ( @solson und @nikomatsakis haben gerade im IRC darüber gesprochen).

Das Problem, das ich bei diesem RFC sehe, ist, dass Sie als Benutzer so viele Funktion const fn wie möglich markieren müssen, da dies wahrscheinlich die beste Vorgehensweise ist.

Wir könnten zwar beliebige Funktionen aufrufbar machen, aber wenn diese Funktionen auf C-Code oder Statik zugreifen, können wir sie nicht berechnen. Als Lösung schlage ich einen Lint vor, der vor öffentlichen Funktionen warnt, die const fn sein könnten.

Bei den Flusen stimme ich zu. Es ähnelt den vorhandenen integrierten Lints missing_docs , missing_debug_implementations und missing_copy_implementations .

Es gibt jedoch ein Problem damit, den Flusen standardmäßig aktiviert zu haben ... er würde vor Funktionen warnen, die Sie ausdrücklich nicht als const haben wollen, sagen wir, weil Sie vorhaben, die Funktion später so zu ändern, dass sie es ist kann nicht const sein und möchte Ihre Schnittstelle nicht auf const festschreiben (das Entfernen const ist eine bahnbrechende Änderung).

Ich denke, #[allow(missing_const)] fn foo() {} könnte in diesen Fällen funktionieren?

@eddyb @nikomatsakis Mein Punkt „Das Entfernen const ist eine bahnbrechende Änderung“ legt nahe, dass wir das Schlüsselwort doch haben wollen, da es ein Versprechen an Downstream ist, dass fn const _bleibt_

Es wird eine Schande sein, wie viel const durch std und andere Bibliotheken gestreut werden muss, aber ich sehe nicht, wie Sie es vermeiden können, es sei denn, es war nur für öffentliche erforderlich- mit Blick auf Gegenstände, und das scheint eine verwirrende Regel zu sein.

es sei denn, es war nur für öffentlich zugängliche Elemente erforderlich, und das scheint eine verwirrende Regel zu sein.

Ich mag dieses ... Ich glaube nicht, dass es verwirrend wäre. Ihre öffentliche Schnittstelle ist geschützt, da Sie keine Funktion nicht konstant machen können, die von const fn aufgerufen wird

Technisch gesehen wäre es besser, Funktionen als notconst zu kommentieren, da ich erwarte, dass es viel mehr const fn gibt als umgekehrt.

notconst würde auch besser zur Designphilosophie von Rust passen. (dh " mut , nicht const ")

es sei denn, es war nur für öffentlich zugängliche Elemente erforderlich, und das scheint eine verwirrende Regel zu sein.

Ich mag dieses ... Ich glaube nicht, dass es verwirrend wäre.

Ich bin Flip-Flops auf dieser Idee. Es hat seine Vorteile (denken Sie nur an const fn , wenn Sie Entscheidungen über die öffentliche Schnittstelle treffen), aber ich dachte an eine andere Möglichkeit, wie es verwirrend sein könnte:

Ihre öffentliche Schnittstelle ist geschützt, da Sie keine Funktion nicht konstant machen können, die von const fn aufgerufen wird

Dies ist wahr, und leider würde dies bedeuten, dass ein Autor einer Bibliothek, wenn er eine öffentliche Funktion const markiert, implizit alle Funktionen markiert, die von dieser Funktion const transitiv aufgerufen werden, und es besteht eine Chance Sie markieren unbeabsichtigt Funktionen, die sie nicht wollen, und hindern sie so daran, diese internen Funktionen in Zukunft mit nicht konstanten Funktionen neu zu schreiben.


Ich erwarte, dass es viel mehr const fn gibt als umgekehrt.

Das dachte ich eine Zeit lang, aber es wird nur für reine Rust-Bibliothekskisten gelten. Es wird nicht möglich sein, FFI-basierte fns konstant zu machen (selbst wenn sie nur transitiv FFI-basiert sind, was eine Menge Zeug ist), also ist die schiere Menge von const fn vielleicht nicht ganz so schlimm wie du und ich dachten.


Mein aktuelles Fazit: Jedes nicht explizite const fn erscheint problematisch. Es könnte einfach keine gute Möglichkeit geben, das Keyword nicht zu oft zu schreiben.

Außerdem, fürs Protokoll, notconst wäre eine bahnbrechende Veränderung.

@solson Ein sehr guter Punkt.

Denken Sie daran, dass das Keyword noch haariger wird, wenn Sie versuchen, es mit Trait-Methoden zu verwenden. Die Beschränkung auf die Eigenschaftsdefinition ist nicht sinnvoll genug, und das Kommentieren von impls führt zu unvollkommenen "const fn parametrim"-Regeln.

Ich habe das Gefühl, dass dieser Kompromiss ziemlich gründlich diskutiert wurde, als wir const fn überhaupt eingeführt haben. Ich denke, die Analyse von @solson ist auch richtig. Ich denke, das Einzige, was sich geändert hat, ist, dass vielleicht der Prozentsatz der Constable FNS größer geworden ist, aber ich denke nicht genug, um den grundlegenden Kompromiss hier zu ändern. Es wird ärgerlich sein, nach und nach const fn in Ihre öffentlichen Schnittstellen und so weiter einfügen zu müssen, aber so ist das Leben.

@nikomatsakis Was mich beunruhigt, ist die Kombination dieser beiden Tatsachen:

  • wir können nicht alles im Voraus überprüfen, unsafe -Code kann "dynamisch nicht konstant" sein
  • Was auch immer wir für Generika und Trait-Impls tun, es wird ein Kompromiss zwischen „korrekt“ und flexibel sein

Angesichts der Tatsache, dass "globale Nebeneffekte" die Hauptsache sind, die verhindert, dass Code const fn ist, ist das nicht das "Effektsystem", das Rust früher hatte und entfernt wurde?
Sollten wir nicht von „Effektstabilität“ sprechen? Scheint Code ähnlich zu sein, vorausgesetzt, eine Bibliothek gerät meiner Meinung nach nie in Panik.

@eddyb absolut const ist ein Effektsystem und ja, es kommt mit all den Nachteilen, die uns dazu gebracht haben, sie so weit wie möglich zu vermeiden ... Es ist plausibel, dass, wenn wir den Schmerz des Hinzufügens ertragen wollen In einem Effektsystem möchten wir vielleicht eine Syntax in Betracht ziehen, die wir auf andere Arten von Effekten skalieren können. Als Beispiel zahlen wir einen ähnlichen Preis mit unsafe (ebenfalls ein Effekt), obwohl ich nicht sicher bin, ob es sinnvoll ist, darüber nachzudenken, diese zu vereinheitlichen.

Die Tatsache, dass Verstöße dynamisch auftreten können, scheint jedoch ein weiterer Grund zu sein, dieses Opt-in vorzunehmen, oder?

Wie wäre es damit:

Im Allgemeinen denke ich, dass const fn s nur für Konstruktoren ( new ) oder wo absolut notwendig verwendet werden sollten.

Manchmal möchten Sie jedoch möglicherweise andere Methoden verwenden, um bequem eine Konstante zu erstellen. Ich denke, wir könnten dieses Problem für viele Fälle lösen, indem wir constness zum Standard machen , aber nur für das definierende Modul . Auf diese Weise können Abhängige keine Konstanz annehmen, es sei denn, dies wird ausdrücklich mit const garantiert, während sie dennoch bequem Konstanten mit Funktionen erstellen können, ohne alles zu const zu machen .

@torkleyy Sie können das bereits tun, indem Sie Helfer haben, die nicht exportiert werden.

Ich sehe kein starkes Argument dafür, dass private Hilfsfunktionen nach Möglichkeit nicht implizit const sein sollten. Ich denke, @solson hat gesagt, dass die explizite Angabe von const , selbst für Hilfsfunktionen, den Programmierer dazu zwingt, anzuhalten und zu überlegen, ob er sich darauf festlegen möchte, dass diese Funktion const ist. Aber wenn Programmierer schon bei öffentlichen Veranstaltungen darüber nachdenken müssen, reicht das nicht? Wäre es das nicht wert, nicht überall const schreiben zu müssen?

Im IRC schlug @eddyb vor, dieses Feature-Gate aufzuteilen, damit wir Aufrufe an const fns stabilisieren könnten, bevor wir Details ihrer Deklaration und Körper herausfinden. Klingt das nach einer guten Idee?

@durka Das klingt für mich als Rust-Benutzer, der nicht viel über Compiler-Interna weiß, großartig.

Entschuldigen Sie mein Unverständnis hier, aber was bedeutet es, den Aufruf einer const -Funktion zu stabilisieren, ohne die Deklaration zu stabilisieren?

Wollen wir sagen, dass der Compiler irgendwie weiß, was konstant ist und was nicht, aber diesen Teil vorerst für Diskussionen/Implementierungen offen lässt?

Wie können dann die Aufrufe stabilisiert werden, wenn der Compiler später seine Meinung über die Konstante ändern könnte?

@nixpulvis Einige const fn s existieren bereits in der Standardbibliothek, zum Beispiel UnsafeCell::new . Dieser Vorschlag würde es erlauben, solche Funktionen in konstanten Kontexten aufzurufen, zum Beispiel den Initialisierer eines static -Elements.

@nixpulvis Was ich meinte, waren Aufrufe von const fn Funktionen , die durch instabilen Code (wie die Standardbibliothek) definiert wurden, aus konstanten Kontexten, nicht aus regulären Funktionen, die in stabilem Rust-Code definiert sind.

Ich bin zwar dafür, dass Anrufe zu const fn zuerst stabilisiert werden, wenn das schneller gehen kann, aber mir ist nicht klar, was die Stabilisierung der gesamten const fn -Funktion blockiert. Was sind die verbleibenden Bedenken heute? Was wäre ein Weg, um sie anzusprechen?

@SimonSapin Es ist mehr so, dass wir das Design für die Deklaration const fn s heute nicht gut skalieren, noch sind wir uns sicher, welche Wechselwirkungen zwischen ihnen und Merkmalen bestehen und wie viel Flexibilität es geben sollte.

Ich denke, ich bin geneigt, die Verwendung von const fn zu stabilisieren. Dies scheint ein ergonomischer und ausdrucksstarker Gewinn zu sein, und ich kann mir immer noch keinen besseren Weg vorstellen, um mit der konstanten Auswertung zur Kompilierzeit umzugehen, als nur "normalen Code schreiben" zu können.

stabilisieren Verwendungen von const fn.

Dadurch werden auch einige Funktionen in der Standardbibliothek als const stabilisiert, das Bibliotheksteam sollte zumindest einige Audits durchführen.

Ich habe eine PR https://github.com/rust-lang/rust/issues/43017 eingereicht, um Aufrufe zu stabilisieren, zusammen mit einer Liste von Funktionen, die per @petrochenkov geprüft werden sollen.

Ich habe eine Frage/Anmerkung dazu, wie dies in bestimmten Trait/Impl-Situationen verwendet werden könnte. Nehmen wir hypothetisch an, wir haben eine mathematische Bibliothek mit einem Zero -Merkmal:

pub trait Zero {
    fn zero () -> Self;
}

Diese Eigenschaft erfordert nicht, dass die Methode zero const ist, da dies verhindern würde, dass sie von einem Typ BigInt implementiert wird, der von einem Vec unterstützt wird. Aber für Maschinenskalare und andere einfache Typen wäre es viel praktischer, wenn die Methode const wäre.

impl Zero for i32 {
    const fn zero () -> i32 { 0 } // const
}

impl Zero for BigInt {
    fn zero () -> BigInt { ... } // not const
}

Die Eigenschaft erfordert nicht, dass die Methode const ist, sollte aber dennoch erlaubt sein, da const der Implementierung eine Einschränkung hinzufügt und keine ignoriert. Dadurch wird verhindert, dass für einige Typen eine normale Version und eine const -Version derselben Funktion vorhanden sind. Was mich wundert ist, wurde das schon angesprochen?

Warum möchten Sie, dass sich verschiedene Implementierungen des Merkmals unterschiedlich verhalten? Sie können das nicht in einem generischen Kontext verwenden. Sie können einfach mit einem konstanten fn ein lokales Impl auf dem Skalar erstellen.

@Daggerbot Das ist der einzige Weg, den ich für const fn in Eigenschaften sehe - die Eigenschaft zu haben, dass alle Impls const fn sind, ist weitaus seltener, als effektiv " const impl s" zu haben. .

@jethrogb Sie könnten, obwohl es erfordert, dass die Konstanz eine Eigenschaft des impl ist.
Was ich im Sinn habe ist, dass ein generisches const fn mit einem, zB T: Zero gebundenen impl von Zero für T benötigt. const fn Methoden zu enthalten, wenn der Aufruf aus einem konstanten Kontext selbst kommt (zB ein anderer const fn ).

Es ist nicht perfekt, aber es wurde keine überlegene Alternative vorgeschlagen - IMO, die dem am nächsten kommt, wäre "alle Aufrufe und Fehler tief aus dem Aufrufstapel zulassen, wenn etwas versucht wird, das zur Kompilierzeit nicht möglich ist", was nicht so schlimm ist mag auf den ersten Eindruck erscheinen - die meisten Bedenken haben mit Abwärtskompatibilität zu tun, dh das Markieren einer Funktion const fn stellt sicher, dass die Tatsache aufgezeichnet wird, und das Ausführen von Operationen, die zur Kompilierzeit nicht gültig sind, würde erfordern, dass dies nicht der Fall ist const fn .

Würde dies das Problem nicht lösen?

pub trait Zero {
    fn zero() -> Self;
}

pub trait ConstZero: Zero {
    const fn zero() -> Self;
}

impl<T: ConstZero> Zero for T {
    fn zero() -> Self {
        <Self as ConstZero>::zero()
    }
}

Die Boilerplate konnte mit Makros verkleinert werden.

Abgesehen von der geringfügigen Unannehmlichkeit, zwei getrennte Merkmale ( Zero und ConstZero ) zu haben, die fast genau dasselbe tun, sehe ich ein potenzielles Problem bei der Verwendung einer pauschalen Implementierung:

// Blanket impl
impl<T: ConstZero> Zero for T {
    fn zero () -> Self { T::const_zero() }
}

pub struct Vector2<T> {
    pub x: T,
    pub y: T,
}

impl<T: ConstZero> ConstZero for Vector2<T> {
    const fn const_zero () -> Vector2<T> {
        Vector2 { x: T::const_zero(), y: T::const_zero() }
    }
}

// Error: This now conflicts with the blanket impl above because Vector2<T> implements ConstZero and therefore Zero.
impl<T: Zero> Zero for Vector2<T> {
    fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

Der Fehler würde verschwinden, wenn wir das Blanket Impl entfernen würden. Alles in allem ist dies wahrscheinlich am einfachsten in einem Compiler zu implementieren, da es die Sprache am wenigsten komplizierter macht.

Aber wenn wir const zu einer implementierten Methode hinzufügen könnten, wo es nicht erforderlich ist, können wir diese Duplizierung vermeiden, wenn auch immer noch nicht perfekt:

impl<T: Zero> Zero for Vector2<T> {
    const fn zero () -> Vector2<T> {
        Vector2 { x: T::zero(), y: T::zero() }
    }
}

IIRC, C++ erlaubt so etwas bei der Arbeit mit constexpr . Der Nachteil hier ist, dass dieses const nur anwendbar wäre, wenn <T as Zero>::zero auch const ist. Sollte dies ein Fehler sein, oder sollte der Compiler dieses const ignorieren, wenn es nicht anwendbar ist (wie C++)?

Keines dieser Beispiele löst das Problem perfekt, aber ich kann mir keinen besseren Weg vorstellen.

Bearbeiten: Der Vorschlag von @andersk würde das erste Beispiel fehlerfrei ermöglichen. Dies wäre wahrscheinlich die beste/einfachste Lösung für die Compiler-Implementierung.

@Daggerbot Das klingt wie ein Anwendungsfall für die „Gitter“-Regel, die gegen Ende von RFC 1210 (Spezialisierung) vorgeschlagen wurde. Wenn du schreibst

impl<T: ConstZero> Zero for T {…}  // 1
impl<T: ConstZero> ConstZero for Vector2<T> {…}  // 2
impl<T: Zero> Zero for Vector2<T> {…}  // 3
impl<T: ConstZero> Zero for Vector2<T> {…}  // 4

dann, obwohl 1 mit 3 überlappt, wird ihr Schnittpunkt genau von 4 abgedeckt, so dass dies nach der Gitterregel zulässig wäre.

Siehe auch http://smallcultfollowing.com/babysteps/blog/2016/09/24/intersection-impls/.

Das ist ein unglaublich komplexes System, das wir vermeiden wollen.

Ja, Gitterregel wäre erforderlich.

@eddyb was hältst du für komplex?

@Kixunil Duplizieren fast jeder einzelnen Eigenschaft in der Standardbibliothek, anstatt "einfach" einige impl s als const fn zu markieren.

Wir geraten hier aus der Bahn. Derzeit geht es darum, die Verwendung von const fn zu stabilisieren. Das Zulassen const fn Trait-Methoden oder const impl Trait for Foo sind orthogonal zueinander und zu den akzeptierten RFCs.

@oli-obk Dies ist nicht der neue RFC, sondern das Tracking-Problem für const fn .

Ist mir gerade aufgefallen und habe meinen Kommentar editiert.

@eddyb ja, aber es ist einfacher für den Compiler (minus Spezialisierung, aber wir werden wahrscheinlich sowieso eine Spezialisierung wollen) und ermöglicht es den Leuten, auch an ConstTrait gebunden zu sein.

Wie auch immer, ich bin nicht dagegen, impls als const zu markieren. Ich stelle mir auch vor, dass der Compiler automatisch ConstTrait: Trait generiert.

@Kixunil Es ist nicht viel einfacher, besonders wenn Sie es mit Spezialisierung tun können.
Der Compiler müsste nichts wie ConstTrait: Trait automatisch generieren, noch muss das Trait-System etwas davon wissen, man muss nur durch die Implementierungen rekursiv gehen (entweder ein konkretes impl oder eine where -Grenze), die das Trait-System bereitstellt, und überprüfe sie.

Ich frage mich, ob const fns den Zugriff auf UnsafeCell verbieten sollte. Es ist wahrscheinlich notwendig, wirklich konstantes Verhalten zuzulassen:

const fn dont_change_anything(&self) -> bool {
    let old = self.cell.get();
    self.cell.set(!old);
    old
}

Bisher habe ich gesehen, dass set nicht const ist. Die Frage ist, ob das für immer so bleiben wird. Mit anderen Worten: Kann sich unsafe -Code darauf verlassen, dass beim Ausführen derselben const -Funktion für unveränderliche Daten heute und in jeder zukünftigen Version der Sprache/Bibliothek immer dasselbe Ergebnis zurückgegeben wird?

const fn bedeutet nicht unveränderlich, sondern kann zur Kompilierzeit aufgerufen werden.

Ich verstehe. Ich würde mich sehr freuen, wenn ich irgendwie garantieren könnte, dass eine Funktion immer dasselbe zurückgibt, wenn sie mehrmals aufgerufen wird, ohne unsafe -Eigenschaften zu verwenden, wenn es irgendwie möglich ist.

@jethrogb Danke für den Link!

Mir ist aufgefallen, dass mem::size_of nachts als const fn implementiert ist. Wäre dies für mem::transmute und andere möglich? Rust-Intrinsics arbeiten intern im Compiler, und ich bin nicht vertraut genug, um die entsprechenden Änderungen vorzunehmen, um dies zu ermöglichen. Ansonsten setze ich es gerne um.

Leider ist es etwas schwieriger, mit Werten zu arbeiten, als nur magisch welche zu erschaffen. transmute benötigt miri. Ein erster Schritt, miri in den Compiler zu bekommen, ist bereits im Gange: #43628

Damit! Irgendwelches Interesse daran, *Cell::new , mem::{size,align}_of , ptr::null{,_mut} , Atomic*::new , Once::new und {integer}::{min,max}_value konstant zu stabilisieren? Sollen wir FCPs hier drin haben oder individuelle Tracking-Probleme erstellen?

Jawohl.

Ich gehöre keinem Team an, das diesbezüglich die Entscheidungsbefugnis hat, aber meine persönliche Meinung ist, dass all dies außer mem::{size,align}_of trivial genug ist, dass sie jetzt stabilisiert werden könnten, ohne die Bewegungen eines Gummistempels durchlaufen zu müssen FCP.

Als Benutzer würde ich gerne so schnell wie möglich mem::{size,align}_of in konstanten Ausdrücken verwenden, aber ich habe gelesen, dass @nikomatsakis Bedenken äußert, dass sie insta-const-stabil sind, als sie zu const fn gemacht wurden . Ich weiß nicht, ob es spezielle Bedenken oder nur allgemeine Vorsicht gibt, aber IIRC ist der Grund, warum Feature-Gates pro Funktion hinzugefügt wurden. Ich kann mir vorstellen, dass die Bedenken für diese beiden ähnlich genug wären, dass sie sich ein FCP teilen könnten. Ich weiß nicht, ob @rustbot separate FCPs im selben GitHub-Thread verarbeiten kann, daher ist es wahrscheinlich besser, separate Issues zu öffnen.

@durka können Sie ein einzelnes Tracking-Problem öffnen, um die Konstanz all dieser Funktionen zu stabilisieren? Ich werde FCP vorschlagen, sobald es oben ist.

So folgen Sie einer Spur in einer Diskussion über const fns auf alloc::Layout :
Kann Panik in einem const fn zugelassen und als Kompilierungsfehler behandelt werden? Das ähnelt dem, was jetzt mit konstanten arithmetischen Ausdrücken gemacht wird, nicht wahr?

Ja, das ist eine supertriviale Funktion, sobald miri zusammengeführt ist

Ist dies der richtige Ort, um zusätzliche std -Funktionen anzufordern, die zu const werden? Wenn ja, sollten Duration:: { new , from_secs , from_millis } alle sicher sein, um const zu machen.

@remexre Der einfachste Weg, dies zu erreichen, besteht wahrscheinlich darin, eine PR zu erstellen und dort nach einer Überprüfung des Libs-Teams zu fragen.

PR'd als https://github.com/rust-lang/rust/pull/47300. Ich habe auch const zu den instabilen Konstruktoren hinzugefügt, während ich gerade dabei war.

Irgendwelche Gedanken darüber, dass weitere std Funktionen deklariert werden können const ? Insbesondere mem::uninitialized und mem::zeroed ? Ich glaube, dass beides geeignete Kandidaten für zusätzliche const -Funktionen sind. Der einzige Nachteil, den ich mir vorstellen kann, ist der gleiche Nachteil von mem::uninitialized , bei dem die Erstellung von Strukturen, die Drop implementieren, erstellt und ohne ptr::write überschrieben wird.

Ich kann auch eine PR anhängen, wenn das passend klingt.

Was ist die Motivation dafür? Es scheint eine nutzlose Fußwaffe zu sein, ungültige Bitmuster zu erstellen, die dann nicht überschrieben werden können (weil sie in einer Konstante sind), aber vielleicht übersehe ich das Offensichtliche.

mem::uninitialized ist absolut eine Fußwaffe, eine, die auch durch deine Hände schießt, wenn sie nicht richtig zielt. Im Ernst, ich kann nicht genug betonen, wie unglaublich gefährlich die Verwendung dieser Funktion sein kann, obwohl sie als unsafe ist.

Die Motivation hinter der Deklaration dieser zusätzlichen Funktionen const ergibt sich aus der Natur dieser Funktionen, da der Aufruf mem::uninitialized<Vec<u32>> jedes Mal das gleiche Ergebnis ohne Nebeneffekte zurückgibt. Offensichtlich ist dies eine schreckliche Sache, wenn es nicht initialisiert bleibt. Daher ist das unsafe immer noch vorhanden.

Betrachten Sie für einen Anwendungsfall jedoch einen globalen Timer, der den Start einer Funktion verfolgt. Sein interner Zustand wird zu einem späteren Zeitpunkt bestimmt, aber wir brauchen eine Möglichkeit, ihn als statische globale Struktur darzustellen, die bei der Ausführung erstellt wird.

use std::time::Instant;

pub struct GlobalTimer {
    time: UnsafeCell<Instant>
}

impl TimeManager {
    pub const fn init() -> TimeManager {
        TimeManager {
            time: UnsafeCell::new(Instant::now())
        }
    }
}

Dieser Code lässt sich nicht kompilieren, da Instant::now() keine const -Funktion ist. Das Ersetzen Instant::now() durch mem::uninitialized::<Instant>()) würde dieses Problem beheben, wenn mem::uninitialized ein const fn wäre. Idealerweise initialisiert der Entwickler diese Struktur, sobald das Programm mit der Ausführung beginnt. Und während dieser Code als unidiomatischer Rost gilt (der globale Zustand ist im Allgemeinen sehr schlecht), ist dies nur einer von vielen Fällen, in denen globale statische Strukturen nützlich sind.

Ich denke, dieser Beitrag bietet eine gute Grundlage für die Zukunft von Rust-Code, der zur Kompilierzeit ausgeführt wird. Globale statische Strukturen zur Kompilierzeit sind ein Feature mit einigen wichtigen Anwendungsfällen (auch Betriebssysteme kommen mir in den Sinn), die Rust derzeit vermisst. Kleine Schritte in Richtung dieses Ziels können gemacht werden, indem man langsam const zu Bibliotheksfunktionen hinzufügt, die als geeignet erachtet werden, wie mem::uninitialized und mem::zeroed , trotz ihrer unsafe Markierungen.

Bearbeiten: Habe die const in der Funktionssignatur von TimeManager::init() vergessen

Hmm, dieser Code wird kompiliert, also fehlt mir hier immer noch die genaue Motivation ... wenn Sie Code wie schreiben könnten

const fn foo() -> Whatever {
    unsafe { 
        let mut it = mem::uninitialized();
        init_whatever(&mut it);
        it
    }
}

Aber const fns sind derzeit so eingeschränkt, dass man das nicht einmal schreiben kann ...

Ich weiß die theoretische Begründung zu schätzen, aber const ist nicht dasselbe wie pure , und ich denke nicht, dass wir irgendetwas tun sollten, um die Verwendung dieser Funktionen zu fördern, wenn dies nicht für einen überzeugenden Anwendungsfall erforderlich ist .

Ich denke, es gibt viel niedrigere hängende Früchte, die zuerst stabilisiert werden könnten. Ohne miri machen die nicht initialisierten und genullten Intrinsics sowieso wenig Sinn. Ich würde sie aber gerne eines Tages sehen. Wir könnten sie sogar anfänglich stabilisieren und fordern, dass alle Konstanten ein initialisiertes Ergebnis liefern müssen, selbst wenn Zwischenberechnungen nicht initialisiert werden können.

Das heißt, mit Unions und unsicherem Code können Sie sowieso nicht initialisiert oder auf Null gesetzt emulieren, daher macht es nicht viel Sinn, sie nicht konstant zu halten

Mit Hilfe von union s wird nun der bisherige Code kompiliert . Es ist absolut erschreckend 😅.

Auch alles Gute. Diese intrinsischen Funktionen stehen ziemlich weit unten auf der Liste der Anwendungsfälle, aber sie sind immer noch geeignete Kandidaten für eine eventuelle const -ness.

Das ist erschreckend erstaunlich.

Also ... warum genau plädieren Sie dafür, mem::uninitialized, as zu konstituieren?
im Gegensatz zu, sagen wir, Instant::now? :)

Die Notwendigkeit, konstante Initialisierer für Strukturen mit nicht konstanten Werten zu haben
interiors ist echt (siehe: Mutex). Aber ich glaube nicht, dass ich diesen Malarkey mache
einfacher ist der richtige Weg, das zu bekommen!

Am Do, 25. Januar 2018 um 2:21 Uhr, Stephen Fleischman <
[email protected]> schrieb:

Mit Hilfe von Unions wird nun der bisherige Code kompiliert
https://play.rust-lang.org/?gist=be075cf12f63dee3b2e2b65a12a3c854&version=nightly .
Es ist absolut erschreckend 😅.


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/24111#issuecomment-360382201 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAC3n-HyWD6MUEBfHkUUXonh9ORGPSRoks5tOCtegaJpZM4D66IA
.

Instant::now kann nicht konstant sein. Was würde diese Funktion zurückgeben? Zeitpunkt der Zusammenstellung?

Kann jemand zusammenfassen, was getan werden muss, um dies zu stabilisieren? Welche Entscheidung muss getroffen werden? Ob sich das überhaupt stabilisieren lässt?

Integration mit Mustern (zB https://gist.github.com/d0ff1de8b6fc15ef1bb6)

Ich habe das Wesentliche bereits kommentiert, aber da const fn derzeit nicht in einem Muster abgeglichen werden kann, sollte dies die Stabilisierung nicht blockieren, oder? Wir könnten es im Nachhinein immer noch zulassen, wenn es Sinn macht.

Instant::now kann nicht konstant sein. Was würde diese Funktion zurückgeben? Zeitpunkt der Zusammenstellung?

Aber es könnte Instant::zero() oder Instant::min_value() geben, was konstant ist.

Kann jemand zusammenfassen, was getan werden muss, um dies zu stabilisieren? Welche Entscheidung muss getroffen werden? Ob sich das überhaupt stabilisieren lässt?

Ich denke, die einzige offene Frage ist, ob unsere const fn-Prüfungen streng genug sind, um nicht versehentlich etwas zuzulassen/zu stabilisieren, das wir nicht in const fn wollen.

Können wir die Integration mit Mustern über rust-lang/rfcs#2272 durchführen? Muster sind bereits so schmerzhaft, wie sie es derzeit sind, machen wir sie nicht noch schmerzhafter.

Ich denke, die einzige offene Frage ist, ob unsere const fn-Prüfungen streng genug sind, um nicht versehentlich etwas zuzulassen/zu stabilisieren, das wir nicht in const fn wollen.

Korrigieren Sie mich, wenn ich falsch liege, aber sind diese Überprüfungen nicht identisch mit den Überprüfungen auf das, was derzeit im Körper eines const -Rvalue zulässig ist? Ich hatte den Eindruck, dass const FOO: Type = { body }; und const FOO: Type = foo(); const fn foo() -> Type { body } identisch sind in dem, was sie für jeden beliebigen Körper zulassen

@sgrif Ich denke, die Sorge dreht sich um Argumente, die const fn haben, aber const nicht.
Außerdem ist nicht klar, ob wir langfristig das const fn -System von heute beibehalten wollen.

Schlagen Sie stattdessen const-Generika (in beide Richtungen) vor? (zB <const T> + const C<T> auch bekannt als const C<const T> ?)

Ich hätte wirklich gerne ein try_const! -Makro, das versucht, jeden Ausdruck zur Kompilierzeit auszuwerten, und in Panik gerät, wenn dies nicht möglich ist. Dieses Makro kann nicht-konstante fns aufrufen (mit miri?), sodass wir nicht warten müssen, bis jede Funktion in std als const fn markiert wurde. Wie der Name jedoch schon sagt, kann es jederzeit fehlschlagen. Wenn also eine Funktion aktualisiert wird und jetzt nicht konstant sein kann, stoppt sie die Kompilierung.

@Badel2 Ich verstehe, warum Sie eine solche Funktion wünschen, aber ich vermute, dass eine weit verbreitete Verwendung für das Kisten-Ökosystem wirklich schlecht wäre. Denn auf diese Weise hängt Ihre Kiste möglicherweise davon ab, dass eine Funktion in einer anderen Kiste zur Kompilierzeit auswertbar ist, und der Autor der Kiste ändert dann etwas, das sich nicht auf die Funktionssignatur auswirkt, aber verhindert, dass die Funktion zur Kompilierzeit auswertbar ist.

Wenn die Funktion überhaupt mit const fn gekennzeichnet war, dann hätte der Crate-Autor das Problem direkt beim Versuch, die Crate zu kompilieren, erkannt und Sie können sich auf die Anmerkung verlassen.

Wenn das nur auf dem Spielplatz funktionieren würde... https://play.rust-lang.org/?gist=6c0a46ee8299e36202f959908e8189e6&version=stable

Dies ist eine nicht-portable (in der Tat so nicht-portable, dass es auf meinem System funktioniert, aber nicht auf dem Spielplatz - obwohl sie beide Linux sind), um die Erstellungszeit in das erstellte Programm aufzunehmen.

Der portable Weg wäre, SystemTime::now() in der konstanten Auswertung zuzulassen.

(Dies ist ein Argument für const/compile-time-eval von JEDER Funktion/jedem Ausdruck, unabhängig davon, ob es sich um const fn handelt oder nicht.)

Das klingt für mich nach einem Argument dafür, absolute Pfade in include_bytes zu verbieten 😃

Wenn Sie SystemTime::now in const fn zulassen, würde const FOO: [u8; SystemTime::now()] = [42; SystemTime::now()]; zufällig Fehler machen, abhängig von Ihrer Systemleistung, Ihrem Scheduler und der Position von Jupiter.

Noch schlimmer:

const TIME: SystemTime = SystemTime::now();

Bedeutet nicht, dass der Wert von TIME an allen Verwendungsstandorten gleich ist, insbesondere über Zusammenstellungen mit inkrementellen und über Crates hinweg.

Und noch verrückter ist, dass Sie foo.clone() auf sehr unsolide Weise vermasseln können, weil Sie am Ende vielleicht den Klon impl aus einem Array mit der Länge 3 auswählen, aber der Rückgabetyp ein Array der Länge 4 sein könnte.

Selbst wenn wir den Aufruf beliebiger Funktionen zulassen würden, würden wir niemals zulassen, dass SystemTime::new() erfolgreich zurückkehrt, genauso wie wir niemals echte Zufallszahlengeneratoren zulassen würden

@SoniEx2 Ich denke, das ist hier ein bisschen offtopic, aber Sie können so etwas bereits heute mit einer Cargo- build.rs -Datei implementieren. Siehe Build Scripts im Cargo Book, insbesondere den Abschnitt über die Fallstudie der Codegenerierung.

@oli-obk Ich denke, es ist nicht ganz dasselbe Problem, da es bei einem um die Versionierung der API-Sicherheit geht, während es bei dem anderen um die Build-Umgebung geht. Ich stimme jedoch zu, dass beide zu einem Bruch des Ökosystems führen können, wenn sie nicht mit Sorgfalt angewendet werden.

Bitte erlauben Sie nicht , die aktuelle Uhrzeit in einem const fn ; Wir müssen keine weiteren/einfacheren Möglichkeiten hinzufügen, um Builds nicht reproduzierbar zu machen.

Wir können keine Art von Nicht-Determinismus (wie Zufallszahlen, die aktuelle Zeit usw.) in const fn zulassen - dies zuzulassen führt zu einer Unzuverlässigkeit des Typsystems, da rustc davon ausgeht, dass konstante Ausdrücke immer zu demselben gegebenen Ergebnis ausgewertet werden die gleiche Eingabe. Siehe hier für ein bisschen mehr Erklärung.

Eine zukünftige Methode zur Behandlung von Fällen wie in https://github.com/rust-lang/rust/issues/24111#issuecomment -376352844 wäre die Verwendung eines einfachen prozeduralen Makros, das die aktuelle Uhrzeit erhält und als einfache Zahl oder ausgibt String-Token. Prozedurale Makros sind mehr oder weniger vollständig uneingeschränkter Code, der die Zeit auf eine der üblichen tragbaren Arten abrufen kann, die nicht-konstanter Rust-Code verwenden würde.

@rfcbot fcp zusammenführen

Ich schlage vor, dass wir dies zusammenführen, weil es eine einigermaßen vernünftige Option ist, keine Breaking Change ist, versehentliche Breaking Changes verhindert (Ändern einer Funktion auf eine Weise, die sie nicht konstant auswertbar macht, während andere Crates die Funktion in konstanten Kontexten verwenden) und die einzige Wirklich schlimm daran ist, dass wir const vor eine Reihe von Funktionsdeklarationen werfen müssen.

@rfcbot fcp merge im Namen von @oli-obk - scheint es wert zu sein, über Stabilisierung nachzudenken und die Probleme zu diskutieren

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

  • [x] @aturon
  • [x] @cramertj
  • [ ] @eddyb
  • [x] @joshtriplett
  • [ ] @nikomatsakis
  • [x] @nr
  • [ ] @pnkfelix
  • [x] @scottmcm
  • [x] @ohneBoote

Bedenken:

  • Design (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588)
  • parallel-const-traits gelöst von https://github.com/rust-lang/rust/issues/24111#issuecomment -377133537
  • Priorität (https://github.com/rust-lang/rust/issues/24111#issuecomment-376652507)
  • Laufzeitzeigeradressen (https://github.com/rust-lang/rust/issues/24111#issuecomment-386745312)

Sobald eine Mehrheit der Gutachter zustimmt (und keine Einwände hat), tritt die letzte Kommentierungsfrist ein. 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 markierte Teammitglieder mir geben können.

@rfcbot betrifft die Priorität

Wir sollten uns vielleicht bis nach der Ausgabe damit beschäftigen, da ich nicht glaube, dass wir die Bandbreite haben, um mit irgendwelchen Folgen fertig zu werden.

@rfcbot betrifft alles-const

Wir landen in einer Art C++-Welt, in der es einen Anreiz gibt, jede mögliche Funktion zu const zu machen.

Eine Zusammenfassung der kurzen Diskussion mit @oli-obk:

  1. In Zukunft könnte fast jede Funktion mit const gekennzeichnet werden. Zum Beispiel könnte alles auf Vec const sein. In dieser Welt könnte es sinnvoll sein, das Schlüsselwort const ganz loszuwerden: Fast alles kann const sein, und man muss sich große Mühe geben, eine Funktion von const auf non zu ändern -const, also wären die Gefahren der Abwärtskompatibilität in Bezug auf die abgeleitete Konstanz wahrscheinlich nicht besonders hoch.

  2. Es ist jedoch nicht möglich, const heute loszuwerden. Das miri von heute kann nicht alles interpretieren und wird in der Produktion nicht wirklich gründlich getestet.

  3. Es ist tatsächlich abwärtskompatibel, heute const zu verlangen und dann dieses Schlüsselwort zu verwerfen und in Zukunft auf abgeleitete Konstanz umzuschalten.

Wenn man 1, 2 und 3 zusammennimmt, scheint es eine gute Option zu sein, das Schlüsselwort const heute zu stabilisieren, als den Satz konstant auswertbarer Funktionen in zukünftigen Versionen zu erweitern. Nach einiger Zeit haben wir einen durch und durch kampferprobten Honigdachs -Dauerbewerter, der alles auswerten kann. An diesem Punkt können wir zu Inferred Const wechseln.

In Bezug auf die Fallout-Gefahren: const fn wurde wild auf nightly verwendet, insbesondere auf embedded. Außerdem ist der const fn-Checker derselbe Checker wie derjenige, der für statische Initialisierer und Konstanten verwendet wird (mit Ausnahme einiger statischer spezifischer Prüfungen und Funktionsargumente).

Der Hauptnachteil, den ich sehe, ist, dass wir im Wesentlichen dafür plädieren, const großzügig über die Kisten zu sprühen (für den Moment siehe @matklad 's Beitrag für zukünftige Ideen

@rfcbot betrifft Parallel-Const-Traits

Es fühlt sich an, als würde die Stabilisierung sofort zu einer Reihe von Kisten führen, die eine parallele Merkmalshierarchie mit Const an der Vorderseite bilden: ConstDefault , ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto usw. und nach ConstIndex fragen und so weiter. Das ist nicht schlimm – wir haben das heute sicherlich ein bisschen mit Try , obwohl die Stabilisierung von TryFrom helfen wird – aber ich denke, es wäre schön, zumindest eine Skizze des Plans zu haben, um es schöner zu lösen. (Ist das https://github.com/rust-lang/rfcs/pull/2237? Ich weiß es nicht).

( @nrc : Es sieht so aus, als ob der Bot nur eines Ihrer Bedenken registriert hat)

Parallel-const-Traits haben die triviale Lösung in der hypothetischen zukünftigen const-all-the-things-Version. Sie würden einfach funktionieren.

In der const fn-Welt würden Sie nicht mit Trait-Duplizierung enden, solange wir keine const fn-Trait-Methoden zulassen (was wir nicht tun), nur weil Sie es nicht können. Sie könnten natürlich zugeordnete Konstanten erstellen (on nightly), was ungefähr der Situation entspricht, in der sich die libstd vor einem Jahr befand, als wir eine Reihe von Konstanten zum Initialisieren verschiedener Typen innerhalb von Statics/Konstanten hatten, ohne ihre privaten Felder offenzulegen. Aber das ist etwas, das schon eine Weile hätte passieren können und nicht passiert ist.

Um es klar zu sagen, ConstDefault ist heute bereits ohne const fn möglich, und die restlichen Beispiele ( ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto ) wird nicht möglich sein, selbst wenn diese Funktion stabilisiert ist, da sie keine konstanten Trait-Methoden hinzufügt, wie @oli-obk erwähnt hat.

( ConstDefault ist möglich, indem eine zugeordnete Konstante anstelle einer zugeordneten Konstante fn verwendet wird, aber soweit ich weiß, ist die Leistung gleichwertig.)

@scottmcm const fn in Eigenschaftsdefinitionen ist heute nicht möglich (oh @solson hat es bereits erwähnt).

@eddyb zufällige Idee: Was wäre, wenn wir es ermöglichen würden, const impl ein Merkmal zu const impl zu machen, anstatt const fn in Merkmalsdefinitionen hinzuzufügen? (Diese beiden schließen sich auch nicht gegenseitig aus.)

@whitequark https://github.com/rust-lang/rfcs/pull/2237 deckt diese Idee durch eine Kombination von const impl ab, die sich bei jedem fn auf const fn #$ ausdehnt impl , und einem impl mit allen const Methoden erlauben, eine T: const Trait Grenze zu erfüllen, ohne eine der Methoden const in zu markieren die Eigenschaftsdefinition selbst.

@rfcbot betrifft das Design

Wir haben in der Vergangenheit aus mehreren Gründen versucht, ein bestimmtes const fn -System zu stabilisieren:

  • die aktuelle unterstützt keine trait s, die const fn Methoden erfordern, oder Trait impl s, die const fn Methoden bereitstellen (siehe https://github. com/rust-lang/rfcs/pull/2237 für einige Möglichkeiten, dies zu tun)
  • entsprechend gibt es das Problem, nach einer Methode zu fragen, die über T: Trait verwendet wird, die an const fn $ gebunden ist, ohne separate Merkmale zu haben, und vorzugsweise nur , wenn sie zur Kompilierungszeit verwendet wird (z. B. Option::map würde zur Laufzeit genauso funktionieren, aber einen konstant aufrufbaren Abschluss in CTFE erfordern)
  • Da viele Algorithmen, Sammlungen und Abstraktionen potenziell konstant auswertbar sind, könnte es ganze Kisten geben, die überall const fn verwenden würden ( libcore kommt mir in den Sinn).

Es gibt verschiedene Designmöglichkeiten, die die meisten oder alle dieser Probleme lindern würden (auf Kosten der Einführung anderer), zum Beispiel sind dies einige der aufgetretenen:

  • es sind keine Anmerkungen erforderlich und es werden nur Compilerfehler ausgegeben, und miri kann nicht ausgewertet werden

    • Vorteile: Sauberere Codebasen, ständige Auswertung kann je nach beteiligten Werten bereits fehlschlagen

    • Nachteile: keine Verhaltensdokumentation auf Sprachebene und keine Semver-Grenze, Sie können den Code von anderen auf miri werfen und jede geringfügige Änderung beobachten, die sie vorgenommen haben, zB Laufzeit-Debug-Protokollierung, die in einer Patch-Version einer Ihrer Abhängigkeiten hinzugefügt wird; Außerdem ist die Rvalue-Promotion schwieriger durchzuführen

  • eine Möglichkeit, sich für das obige Verhalten per Funktion/impl/Modul/etc.

    • Die Körper dieser Funktionen würden sich (zumindest in Bezug auf const fn ) wie Makros verhalten

    • Vorteile: keine schwerwiegenden Auswirkungen, Einschränkung des Umfangs einiger Analysen

    • Nachteile: immer noch keine großartige Dokumentation, nicht klar, welches Verhalten betroffen sein sollte ( nur konstante Auswertbarkeit?), Jede Änderung im Körper könnte als semver-breaking gelten

Denn auf diese Weise hängt Ihre Kiste möglicherweise davon ab, dass eine Funktion in einer anderen Kiste zur Kompilierzeit auswertbar ist, und der Autor der Kiste ändert dann etwas, das sich nicht auf die Funktionssignatur auswirkt, aber verhindert, dass die Funktion zur Kompilierzeit auswertbar ist.

@leoschwarz ist das nicht schon ein Problem mit Auto Traits? Vielleicht besteht die Lösung dafür darin, Rostlöser in die Ladung zu integrieren, um diese Art von unbeabsichtigtem Bruch zu erkennen.

Allerdings ist mir nicht klar, was passiert, wenn miri ein Evaluierungszeitlimit hat, das Sie (als Bibliotheksautor) versehentlich überschreiten, was zu einem nachgelagerten Kompilierungsfehler führt.

@nrc Ich denke, "alles-const" ist wahr, aber kein Problem. Ja, wir werden am Ende eine Menge Dinge markieren const .

Ich möchte nur darauf hinweisen, dass ich nicht sicher bin, ob ich alles gefolgert haben möchte
konst. Es ist eine Entscheidung darüber, ob Laufzeit oder Kompilierzeit mehr ist
wichtig. Manchmal denke ich, dass der Compiler genug Berechnungen anstellt
Kompilierzeit!

Am Mittwoch, 28. März 2018 um 14:49 Uhr, Josh Triplet [email protected]
schrieb:

@nrc https://github.com/nrc Ich denke, "alles-const" ist wahr, aber nicht
ein Problem. Ja, wir werden am Ende eine riesige Menge von Dingen markieren, die konstant bleiben.


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/24111#issuecomment-376914220 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAC3ny9Wm9JK6p-fXf6gbaEgjFtBpMctks5ti6LigaJpZM4D66IA
.

Auswertungszeitlimit

Diese Grenze ist bald vorbei.

Ich möchte nur darauf hinweisen, dass ich nicht sicher bin, ob ich möchte, dass alles, was abgeleitet wird, konstant ist.

Oh nein, wir werden die Dinge nicht zufällig zur Kompilierzeit berechnen. Lassen Sie einfach zu, dass zufällige Dinge im Hauptteil von Statiken, Konstanten, Enum-Varianten-Diskriminanten und Array-Längen berechnet werden

@rfcbot löste parallel-const-traits

Danke für die Korrekturen, Leute!

Diese Grenze ist bald vorbei.

Fantastisch. In diesem Fall klingt auto-const-fn (in Kombination mit einer Integration von rust-semverver oder ähnlichem, um Informationen über Brüche zu geben) großartig, obwohl das „Add-Protokollierung und Bruch verursachen“ problematisch sein könnte. Obwohl Sie die Versionsnummer erhöhen können, denke ich, ist es nicht so, als wären sie endlich.

Protokollieren und Drucken sind "feine" Nebeneffekte in meinem Konstantenmodell. Wir könnten eine Lösung dafür finden, wenn alle zustimmen. Wir könnten sogar in Dateien schreiben (nicht wirklich, aber so tun, als ob wir es täten, und alles wegwerfen).

Ich bin wirklich besorgt darüber, Nebenwirkungen stillschweigend wegzuwerfen.

Wir können das besprechen, sobald wir einen RFC um sie herum erstellt haben. Im Moment können Sie in Konstanten einfach keine "Nebenwirkungen" haben. Das Thema ist orthogonal zur Stabilisierung von const fn

Ich bin ein bisschen besorgt über den Ansatz "nur eine Semver-Warnung" für abgeleitete
Konstanz. Wenn ein Kistenautor, der nie über Konstanz nachgedacht hat, das sieht
"Warnung: Die gerade vorgenommene Änderung macht es unmöglich, foo() aufzurufen
const-Kontext, was vorher möglich war", werden sie das nur als a sehen
non-sequitur und zum Schweigen bringen? Offensichtlich denken die Leute in dieser Ausgabe häufig nach
darüber, welche Funktionen konstant sein können. Und es wäre schön, wenn mehr Leute das tun würden
dass (sobald const_fn stabil ist). Aber aus heiterem Himmel ist Warnungen das Richtige
Möglichkeit, das zu fördern?

Am Do, 29. März 2018 um 4:36 Uhr, Oliver Schneider [email protected]
schrieb:

Wir können das besprechen, sobald wir einen RFC um sie herum erstellt haben. Für jetzt nur
kann keine "Nebenwirkungen" in Konstanten haben. Das Thema ist orthogonal zu
stabilisierende konstante Fn


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/24111#issuecomment-377164275 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAC3n3MtmvrDF42Iy0nhZ2q8xC-QGcvXks5tjJ0ggaJpZM4D66IA
.

Ich denke, explizites const fn kann ärgerlich sein und viele APIs überladen, aber ich denke, die Alternative, implizit anzunehmen, hat viel zu viele Probleme, um praktikabel zu sein:

  • Das Wegwerfen von Nebeneffekten kann dazu führen, dass sich Code anders verhält (z. B. eine Datei schreiben und dann lesen), wenn er const oder non const heißt.
  • Das Aufrufen externer Funktionen bedeutet, dass es immer Nebenwirkungen geben kann, sodass auf unsicheren Code wahrscheinlich nie const fn geschlossen werden kann.
  • Wann kann const fn für generischen Code abgeleitet werden? Würde dies zur Monomorphisierungszeit erfolgen?

Ich sehe jedoch das größte Problem darin, es nicht explizit zu machen, dass jemand versehentlich viel Code mit einer einzigen Änderung beschädigen kann, ohne sich dessen bewusst zu sein. Dies ist besonders bei den im Rust-Ökosystem üblichen langen Abhängigkeitsgraphen von Bedeutung. Wenn eine explizite Änderung der Funktionssignatur erforderlich ist, wird man sich leichter bewusst, dass dies eine Breaking Change ist.

Vielleicht könnte ein solches Feature als Konfigurations-Flag auf Crate-Ebene implementiert werden, das im Stammverzeichnis des Crates hinzugefügt werden kann, #![infer_const_fn] oder so ähnlich, und für immer Opt-in bleiben. Wenn das Flag hinzugefügt wird, würde const fn nach Möglichkeit in der Crate abgeleitet und auch in den Dokumenten widergespiegelt (und es würde erfordern, dass die aufgerufenen Funktionen auch const fn sind). Wenn ein Crate-Autor dieses Flag hinzufügt, versprechen sie, vorsichtig zu sein über Versionierung und vielleicht Rost-Semverver könnte sogar erzwungen werden.

Wie wäre es, es rückwärts zu machen?

Anstatt konstante fn zu haben, haben Sie seitliche fn.

Es ist immer noch explizit (Sie müssen Seite fn setzen, um Seite fn aufzurufen, wodurch die Kompatibilität explizit unterbrochen wird) und beseitigt Unordnung. (Einige) Intrinsics und alles mit asm wäre ein Seiten-Fn.

Das ist nicht abwärtskompatibel, obwohl ich denke, dass es in einer Edition hinzugefügt werden kann?

Am 30. März 2018 02:43:06 GMT+08:00, "Soni L." [email protected] schrieb:

Wie wäre es, es rückwärts zu machen?

Anstatt konstante fn zu haben, haben Sie seitliche fn.

Es ist immer noch explizit (Sie müssen Seite fn setzen, um Seite fn aufzurufen,
ausdrücklich die Kompatibilität brechen) und beseitigt Unordnung. (Etwas)
Intrinsic und alles mit asm wäre ein Seiten-Fn.

--
Sie erhalten dies, weil Sie erwähnt wurden.
Antworten Sie direkt auf diese E-Mail oder sehen Sie sie auf GitHub an:
https://github.com/rust-lang/rust/issues/24111#issuecomment -377333542

--
Von meinem Android-Gerät mit K-9 Mail gesendet. Bitte entschuldigen Sie meine Kürze.

Ich denke, das größere Problem ist, dass es für Anfänger ein echter Schock wäre, da die meisten Programmiersprachen das nicht tun.

@whitequark Ich bin mit allem nicht einverstanden, was genau das tut ("Nebenwirkungen wegwerfen"). Ich glaube, @oli-obk hat über eine zukünftige Erweiterung gesprochen, aber aus den Diskussionen, an denen ich beteiligt war, weiß ich Folgendes

  • wir können "deterministische Nebeneffekte" unterscheiden (die Ihnen keine unreinen Daten zurückgeben )
  • Wir könnten spezielle APIs haben, wenn wir zB "Daten zur Kompilierzeit protokollieren" wollten.

    • Dies ist ziemlich schwierig, je nachdem, welches Maß an externem Determinismus Sie möchten, da die inkrementelle Kompilierung bei Bedarf nicht unbedingt zu einer konsistenten Ausgabe führt

  • Insbesondere würden wir nichts anfassen, was derzeit existiert und Globals / C FFI verwendet

EDIT : Nur damit die Diskussion nicht entgleist, zB:

Das Wegwerfen von Nebeneffekten kann dazu führen, dass sich Code anders verhält (z. B. eine Datei schreiben und dann lesen), wenn er const oder non const heißt.

Wir können (wahrscheinlich?) alle davon ausgehen, dass @oli-obk sich in Bezug auf das Wegwerfen solcher Nebenwirkungen geirrt hat.

Vielleicht könnte ein solches Feature als Konfigurations-Flag auf Crate-Ebene implementiert werden, das im Stammverzeichnis des Crates hinzugefügt werden kann

Das ist eine Teilmenge des zweiten Beispiels früherer Vorschläge von https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588.
Wenn wir ein bereichsbezogenes "Konfigurationsflag" haben, sollte der Benutzer IMO in der Lage sein, feinkörnigere Bereiche auszuwählen.

Wie wäre es, es rückwärts zu machen?
Anstatt konstante fn zu haben, haben Sie seitliche fn.
Es ist immer noch explizit (Sie müssen Seite fn setzen, um Seite fn aufzurufen, wodurch die Kompatibilität explizit unterbrochen wird) und beseitigt Unordnung. (Einige) Intrinsics und alles mit asm wäre ein Seiten-Fn.

In https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588 habe ich versucht darauf hinzuweisen, dass ganze Bibliotheken „alle const fn “ oder „alle side fn “ sein könnten ".
Wenn es sich nicht um Funktionsdeklarationen, sondern um Bereiche handelt, könnte es vielleicht in einer zukünftigen Ausgabe funktionieren.
Ohne „vom Körper ableiten“-Semantik müssen Sie jedoch die Merkmalsinteraktionen selbst für das Opt-in side fn entwerfen, sodass Sie nichts gewinnen und potenziell massive Reibungen einführen.

Abschnitt 3.3 von Kenton Vardas „Singletons Considered Damaged“-Bericht scheint hier relevant zu sein (ehrlich gesagt, das Ganze ist sehr lesenswert).

Was ist mit der Debug-Protokollierung?

In der Praxis erkennt jeder an, dass die Debug-Protokollierung für jeden Codeabschnitt verfügbar sein sollte. Dafür machen wir eine Ausnahme. Die genaue theoretische Grundlage für diese Ausnahme kann für diejenigen, die sich darum kümmern, auf verschiedene Weise bereitgestellt werden.

Aus Sicherheitssicht ist die Debug-Protokollierung ein harmloser Singleton. Es kann nicht als Kommunikationskanal verwendet werden, da es nur Schreibzugriff hat. Und es ist eindeutig unmöglich, irgendeinen Schaden durch das Schreiben in ein Debug-Log anzurichten, da das Debug-Logging keinen Einfluss auf die Korrektheit des Programms hat. Selbst wenn ein bösartiges Modul das Protokoll "spammt", können Nachrichten von diesem Modul leicht herausgefiltert werden, da Debug-Protokolle normalerweise genau identifizieren, welches Modul die jeweilige Nachricht erzeugt hat (manchmal liefern sie sogar einen Stack-Trace). Daher ist die Bereitstellung kein Problem.

Analoge Argumente können vorgebracht werden, um zu zeigen, dass die Debug-Protokollierung die Lesbarkeit, Testbarkeit oder Wartbarkeit nicht beeinträchtigt.

Eine weitere theoretische Rechtfertigung für das Debug-Logging besagt, dass die Debug-Log-Funktion wirklich nur eine No-Op ist, die zufällig vom Debugger beobachtet wird. Wenn kein Debugger läuft, macht die Funktion nichts. Das Debuggen im Allgemeinen bricht offensichtlich das gesamte Objektfähigkeitsmodell, aber es ist auch offensichtlich eine privilegierte Operation.

Meine Aussage über "wir können eine Lösung [für das Debuggen] finden" bezog sich tatsächlich auf eine potenzielle zukünftige API, die von consts aufgerufen werden kann, aber irgendeine Form von Drucken hat. Das zufällige Implementieren von plattformspezifischen Druckoperationen (nur damit wir vorhandenen Code mit print/debug-Anweisungen konstant machen können) ist nichts, was ein const-Evaluator tun sollte. Dies wäre ein reines Opt-In, explizit kein unterschiedliches beobachtbares Verhalten (z. B. Warnungen in const eval und Befehlszeilen-/Dateiausgabe zur Laufzeit). Die genaue Semantik bleibt zukünftigen RFCs überlassen und sollte im Allgemeinen als vollständig orthogonal zu const fn betrachtet werden

Gibt es wesentliche Nachteile von const impl Trait und T: const Trait ?

Abgesehen davon, dass noch mehr const herumgesprüht wird, könnten nur einige Eigenschaftsmethoden konstant sein, was eine feinkörnigere Kontrolle erfordern würde. Ich kenne jedoch keine ordentliche Syntax, um dies anzugeben. Vielleicht where <T as Trait>::some_method is const ( is könnte ein kontextbezogenes Schlüsselwort sein).

Auch das ist orthogonal zu const fn.

[u8; SizeOf<T>::Output]

Wenn const und side fns getrennt sind, müssen wir tatsächliche Designüberlegungen berücksichtigen. Der einfachste Weg, sie voneinander zu trennen, besteht darin, const fns zu einer Erweiterung von etwas zu machen, das wir heute haben - dem turing-complete-Typsystem.

Alternativ können Sie const fns wirklich zu const machen: Alle konstanten fn sollten so ausgewertet werden, als ob jeder Parameter eine generische Konstante wäre.

Dies macht es viel einfacher, über sie nachzudenken, da ich persönlich nicht über const fns nachdenken kann, wie sie derzeit sind. Ich kann über Turing-Complete-Typen, Makros, normale fns usw. nachdenken, aber ich finde es unmöglich, über const fn nachzudenken, da selbst geringfügige Details ihre Bedeutung vollständig ändern.

da selbst kleine Details ihre Bedeutung völlig ändern.

Könnten Sie das näher erläutern? Meinen Sie Erweiterungen wie const fn -Zeiger, const -Eigenschaftsgrenzen, ...? Weil ich in dem bloßen Vorschlag const fn keine kleinen Details sehe.

Alternativ können Sie const fns wirklich zu const machen: Alle konstanten fn sollten so ausgewertet werden, als ob jeder Parameter eine generische Konstante wäre.

Das machen wir zur Kompilierzeit. Nur dass die Funktion zur Laufzeit wie jede andere Funktion verwendet wird.

Das Problem ist, dass jedes kleine Detail eine konstante Auswertung in eine Laufzeitauswertung verwandeln kann. Dies mag auf den ersten Blick nicht wie eine große Sache erscheinen, kann es aber sein .

Nehmen wir an, der Funktionsaufruf ist wirklich lang, weil er alles aus konstanten fns besteht? Und Sie möchten es in mehrere Zeilen aufteilen.

Sie fügen also einige let s hinzu.

Jetzt dauert die Ausführung Ihres Programms 20x länger.

@SoniEx2 Die Größe von Arrays ( $N in [u8; $N] ) wird immer zur Kompilierzeit ausgewertet. Wenn dieser Ausdruck nicht const ist, schlägt die Kompilierung fehl. Umgekehrt ruft $#$ let x = foo() $#$ zur Laufzeit foo auf, unabhängig davon, ob es sich um ein const fn handelt oder nicht (modulo das Inlining und die konstante Weitergabe des Optimierers, aber das ist völlig unabhängig von const fn ). Wenn Sie das Ergebnis der Auswertung eines Ausdrucks zur Kompilierzeit benennen möchten, benötigen Sie ein const -Element.

Jetzt dauert die Ausführung Ihres Programms 20x länger.

So funktioniert const fn überhaupt nicht!

Wenn Sie eine Funktion const fn deklarieren und ihr eine let -Bindung hinzufügen, wird Ihr Code nicht mehr kompiliert.

Wenn Sie const aus einem const fn entfernen, ist dies eine Breaking Change und wird alle Verwendungen dieser Funktion innerhalb von z. B. const , static oder Array-Längen unterbrechen. Ihr Code, der Laufzeitcode war und const fn , würde niemals zur Kompilierzeit ausgeführt werden. Es ist nur ein normaler Laufzeitfunktionsaufruf, er wird also nicht langsamer.

Edit: @SimonSapin ist mir zuvorgekommen :D

Const fn wird nach Möglichkeit zur Kompilierzeit ausgewertet.

Das ist,

const fn random() -> i32 {
    4
}

fn thing() -> i32 {
    let i = random(); // the RHS of this binding is evaluated at compile-time, there is no call to random at runtime.
}

Nehmen wir nun an, Sie haben eine Konstante fn, die Argumente akzeptiert. Dies würde zur Kompilierzeit ausgewertet werden:

fn thing() {
    let x = const_fn_with_1_arg(const_fn_returns_value());
}

Dies würde dazu führen, dass const_fn_with_1_arg zur Laufzeit ausgewertet wird:

fn thing() {
    let x = const_fn_returns_value();
    let y = const_fn_with_1_arg(x); // suddenly your program takes 20x longer to run, and compiles 20x faster.
}

@eddyb Ich frage mich, ob das Designproblem durch die Beobachtung gelöst werden kann, dass "minimal const fn" mit allen potenziellen zukünftigen Erweiterungen aufwärtskompatibel ist? Das heißt, ich verstehe, dass wir uns stabilisieren wollen

Freie Funktionen und inhärente Methoden werden als konstant markiert, sodass sie in konstanten Kontexten mit konstanten Argumenten aufgerufen werden können.

Dies scheint trivialerweise vollständig aufwärtskompatibel mit allen "const effects for traits"-Designs zu sein. Es ist auch kompatibel mit dem "inferred const"-Design, da wir const später optional machen können.

Gibt es alternative zukünftige Designs, die mit dem aktuellen Vorschlag "minimal const fn" nicht kompatibel sind?

@nrc

Beachten Sie, dass rfcbot Ihr everything-const -Anliegen nicht registriert hat (ein Anliegen pro Kommentar!). vollständig mit allem kompatibel ist, könnten wir das Schlüsselwort const in Zukunft optional machen).

Was die Priorität/Fallout-Bedenken betrifft, möchte ich dokumentieren, was wir in allen Händen besprochen haben und was wir noch nicht dokumentiert haben:

  • const fn foo(x: i32) -> i32 { body } ist eine relativ geringfügige Ergänzung zu const FOO: i32 = body; , daher ist das Risiko eines Fallouts gering. Das heißt, der größte Teil des Codes, der const fn tatsächlich implementiert, arbeitet bereits hart im Stable-Compiler (Haftungsausschluss: Dies habe ich von @oli-obk gehört, ich habe möglicherweise falsch gehört).

  • Eingebettete Arbeitsgruppe will unbedingt const fn :)

Beachten Sie außerdem, dass das Nichtstabilisieren const fn zu einer Verbreitung von suboptimalen APIs in den Bibliotheken führt, da sie Tricks wie ATOMIC_USIZE_INIT verwenden müssen, um das Fehlen von konstanten fns zu umgehen.

lassen Sie i = zufällig (); // Die RHS dieser Bindung wird zur Kompilierzeit ausgewertet, es gibt keinen Aufruf von random zur Laufzeit.

Nein, das passiert überhaupt nicht. Es könnte passieren (und llvm tut dies wahrscheinlich), aber Sie können nicht erwarten, dass Compiler-Optimierungen, die von Heuristiken abhängen, tatsächlich stattfinden. Wenn Sie möchten, dass etwas zur Kompilierzeit berechnet wird, stecken Sie es in ein const und Sie erhalten diese Garantie.

also wird const fn nur in einem const ausgewertet, und das ist sonst im Grunde nutzlos?

warum dann nicht const und non-const fn strikt trennen?

Sehen Sie, die Semantik ist ein Durcheinander, weil sie absichtlich Kompilierzeit- und Laufzeit-Zeug verwechseln.

also wird const fn nur in einem const ausgewertet, und das ist sonst im Grunde nutzlos?

Sie sind nicht nutzlos, sie werden wie jede andere Funktion zur Laufzeit ausgeführt. Das bedeutet, dass Sie nicht verschiedene "Untersprachen" von Rust verwenden müssen, je nachdem, ob Sie sich in einer konstanten Auswertung befinden oder nicht.

warum dann nicht const und non-const fn strikt trennen?

Die ganze Motivation für const fn ist, diese Trennung nicht zu haben. Andernfalls müssten wir alle Arten von Funktionen duplizieren: AtomicUsize::new() + AtomicUsize::const_new() , obwohl beide Körper identisch sind.

Wollen Sie wirklich 90 % von libcore zweimal schreiben müssen, einmal für const eval und einmal für die Laufzeit? Dasselbe gilt wahrscheinlich für viele andere Kisten.

Ich dachte AtomicUsize::Of<value> . Und ja, ich muss lieber alles doppelt schreiben, als unbekannte Garantien zu haben. (Außerdem würde sich dies nicht anders verhalten, je nachdem, ob etwas konstant ausgewertet wird oder nicht.)

Können Sie consts in const fn deklarieren, um eine konstante Auswertung zu gewährleisten (für rekursive const fn)? Oder müssen Sie Const-Generika durchgehen? Usw.

@SoniEx2 als Beispiel dafür, wie Ihr Beispiel geschrieben werden sollte, um const fn zu nutzen und zu einem Kompilierungsfehler zu werden, wenn eine der Funktionen nicht const wird:

fn thing() {
    const x: u32 = const_fn_returns_value();
    const y: u32 = const_fn_with_1_arg(x);
}

(Volllaufbeispiel auf Spielplatz)

Etwas weniger ergonomisch, da kein Typschluss vorhanden ist, aber wer weiß, vielleicht ändert sich das in Zukunft.

als unbekannte Garantien zu haben.

würden Sie so freundlich sein und einige Beispiele nennen, wo Ihrer Meinung nach etwas unklar ist?

Können Sie consts in const fn deklarieren, um eine konstante Auswertung zu gewährleisten (für rekursive const fn)? Oder müssen Sie Const-Generika durchgehen? Usw.

Der Sinn von const fn besteht nicht darin, Dinge zur Kompilierzeit auf magische Weise auszuwerten. Es soll in der Lage sein, Dinge zur Kompilierzeit auszuwerten.

Die magische Bewertung von Dingen zur Kompilierungszeit findet bereits statt, seit rustc auf llvm basierte. Also ... genau als es aufgehört hat, in ocaml implementiert zu werden. Ich glaube nicht, dass irgendjemand die ständige Ausbreitung von rustc entfernen möchte.

const fn beeinflusst die konstante Ausbreitung in keiner Weise. Wenn Sie eine Funktion hatten, die versehentlich konstant weitergegeben werden konnte, und llvm dies tat, und Sie diese Funktion so geändert haben, dass sie nicht mehr konstant weitergegeben werden kann, würde llvm damit aufhören. Dies ist völlig unabhängig vom Anhängen von const an eine Funktion. Für llvm gibt es keinen Unterschied zwischen const fn und fn .

Gleichzeitig ändert rustc sein Verhalten überhaupt nicht, wenn Sie const an fn anhängen (vorausgesetzt, die Funktion ist eine gültige Konstante fn und wird daher auch danach noch kompiliert). Ab sofort können Sie diese Funktion nur noch in Konstanten aufrufen.

Ich denke nicht an LLVM, ich denke an Rost. LLVM spielt für mich hier keine Rolle.

@SoniEx2

Const fn wird nach Möglichkeit zur Kompilierzeit ausgewertet.

Das ist nicht richtig. const fn wird, wenn es in einem bestimmten Kontext aufgerufen wird, zur Kompilierzeit ausgewertet. Wenn das nicht möglich ist, liegt ein harter Fehler vor. In allen anderen Kontexten spielt der Teil const überhaupt keine Rolle.

Beispiele für solche Kontexte, die const fn erfordern, sind Array-Längen. Sie können [i32; 15] schreiben. Sie können auch [i32; 3+4] schreiben, da der Compiler die 7 berechnen kann. Sie können nicht [i32; read_something_from_network()] schreiben, denn wie würde das irgendeinen Sinn machen? Mit diesem Vorschlag KÖNNEN Sie [i32; foo(15)] schreiben, wenn foo const fn ist, wodurch sichergestellt wird, dass es sich eher um eine Addition und weniger um einen Zugriff auf das Netzwerk handelt.

Hier geht es überhaupt nicht um Code, der ausgeführt werden kann oder wird, wenn das Programm ausgeführt wird. Es gibt kein "Vielleicht zur Kompilierzeit auswerten". Es gibt nur "muss zur Kompilierzeit auswerten oder Kompilierung abbrechen".

Bitte lesen Sie auch den RFC: https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md

Was wäre, wenn es sich statt einer const fn -Anmerkung um eine abgeleitete Eigenschaft handeln würde? Es wäre im Quellcode nicht explizit, könnte aber in der automatisch generierten Dokumentation automatisch als solches gekennzeichnet werden. Dies würde eine eventuelle Erweiterung dessen ermöglichen, was als const betrachtet wird, ohne dass Bibliotheksautoren ihren Code ändern müssten. Zunächst könnte die Inferenz auf das beschränkt werden, was const fn derzeit unterstützt (reine, deterministische Funktionen ohne let-Bindungen?).

Bei diesem Ansatz würde die Auswertung zur Kompilierzeit erfolgen, wenn das Ergebnis an eine const -Variable gebunden ist, andernfalls zur Laufzeit. Dies erscheint wünschenswerter, da es dem Aufrufer (und nicht dem Aufgerufenen) die Kontrolle darüber gibt, wann die Funktion ausgewertet wird.

Darüber wurde schon ziemlich ausführlich diskutiert. Der Nachteil dieses Ansatzes besteht darin, dass es einfacher ist, versehentlich in die andere Richtung zu gehen - jemand verwendet möglicherweise eine Bibliotheksfunktion in einem const -Kontext, aber dann macht der Bibliotheksautor sie möglicherweise nicht mehr const ohne es zu merken.

Hmm, ja das ist ein Problem...

Bearbeiten: Die einzige Lösung, die mir einfällt, ohne bis zu const fn zu gehen, wäre eine Opt-out-Anmerkung, damit der Autor der Bibliothek sich das Recht vorbehalten kann, const zu brechen ness. Ich bin mir nicht sicher, ob das besser ist, als überall const fn zu streuen. Der einzige wirkliche Vorteil wäre eine schnellere Übernahme einer erweiterten Definition von const .

Bearbeiten 2: Ich nehme an, das würde die Abwärtskompatibilität beeinträchtigen, also ist es kein Starter. Entschuldigung für die Nebenspur.

Also... die Diskussion hat sich gelegt. Fassen wir zusammen:

Der RFCbot-Kommentar lautet https://github.com/rust-lang/rust/issues/24111#issuecomment -376649804

Aktuelle Bedenken

  • Dies hat keine Priorität, und die Veröffentlichung von 2018 hat bereits genug auf unsere Teller gebracht
  • Wir fangen an, alles const zu markieren, was ärgerlich ist
  • Ist dies das Design, dem wir uns verpflichten wollen?

Ich kann nicht wirklich über die Prioritätssache + Fallout-Bedenken sprechen, außer dass const fn jede Nacht eine lange, lange Zeit eingebrannt hat

Die beiden anderen Punkte hängen eng zusammen. Das Design von @eddyb (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588 wie ich es verstanden habe) ist, nicht const fn zu haben, sondern stattdessen #[const] -Attribut, das Sie auf Dinge schlagen können:

#[const]
mod foo {
    pub fn square(i: i32) -> i32 { i * i }
}
#[const]
fn bar(s: &str) -> &str i{ s }
#[const]
fn boo() -> fn(u32) -> u32 { meh }
fn meh(u: u32) -> u32 { u + 1 }

und das geht rekursiv in alles, was damit markiert ist, also ist jede Funktion innerhalb eines #[const] Moduls ein #[const] fn . Eine innerhalb von #[const] fn deklarierte Funktion ist auch ein #[const] fn .

Dies verringert die Anzahl der erforderlichen Anmerkungen, da einige Kisten einfach #![const] in die lib.rs schlagen und damit fertig sind.

Probleme, die ich bei diesem Design sehe (aber diese Probleme gibt es auch oft in const fn ):

  • erfordern möglicherweise eine Opt-out-Unterstützung, da Sie möglicherweise in der Lage sein möchten, einige wenige nicht-konstante Funktionen tief in einem ansonsten #[const] -Modulbaum zu deklarieren.
  • Müssen Funktionszeiger auf #[const] fn in der Position des Argument-/Rückgabetyps #[const] fn sein?

    • Auch hier wäre ein Opt-out erforderlich

Wir müssen über diese Dinge nachdenken, also entwerfen wir kein System, das mit einer zukünftigen Version inkompatibel sein wird, in der wir in der Lage sein wollen, während einer const eval Funktionen über Funktionszeiger aufzurufen.

Beachten Sie, dass ich kein bestimmtes Design vorgeschlagen habe, sondern lediglich einige bekannte plausible Richtungen aufgelistet habe.
Die ursprüngliche Idee war ein verallgemeinertes Attribut „Expose Body of Function“, das nicht auf const beschränkt ist, aber es gibt viele mögliche Variationen, und einige davon könnten sogar gut sein .

EDIT : (will das nicht vergessen) @solson hat mir gezeigt, wie Lean Attribute wie @pattern hat, die automatisch verschiedene Dinge aus dem Körper einer Funktion ableiten.

@oli-obk Ich denke, wir sollten nicht mit Attributen arbeiten, weil unsafe kein Attribut verwendet.
Auch async tut dies derzeit nicht. Und wenn wir try fn gegebenen try { .. } Blöcken einführen, dann haben wir eine andere Sache, die nicht attributbasiert ist. Ich denke, wir sollten versuchen, Dinge, die effektähnlich sind, so konsistent wie möglich zu halten. Attribute verwenden oder nicht. #[target_feature(..)] bringt allerdings eine Falte in die Gesamtkonsistenz.

PS: Sie könnten const mod { .. } verwenden, um mehr oder weniger den gleichen Effekt wie #![const] zu erzielen. Dies könnte auch für try mod , async mod , unsafe mod gelten.

Ich werde immer dazu tendieren, Dinge mit besonderen Typen zu machen.

struct SizeOf<T>;

impl<T> SizeOf<T> {
    const intrisic Result: usize;
}

Es ist einfacher, neue Typen mit vorhandener Syntax zu lernen, als neue Konzepte mit neuer Syntax zu lernen.

und wir können das Typsystem später zur Laufzeit unterstützen.

fn sq(v: i32) -> i32 {
    Square<v>::Result
}

Typen zur Kompilierzeit, const Generika entweder zur Kompilierzeit oder zur Laufzeit.

Also ... ich schlage vor, die Tatsache zu ignorieren, dass es möglicherweise ein besseres Design gibt, weil wir ein Design haben, das ist

  1. Leicht zu begründen
  2. Aufwärtskompatibel zu freizügigeren Designs
  3. Wurde in der Vergangenheit mit großem Erfolg in instabilem Code verwendet, wobei die Hauptbeschwerde darin bestand, dass nicht genügend Funktionen vorhanden waren.
  4. Kann lintiert werden, um vorzuschlagen, die Anmerkung zu noch nicht kommentierten Funktionen hinzuzufügen, die einen Körper haben, der das Hinzufügen erlaubt.
  5. Ich persönlich sehe, wie in der Merge-Anfrage gepostet, das einzige Design, das wir ohne eine weitere mehrjährige Testphase stabilisieren können.
  6. Ermöglicht die gemeinsame Nutzung von Code zwischen Laufzeit- und konstantem Evaluierungscode, anstatt dass jeweils benutzerdefinierter Code erforderlich ist

und wir können das Typsystem später zur Laufzeit unterstützen.

Das ist eine abhängige Eingabe, die weit entfernt ist , während das Aufrufen von const fn zur Laufzeit heute problemlos funktioniert.

@oli-obk Was ist mit Eigenschaften? Ich möchte const fn nicht stabilisieren, ohne eine Vorstellung davon zu haben, was wir für Merkmalsmethoden tun werden, die const fn nur in einigen der impl s des Merkmals enthalten sind.

@eddyb scheint, dass ich das Schreiben der neuen konstanten Grenzen und Methoden dann beschleunigen sollte. :)

@Centril Mein Punkt ist, dass der Attributvorschlag (unabhängig davon, ob ein Schlüsselwort verwendet wird oder nicht) zu einem viel unterschiedlicheren Ansatz für den Umgang mit Merkmalsmethoden führen würde, und wir müssen das vergleichen .
Der aktuelle const fn -Ansatz mag einfach und erweiterbar erscheinen, aber nicht , wenn er tatsächlich erweitert wird.

generische Konstanten + konstante Generika:

intrinsic const SizeOf<T>: usize;

const Pow<const V: usize>: usize = V*V;

@eddyb Ich habe verschiedene Lösungen im Sinn, die vollständig mit dem const fn-Design kompatibel sind. Ich werde es aufschreiben.

Woah, ich habe gerade gesehen, dass es zwei Jahre her ist, die begonnen haben. Gibt es ein voraussichtliches Datum der Stabilisierung? Ich habe eine Kiste, die fast auf Stable verfügbar ist, weil sie darauf wartet, dass diese Erweiterung stabilisiert wird. :)

@rfcbot betrifft Laufzeitzeigeradressen

Bei einem anderen Thema tauchte die Frage auf, ob wir referenzielle Transparenz von const fn wollen, und das Problem, dass die Adressen von Rohzeigern als nicht deterministisches Orakel verwendet werden, tauchte auf: https://github.com/rust- lang/rust/issues/49146#issuecomment -386727325. Dort wird eine Lösung skizziert, aber sie beinhaltet die Durchführung einiger roher Zeigeroperationen unsafe (nicht sicher, wie viele davon heute überhaupt erlaubt sind), bevor die Stabilisierung erfolgt.

@eddyb Würde E0018 nicht auch für const fn s gelten?

Der C-Weg ist, dass Objektzeiger alle 0 sein dürfen, es sei denn, sie sind relativ (dh innerhalb eines Objekts) und werden zur Laufzeit irgendwie verfolgt.

Ich bin mir nicht sicher, ob Rust die Aliasing-Regeln von C unterstützt.

@sgrif Viele der Fehler, die über Konstanten ausgegeben werden, werden früher oder später verschwinden - miri ist es egal, als welcher Typ ein Wert gesehen wird, ein abstrakter Ort, der sich in einem usize -Wert befindet, ist immer noch ein abstrakter Ort (und das Umwandeln in einen Zeiger gibt Ihnen den ursprünglichen Zeiger zurück).

Ich habe gerade nachgesehen und vorerst sind sowohl das Umwandeln von Zeigern in Ganzzahlen als auch Vergleichsoperatoren zwischen Zeigern in konstanten Kontexten verboten. Aber das ist genau das, woran wir gedacht haben, ich habe immer noch Angst.

@eddyb Fair genug. Ich würde jedoch erwarten, dass alle Ihre Bedenken, die const fn getroffen haben, heute bereits alle const -Blöcke getroffen haben.

@sgrif Der Unterschied besteht darin, dass const (selbst zugehörige const s, die von generischen Typparametern abhängen) zur Kompilierzeit unter miri vollständig ausgewertet werden, während const fn ein Nicht- const fn für Laufzeitaufrufe.
Wenn wir also wirklich referenzielle Transparenz wollen, müssen wir sicherstellen, dass wir (zumindest in sicherem Code) keine Dinge zulassen, die Laufzeit-Nichtdeterminismus verursachen können, selbst wenn sie unter miri in Ordnung sind .
Was wahrscheinlich bedeutet, dass es auch ein Problem ist, die Float-Bits zu bekommen, weil zB NaN-Payloads.

Dinge, die Sie in miri in Betracht ziehen sollten:

Alle Zeiger sind 0, sofern sie nicht relativ sind. Z.B:

#[repr(C)]
struct X {
    a: usize,
    b: u8,
}
let x = X { a: 1, b: 2 };
let y: usize = 3;
assert_eq!(&x as *const _ as usize, 0);
assert_eq!(&x.a as *const _ as usize, 0);
assert_eq!(&x.b as *const _ as usize, 8);
assert_eq!(&y as *const _ as usize, 0);

Dann verfolgst du sie bei miri Auswertung. Einige Dinge wären UB, wie das Wechseln von Zeiger zu Verwendung zurück zu Zeiger, aber diese sind leicht zu verbieten (Konvertierungen von Verwendung zu Zeiger zu verbieten, da Sie Zeiger bereits zur Laufzeit/Evaluierungszeit verfolgen würden).

Wie für Float-Bits, NaN-Normalisierung?

Ich würde denken, dass beides das Ganze deterministisch machen würde.

Was wahrscheinlich bedeutet, dass es auch ein Problem ist, die Float-Bits zu bekommen, weil zB NaN-Payloads.

Ich denke, dass wir für vollständige referenzielle Transparenz alle Float-Operationen unsicher machen müssten.

Gleitkomma-Determinismus ist schwierig

Der wichtigste Punkt hier ist, dass der Optimierer von LLVM die Reihenfolge von Float-Operationen ändern kann und dies auch tut, sowie Fuse-Operationen ~ausführt~, für die er einen kombinierten Opcode hat. Diese Änderungen wirken sich auf das Ergebnis der Operation aus, auch wenn der tatsächliche Unterschied gering ist. Dies wirkt sich auf die referenzielle Transparenz aus, da miri das nicht für llvm optimierte mir ausführt, während das Ziel den für llvm optimierten und möglicherweise neu geordneten Code ausführt und daher eine andere Semantik als nativer Code hat.

Ich würde vorerst einem ersten stabilen const fn-Feature ohne Floats zustimmen, bis eine Entscheidung darüber vorliegt, wie wichtig referentielle Transparenz ist, möchte aber nicht, dass const fn durch diese Diskussion verlangsamt oder blockiert wird.

Der wichtigste Punkt hier ist, dass der Optimierer von LLVM die Reihenfolge von Gleitkommaoperationen ändern kann und tut, sowie Sicherungsoperationen durchführt, für die er einen kombinierten Opcode hat. Diese Änderungen wirken sich auf das Ergebnis der Operation aus, auch wenn der tatsächliche Unterschied gering ist.

Fusionsoperationen (ich nehme an, Sie beziehen sich auf mul-add) sind ohne Fast-Math-Flags nicht erlaubt / nicht ausgeführt, gerade weil sie die Rundung der Ergebnisse ändern. LLVM ist sehr darauf bedacht, die exakte Semantik von Gleitkommaoperationen bei Zielen auf IEEE-konforme Hardware innerhalb der durch die "Standard-Gleitkommaumgebung" festgelegten Grenzen beizubehalten.

Zwei Dinge, die LLVM nicht zu bewahren versucht, sind NaN-Nutzlasten und die Signalisierung von NaNs, da beides in der Standard-FP-Umgebung nicht beobachtet werden kann – nur durch Untersuchen der Float-Bits, die wir daher verbieten müssten. (Und selbst wenn LLVM diesbezüglich vorsichtiger war, variiert die Hardware auch in der Behandlung von NaN-Nutzlasten, sodass Sie dies nicht an LLVM anheften können.)

Der andere wichtige Fall, den ich kenne, wo die Entscheidung des Compilers einen Unterschied für Gleitkommaergebnisse machen kann, ist die Position von Überläufen und Neuladungen in x87 -Code (vor SSE). Und das ist meistens ein Problem, weil x87 standardmäßig auf 80 Bit für Zwischenergebnisse und auf 32 oder 64 Bit in Stores rundet. Es ist möglich, den Rundungsmodus vor jeder FPU-Anweisung richtig einzustellen, um korrekt gerundete Ergebnisse zu erzielen, aber es ist nicht wirklich praktisch und wird daher (glaube ich) von LLVM nicht unterstützt.

Auf vollständiger referenzieller Transparenz

Meiner Ansicht nach sollten wir mit vollständiger referenzieller Transparenz vorgehen, da dies mit der allgemeinen Rust-Botschaft übereinstimmt, Sicherheit/Korrektheit über Bequemlichkeit/Vollständigkeit zu stellen.

Die referenzielle Transparenz fügt viele Argumentationsvorteile hinzu, z. B. das Ermöglichen von Gleichungsdenken (bis zum Boden, aber schnelles und lockeres Denken ist moralisch korrekt ).

Allerdings gibt es natürlich auch Nachteile bzgl. an Vollständigkeit verlieren bzgl. CTFE. Damit meine ich, dass ein referenziell transparenter const fn -Mechanismus zur Kompilierzeit nicht so viel auswerten könnte wie ein nicht-ref-transparentes const fn -Schema. Diejenigen, die vorschlagen, nicht an der referenziellen Transparenz festzuhalten, möchte ich bitten, dass sie so viele konkrete Anwendungsfälle wie möglich gegen diesen Vorschlag liefern, damit wir die Kompromisse bewerten können.

Okay, Sie scheinen Recht mit dem LLVM-Punkt zu haben, es scheint tatsächlich rechnerisch falsche Operationen zu vermeiden, es sei denn, Sie aktivieren den schnellen Mathematikmodus.

Es gibt jedoch immer noch eine Reihe von Zuständen , von denen Gleitkommaoperationen wie die interne Genauigkeit abhängen. Kennt der CTFE Float-Evaluator den internen Genauigkeitswert zur Laufzeit?

Außerdem konvertieren wir beim Übertragen von Werten in den Speicher den internen Wert in das ieee754-Format und ändern somit die Genauigkeit. Dies kann sich auch auf das Ergebnis auswirken, und der Algorithmus, mit dem Compiler Spilling durchführen, ist nicht angegeben, oder?

@est31 Beachten Sie, dass ich davon ausgegangen bin, dass es uns egal ist, ob sich das Verhalten zur Kompilierzeit und zur Laufzeit unterscheidet, sondern dass wiederholte Aufrufe (mit eingefrorenen Objektdiagrammen) konsistent und global frei von Nebenwirkungen sind.

Wenn wir also wirklich referenzielle Transparenz wollen, müssen wir sicherstellen, dass wir (zumindest in sicherem Code) keine Dinge zulassen, die Laufzeit-Nichtdeterminismus verursachen können, selbst wenn sie unter miri in Ordnung sind.

Das Ziel hier ist also zu garantieren, dass ein const fn zur Laufzeit deterministisch und nebenwirkungsfrei ist, selbst wenn miri während der Ausführung einen Fehler machen würde , zumindest wenn das const fn vollständig sicher ist Code? Ich habe das nie für wichtig gehalten, TBH. Das ist eine ziemlich starke Anforderung, und tatsächlich müssten wir dann zumindest ptr-to-int und float-to-bits unsicher machen, was schwer zu erklären sein wird. OTOH, dann sollten wir auch den Raw-Pointer-Vergleich unsicher machen und darüber würde ich mich freuen :D

Was ist die Motivation dafür? Scheint, als würden wir versuchen, Reinheit und ein Effektsystem wieder einzuführen, die Rust einmal hatte und verloren hat, vermutlich weil sie ihr Gewicht nicht getragen haben (EDIT: oder weil es einfach nicht flexibel genug war, um all die anderen zu tun Dinge, für die die Leute es verwenden wollten, siehe https://mail.mozilla.org/pipermail/rust-dev/2013-April/003926.html).

ptr-to-int ist sicher, wenn es vom Anfang eines Objekts konvertiert wird und Sie zulassen, dass int-to-ptr fehlschlägt. es wäre 100% deterministisch. und jeder Aufruf würde die gleichen Ergebnisse liefern.

ptr-to-int ist sicher, int-to-ptr ist unsicher. ptr-to-int sollte "unerwartete" Ergebnisse bei der konstanten Auswertung zulassen.

und Sie sollten wirklich die NaN-Kanonisierung in einem Interpreter / einer VM verwenden. (Ein Beispiel dafür ist LuaJIT, das alle NaN-Ergebnisse zum kanonischen NaN macht und jedes andere NaN zu einem gepackten NaN)

ptr-to-int ist sicher, wenn es vom Anfang eines Objekts konvertiert wird und Sie zulassen, dass int-to-ptr fehlschlägt. es wäre 100% deterministisch. und jeder Aufruf würde die gleichen Ergebnisse liefern.

Wie schlagen Sie vor, dass wir dies zur Laufzeit implementieren? (&mut *Box::new(...)) as usize ist im Moment eine nicht deterministische Funktion und sollte alle const fn sein. Wie schlagen Sie also vor, dass wir sicherstellen, dass es "zur Laufzeit referenziell transpent" ist, in dem Sinne, dass immer derselbe Wert zurückgegeben wird?

Box::new sollte einen nachverfolgten Zeiger in der VM zurückgeben.

Die Konvertierung würde zur Kompilierzeit (dh in einer konstanten Auswertung) zu 0 führen. Bei einer nicht konstanten Auswertung würde es alles zurückgeben.

Ein verfolgter Zeiger funktioniert wie folgt:

Sie weisen einen Zeiger zu und weisen ihn zu. Wenn Sie ihn zuweisen, setzt die VM den Zeigerwert auf 0, nimmt jedoch die Speicheradresse des Zeigers und hängt sie an eine Nachschlagetabelle an. Wenn Sie den Zeiger verwenden, gehen Sie durch die Nachschlagetabelle. Wenn Sie den Wert des Zeigers erhalten (dh die Bytes, aus denen er besteht), erhalten Sie 0. Angenommen, es ist der Basiszeiger eines Objekts.

Box::new sollte einen nachverfolgten Zeiger in der VM zurückgeben.

Wir sprechen hier vom Laufzeitverhalten. Wie in, was passiert, wenn dies in der Binärdatei ausgeführt wird. Es gibt keine VM.

miri kann das alles schon prima und vollkommen deterministisch handhaben.


Um auch auf die Besorgnis über const fn und Laufzeitdeterminismus zurückzukommen: Wenn wir das wollen (was ich nicht sicher bin, da ich immer noch darauf warte, dass mir jemand erklärt, warum es uns interessiert :D ), könnten wir es einfach erklären ptr-to-int und float-to-bits müssen nicht-konstante Operationen sein. Gibt es ein Problem damit?

@RalfJung Ich habe https://github.com/rust-lang/rust/issues/49146#issuecomment -386727325 bereits im Anliegenkommentar verlinkt, vielleicht ist das unter anderen Meldungen untergegangen - darin ist eine Beschreibung einer von @Centril vorgeschlagenen Lösung enthalten ( make operations unsafe und ein paar "korrekte Verwendungen" auf die Whitelist setzen, zB offset_of für ptr-to-int und NaN-normalized float-to-bits).

@eddyb sicher, es gibt verschiedene Lösungen; Was ich verlange, ist die Motivation. Ich habe es dort auch nicht gefunden.

Ich zitiere mich auch selbst aus dem IRC: Ich denke, wir können sogar zu Recht argumentieren, dass CTFE deterministisch ist , selbst wenn ptr-to-int-Cast erlaubt ist. Es ist immer noch Nicht-CTFE-Code erforderlich (z. B. Extrahieren von Bits eines ptr-Casts in ein int), um tatsächlich einen Nicht-Determinismus zu beobachten.

const fn thing() -> usize {
    (*Box::new(0)) as *const _ as usize
}

const X: usize = thing();
const Y: usize = thing();

ist X == Y?

mit meinem Vorschlag, X == Y.

@SoniEx2 , das foo as *const _ as usize as *const _ bricht, was im Moment absolut ein Noop ist

Wrt Motivation:
Ich persönlich sehe kein Problem damit, dass sich das Laufzeitverhalten vom Kompilierzeitverhalten unterscheidet oder sogar const fn zur Laufzeit nicht deterministisch ist. Sicherheit ist hier nicht das Thema, also unsicher ist imo das völlig falsche Stichwort. Dies ist nur ein überraschendes Verhalten, das wir bei der Auswertung zur Kompilierzeit vollständig abfangen. Sie können dies bereits in normalen Funktionen tun, also keine Überraschungen. Bei Const fn geht es darum, vorhandene Funktionen zur Kompilierzeit als auswertbar zu markieren, ohne die Laufzeit zu beeinträchtigen. Es geht nicht um Reinheit, zumindest ist das die Stimmung, die ich bekomme, wenn Leute über die "reine Hölle" sprechen (ich war nicht da, also interpretiere ich es vielleicht falsch)

@SoniEx2 Ja, X == Y immer.

Wenn Sie jedoch Folgendes haben:

const fn oracle() -> bool { let x = 0; let y = &x as *const _ as usize; even(y) }

fn main() {
    assert_eq!(oracle(), oracle());
}

Dann kann main zur Laufzeit panisch werden.

@ Ralf Jung

oder weil es einfach nicht flexibel genug war, um all die verschiedenen Dinge zu tun, für die die Leute es verwenden wollten [..]

Was waren das für Dinge? Ohne Konkretisierung ist dies schwer zu beurteilen.

Ich denke, wir können sogar zu Recht argumentieren, dass CTFE deterministisch ist, selbst wenn ptr-to-int-Cast erlaubt ist.
[...]
Es ist immer noch Nicht-CTFE-Code erforderlich (z. B. Extrahieren von Bits eines ptr-Casts in ein int), um tatsächlich einen Nicht-Determinismus zu beobachten.

Ja, const fn ist immer noch deterministisch, wenn es zur Kompilierzeit ausgeführt wird, aber ich glaube nicht, dass es zur Laufzeit deterministisch ist, weil es immer einige Nicht- const fn geben wird (nur fn ) Code, wenn das Ergebnis von const fn zur Laufzeit für irgendetwas nützlich sein soll, und dann wird dieser fn Code Seiteneffekte von dem ausgeführten const fn beobachten.

Der springende Punkt bei der Trennung zwischen reinem und nicht reinem Code in Haskell ist, dass Sie die Entscheidung „soll es Nebenwirkungen geben“ lokal treffen können, ohne dass Sie global über mögliche Nebenwirkungen nachdenken müssen. Das heißt, gegeben:

reverse :: [a] -> [a]
reverse []       = []
reverse (x : xs) = reverse xs ++ [x]

putStrLn :: String -> IO ()
getLine :: IO String

main :: IO ()
main = do
    line <- getLine
    let revLine = reverse line
    putStrLn revLine

Sie wissen, dass das Ergebnis von revLine in let revLine = reverse line nur von dem Zustand abhängen kann, der an reverse übergeben wird, was line ist. Dies bietet Argumentationsvorteile und eine saubere Trennung.

Ich frage mich, wie das Effektsystem damals aussah ... Ich denke, wir brauchen sowieso ein gegebenes const fn , async fn usw., um es sauber zu machen und Code wiederzuverwenden (etwas https:// github.com/rust-lang/rfcs/pull/2237 aber mit einigen Änderungen ...) und Parametrik scheint dafür eine gute Idee zu sein.

In den unsterblichen Worten von Phil Wadler und Conor McBride:

Soll ich rein oder unrein sein?
—Philipp Wadler [60]

Wir sagen „Ja“: Reinheit ist eine Entscheidung, die vor Ort getroffen werden muss

https://arxiv.org/pdf/1611.09259.pdf

@oli-obk

Ich beneide die Person nicht, die dieses überraschende Verhalten dokumentieren muss, dass das Ergebnis der Ausführung für const fn unterschiedlich sein kann, wenn es zur Laufzeit oder zur Kompilierzeit mit den gleichen Argumenten ausgeführt wird.

Als konservative Option schlage ich vor, dass wir die Entscheidung über referenzielle Transparenz/Reinheit verzögern, indem wir const fn als referenziell transparent stabilisieren, wodurch es unsafe (oder nicht- const ) verletzt wird es, aber wir garantieren nicht wirklich referenzielle Transparenz, also können die Leute es auch nicht annehmen.

Zu einem späteren Zeitpunkt, wenn wir Erfahrungen gesammelt haben, können wir dann die Entscheidung treffen.
Das Sammeln von Erfahrungen umfasst Menschen, die versuchen, den Nichtdeterminismus zu nutzen, aber scheitern, und dies dann melden und uns von ihren Anwendungsfällen erzählen.

Ja, aber das ist keine konstante Bewertung.

Das Konvertieren von int in ptr in const scheint kein großer Verlust zu sein. Das Erhalten von Feld-Offsets scheint wichtiger zu sein, und das versuche ich zu bewahren.

Ich möchte das Verhalten nicht stillschweigend ändern, wenn ich const zu einer Funktion @SoniEx2 hinzufüge , und das ist im Wesentlichen das, was Ihr Vorschlag tun würde.

@centril Ich stimme zu, lass uns jetzt das konservative Ding machen. also machen wir diese nicht unsicher, sondern instabil. Auf diese Weise kann die libstd es verwenden, aber wir können später über die Details entscheiden.

Ein Problem, das wir haben, ist, dass Benutzer jede Analyse, die wir durchführen, jederzeit blockieren können, indem sie Unions einführen und einige ausgefallene Konvertierungen durchführen.

es wäre zur Laufzeit immer noch dasselbe, die const würde nur Dinge zur const-Zeit ändern, was ... ohne die const nicht einmal möglich wäre.

Es ist in Ordnung, wenn const fns eine Untersprache mit unterschiedlichen Zeiger-Aliasing-Regeln ist.

@Centril @est31 Ich bin mir nicht sicher, was Chlortrifluorethylen mit irgendetwas zu tun hat (können wir es vermeiden, eine Reihe von Akronymen zu verwenden, ohne sie zu definieren, bitte?)

@sgrif CTFE (Compile-Time Function Evaluation) wird seit langem für Rust verwendet (auch in den jahrelangen Teilen dieses Threads). Es ist einer dieser Begriffe, die bereits irgendwo zentral definiert werden sollten.

@sgrif lol Entschuldigung, meistens bin ich die Person, die sich fragt: "Welche seltsamen Wörter verwenden sie jetzt?". Ich glaube, ich habe mit @eddyb im IRC gesprochen, als er "CTFE" erwähnte, und ich musste ihn fragen, was es bedeutete: p.

@eddyb ctrl+f + CTFE auf dieser Seite führt zu nichts, was es definiert. So oder so, ich gehe meistens nur davon aus, dass wir die Leute nicht zwingen wollen, viel zu graben, um hier an Diskussionen teilzunehmen. "Sie sollten bereits wissen, was das bedeutet" ist meiner Meinung nach ziemlich ausschließend.

@Centril @est31 danke :herz:

Also ... irgendwelche Gedanken darüber

Ein Problem, das wir haben, ist, dass Benutzer jede Analyse, die wir durchführen, jederzeit blockieren können, indem sie Unions einführen und einige ausgefallene Konvertierungen durchführen.

Sollten Zugriffe auf Union-Felder innerhalb von const fn daher auch instabil sein?

ctrl+f + CTFE auf dieser Seite führt zu nichts, was es definiert.

Tut mir leid, was ich damit meinte, ist, dass seine Verwendung in Rusts Design und Entwicklung vor dieser Ausgabe vor Version 1.0 liegt.
Es sollte ein zentrales Dokument für solche Begriffe geben, aber leider fehlt das Glossar der Referenz wirklich .
HRTB und NLL sind zwei Beispiele für andere Akronyme, die neuer sind, aber ebenfalls nicht inline erklärt werden.

Ich möchte keine ausschließenden Diskussionen, aber ich denke nicht, dass es fair ist, entweder @Centril oder @est31 zu bitten, CTFE zu definieren, da keiner von ihnen das Akronym überhaupt eingeführt hat.
Es gibt eine Lösung, die leider nicht unbedingt auf GitHub funktionieren würde, die ich in einem bestimmten Subreddit gesehen habe, wo ein Bot einen Kommentar der obersten Ebene erstellt und ihn aktualisiert , mit einer Liste von Erweiterungen für alle häufig verwendeten Akronyme dieses Subreddits in der Diskussion auftaucht.

Außerdem bin ich gespannt, ob ich mich in einer Google-Blase befinde, da die beiden Wikipedia-Artikel für CTFE ("Chlorotrifluoroethylene" und "Compile Time Function Execution") die ersten beiden Ergebnisse für mich sind. Selbst dann beginnt Ersteres mit "Für das Compiler-Feature siehe Ausführung der Kompilierzeitfunktion.".

( Ich habe den CTFE-Wiki-Link oben eingefügt; können wir jetzt bitte zu const fn s zurückkehren? :P)

Könnte jemand CTFE zum Glossar des Rustc-Leitfadens hinzufügen (ich bin auf dem Handy)?

Außerdem denke ich, dass wir das Nötigste stabilisieren und sehen sollten, was in der Praxis oft auf Leute stößt, die mit konstanten if- und match-Ausdrücken den aktuellen Ansatz senden.

@ Centril

Ich beneide die Person nicht, die dieses überraschende Verhalten dokumentieren muss, dass das Ergebnis der Ausführung für const fn unterschiedlich sein kann, wenn es zur Laufzeit oder zur Kompilierzeit mit denselben Argumenten ausgeführt wird.

Das oben geschriebene even würde fehlschlagen, wenn es zur CTFE-Zeit ausgeführt würde, da es die Bits eines Zeigers überprüft. (Obwohl wir deterministisch sagen können, dass dies sogar auf die Ausrichtung zurückzuführen ist, und wenn der Gleichmäßigkeitstest über Bitoperationen durchgeführt wird, würde "full miri" dies ordnungsgemäß tun. Aber nehmen wir an, Sie testen das gesamte niederwertigste Byte, nicht nur das letztes Stück.)
Der Fall, über den wir hier sprechen, ist eine Funktion, die zur Kompilierzeit mit einem Interpreterfehler fehlschlägt, aber zur Laufzeit erfolgreich ist. Der Unterschied besteht darin, ob die Funktion die Ausführung überhaupt abschließt. Ich glaube nicht, dass das schwer zu erklären ist.

Ich stimme zu, dass, wenn CTFE mit einem Ergebnis erfolgreich ist, die Laufzeitversion der Funktion garantiert auch mit demselben Ergebnis erfolgreich sein sollte. Aber das ist eine viel schwächere Garantie als das, worüber wir hier sprechen, nicht wahr?

@oli-obk

Ich stimme zu, lass uns jetzt die konservative Sache machen. also machen wir diese nicht unsicher, sondern instabil. Auf diese Weise kann die libstd es verwenden, aber wir können später über die Details entscheiden.

Ich habe den Kontext verloren, was ist "diese" hier?

Im Moment weigert sich CTFE miri glücklicherweise geradewegs, irgendetwas mit Zeigerwerten zu tun – Arithmetik, Vergleiche, alles Fehler. Dies ist eine Überprüfung, die zur CTFE-Zeit durchgeführt wird, basierend auf den tatsächlich in der Berechnung verwendeten Werten, sie kann nicht durch Vereinigungen umgangen werden, und außerdem existiert der Code, der für die Arithmetik/den Vergleich benötigt würde, einfach nicht . Daher bin ich mir ziemlich sicher, dass wir die oben genannte Garantie erfüllen.

Ich könnte mir Probleme vorstellen, wenn CTFE einen Zeigerwert zurückgeben würde, aber wie würde ein zur Kompilierzeit berechneter Zeigerwert überhaupt irgendwo Sinn machen? Ich nehme an, wir überprüfen bereits, was Miri berechnet, um keine Zeigerwerte zu enthalten, weil wir es in Bits umwandeln müssen?

Wir könnten vorsichtig Operationen zu CTFE miri hinzufügen, und tatsächlich brauchen wir für offset_of [1] von @eddyb nur eine Zeigersubtraktion. Dieser Code existiert in "full miri" und ist nur erfolgreich, wenn sich beide Zeiger in derselben Zuordnung befinden, was ausreicht, um die obige Garantie aufrechtzuerhalten. Was nicht funktionieren würde, ist das assert , das @eddyb als Schutz hinzugefügt hat.
Wir könnten auch Bitoperationen auf Zeigerwerten zulassen, wenn die Operationen nur den ausgerichteten Teil des Zeigers betreffen, das immer noch deterministisch ist und der Code tatsächlich bereits in "vollständigem Miri" existiert.

EDIT: [1] Als Referenz beziehe ich mich auf sein Makro in diesem Thread , auf das wir nicht verlinken können, weil es als Off-Topic markiert wurde, also hier eine Kopie:

macro_rules! offset_of {
    ($Struct:path, $field:ident) => ({
        // Using a separate function to minimize unhygienic hazards
        // (e.g. unsafety of #[repr(packed)] field borrows).
        // Uncomment `const` when `const fn`s can juggle pointers.
        /*const*/ fn offset() -> usize {
            let u = $crate::mem::MaybeUninit::<$Struct>::uninit();
            // Use pattern-matching to avoid accidentally going through Deref.
            let &$Struct { $field: ref f, .. } = unsafe { &*u.as_ptr() };
            let o = (f as *const _ as usize).wrapping_sub(&u as *const _ as usize);
            // Triple check that we are within `u` still.
            assert!((0..=$crate::mem::size_of_val(&u)).contains(&o));
            o
        }
        offset()
    })
}

EDIT2: Eigentlich hat er es auch hier gepostet.

"diese" sind Float -> Bitkonvertierung und Zeiger -> Konvertierungen verwenden

Ich stimme zu, dass, wenn CTFE mit einem Ergebnis erfolgreich ist, die Laufzeitversion der Funktion auch garantiert mit demselben Ergebnis erfolgreich sein sollte. Aber das ist eine viel schwächere Garantie als das, worüber wir hier sprechen, nicht wahr?

Eine Funktion, die zur Laufzeit mit Argumenten aufgerufen wird, ist also nur dann rein, wenn sie tatsächlich beendet wird, wenn sie zur Kompilierzeit mit denselben Argumenten ausgewertet wird?

Das würde unser Leben millionenfach einfacher machen, zumal ich keinen Weg sehe, den Nichtdeterminismus zu verhindern, ohne entweder Schlupflöcher zu hinterlassen oder const fn auf bahnbrechende Weise lahmzulegen.

Eine Funktion, die zur Laufzeit mit Argumenten aufgerufen wird, ist also nur dann rein, wenn sie tatsächlich beendet wird, wenn sie zur Kompilierzeit mit denselben Argumenten ausgewertet wird?

Ja, das schlage ich vor.


Wenn ich etwas länger darüber nachdenke (und die Antwort von @oli-obk lese, die erschien, während ich dies schrieb), habe ich das Gefühl, dass Sie eine zusätzliche Garantie im Sinne von „ein sicheres const fn wird keine Fehler machen wollen CTFE-Zeit (außer Panik) bei Aufruf mit gültigen Argumenten". Eine Art "const safety"-Garantie. Zusammen mit der Garantie, die ich oben angegeben habe, dass ein erfolgreiches CTFE mit dem Laufzeitverhalten übereinstimmt, würde dies eine Garantie dafür bieten, dass ein sicheres const fn zur Laufzeit deterministisch ist, da es mit der erfolgreichen CTFE-Ausführung übereinstimmt.

Ich stimme zu, dass es schwieriger ist, eine Garantie zu erhalten. Im Guten wie im Schlechten verfügt Rust über verschiedene sichere Operationen, deren erfolgreiche Ausführung CTFE miri nicht garantieren kann, während der Determinismus aufrechterhalten wird, wie die Variante von oracle von @Centril , die das niedrigstwertige Byte auf 0 testet. Aus der Perspektive von CTFE in dieser Einstellung stellen die "gültigen Werte vom Typ usize " nur Werte dar, die PrimVal::Bytes sind, während ein PrimVal::Ptr nicht erlaubt sein sollte [1]. Es ist, als hätten wir ein etwas anderes Typsystem im const-Kontext – ich schlage nicht vor, dass wir ändern, was miri tut, ich schlage vor, dass wir ändern, was die verschiedenen Rust-Typen „bedeuten“, wenn sie an ein const fn angehängt sind. Ein solches Typsystem würde garantieren, dass alle sicheren Arithmetik- und Bitoperationen in CTFE nicht schief gehen können: Bei CTFE-gültigen Eingaben kann die ganzzahlige Subtraktion in CTFE niemals fehlschlagen, da beide Seiten PrimVal::Bytes sind. Natürlich muss ptr-to-int in dieser Einstellung eine unsichere Operation sein, da sein Rückgabewert vom Typ usize ist, aber kein CTFE-gültiges usize ist.

Wenn dies eine Garantie ist, die uns wichtig ist, erscheint es mir nicht unangemessen, das Typsystem bei der Überprüfung von CTFE-Funktionen strenger zu machen; Schließlich wollen wir damit mehr Dinge tun (überprüfen von "const safety"). Ich denke, es würde sehr viel Sinn machen, ptr-to-int casts unsafe im const Kontext zu deklarieren, mit dem Argument, dass dies notwendig ist, weil der const Kontext es macht zusätzliche Garantien.

Genauso wie unsere normale „Laufzeitsicherheit“ durch unsicheren Code untergraben werden kann, kann dies auch „konstante Sicherheit“ sein, und das ist in Ordnung. Ich sehe keine Probleme mit unsicherem Code, der weiterhin ptr-to-int-Casts über Unions durchführen kann - das ist schließlich unsicherer Code . In dieser Welt sind die Nachweispflichten für unsicheren Code in einem konstanten Kontext stärker als die in einem nicht konstanten Kontext; Wenn Ihre Funktion eine ganze Zahl zurückgibt, müssen Sie beweisen, dass dies immer ein PrimVal::Bytes und niemals ein PrimVal::Ptr sein wird.

Das Makro von @eddyb würde einen unsicheren Block benötigen, aber es wäre immer noch sicher zu verwenden, da es nur diese "const unsicheren Funktionen" verwendet (ptr-to-usize und dann das Ergebnis subtrahieren oder einfach die intrinsische Zeigersubtraktion verwenden direkt) auf eine Weise, die garantiert keinen CTFE-Fehler auslöst.

Die Kosten für ein solches System wären, dass ein sicherer const fn höherer Ordnung eine const fn Schließung vornehmen müsste, um garantieren zu können, dass die Ausführung der Schließung nicht selbst die „konstante Sicherheit“ verletzt. Dies ist der Preis, um tatsächlich angemessene Garantien für sichere const fn zu erhalten.

[1] Ich ignoriere Floats hier völlig, da ich nicht viel darüber weiß, wo der Nichtdeterminismus entstehen würde. Kann jemand ein Beispiel für Gleitkommacode geben, der sich zur CTFE-Zeit anders verhalten würde als zur Laufzeit? Wäre es ausreichend, zB einen Miri-Fehler zu machen, wenn bei einer Gleitkommaoperation einer der Operanden ein Signalisierungs-NaN ist (um die erste Garantie zu erhalten, die aus meinem vorherigen Beitrag), und zu sagen, dass das System vom Typ CTFE ist erlaubt keine Signalisierung von NaNs bei Typ f32 / f64 (um "const safety" zu erhalten)?

Was nicht funktionieren würde, ist die Behauptung, die @eddyb als Schutz hinzugefügt hat.

Sicher, aber Sie können vorerst assert!(condition); in [()][!condition as usize]; umschreiben.

Sicher, aber Sie können assert!(condition) umschreiben; to [()][!condition as usesize]; zur Zeit.

Es ist nicht die Behauptung, an die ich gedacht habe, es ist der Zeigergleichheitstest in Ihrem Zustand. Zeigergleichheit ist böse und ich würde es vorziehen, wenn wir sie in CTFE nicht zulassen könnten.

EDIT: Macht nichts, ich habe gerade festgestellt, dass die assert den Offset testen. Tatsächlich kann es also niemals zur CTFE-Zeit fehlschlagen, denn wenn sich die Zeiger bei der Ausführung wrapping_sub nicht im selben Block befinden, wird miri einen Fehler verursachen.

// guess we can't have this as const fn
fn is_eq<T>(a: &T, b: &T) -> bool {
    a as *const _ == b as *const _
}

Wie ich bereits sagte, verwenden Sie in Miri virtuelle Zeiger anstelle von echten Zeigern. es kann konstanten Determinismus zur konstanten Zeit bereitstellen. Wenn die Funktion korrekt geschrieben ist, sollten Laufzeit- und Kompilierzeitverhalten die gleichen Ergebnisse liefern, unabhängig davon, ob die Laufzeit nicht deterministisch ist, während die Kompilierzeit deterministisch ist. Sie können deterministisches Verhalten in nicht deterministischen Umgebungen haben, wenn Sie dafür codieren.

Das Erhalten eines Feldoffsets ist deterministisch. bei virtuellen Zeigern bleibt es deterministisch. mit echten Zeigern ist es immer noch deterministisch.

Das Erhalten der Gleichmäßigkeit des 14. Bits eines Zeigers ist nicht deterministisch. mit virtuellen Zeigern wird es deterministisch. mit echten Zeigern ist es nicht deterministisch. Dies ist in Ordnung, da das eine zur Kompilierzeit (eine deterministische Umgebung) und das andere zur Laufzeit (eine nicht deterministische Umgebung) geschieht.

const fn sollte so deterministisch sein wie die Umgebung, in der sie verwendet werden.

@SoniEx2

// guess we can't have this as const fn

In der Tat können wir das nicht. Ich denke, ich könnte mit einer Version des rohen Zeigervergleichs leben, die fehlschlägt, wenn einer der Zeiger derzeit nicht in dem Sinne dereferenzierbar ist, dass er sich in einem zugewiesenen Objekt befindet (aber das ist nicht das, was "Full Miri" derzeit implementiert). Das würde is_eq jedoch immer noch nicht „konstanten sicher“ machen, denn wenn T die Größe Null hat, könnte es eins nach dem Ende eines Objekts zeigen, selbst wenn wir nur sicheren Code betrachten.

C++ erlaubt es, Zeiger einen nach dem Ende zu vergleichen, um ein unbestimmtes (denken Sie: nicht deterministisches) Ergebnis zu erhalten. Sowohl C als auch C++ ermöglichen den Vergleich eines hängenden Zeigers, um ein unbestimmtes Ergebnis zu erhalten. Es ist nicht klar, was LLVM garantiert, aber ich würde lieber nicht auf Garantien setzen, die über das hinausgehen, was sie für C/C++ garantieren müssen (die schwächere der beiden, wenn sie sich unterscheiden). Dies ist ein Problem, wenn wir Laufzeitdeterminismus für alles garantieren wollen, was in CTFE erfolgreich ausgeführt wird, was wir meiner Meinung nach tun.

@ Ralf Jung

Der Unterschied besteht darin, ob die Funktion die Ausführung überhaupt abschließt.

Devils Advocate: "Returning ⊥" in einem Fall ist dasselbe wie es unterschiedliche Ergebnisse hat.

Ich glaube nicht, dass das schwer zu erklären ist.

Ich persönlich betrachte es als überraschendes Verhalten; Sie können es erklären, und ich verstehe es vielleicht (aber ich bin nicht repräsentativ ...), aber es entspricht nicht meiner Intuition.

@oli-obk

Eine Funktion, die zur Laufzeit mit Argumenten aufgerufen wird, ist also nur dann rein, wenn sie tatsächlich beendet wird, wenn sie zur Kompilierzeit mit denselben Argumenten ausgewertet wird?

Ich persönlich finde diese Garantie nicht ausreichend. Ich denke, wir sollten zuerst sehen, wie weit wir mit Reinheit kommen können, und erst wenn wir wissen, dass es in der Praxis lähmend ist, sollten wir zu schwächeren Garantien übergehen.

@ Ralf Jung

das würde garantieren, dass eine sichere Konstante fn zur Laufzeit deterministisch ist, da sie mit der erfolgreichen CTFE-Ausführung übereinstimmt.

OK; Du hast mich verloren; Ich verstehe nicht, wie Sie angesichts der beiden Prämissen zu dieser Garantie "safe const fn is deterministic" gekommen sind. könntest du die begründung näher erläutern?

Wenn dies eine Garantie ist, die uns wichtig ist, erscheint es mir nicht unangemessen, das Typsystem bei der Überprüfung von CTFE-Funktionen strenger zu machen; Schließlich wollen wir damit mehr Dinge tun (überprüfen von "const safety"). Ich denke, es wäre daher sehr sinnvoll, ptr-to-int-Casts im const-Kontext als unsicher zu deklarieren und zu argumentieren, dass dies erforderlich ist, da der const-Kontext zusätzliche Garantien bietet.

Genauso wie unsere normale „Laufzeitsicherheit“ durch unsicheren Code untergraben werden kann, kann dies auch „konstante Sicherheit“ sein, und das ist in Ordnung. Ich sehe keine Probleme mit unsicherem Code, der weiterhin ptr-to-int-Umwandlungen über Unions durchführen kann - das ist schließlich unsicherer Code. In dieser Welt sind die Nachweispflichten für unsicheren Code in einem konstanten Kontext stärker als die in einem nicht konstanten Kontext; Wenn Ihre Funktion eine Ganzzahl zurückgibt, müssen Sie beweisen, dass dies immer ein PrimVal::Bytes und niemals ein PrimVal::Ptr sein wird.

Diese Absätze sind Musik in meinen Ohren! ❤️ Dies scheint den Determinismus ("Reinheit") zu gewährleisten? und ist genau das, was ich vorhin im Sinn hatte. Const Safety finde ich auch ein tolles Wort!

Lassen Sie mich diese Garantie für zukünftige Referenzzwecke "CTFE-Zuverlässigkeit" nennen: Wenn CTFE keinen Fehler macht, dann stimmt sein Verhalten mit der Laufzeit überein -- beide weichen voneinander ab oder beide enden mit dem gleichen Wert. (Hier ignoriere ich Rückgabewerte höherer Ordnung völlig.)

@ Centril

Devils Advocate: "Returning ⊥" in einem Fall ist dasselbe wie es unterschiedliche Ergebnisse hat.

Nun, das ist eindeutig eine Frage der Definition. Ich denke, Sie haben die CTFE-Zuverlässigkeitsgarantie verstanden, die ich beschrieben habe, und Sie stimmen zu, dass es eine Garantie ist, die wir wollen; ob es alles ist, was wir wollen, steht zur Diskussion :)

OK; Du hast mich verloren; Ich verstehe nicht, wie Sie angesichts der beiden Prämissen zu dieser Garantie "safe const fn is deterministic" gekommen sind. könntest du die begründung näher erläutern?

Nehmen wir an, wir haben einen Aufruf von foo(x) , wobei foo eine sichere konstante Funktion und x ein konstant gültiger Wert ist (dh es ist nicht &y as *const _ as usize ). Dann wissen wir, dass foo(x) in CTFE ausgeführt wird, ohne einen Fehler auszulösen, durch konstante Sicherheit. Infolgedessen verhält sich foo(x) aufgrund der CTFE-Festigkeit zur Laufzeit genauso wie in CTFE.

Im Wesentlichen denke ich, dass ich Ihre Garantie in zwei Teile zerlegt habe - einen, der sicherstellt, dass eine sichere Konstante fn niemals versucht, etwas zu tun, das CTFE nicht unterstützt (wie das Lesen von stdin oder das Bestimmen, ob das niederwertigste Byte eines Zeigers 0 ist). und eine, die sicherstellt, dass alles, was CTFE unterstützt , zur Laufzeit passt.

Diese Absätze sind Musik in meinen Ohren! Herz Dies scheint Determinismus ("Reinheit") zu gewährleisten? und ist genau das, was ich vorhin im Sinn hatte. Const Safety finde ich auch ein tolles Wort!

Froh, dass Sie es mögen. :) Das bedeutet, dass ich endlich verstehe, wovon wir hier reden. „Reinheit“ kann so viele verschiedene Dinge bedeuten, dass mir oft ein wenig mulmig wird, wenn der Begriff verwendet wird. Und Determinismus ist keine ausreichende Bedingung für konstante Sicherheit, das relevante Kriterium ist, ob die Ausführung in CTFE einen Fehler auslöst. (Ein Beispiel für eine deterministische, nicht konstant sichere Funktion ist meine Variante Ihrer orcale multipliziert mit 0. Dies ist nicht in Ordnung, selbst wenn Sie unsicheren Code verwenden, da miri beim Untersuchen der Bytes eines Zeigers einen Fehler ausgibt. auch wenn die Bytes letztendlich keine Rolle spielen. Es ist, als ob die Operation, die das niedrigstwertige Byte eines Zeigers extrahiert, "const-UB" ist und daher selbst in unsicherem const-Code nicht zulässig ist.)

Ja, ein Zeiger nach dem Ende eines Array-Elements würde wahrscheinlich auf das nächste Array-Element zeigen. Na und? es ist nicht wirklich nicht deterministisch? deterministisch zur Kompilierzeit ist sowieso alles, was zählt. Soweit es mir wichtig ist, könnte die Laufzeitauswertung für die Erschöpfung des Stapels segfault sein.

@ Ralf Jung

Die Kosten für ein solches System wären, dass ein sicheres const fn höherer Ordnung einen const fn-Abschluss vornehmen muss, um garantieren zu können, dass die Ausführung des Abschlusses nicht selbst die „const safety“ verletzt. Dies ist der Preis, um tatsächlich angemessene Garantien für sichere const fn zu erhalten.

Ich glaube, dass dies stark gemildert werden kann, um die Transformation des meisten vorhandenen Codes zu unterstützen, indem ?const eingeführt wird, wo Sie Funktionen höherer Ordnung schreiben können, deren Ergebnis an const gebunden werden kann, wenn und nur wenn die bereitgestellte Funktion ?const fn(T) -> U ist is_const(x : T) ; Also hast du:

?const fn twice(fun: ?const fn(u8) -> u8) { fun(fun(42)) }

fn id_impure(x: u8) -> u8 { x }
const fn id_const(x: u8) -> u8 { x }
?const fn id_maybe_const(x: u8) -> u8 { x }

fn main() {
    let a = twice(id_impure); // OK!
    const b = twice(id_impure); // ERR!
    let c = twice(id_const); // OK!
    const d = twice(id_const); // OK!
    let e = twice(id_maybe_const); // OK!
    const f = twice(id_maybe_const); // OK!
}

Ich werde in einer Woche oder so einen RFC schreiben, der etwas in dieser Richtung (und mehr) vorschlägt.

@Centril An dieser Stelle entwickeln Sie ein Effektsystem mit Effektpolymorphismus. Ich weiß, dass das immer deine geheime (?) Absicht war, dich nur wissen zu lassen, dass es offensichtlich wird.^^

@RalfJung Ich habe das Geheimnis bereits letztes Jahr unter https://github.com/rust-lang/rfcs/pull/2237 gelüftet, aber ich muss es umschreiben ;)
Ziemlich gemeinfrei jetzt ^,-

@SoniEx2

Ja, ein Zeiger nach dem Ende eines Array-Elements würde wahrscheinlich auf das nächste Array-Element zeigen. Na und? es ist nicht wirklich nicht deterministisch? deterministisch zur Kompilierzeit ist sowieso alles, was zählt. Soweit es mir wichtig ist, könnte die Laufzeitauswertung für die Erschöpfung des Stapels segfault sein.

Das Problem liegt in Situationen wie den folgenden (in C++):

int x[2];
int y; // let's assume y is put right after the end of x in the stack frame
if (&x[0] + 2 == &y) {
  // ...
}

C-Compiler wollen (und tun!) diesen Vergleich zu false optimieren. Schließlich zeigt ein Zeiger auf x und einer auf y , sodass es unmöglich ist, dass sie jemals gleich sind.
Außer natürlich, dass die Adressen auf der Maschine gleich sind , weil ein Zeiger genau auf das Ende von x zeigt, was dieselbe Adresse ist wie (der Anfang von) y ! Wenn Sie also den Code so verschleiern, dass der Compiler nicht mehr sieht, woher die Adressen kommen, können Sie feststellen, dass der Vergleich zu true ausgewertet wird. Der C++-Standard lässt daher zu, dass beide Ergebnisse nicht deterministisch auftreten, was sowohl die Optimierung (die false ) als auch die Kompilierung zur Assemblierung (die true sagt) rechtfertigt. Der C-Standard lässt dies nicht zu, was LLVM (und GCC) zu nicht konformen Compilern macht, da beide diese Art von Optimierungen durchführen.

Ich habe eine Zusammenfassung meiner Ideen zu konstanter Sicherheit, konstanter Solidität usw. geschrieben, die hier in diesem Thread und/oder in verwandten Diskussionen im IRC aufgetaucht sind: https://www.ralfj.de/blog/2018/07/19/ const.html

Dieses Thema hier ist etwas schwer zu entwirren, weil so viele Dinge diskutiert wurden. @oli-obk hat hilfreicherweise ein Repo für Const-Eval-Bedenken erstellt, daher ist wahrscheinlich der Issue-Tracker von https://github.com/rust-rfcs/const-eval ein guter Ort, um bestimmte Unterprobleme zu diskutieren.

@Centril schlug vor, eine minimale Version zu stabilisieren, die mit zukünftigen Erweiterungen aufwärtskompatibel ist:

  • keine generischen Argumente mit Eigenschaftsgrenzen
  • keine Argumente oder Rückgabetypen vom Typ fn oder dyn Trait

    • rekursiv auf den Argumenttyp überprüft, sodass Felder von Argumenten möglicherweise auch nicht von diesem Typ sind

  • kein unsicherer Code (weil wir nicht wissen, ob es dort problematische Dinge gibt)

    • Ich persönlich denke, dass das in Ordnung ist, mit Ausnahme von union s, die sich bereits hinter einem zusätzlichen Feature-Gate befinden, und rohen Pointer-Derefs (im Allgemeinen derzeit in jeder Konstante verboten). Jeder andere unsichere Code muss andere unsichere const fns oder const intrinsisch durchlaufen, die ihre eigene Diskussion bezüglich der Stabilisierung erfordern.

(nicht: mein Vorschlag beinhaltete auch eine rekursive Prüfung auf fn -Zeiger oder dyn Trait auf den Rückgabetyp von const fn s)

keine generischen Argumente mit Eigenschaftsgrenzen

Zur Klarstellung: Würde so etwas akzeptiert oder nicht?

struct Mutex<T> where T: Send { /* .. */ }

impl<T> Mutex<T> where T: Send {
    pub const fn new(val: T) -> Self { /* .. */ }
}

Die Grenze ist nicht Teil von const fn selbst.

Auch zur Klarstellung: Ist der Vorschlag, diese Dinge zu stabilisieren und den Rest hinter dem Feature-Gate zu lassen ODER diese zu stabilisieren und den Rest insgesamt zu einem Fehler zu machen?

@mark-im der Rest würde hinter einem Feature-Gate bleiben.

@oli-obk Was ist das Problem mit unsicherem Code? Wir erlauben unsicher in const X : Ty = ... , was dieselben Probleme hat. Ich denke, const fn Körper sollten genau wie const Körper überprüft werden.

Ich denke, wir wollen in Bezug auf "unconst"-Operationen konservativ bleiben - Operationen, die sicher, aber nicht const-safe sind (im Grunde alles auf rohen Zeigern) -, aber diese sind im const-Kontext bereits vollständig verboten, oder?

Die Schranke ist nicht Teil von const fn selbst.

Nein, Grenzen für den Impl-Block wären nach diesem Vorschlag ebenfalls nicht zulässig

Was ist das Problem mit unsicherem Code?

Ich sehe keine Probleme, wie in meinem Kommentar erwähnt. Jede const unsichere Funktion/Funktion muss sowieso ihre eigene Stabilisierung durchlaufen.

@RalfJung Ich denke, das Problem ist " @Centril ist nervös, dass wir etwas übersehen haben. Diese sind im konstanten Kontext bereits vollständig verboten". ;) Aber irgendwann müssen wir unsafe { .. } in const fn stabilisieren, also wenn du sicher bist, dass es keine Probleme gibt und wir alle unkonstanten Operationen abgefangen haben, dann lass es uns tun?

Außerdem, wenn wir etwas übersehen haben, sind wir schon irgendwie am Arsch, da die Leute es auf const verwenden können.

Ich plane immer noch, eine PR zu schreiben, die die const-Sicherheits-/Promotion-Teile des const-fn-RFC-Repos ausfüllt; Das wäre der Zeitpunkt, um sorgfältig zu prüfen, ob wir alles abgedeckt haben (und Testfälle haben).

Eine andere Sache, die auf Discord auftauchte, sind FP-Operationen: Wir können derzeit nicht garantieren, dass sie mit echter Hardware übereinstimmen. CTFE folgt IEEE genau, aber LLVM/Hardware möglicherweise nicht.

Dies gilt auch für const -Elemente, aber diese werden niemals zur Laufzeit ausgeführt – während const fn dies sein könnte. Daher scheint es ratsam, den FP-Betrieb in const fn nicht zu stabilisieren.

OTOH, wir fördern bereits Ergebnisse von FP-Operationen? Da haben wir also bereits diesen Laufzeit-/Kompilierzeit-Unterschied, der auf Stable beobachtbar ist. Wäre es einen Kraterlauf wert, um zu sehen, ob wir das rückgängig machen können?

Für zukünftige Referenzen ist der folgende Artikel in Bezug auf Gleitkommazahlen und Determinismus relevant:

@ Ralf Jung

Wäre es einen Kraterlauf wert, um zu sehen, ob wir das rückgängig machen können?

Ich wäre überrascht, wenn wir das tun könnten, aber es ist zumindest einen Versuch wert. :)

@RalfJung , es könnte weiter oben in diesem Thread interessante Beiträge geben https://github.com/rust-lang/rust/issues/24111#issuecomment -386764565

Angenommen, wir wollen die Schlüsselwortform const fn , denke ich, dass es eine ziemlich anständige Lösung ist, jetzt etwas zu stabilisieren, das begrenzt genug ist (wieso habe ich es vorher nicht gesehen?!)

Wenn wir diese schrittweisen Stabilisierungen durchführen, möchte ich nur a registrieren
Forderung nach einer übersichtlichen Auflistung der Beschränkungen und ihrer Begründungen. Es ist
wird frustrierend sein, wenn Benutzer eine scheinbar harmlose Änderung vornehmen und
läuft auf einen Kompilierungsfehler, damit wir die Fehler zumindest beheben können. ich
erkennen, dass die Argumentation auf viele Diskussionen verteilt ist, von denen nicht alle
Ich bin dem gefolgt, aber ich denke, es sollte eine Tabelle in den Dokumenten (oder der
Referenz oder zumindest das Nomicon), die alle unzulässigen Operationen auflistet, die
Problem, das es verursachen könnte, und die Aussichten auf Stabilisierung (z. B. "niemals",
"wenn RFC XYZ implementiert ist", "nachdem wir diesen Teil der Spezifikation festgelegt haben").

Am Mo, 20. August 2018 um 13:44 Uhr Eduard-Mihai Burtescu <
[email protected]> schrieb:

Angenommen, wir wollen die Schlüsselwortform const fn beibehalten, denke ich, stabilisierend
etwas , das begrenzt genug ist, ist eine ziemlich anständige Lösung (wie
hab ich das nicht schon gesehen?!)


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/24111#issuecomment-414403036 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAC3n80yVxIa3agsJP4wXZFkgyHVmAsjks5uSvWXgaJpZM4D66IA
.

@est31 Wie @rkruppe bereits geschrieben hat, wäre es illegal, diese Sicherungen ohne -ffast-math durchzuführen - und ich denke, LLVM handhabt das richtig.

Soweit ich mich erinnere, ist grundlegende Arithmetik nicht einmal allzu problematisch (außer auf 32-Bit-x86, weil x87 ...), aber transzendente Funktionen sind es. Und das sind nicht const fn , richtig? Meine Hoffnung wäre also, dass wir am Ende auch in dieser Hinsicht eine Parität zwischen const Artikeln, Werbeaktionen und const fn haben können.

@RalfJung Ich bin immer noch nicht davon überzeugt, dass z. B. das Verschütten und dann das Zurückladen in die FPU-Register zwischen einigen Operationen die gleichen Ergebnisse liefert wie dieselbe Berechnung ohne dieses Verschütten.

Soweit ich mich erinnere, ist grundlegende Arithmetik nicht einmal allzu problematisch (außer auf 32-Bit-x86, weil x87 ...), aber transzendente Funktionen sind es.

Wie würden transzendente Funktionen ein Problem bedeuten?

IIRC, obwohl einige transzendente Funktionen von gängigen x86-Prozessoren unterstützt werden, sind diese Funktionen langsam und werden vermieden und nur aus Gründen der Vollständigkeit und Kompatibilität mit bestehenden Implementierungen aufgenommen. Daher werden transzendente Funktionen fast überall in Form von Kombinationen grundlegender arithmetischer Funktionen ausgedrückt. Das bedeutet, dass sich ihre referentielle Transparenz nicht von der arithmetischer Funktionen unterscheidet. Wenn grundlegende Funktionen "sicher" sind, dann ist alles, was darauf aufgebaut ist, einschließlich transzendentaler Funktionen. Die einzige Quelle der "referenziellen Intransparenz" könnten hier unterschiedliche Annäherungen (Implementierungen) dieser transzendentalen Funktionen in Bezug auf diese grundlegenden arithmetischen Funktionen sein. Ist das die Ursache des Problems?

@est31 Während die meisten transzendentalen Funktionen letztendlich nur Bibliothekscode sind, der aus primitiven Integer- und Float-Operationen besteht, sind diese Implementierungen nicht standardisiert, und in der Praxis kann ein Rust-Programm während seiner gesamten Lebensdauer mit vielleicht drei verschiedenen Implementierungen interagieren, von denen einige auch je nach Host oder Ziel variieren Plattform:

  • Es gibt die Laufzeitimplementierung, die das Programm auf dem Ziel verwendet (z. B. die libm der Zielplattform oder unter Umständen Hardwareanweisungen).
  • Es gibt den konstanten Ordner, den LLVM verwendet (anscheinend ist dies nur die Hostplattform C libm)
  • Es gibt alles, was MIRI mit diesen Operationen machen würde (z. B. etwas in rustc_apfloat oder das Interpretieren einer Rust-Implementierung wie https://github.com/japaric/libm/)

Wenn einer dieser Werte nicht miteinander übereinstimmt, können Sie unterschiedliche Ergebnisse erhalten, je nachdem, wann ein Ausdruck ausgewertet wird.

@rfcbot abbrechen

Es ist unwahrscheinlich, dass wir den vollen Monat in naher Zukunft stabilisieren werden.
Stattdessen möchte ich einen Konsens für eine minimalere Teilmenge entwickeln (wie grob in https://github.com/rust-lang/rust/issues/24111#issuecomment-414310119 umrissen), die wir hoffentlich in naher Zukunft stabilisieren können .
Diese Teilmenge wird in #53555 verfolgt. Weitere Beschreibungen finden Sie dort.

@Centril- Vorschlag storniert.

@rkruppe gibt es einen Grund, die transzendentalen Funktionen auf llvm-Intrinsic zu senken? Können wir das gesamte Problem nicht einfach vermeiden, indem wir sie auf bekannte, reine Rostimplementierungen reduzieren, die wir kontrollieren und die auf allen Plattformen gleich sind?

gibt es einen grund, die transzendentalen funktionen auf llvm intrinsisch zu senken?

Neben der Einfachheit, keine ganze plattformübergreifende libm zu implementieren, haben die intrinsischen Eigenschaften im Optimierer und Codegen von LLVM einen Vorteil, den eine gewöhnliche Bibliotheksfunktion nicht bekommt. Offensichtlich ist das konstante Falten (und verwandte Dinge wie die Wertebereichsanalyse) in diesem Zusammenhang ein Problem, aber ansonsten ist es sehr nützlich. Es gibt auch algebraische Identitäten (angewendet durch den SimplifyLibCalls-Pass). Schließlich haben einige Funktionen (hauptsächlich sqrt und sein Kehrwert, die nicht transzendent, aber was auch immer sind) spezielle Unterstützung für die Codegenerierung, um zB sqrtss auf x86 mit SSE zu generieren.

Können wir das gesamte Problem nicht einfach vermeiden, indem wir sie auf bekannte, reine Rostimplementierungen reduzieren, die wir kontrollieren und die auf allen Plattformen gleich sind?

Selbst wenn man alle oben genannten Punkte ignoriert, ist dies meiner Meinung nach keine gute Option. Wenn die Zielplattform über eine C-libm verfügt, sollte es möglich sein, sie zu verwenden, entweder weil sie optimierter ist oder um Aufblähen zu vermeiden.

Die Intrinsics haben im Optimierer und Codegen von LLVM einen Vorteil, den eine gewöhnliche Bibliotheksfunktion nicht bekommt.

Ich habe gedacht, dass solche Optimierungen nur aktiviert werden, wenn schnelle Mathematik aktiviert ist?

Wenn die Zielplattform über eine C-libm verfügt, sollte es möglich sein, sie zu verwenden, entweder weil sie optimierter ist oder um Aufblähen zu vermeiden.

Sicher. Es sollte immer eine Option geben, diese eher akademische Eigenschaft – referenzielle Transparenz – zugunsten von Dingen einzutauschen, die eher von Bedeutung sind, wie eine verbesserte Geschwindigkeit der kompilierten Binärdateien oder kleinere Binärdateien. ZB durch Verwendung der Plattform libm oder durch Einschalten des schnellen Mathematikmodus. Oder schlagen Sie vor, dass wir den schnellen Mathe-Modus für alle Ewigkeit verbieten sollten?

Meiner Meinung nach sollten wir f32::sin() in konstanten Kontexten nicht verbieten, zumindest nicht, wenn wir + , - usw. zulassen. Ein solches Verbot zwingt die Leute dazu, Crates zu erstellen und zu verwenden die const-kompatible Implementierungen bereitstellen.

Ich habe gedacht, dass solche Optimierungen nur aktiviert werden, wenn schnelle Mathematik aktiviert ist?

Die ständige Auswertung dieser Funktionen und die Ausgabe von sqrtss ist ohne -ffast-math leicht zu rechtfertigen, da korrekt gerundet werden kann.

Oder schlagen Sie vor, dass wir den schnellen Mathe-Modus für alle Ewigkeit verbieten sollten?

Ich schlage nichts vor und habe auch keine Meinung (atm) dazu, ob eine solche Eigenschaft garantiert werden sollte. Ich melde nur Einschränkungen.

Es konnte kein offenes Problem für ICE gefunden werden, das durch Vec im const fn-Kontext verursacht wurde.

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2015&gist=508238a9f06fd85720307bf6cc227586

Soll ich dafür ein neues Thema aufmachen?

@Vultapher ja, das sieht aus wie ein neuer ICE.

In Ordnung geöffnet #55063.

Wenn der Compiler prüfen kann, ob eine Funktion in einem constexpr zur Kompilierzeit aufgerufen werden kann, wenn ein Benutzer sie mit const fn annotiert, warum dann nicht einfach automatisch alle Funktionen prüfen? (ähnlich wie Auto-Traits)? Ich kann mir keine wirklichen negativen Auswirkungen vorstellen, und der klare Vorteil ist, dass wir uns nicht auf fehleranfälliges menschliches Urteilsvermögen verlassen müssen.

Der größte Nachteil ist, dass es zu einem öffentlichen Detail wird, also zu einer Implementierung
Die Änderung einer Funktion, die nicht als Konstante gedacht ist, bricht jetzt.

Auch müssen wir uns nicht auf menschliches Urteilsvermögen verlassen. Wir können einen Clippy Lint haben, der uns sagt, wann eine nicht kommentierte Funktion const fn sein könnte: https://github.com/rust-lang/rust-clippy/issues/2440

Dies ähnelt der Art und Weise, wie wir nicht auf die Veränderlichkeit lokaler Variablen schließen, sondern uns stattdessen vom Compiler mitteilen lassen, wo mut hinzugefügt oder entfernt werden soll.

@remexre const fn fungiert als Schnittstellenspezifikation. Ich bin mit den winzigen Details dieser Funktion nicht sehr vertraut (und vielleicht ist das Folgende hier schon durchdacht), aber zwei Fälle, in denen ich mir vorstellen kann, dass der Compiler sagt, wenn eine Funktion fälschlicherweise als const annotiert ist die Kompilierung fehlschlägt, wenn eine solche Funktion &mut als Parameter verwendet oder wenn sie andere Nicht- const -Funktionen aufruft. Wenn Sie also die Implementierung von const fn ändern und diese Einschränkungen brechen, wird der Compiler Sie stoppen. Dann können Sie wählen, ob Sie die Nicht- const -Bits in (einer) separaten Funktion(en) implementieren oder die API unterbrechen, wenn dies eine beabsichtigte Änderung war.

Es gibt einen weiteren Mittelpunkt, über den ich noch nicht gesprochen habe, und es ist die Möglichkeit, ein Gegenteil dieses Markers und eine Art "Funktionsreinheitsschlussfolgerung" einzuführen, wenn er nicht explizit festgelegt ist. Dann würden die Dokumente den tatsächlichen Marker zeigen, aber mit einer Art Warnung, dass die Stabilität dieses Markers nicht garantiert werden kann, wenn es sich um ein const handelt. Das Problem ist, dass dies dazu ermutigen könnte, faul zu sein und dies fast jedes Mal zu tun, was nicht sein Zweck ist.

sollte eine Konstante fn in der Lage sein, eine Ausgabe zu erzeugen? warum sollte &mut nicht erlaubt sein?

@aledomu Mein Kommentar war an @AGaussman gerichtet; Ich spreche von dem Fall, in dem ein Bibliotheksautor eine Funktion verfügbar macht, die nicht als Konstante gedacht ist (insofern die Konstante nicht als Teil der API gedacht ist); Wenn const abgeleitet werden würde, wäre es eine Breaking Change, diese Funktion nicht-const zu machen.

@SoniEx2 const fn ist eine Funktion, die zur Kompilierzeit ausgewertet werden kann, was nur bei reinen Funktionen der Fall ist.

@remexre Wenn es kein stabiler Teil der API sein soll, markieren Sie es einfach nicht.

Für die Schlussfolgerung, die ich kommentiert habe, habe ich die Notwendigkeit einer Warnung in den Kistendokumenten erwähnt.

was ist der Unterschied? absolut keine!

const fn add_1(x: &mut i32) { x += 1; }
let mut x = 0;
add_1(&mut x);
assert_eq!(x, 1);
x = 0;
add_1(&mut x);
assert_eq!(x, 1);

const fn added_1(x: i32) -> i32 { x + 1 }
let mut x = 0;
x = added_1(x);
assert_eq!(x, 1);
x = 0;
x = added_1(x);
assert_eq!(x, 1);

Ich habe gezielte Probleme eingereicht für:

Die folgenden gezielten Probleme bestehen bereits:

Wenn es andere Bereiche gibt, die nicht bereits von anderen Themen verfolgt werden, müssen diese bzgl. const eval und const fn , schlage ich vor, dass die Leute neue Issues erstellen (und mich + @oli-obk auf CC setzen).

Damit ist die Nützlichkeit dieses Themas beendet, das hiermit geschlossen wird.

Ich habe nicht alle Einzelheiten im Kopf, aber gibt es nicht viel mehr, das von miri unterstützt wird, aber noch nicht in min_const_fn aktiviert ist? Zum Beispiel rohe Zeiger.

@SimonSapin Ja, guter Fang. Dafür gibt es einige weitere Probleme. Ich habe den Kommentar + die Problembeschreibung aktualisiert. Wenn etwas nicht abgedeckt ist, auf das Sie stoßen, erstellen Sie bitte neue Probleme.

Ich denke, es ist nicht angemessen, ein Meta-Tracking-Problem zu schließen, wenn überhaupt nicht klar ist, dass das, was es abdeckt, von spezifischeren Problemen vollständig abgedeckt wird.

Wenn ich #![feature(const_fn)] in Servo entferne, sind die Fehlermeldungen:

  • trait bounds other than `Sized` on const fn parameters are unstable
  • function pointers in const fn are unstable

(Diese const fn s sind alle triviale Konstruktoren für Typen mit privaten Feldern. Die frühere Nachricht befindet sich im Konstruktor von struct Guard<T: Clone + Copy> , obwohl Clone nicht im Konstruktor verwendet wird. Die Letzteres dient zum Initialisieren Option<fn()> (vereinfacht) zu entweder None oder Some(name_of_a_function_item) .)

In der Beschreibung dieses Problems werden jedoch weder Traits noch Funktionszeigertypen erwähnt.

Ich meine nicht, dass wir nur zwei spezifischere Probleme für das oben Gesagte haben sollten. Ich meine, wir sollten dieses wieder öffnen, bis wir irgendwie sichergestellt haben, dass alles hinter dem const_fn -Feature-Gate (das immer noch in Fehlermeldungen hierher weist) ein Tracking-Problem hat. Oder bis const_fn vollständig stabilisiert ist.

@SimonSapin

Ich denke, es ist nicht angemessen, ein Meta-Tracking-Problem zu schließen, wenn überhaupt nicht klar ist, dass das, was es abdeckt, von spezifischeren Problemen vollständig abgedeckt wird.

Dieses Problem hat den Geschmack von https://github.com/rust-lang/rust/issues/34511 , was eines der größten Durcheinander ist, das es gibt, was Tracking-Probleme angeht. Dieses Problem ist auch seit einiger Zeit ein Free-for-All-Problem, sodass es derzeit nicht als Meta-Problem fungiert. Verwenden Sie für solche Free-for-alls stattdessen http://internals.rust-lang.org/ .

In der Beschreibung dieses Problems werden jedoch weder Traits noch Funktionszeigertypen erwähnt.

Ich meine nicht, dass wir nur zwei spezifischere Probleme für das oben Gesagte haben sollten.

Genau das sollte meiner Meinung nach getan werden. Aus Sicht der T-Lang-Triage ist es günstig, gezielte und umsetzbare Probleme zu haben.

Ich meine, wir sollten dieses wieder öffnen, bis wir irgendwie sichergestellt haben, dass alles hinter dem const_fn -Feature-Gate (das immer noch in Fehlermeldungen hierher weist) ein Tracking-Problem hat. Oder bis const_fn vollständig stabilisiert ist.

Mir ist nicht einmal klar, was const_fn das Feature Gate überhaupt ausmacht oder dass das alles irgendwann stabilisiert wird. Alles außer Grenzen und Funktionszeigern aus dem ursprünglichen RFC hat offene Probleme und noch einige mehr.

Mir ist nicht einmal klar, was const_fn das Feature-Gate überhaupt ausmacht

Das ist genau der Grund, warum wir es nicht schließen sollten, bis wir das herausgefunden haben, IMO.

Alles

Aber ist es wirklich alles?

Weiß jemand, was mit der Funktion const_string_new passiert ist? Gibt es ein Tracking-Problem dafür? Das Unstable-Buch verlinkt hier einfach.

@phansch Das liegt daran, dass hier alle rustc_const_unstable zeigen. (cc @oli-obk können wir das beheben?)

Dann sollte das Thema offen sein. Es ist einfach beleidigend als User darauf hingewiesen zu werden
zu einem geschlossenen Thema.

Am Mittwoch, 9. Januar 2019, 04:05 Uhr Mazdak Farrokhzad < [email protected]
schrieb:

@phansch https://github.com/phansch Das ist weil alle
rustc_const_unstable Punkt hier.


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/24111#issuecomment-452622097 ,
oder den Thread stumm schalten
https://github.com/notifications/unsubscribe-auth/AAC3n7JhzsZZpizmWlp0Nww5bcfIqH2Vks5vBbC8gaJpZM4D66IA
.

@durka : Es wird immer ein mögliches Fenster geben, in dem etwas nachts geschlossen wird und die Auflösung immer noch nicht in Stable gelandet ist. Wie ist das beleidigend?

Ich habe mich geweigert, hier einen Kommentar abzugeben, und vielleicht sollten wir dieses Gespräch in einen Thread über Interna verschieben (gibt es schon einen?), aber ...

Die Entscheidung, dies zu schließen, ergibt für mich keinen Sinn. Es handelt sich um ein Tracking-Problem, da es in Fehlermeldungen des Compilers auftaucht, und es ist nicht allein, siehe diesen Beitrag für weitere Beispiele: https://internals.rust-lang.org/t/psa-tracking-for-gated -Sprachfunktionen/2887. Das Schließen dieses Themas impliziert für mich Stabilität, was offensichtlich noch nicht der Fall ist.

Ich kann ehrlich gesagt kein Argument dafür sehen, dies zu schließen ... Ich bin froh, dass es jetzt ein gezielteres Problem gibt, sodass die Implementierung mit hoffentlich neuen Diskussionen und Schwerpunkten voranschreiten kann, aber ich sehe keinen klaren Weg, den Compiler zuzuordnen Nachrichten mit denen.

Nochmals, wenn dies einen Thread zu Interna benötigt (oder bereits hat), lasst uns diese Unterhaltung vielleicht dorthin verschieben?

EDIT: Oder ist das Problem nur, dass das Buch veraltet ist? Das Ausprobieren des Beispiels aus dem RFC (es fehlen ein paar #[derive(...)] s) scheint auf Rust rustc 1.31.1 fehlerfrei zu funktionieren. Gibt es hier noch Compiler-Fehlermeldungen? Es wäre schön, einen Ort zu haben, an dem Fehler wie folgt verlinkt werden können:

error: only int, `bool` and `char` operations are stable in const fn

Wenn wir möchten, dass sie mit den spezifischen Themen verknüpft sind, wäre das möglicherweise eine Verbesserung.

Ok, hier sollten einige starke Beweise dafür sein, dass dieses Problem offen bleibt. Soweit ich das beurteilen kann:

https://github.com/rust-lang/rust/blob/6ecad338381cc3b8d56e2df22e5971a598eddd6c/src/libsyntax/feature_gate.rs#L194

ist die einzige active -Funktion, die auf ein geschlossenes Problem hinweist.

In einer idealen Welt sollte diese Art von Diskussion meiner Meinung nach wirklich automatisiert werden, da die Menschen, wie wir festgestellt haben, unterschiedliche Meinungen und Vorstellungen darüber haben, wie die Dinge funktionieren sollten. Aber das ist wirklich kein Thema für diesen Thread...

Wenn wir möchten, dass sie mit den spezifischen Themen verknüpft sind, wäre das möglicherweise eine Verbesserung.

Ja, das ist die richtige Lösung und was @Centril bereits vorgeschlagen hat .

Der anfängliche Kommentar wurde auch bearbeitet, um die Leute zu den spezifischen Problemen weiterzuleiten, die hier in dem "Fenster" ankommen, das @ErichDonGubler erwähnt .

https://github.com/rust-lang/rust/issues/57563 wurde nun geöffnet, um die verbleibenden instabilen Const-Features zu verfolgen.

Könnte dann jemand den Problemtext hier bearbeiten, um einen prominenten Link zu #57563 zu erstellen?

@glaebhörl fertig :)

Hallo, ich bin hierher gekommen, weil ich beim Kompilieren von ncurses-rs error[E0658]: const fn is unstable (see issue #24111) bekommen habe. Was soll ich machen? Rost aufrüsten? ich habe

$ cargo version
cargo 1.27.0
$ rustc --version
rustc 1.27.2

BEARBEITEN: brew uninstall rust gemacht und die Installationsanweisungen von Rustup befolgt , jetzt ist rustc --version rustc 1.33.0 (2aa4c46cf 2019-02-28) und dieser Fehler ist verschwunden.

Ja, um const fn auf Stable verwenden zu können, müssen Sie Ihren Compiler aktualisieren.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen