Tracking-Problem für rust-lang/rfcs#2000
Aktualisierung:
Wenn Sie helfen möchten, werfen Sie einen Blick auf die offenen const-Generika-Probleme und wenden Sie sich an @varkor , @eddyb , @yodaldevoid , @oli-obk oder @lcnr, um Hilfe beim Einstieg zu erhalten!
Blockierende Stabilisierung:
Verbleibende Umsetzungsprobleme:
FIXME(const_generics)
Kommentare auf.FIXME(const_generics:defaults)
).has_infer_types
.{X * 2}
.ConstEvaluatable
Prädikat hinzugefügt, und WF([T; expr])
erfordert jetzt ConstEvaluatable(expr)
, da expr
träge ausgewertet wird (auch wenn es nur ein ganzzahliges Literal ist). Um dieses Prädikat zu erfüllen, muss der Ausdruck erfolgreich ausgewertet werden, während die Normalisierung den Fehler ignoriert und den gefundenen Unevaluated
Ausdruck einfach unberührt lässt, was mehr oder weniger bei zugeordneten Typprojektionen der Fall ist. Ich erwarte, dass das gleiche System auf Const-Generika skaliert.@EpicatSupercell hat Interesse bekundet, daran zu arbeiten, ich werde sie bei der ersten Implementierung begleiten. Wir können jedoch aufgrund der in #44275 beschriebenen Einschränkungen nicht zu weit gehen.
Das heißt, wir benötigen die faule Normalisierung von @nikomatsakis , damit in Typen eingebettete konstante Ausdrücke die Grenzen des Gültigkeitsbereichs (von der Funktion / Typdefinition / Impl / etc., in der sie sich befinden) einhalten können, ohne die Hälfte der Zeit zyklische Abhängigkeiten zu erzeugen.
Implementierungswegpunkte (für ein direkteres Mentoring suchen Sie @eddyb
auf Gitter oder eddyb
auf IRC):
const
Parameter neben Typ-Parametern hinzufügenparse_generic_params
- const IDENT: Type
analysierenDefId
Sammlerty::Generics
- const
Parameter neben Typ-Parametern hinzufügengenerics_of
- Erstellen Sie ty::ConstParameterDef
aus hir::ConstParam
Def
- eine Variante für const
Parameter hinzufügenwith_type_parameter_rib
- unterstützt sowohl Typ- als auch const
GenerikaConstVal
- Param
Variante hinzufügen ähnlich ty::TyParam
TyArray
mit einer Länge von ExprPath
, die in Def::ConstParam
sollte ConstVal::Param
anstelle von ConstVal::Unevaluated
- in ähnlicher Weise wie aus Def::TyParam
ty::TyParam
subst::Kind
- Unterstützen Sie &ty::Const
und überprüfen Sie as_const
auch dort, wo as_type
und as_region
überprüft werdenConstVal
- InferVar
Variante hinzufügen ähnlich ty::ReVar
/ ty::TyInfer(TyVar(_))
InferCtxt
- const
Gegenstücke zu int_unification_table
und seinen UnifyKey
Implty::relate::{Relate,TypeRelation}
- Unterstützung von ty::Const
und Handhabung von ConstVal::InferVar
ähnlich wie bei super_combine_tys
für TypenBeachten Sie, dass all dies impl<T, const N: usize> Trait for [T; N] {...}
zulassen sollte, aber keinen konstanten Ausdruck an einen Typ/eine Funktion übergeben sollte, zB ArrayVec<T, 3>
.
Ich würde das gerne mal ausprobieren :)
@jplatte @eddyb Gibt es dazu Neuigkeiten?
@samsartor gab es ein Refactoring, das vor den Hauptimplementierungsarbeiten durchgeführt werden musste. Das ist jetzt fast geschafft (ich warte derzeit auf Feedback). Ich weiß nicht genau, wie viel Arbeit es danach gibt. Mit dem Parsen von const params habe ich anfangs vor dem Refactoring begonnen. Es ist noch nicht fertig, aber ich sollte in der Lage sein, das relativ bald zu erledigen.
@jplatte Es wäre schön, wenn du den Pull-Request verlinken könntest. Ich konnte es nicht finden.
Es gibt noch keine PR. Alle meine Arbeiten finden Sie hier .
Bisher gute Arbeit
Es gibt jetzt eine PR für die Vorarbeiten ( Generics
Refactoring): #45930
Ich denke, dies ist bereits enthalten, aber es wäre gut, die Faltung im C++-Stil zu handhaben, um n-dimensionale Arrays im linearen Algebra-Stil mit unterschiedlichen Längen zu handhaben, z. B. ein 4x4 [[f64;4];4]oder ein 4x3x5x6-dimensionales Array [[[[ f64;6];5];3];4] und in der Lage sein, spezialisierte Methoden dafür UND richtige Merkmalsimplementierungen für Skalare richtig dimensionierte Vektoren usw. richtig zu umhüllen und zu generieren. Siehe https://gist.github.com/huhlig /8b21850b54a75254be4b093551f8c2cb für ein rudimentäres Beispiel.
Ich erinnere mich nicht, dass jemand Fold-Ausdrücke für Rust vorgeschlagen hat, geschweige denn als Teil dieses RFC. Aber da es sich um Rust handelt, gibt es einen Grund, warum wir Fold-Ausdrücke nicht als normales Makro statt als neue dedizierte Syntax implementieren konnten?
Was sind die nächsten Schritte nach der Zusammenlegung von #45930? @jplatte
@kjaleshire Der nächste Schritt besteht darin, das Parsen von const-Generika zu implementieren (siehe Kommentar von @eddyb weiter oben). Ich habe bereits damit begonnen, bevor klar wurde, dass das Refactoring im Refactoring notwendig wäre. Meine bestehenden Arbeiten dazu finden Sie hier ; es wurde jedoch noch nicht rebasiert, seit die Refactoring-PR zusammengeführt wurde.
@jplatte Ich glaube, du wolltest @kjetilkjeka erwähnen.
Danke für das Update! Ich bin sicher, dass ich bei weitem nicht der einzige bin, der sich auf dieses Feature freut. Mach weiter so!
@jplatte möchte nicht ungeduldig sein, aber wurde daran gearbeitet? Brauchst du Hilfe? Soll jemand helfen?
@est31 Entschuldigung. Ich hatte schon eine Weile keine Zeit mehr, daran zu arbeiten, und ich bin mir nicht ganz sicher, wann ich die Zeit haben werde. Vielleicht ist es am besten, wenn jemand anderes dort weitermacht, wo ich aufgehört habe. Ich denke, der Parsing-Teil ist größtenteils erledigt. Es gibt zwei Stellen im Code, an denen ich nicht sicher war, was zu tun ist, wenn ein generischer Parameter ein const-Parameter ist:
Es gibt auch noch keine Tests für das Parsen von const-Generika (und für den hübschen Druckcode, den ich gleichzeitig aktualisiert habe). Ich bin mir nicht sicher, welche anderen Informationen ich bereitstellen kann, die benötigt / hilfreich wären, damit jemand anderes weiter daran arbeiten kann, aber Sie können mich gerne anpingen, wenn etwas an meinem Code unklar ist.
@jplatte ~ None
im ersten und nichts im zweiten~~ (cc @pnkfelix @nikomatsakis)
EDIT : In beiden Fällen nichts zu tun.
@eddyb Sind Sie sicher, dass für das erste verlinkte Snippet None
werden soll? Ich verstehe vielleicht nicht, was hier vor sich geht, aber mir scheint, dass nichts getan werden sollte. Wenn None
zurückgegeben wird, werden andere Parameter, die möglicherweise unsicher sind, übersprungen.
@yodaldevoid Entschuldigung, du hast Recht, ich wusste nicht, dass dies auf Generics
passiert .
Ich würde gerne dort weitermachen , wo @eddyb durch, da ich Zeit hatte, nur zu sehen, ob ich etwas erreichen könnte. Ich bin an dieser Stelle zum Abschnitt "Use / Semantik" gekommen. Ich werde wahrscheinlich früh genug bei einem der Chats vorbeischauen, um Fragen zu stellen.
@yodaldevoid Toll zu hören. Ich weiß nichts über Gitter, aber Sie werden auf jeden Fall viele Anleitungen zu #rustc und/oder #rust-internals im IRC erhalten!
@yodaldevoid : bereit für eine Zusammenarbeit? Ich habe auch einige Nachforschungen zu diesem Thema angestellt (😅) – vielleicht könnten wir die Lücken in der Arbeit des anderen schließen. (Sie können hier sehen, was ich bisher gemacht habe.) Vielleicht könnten wir uns im IRC (#rustc oder #rust-internals) darüber unterhalten?
@varkor Es sieht so aus, als ob du weiter bist als ich. Ich wäre auf jeden Fall bereit mitzuarbeiten. Ich werde versuchen, dich irgendwann im IRC zu schnappen und in der Zwischenzeit zu sehen, ob ich etwas getan habe, was du noch nicht getan hast.
Gibt es diesbezüglich Fortschritte?
Ich schreibe Code für Embedded, und ich brauche wirklich Const-Generika.
@qwerty19106
Hier werden, wenn auch langsam, Fortschritte gemacht. @varkor und ich haben ab und zu daran gearbeitet, wie wir Zeit hatten. Ich habe diese Woche einige Fortschritte gemacht und wir sehen das Licht am Ende des Tunnels für die grundlegende Nutzung.
Über die bloße Implementierung von const-Generika hinaus haben wir (varkor) einige Bereinigungen vorgenommen, um dies alles möglich zu machen (siehe #48149 und #48523). Ich glaube, der aktuelle Plan besteht darin, zu warten, bis diese beiden Pull-Requests abgeschlossen sind, bevor Const-Generika eingezogen werden, aber Varkor kann mehr dazu sagen.
Ich verstehe Ihren Bedarf an const-Generika für eingebettete Arbeit wirklich. Ich habe damit angefangen, weil ich auch wirklich Const-Generika möchte, damit ich große Teile des eingebetteten Codes aufräumen und typsicher machen kann.
TL;DR: Es geht voran, aber das ist komplex. Ich fühle dich auf der eingebetteten Front.
Für das Kontrollkästchen "Dokumentation" wäre es toll, etwas in der rustc-Anleitung zu finden .
Danke @yodaldevoid für deine Antwort. Ich freue mich auf das Ende Ihrer Arbeit.
Update für Neugierige (da war ich auch neugierig). Betreff: die beiden oben genannten PRs: #48523 hat sich zusammengeschlossen und #48149 macht stetig Fortschritte.
@mark-im Gutes Zeug! Schöne Arbeit von @varkor. Wann ist ungefähr die ETA, weißt du? :-)
@alexreg https://github.com/rust-lang/rust/issues/51192#issuecomment -394126083
Hallo, @flip111.
Sieht so aus, als ob die zweite große Refactoring-PR #48149 zusammengeführt wurde :)
/cc ich
Weiter @varkor PR #51880
Ich wollte nur ein kurzes Update geben, da ich weiß, dass einige Leute nach dem Fortschritt bei const-Generika gefragt haben.
Um etwas Kontext zu geben, hatten @yodaldevoid und ich im März eine erste Implementierung, die fast funktionierte (der Großteil der Implementierung schien abgeschlossen zu sein und wir waren gerade dabei, einige verbleibende Abstürze zu beseitigen). Der Zweig, an dem wir arbeiteten, war jedoch Pre-Miri. Als miri zusammengeführt wurde (und in einer Reihe von nachfolgenden PRs), änderte sich der Code für den Umgang mit ständiger Bewertung erheblich, was bedeutete, dass vieles von dem, was wir gemacht hatten, veraltet war.
Darüber hinaus wurde entschieden, dass der generische Parametercode im Allgemeinen bereinigt werden muss, bevor Const-Generika angehängt werden, sowohl um die Lesbarkeit zu verbessern, als auch um Fehler zu machen, bei denen wir in einem bestimmten Fall vergessen haben, mit const-Generika umzugehen machen. Dies wurde in https://github.com/rust-lang/rust/pull/48523 , https://github.com/rust-lang/rust/pull/48149 durchgeführt und wird in https://github abgeschlossen. com/rust-lang/rust/pull/51880. Diese waren etwas komplizierter, als ich ursprünglich erwartet hatte, und es dauerte etwas länger, als erwartet.
In der Zwischenzeit haben @yodaldevoid und ich daran gearbeitet, unsere ursprüngliche Implementierung mit allen nachfolgenden Änderungen in rustc kompatibel zu machen. Es hat eine Weile gedauert, aber wir kommen dahin (obwohl es das ewige Problem gibt, nie so viel Zeit zu haben, wie Sie erwartet hatten). Ich hoffe, dass wir in dieser Hinsicht bald gute Nachrichten haben. (Inzwischen macht https://github.com/rust-lang-nursery/chalk gute Fortschritte, was einige der ursprünglich beschriebenen Schwierigkeiten von @eddyb beheben sollte .)
Einige Leute haben gefragt, wie sie helfen können: Ich denke, zu diesem Zeitpunkt wird es für uns einfacher sein, die anfängliche Implementierung abzuschließen und dann zu sehen, welche Teile Aufmerksamkeit erfordern. Sobald das fertig ist, werden wir viele Tests brauchen, die const-Generika an verschiedenen Stellen verwenden (einschließlich ungültiger, für Fehlermeldungen), also könnten wir das definitiv mit viel Hilfe tun! Wir informieren Sie, wenn das passiert!
Tut mir leid, wenn dies nicht der richtige Ort dafür ist, aber ich habe einen Vorschlag zur Gleichheit abstrakter const-Ausdrücke. Im Allgemeinen reduziert es sich direkt auf volle abhängige Typisierung und unentscheidbares Territorium. In Rust wird jedoch schließlich alles mit konkreten Werten/Typen instanziiert, sodass wir einige Gleichheiten geltend machen und deren Überprüfung auf monomorphe Instanzen verschieben können.
Was ich meine ist, dass eine relativ einfache Möglichkeit, die Gleichheit abstrakter const-Ausdrücke zu überprüfen, die folgende ist:
N+1 == N+1
sollte sofort funktionieren)N+M == M+N
hinzuzufügen (vielleicht in der where
Klausel?). Diese Gleichungen können von der Gleichheitsprüfung (unter Verwendung einer Form von Kongruenzabschluss) verwendet werden. Eine Definition, die keine Prüfung mit diesen bereitgestellten Gleichungen durchführt, wird abgelehnt.false
reduziert wird, könnte entweder ein Kompilierungsfehler vorliegen ("Gleichungen sind Axiome") oder das Merkmal kann nicht instanziiert werden ("Gleichungen sind Einschränkungen")f
mit N
f
parametrisiert ist, wobei N+0==N
, darf diese Gleichung nicht in einem Aufrufer g
da es an jeder monomorphen Stelle überprüft werden muss, an der g
aufgerufen wird.Der Vorteil dieser Methode besteht darin, dass in Rustc selbst kein Theorembeweiser, SMT-Löser oder arithmetischer Umschreiber erforderlich ist. "Nur" eine syntaktische Gleichheitsprüfung von Typen, Modulo-Aliasnamen und Modulo ein vom Benutzer bereitgestellter Satz von Gleichungen.
Der Nachteil ist, dass es für den Benutzer manueller ist, da scheinbar offensichtliche Gleichheiten wie M+N=N+M
explizit hinzugefügt werden müssen.
/// this works directly because of syntactic checks
fn add_end<T:Copy, const N: usize>(a: &[T;N], b: T) -> [T;N+1] {
let x : [T;N+1] = [b;N+1];
x[0..N] = a;
x
}
/// this should work already
fn append<T:Copy, const M:usize, const N:usize>(a: &[T;M], b: &[T;N])
-> [T;{M+N}]
{ … }
/// Here the equation M+M==N must be carried over or checked whenever this function is used
fn sum_pairwise_append<const M: usize, const N: usize>(a: &[i32, M],b: &[i32;N])-> [T;N]
where M+M == N {
let mut res : [i32; N] = append(a,a);
for i in 0 .. N { res[i] += b[i] };
res
}
fn main() {
let a: [i32; 2] = [1;2];
let b: [i32; 4] = [2;4];
let _ = sum_pairwise_append(a, b); // need to check 2+2=4
}
Das bedeutet, dass e1 == e2
immer wahr sein sollte, also ist es nicht wirklich eine where
Einschränkung, sondern eher eine assert_eq!(e1,e2)
, die von der Typprüfung verwendet werden kann. Hier verspricht der Implementierer, dass dies immer der Fall ist, und setzt seine Benutzer Kompilierungsfehlern aus, wenn sie Parameter bereitstellen, die die Gleichung widerlegen.
Hier ist eine Klausel where e1 == e2
eine Bedingung, die für die erfolgreiche Verwendung dieses parametrisierten Merkmals/dieser parametrisierten Funktion erfüllt sein muss. Das bedeutet, dass e1 == e2
nicht immer gelten muss, was für Gleichungen interessant sein kann, die nur über eine Domäne wahr sind, wie etwa (x*y) / z == (y*x) / z
die für z==0
nicht instanziiert werden können.
@c-cube Wir haben bereits ein System für "implizite where
Klauseln", zumindest für Funktionen, abgeleitet von " WF
(wohlgeformtheits-)Regeln", dh wenn Sie eine fn foo<'a, 'b>(x: &'a &'b i32) {}
dann müssen Anrufer 'b: 'a
erfüllen (als Ergebnis von WF(&'a &'b i32)
-> &'b i32: 'a
-> 'b: 'a
).
Wir können dieses System wiederverwenden (tatsächlich ist dies bereits implementiert), um durch die Signatur gegebene Einschränkungen (der Form "dieser konstanter Ausdruck wird erfolgreich ausgewertet") an die Aufrufer zu übertragen, während alles, was nur innerhalb des Rumpfes verwendet wird, immer noch where
Klauseln.
Das meiste andere, was Sie beschreiben, scheint dem Geplanten nahe zu kommen (einschließlich einer Form von "syntaktischer Gleichheit"), aber wir wollen keine "Monomorphisierungszeit"-Überprüfungen, wir können stattdessen Wege haben (entweder implizit oder durch where
Klauseln), um "Constraints an Instanziatoren weiterzugeben", und dann erzeugen wir Fehler, bei denen Einschränkungen "noch zu generisch" sind (also unbekannt, wenn sie gelten), oder sonst, wenn wir sie auswerten können und sie zufällig nicht gelten.
Alles andere, was du beschreibst, scheint dem, was geplant ist, nahe zu kommen
@eddyb kennen Sie Referenzbeiträge, in denen diese Pläne diskutiert werden?
@flip111 Wahrscheinlich einige der RFCs. Vielleicht weiß @Withoutboats es besser.
# 51880 ist fertig: tada:: tada:
@withoutboats @oli-obk Was halten Sie davon, die richtige Vereinheitlichung von Ausdrücken wie N + 1
(von zB [T; N + 1]
) zu blockieren, um eine grundlegende Form der symbolischen Auswertung in miri
?
Ich denke, wir hätten bereits einen Mechanismus, um es zu codieren. Wenn @varkor ConstValue::{Infer,Param}
hinzufügt, können wir damit "(flach) gültige, aber unbekannte" (symbolische) Werte verfolgen und dann auch Werte einbeziehen (hauptsächlich Ganzzahlen) Operationen und Aufrufe darüber.
Alle Nicht- assert
Kontrollfluss-Entscheidungen würden immer noch bekannte Werte erfordern, aber assert
selbst könnte vielleicht als AssertThen(condition, assert message, success value)
.
(Sowohl condition
als auch success value
könnten symbolisch sein!)
Die Möglichkeit, die symbolischen Werte in ty::Const
zu exportieren, bedeutet, dass wir einige der Vereinigungen außerhalb von miri implementieren können und (die meisten?) Operationen als undurchsichtig behandeln.
Wir müssen aufpassen, dass wir nichts von Inferenzvariablen annehmen, aber für zB N + 1
denke ich, dass wir die Vereinigung von zwei Add(Param(N), Bits(1))
.
@varkor Ein Testfall sollte meiner Meinung nach nur mit fauler Normalisierung funktionieren, ohne Vereinheitlichung:
/*const*/ fn append<const N: usize, T>(xs: [T; N - 1], x: T) -> [T; N] {
let new = MaybeUninit::<[T; N]>::uninitialized();
unsafe {
let p = new.as_mut_ptr() as *mut T;
(p as *mut _).write(xs);
p.add(N - 1).write(x);
}
new.into_inner()
}
Das würde nur funktionieren, weil:
N - 1
wird auf der Typebene genau einmal angezeigttype ArrayWithoutLast<T, const N: usize> = [T; N - 1];
)N
ArrayWithoutLast
where
Klausel eingebettet werden", zB [T; N - 1]: Sized
where [T; N - 1]:
missbrauchen (wird das heute ignoriert? Autsch!)where ArrayWithoutLast<T, N>: ...
Insgesamt können wir uns also wahrscheinlich auf WF-Regeln verlassen, um den Benutzern die "Validierung" von const-Ausdrücken zu erzwingen, aber wir möchten immer noch eine Vereinheitlichung für andere Dinge (einschließlich der Vermeidung von Hacks wie ArrayWithoutLast
).
Kleine Frage, die aus der Diskussion über Maßeinheiten stammt. Was sollten wir tun, wenn der Typ generisch über konstant nicht direkt davon abhängt? Zum Beispiel:
struct Foo<T, const N: usize> {
a: T,
// Will such array be "the way" to handle this problem?
// Or do we want some kind of `std:marker::PhantomConstData`? (which can be a simple
// type alias to the same array)
// Or maybe make `PhantomData` to accept constants?
_phantom: [(), N],
}
_phantom: [(), N],
Ich nehme an, Sie meinten [(); N]
, aber das funktioniert nur für usize
.
Ich denke, es wäre sinnvoll, ein spezielles marker::PhantomConst
das jeden const
Typ erlaubt.
Hmm… Beim erneuten Lesen des RFC mit den neuesten Kommentaren frage ich mich: wäre so etwas erlaubt?
struct Foo<T, const V: T> {
_phantom: PhantomConst<T, V>,
}
Ich kann mir nirgendwo ein Verbot vorstellen, hätte aber gedacht, dass es zumindest ein Beispiel verdient.
@Ekleog : Soweit mir bekannt ist, hängen const-Parametertypen anfangs möglicherweise nicht von Typparametern ab (obwohl dies für eine Erweiterung definitiv sinnvoll wäre). (Die Implementierung ist schwieriger, wenn wir dies zulassen, daher ist es sinnvoll, mit der einfachsten Version zu beginnen.)
Wie ist der Fortschritt dabei? Kennen wir eine ungefähre Zeit, zu der dies jede Nacht eintreten sollte?
@Zauberklavier Sobald dieser Pull-Request erledigt ist. Um daraus zu zitieren:
Es ist ein langer Weg
@newpavlov Ich ging davon aus, dass konstante Parameter zulässig wären, ohne in Feldern verwendet zu werden.
Der Grund, warum Typparameter verwendet werden müssen, ist die Varianz, aber Konstanten haben dieses Problem nicht.
@eddyb daran erinnere ich mich auch aus der RFC-Diskussion.
@varkor Das bedeutet also, dass PhantomConst
das von @cuviper vorgeschlagen wurde, im aktuellen Status dieses RFC nicht existieren kann ... richtig? obwohl die neuesten Kommentare darauf hindeuten, dass PhantomConst
sowieso nicht erforderlich ist.
Beeinflussen const-Parameter die Varianz der Typparameter, die sie beinhalten?
Derzeit glaube ich, dass const-Generika keine Typparameter in ihrem Typ haben können.
Mir ist nicht klar, was die erste funktionierende Version davon kann.
Zum Beispiel, ob der Code in diesem Kommentar kompilieren würde:
https://github.com/rust-lang/rfcs/pull/2581#discussion_r230043717
Wenn dieser Code kompiliert würde, wäre dies eine Möglichkeit, Einschränkungen für Konstanten zu erzwingen, bevor eine Syntax dafür erhalten wird.
@rodrimati1992 Sie können wahrscheinlich einfach (): IsTrue<{N < 128}>
ohne einen separaten Typ ausführen .
Ich nehme an, Sie möchten die Einschränkung wiederverwenden, damit sie tatsächlich funktioniert? (weil das Kopieren des N < 128
Ausdrucks zunächst nicht funktioniert)
Du könntest trait Lt128<const N: usize> = IsTrue<{N < 128}>;
, denke ich.
Es gibt auch die Möglichkeit, einfach where [(); 128 - N],
schreiben, denke ich (aber ich bin mir nicht sicher, ob das in Panik gerät).
@rodrimati1992 Sie können wahrscheinlich einfach
(): IsTrue<{N < 128}>
ohne einen separaten Typ ausführen .
Ich nehme an, Sie möchten die Einschränkung wiederverwenden, damit sie tatsächlich funktioniert? (weil das Kopieren desN < 128
Ausdrucks zunächst nicht funktioniert)
Du könntesttrait Lt128<const N: usize> = IsTrue<{N < 128}>;
, denke ich.
Es gibt auch die Möglichkeit, einfachwhere [(); 128 - N],
schreiben, denke ich (aber ich bin mir nicht sicher, ob das in Panik gerät).
Also, mit Merkmalsaliasen könnte ich das so umschreiben?:
trait AssertLessThan128<const N:usize>=
Assert<{N<=128}, (
Str<"uint cannot be constructed with a size larger than 128,the passed size is:",
Usize<N>>
) >;
Die Idee mit dem Assert
(Str<"uint cannot be constructed with a size larger than 128,the passed size is:",Usize<N>>)
von dem ich erwarte, dass es hilfreicher ist als where [(); 128 - N],
, da es Ihnen in der Fehlermeldung sagt, warum der Kompilierzeitfehler aufgetreten ist.
Du kannst auch if !(N < 128) { panic!("...") }
in einer Konstante machen, denke ich?
Ich dachte, die std::fmt-Architektur wird nicht da sein, bis die const trait
Einschränkungen (oder etwas Ähnliches) eintreffen?
Mit diesem Ding können Sie Fehlermeldungen mit mehreren Werten haben (in Typen eingebettet).
Vielleicht ist es besser, darauf zu warten, dass const-Generika darüber sprechen, da es einfacher ist, ausführbare Beispiele zu zeigen.
@rodrimati1992 siehe https://github.com/rust-lang/rfcs/blob/master/text/2345-const-panic.md , es wird einen Sonderfall geben, um const panic!()
bevor es so wäre über andere Funktionen möglich sein (vermutlich wird es zunächst keine benutzerdefinierte Formatierung von Werten ermöglichen).
@rodrimati1992 Ahh, ich
Wie ist der Stand davon?
Der nächste Schritt ist immer noch https://github.com/rust-lang/rust/pull/53645 AFAIK.
Das ist richtig. Um ein schnelles Update für diejenigen zu geben, die die PR #53645 nicht befolgen, haben const generics mindestens einen einfachen Anwendungsfall kompiliert und arbeiten. Was bleibt, ist die Fertigstellung von Codegen für andere Anwendungsfälle, einschließlich const generics in arrys, und einige Bereinigungen der Fehlerausgabe. Danach sollte die PR fertig sein und die Leute können damit spielen.
Könnte nicht zum Thema gehören, aber wird dies einer Variante oder Abzweigung von Chunks und verwandten Methoden ermöglichen, dass Item
ein Array mit fester Größe während der Kompilierung anstelle eines Slice ist? Dies würde es einer for
Schleife ermöglichen, ein unwiderlegbares Muster zu destrukturieren/zu binden, das die Elemente im Chunk auf Variablen wie for (first, second) in arr.chunks(2)
abbildet, was derzeit fehlschlägt, und ich vermute (ohne Begründung) zugegeben), dass es in bestimmten Anwendungsfällen mehr Optimierung ermöglichen würde.
@jeffvandyke Sie denken vielleicht speziell an
@jeffvandyke Sie denken vielleicht speziell an
Ich warte nur darauf, dass dies implementiert und stabilisiert wird, bevor ich einen PR vorschlage, der eine solche API zusätzlich zu ChunksExact
und seinen Varianten hinzufügt :)
Die Laufzeit- und Kompilierzeit-Varianten haben beide ihre Anwendungsfälle, Sie kennen Ihre Chunk-Größe nicht immer im Voraus. Optimierungstechnisch, wenn Sie ChunksExact
mit einer Konstanten verwenden, sollte es nach meinen Tests mehr oder weniger gleich sein. Der Compiler kann alle Grenzüberprüfungen weg optimieren.
umgesetzt und stabilisiert werden
Ich würde vorschlagen, nicht auf die Stabilisierung zu warten, da eine solche API eine weitere gute Verwendung wäre, um diese Funktion zur Stabilisierung auszuüben.
Ich vermute, dass dies für impl
Blöcke noch nicht funktioniert? Ich habe es versucht
#![feature(const_generics)]
struct The<const Val: u64>();
impl<const Val: u64> The<Val> {
fn the() {
println!("{}", Val);
}
}
aber, wie in der Tat gewarnt, stürzte der Compiler mit
thread 'rustc' panicked at 'slice index starts at 1 but ends at 0', src/libcore/slice/mod.rs:2419:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
stack backtrace:
0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
1: std::sys_common::backtrace::_print
2: std::panicking::default_hook::{{closure}}
3: std::panicking::default_hook
4: rustc::util::common::panic_hook
5: std::panicking::rust_panic_with_hook
6: std::panicking::continue_panic_fmt
7: rust_begin_unwind
8: core::panicking::panic_fmt
9: core::slice::slice_index_order_fail
10: rustc_resolve::Resolver::resolve_ident_in_lexical_scope
11: rustc_resolve::Resolver::resolve_path
12: rustc_resolve::Resolver::resolve_path_without_parent_scope
13: rustc_resolve::Resolver::smart_resolve_path_fragment
14: rustc_resolve::Resolver::smart_resolve_path
15: <rustc_resolve::Resolver<'a> as syntax::visit::Visitor<'tcx>>::visit_ty
16: syntax::visit::walk_generic_args
17: syntax::visit::walk_ty
18: rustc_resolve::Resolver::with_generic_param_rib
19: rustc_resolve::Resolver::resolve_item
20: rustc_resolve::Resolver::resolve_crate
21: rustc::util::common::time
22: rustc_interface::passes::configure_and_expand_inner
23: rustc_interface::passes::configure_and_expand::{{closure}}
24: <rustc_data_structures::box_region::PinnedGenerator<I, A, R>>::new
25: rustc_interface::passes::configure_and_expand
26: <rustc_interface::queries::Query<T>>::compute
27: <rustc_interface::queries::Query<T>>::compute
28: <rustc_interface::queries::Query<T>>::compute
29: rustc_interface::queries::<impl rustc_interface::interface::Compiler>::prepare_outputs
30: rustc_interface::interface::run_compiler_in_existing_thread_pool
31: <std::thread::local::LocalKey<T>>::with
32: <scoped_tls::ScopedKey<T>>::set
33: syntax::with_globals
Der Fehler hängt mit const
in impl<const Val: u64>
da das Entfernen dieses Teils andere Fehler verursacht, aber nicht abstürzt.
aber lobt Rust dafür, dass es diese Funktion überhaupt in Betracht zieht. Ich hatte keine Ahnung, ob es funktionieren würde, aber die Syntax schien natürlich, ich habe mich dafür entschieden und lo Rustc sagte, dass es existiert :)
Ich bin nicht überrascht, dass es nicht funktioniert, da dieses mit Spannung erwartete Feature immer noch durch den Compiler gelotet wird (siehe zB #59008 und #58581 und die frühere Arbeit an #53645, die aufgegeben wurde, weil die PR zu groß war .) , aber immer noch als Tracker offen gehalten, um den Fortschritt anzukündigen).
Ich bin mir jedoch nicht sicher, ob von den aktuellen Implementierungsstubs Zugriffe auf Slices außerhalb der Grenzen zu erwarten sind. @varkor @yodaldevoid , kannst du mal nachsehen?
Ja, die Warnung ist richtig: const-Generika sind noch in keiner Form funktionsfähig. Es gibt noch ein paar weitere Pull-Requests, bevor sie bereit sind, damit herumzuspielen.
Tut mir leid, wenn dies nicht der richtige Ort ist, um Fragen zu stellen, aber ich habe nirgendwo besseres gefunden. Nur 2 Fragen:
Kann eine einzelne Funktion bedingt const sein? Zum Beispiel könnte eine Funktion 2 Signaturen haben wie:
const fn foo<A: const T>(x: T) // `A` const implements `T`
fn foo<A: T>(x: A) // `A` implements `T` normally
In diesem Fall ist foo
konstant, wenn A: const T
; Wenn A
nicht const T
implementiert, erfüllt es immer noch die Grenze, aber foo
ist nicht mehr const. Es ist auch wichtig, dass der Autor in der Lage ist, für komplexere Beispiele eine beliebige generische Grenze anzugeben (z. B. where A::Output : Bar
). Ein gutes Beispiel dafür ist sogar einfache Arithmetik:
// This only accepts types that const implement `T`
const fn square_const<T: const Mul>(x: T) -> T {
x*x
}
// This accepts types that implement `T` any way, but it is not const
// This function behaves identically to `square_const`
// But has a different signature and needs a different name
fn square<T: Mul>(x: T) -> T {
square_const(x)
}
let a: u8 = 5;
let b: FooNumber = FooNumber::new();
square_const(a); // `u8` const implements Mul
square(b); // `FooNumber` implements `Mul` normally, so we need a separate function?
Ich bin der festen Überzeugung, dass es definitiv eine Möglichkeit geben sollte, dies zu tun, und ich bin überrascht, dass dies im RFC nicht erwähnt wird (es sei denn, ich habe es übersehen?).
_[weniger wichtig]:_ Wird es eine Möglichkeit geben, im Hauptteil einer const-Funktion zu erkennen, ob wir zur Kompilierzeit oder zur Laufzeit laufen? Ich denke, dass ein Makro, das cfg!()
ähnelt, eine nützliche Erweiterung wäre, wenn auch aus keinem anderen Grund als dem Debuggen. cfg!()
wird derzeit bereits zur Kompilierzeit ausgewertet, daher denke ich (/schätze), dass es in der Lage sein sollte, zu wissen, ob die Funktion als const oder als reguläre Funktion verwendet wird, da auch dies beim Kompilieren bestimmt wird -Zeit. Dies ist jedoch weniger wichtig als meine erste Frage.
@Coder-256
@Coder-256 Diese beiden Fragen beziehen sich nicht auf const-Generika, sondern auf const-Funktionen. Const-Generika dienen dazu, generisch gegenüber consts zu sein (zB foo<2>()
), anstatt dass Funktionen zur Kompilierzeit ausgeführt werden können. Ich kann mir vorstellen, dass Sie deshalb in RFC 2000 keine Antworten auf Ihre Fragen gefunden haben.
@rpjohnst Danke, aber ich glaube, ich war vielleicht unklar. Ich habe bereits sowohl rust-lang/rfcs#2632 als auch rust-lang/rfcs#2000 gesehen, aber ich bin mir ziemlich sicher, dass dies in beiden nicht erwähnt wird. (aber ich könnte mich irren?) Was ich frage, sind bedingte konstante Funktionen. Siehe die Beispiele, die ich geschrieben habe, da es schwer zu beschreiben ist.
@yodaldevoid Whoops du hast recht, wo soll ich das fragen?
Was die Makrofrage angeht, stimme ich zu, dass sie jetzt, wo ich darüber nachdenke, nicht wirklich viel nützt
Was ich frage, sind bedingte const-Funktionen. Siehe die Beispiele, die ich geschrieben habe, da es schwer zu beschreiben ist.
Ihre square_const
Definition kann anstelle von square
(dh sie wird zur Laufzeit zu einer Funktion mit der entsprechenden Signatur gezwungen). Weitere Informationen zu diesem Verhalten finden Sie unter const fn
und Merkmalsgrenzen zu stellen).
@varkor Ich bin nicht davon überzeugt, dass dies der Fall ist (da sich die Merkmalsgrenzen ändern), aber ich frage in rust-lang/rfcs#2632.
Unfallbericht:
Code:
#![feature(const_generics)]
use std::marker::PhantomData;
struct BoundedU32<const LOWER: u32, const UPPER: u32> {
value: u32,
_marker: PhantomData<(LOWER, UPPER)>,
}
Kompilierer:
thread 'rustc' panicked at 'slice index starts at 1 but ends at 0', src/libcore/slice/mod.rs:2419:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
error: internal compiler error: unexpected panic
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports
note: rustc 1.35.0-nightly (719b0d984 2019-03-13) running on x86_64-unknown-linux-gnu
note: compiler flags: -C codegen-units=1 -C debuginfo=2 --crate-type lib
note: some of the compiler flags provided by cargo are hidden
error: Could not compile `playground`.
To learn more, run the command again with --verbose.
@Jezza : const generics ist noch nicht vollständig implementiert und wird voraussichtlich nicht funktionieren. Wir werden eine Ankündigung machen, wenn es Zeit ist, mit #![feature(const_generics)]
zu experimentieren (über die Sie benachrichtigt werden, wenn Sie diese Ausgabe abonniert haben).
@varkor Warte , @Jezzas Beispiel hat _marker: PhantomData<(LOWER, UPPER)>
- das sind zwei const
Parameter, die als Typen verwendet werden, warum hat rustc_resolve
keinen Fehler erzeugt?
Guter Punkt: Ich werde das untersuchen. Beachten Sie, dass dies nur ein Problem mit #![feature(const_generics)]
, also kein kritisches Problem (die Verwendung nichtgenerischer consts führt zu dem erwarteten Fehler). Vielleicht erreicht es nie rustc_resolve
.
~ @eddyb : dieser ICE kommt von resolve_ident_in_lexical_scope
, also stelle ich mir vor, dass er wahrscheinlich mit https://github.com/rust-lang/rust/issues/58307 zusammenhängt. ~
Bearbeiten: Eigentlich vielleicht nicht – das scheint nur für macro_rules!
.
Minimiert:
#![feature(const_generics)]
struct S<const C: u8>(C);
Beheben Sie ICEs, bevor Sie den Fehler "erwarteter Typ, gefundener Wert" erzeugen.
Der Index ist hier außerhalb der Grenzen:
https://github.com/rust-lang/rust/blob/master/src/librustc_resolve/lib.rs#L3919
Aktuelles nightly produziert "Parameter N
wird nie verwendet" für den folgenden Code:
struct Foo<const N: usize> { }
Aus der vorherigen Diskussion dachte ich, compilieren sollte diesen Code akzeptieren. Ist es einfach ein Artefakt der unvollendeten Implementierung?
@newpavlov normale Typparameter müssen verwendet werden, sollte man PhantomData<n>
tun können?
Ich denke, es ist bereits möglich, PhantomData<[(); N]>
zu tun. Ich bin mir jedoch nicht sicher, ob das etwas ist, was wir tatsächlich durchsetzen wollen, da AFAIU der Sinn von PhantomData
besteht, die Varianz zu markieren, und AFAIU gibt keinen Begriff von Varianz in Bezug auf. ein generischer const-Parameter.
Und das funktioniert nur, wenn N vom Typ usize
.
Wir haben entschieden, dass es während der RFC-Diskussion nicht notwendig ist, const-Parameter zu verwenden, und die aktuelle Implementierung ist ein Fehler.
@ withoutboats Können Sie darauf hinweisen, wo im RFC das angegeben ist? Ich habe versucht, etwas in diesem Sinne zu finden und muss es übersehen haben.
Ich weiß nicht, ob es im RFC-Text enthalten war
@ withoutboats Würde es Ihnen etwas
@yodaldevoid Auf diesen Kommentar wurde bereits verwiesen: https://github.com/rust-lang/rust/issues/44580#issuecomment -419576947 . Aber es ging nicht um die RFC-PR, sondern um dieses Thema.
Ich kann den Kommentarverlauf von vor einigen Jahren jetzt nicht durchforsten, aber ich kann erklären: Die Verwendung von Typvariablen ist als Blocker erforderlich, um Sie über die Varianz dieser Parameter nachdenken zu lassen (IMO ist dies auch unnötig und wir könnten standardmäßig kovariant sein, aber das ist ein separates Thema). Konstante Parameter haben keine Wechselwirkung mit der Varianz, so dass dies keine Motivation hätte.
@HadrienG2 Vielen Dank, dass Sie einen relevanten Kommentar gefunden haben.
@ withoutboats Ich habe nicht wirklich erwartet, dass du all die Jahre der Kommentare dafür hattest .
Vielen Dank für die Erklärung. Ich muss zugeben, dass ich mich nie mit Varianz auseinandersetzen kann, egal wie oft ich versuche, sie zu lernen, aber auch ohne das macht es bei erneutem Nachdenken Sinn, keine Kostenparameter zu verwenden. Ich werde die Behebung dieses Fehlers auf unsere Liste der FIXMEs werfen.
Hier ist eine Zusammenfassung der bisherigen Fortschritte bei const-Generika.
Bevor die Arbeit an const-Generika richtig beginnen konnte, mussten einige Refactorings durchgeführt werden. @jplatte hat die Aufgabe mit https://github.com/rust-lang/rust/pull/45930 übernommen. @jplatte begann dann
@yodaldevoid und ich haben dort weitergemacht, wo @jplatte aufgehört hat, stellten aber schnell fest, dass der Fortschritt durch die Art und Weise, wie generische Parameter im Allgemeinen gehandhabt wurden, behindert wurde. So begann eine Reihe von Änderungen, um die Handhabung von Generika in der gesamten Codebasis zu überarbeiten: https://github.com/rust-lang/rust/pull/48149 , https://github.com/rust-lang/rust/pull /48452 , https://github.com/rust-lang/rust/pull/48523 , https://github.com/rust-lang/rust/pull/51880.
Damit könnte die Implementierung von const-Generika ernsthaft beginnen. Seitdem haben @yodaldevoid und ich langsam aber sicher schrittweise Unterstützung für const-Generika hinzugefügt: https://github.com/rust-lang/rust/pull/58191 , https://github.com/rust-lang/rust /pull/58503 , https://github.com/rust-lang/rust/pull/58581 , https://github.com/rust-lang/rust/pull/58583 , https://github.com/rust -lang/rust/pull/59170 , https://github.com/rust-lang/rust/pull/59355 , https://github.com/rust-lang/rust/pull/59415 , https://github .com/rust-lang/rust/pull/60058 , https://github.com/rust-lang/rust/pull/60280 , https://github.com/rust-lang/rust/pull/60284 und die meisten vor kurzem https://github.com/rust-lang/rust/pull/59008. (Diese wurden größtenteils aus der Haupt-Pull-Anfrage von
Wie ist der Stand jetzt? Einige konst Generika Tests jetzt 🎉However arbeiten, gibt es noch diejenigen , die dies nicht tun, und es gibt eine Reihe von FIXME
s über den gesamten Code , dass wir noch arbeiten müssen. Wir nähern uns jetzt jedoch näher und wir sind an einem Punkt angelangt, an dem es einige niedrig hängende Früchte gibt, wenn Sie helfen möchten.
FIXME(const_generics)
die über den Code verstreut sind. Wir planen, uns durch sie zu arbeiten, aber @yodaldevoid und ich haben nur so viel Zeit. t doppelter Aufwand).Ich habe im oberen Post einen Überblick über einige der verbleibenden Implementierungsprobleme geschrieben, bevor const generics für die richtigen Tests bereit ist, um eine grobe Vorstellung davon zu geben, was noch zu tun ist.
Es hat seine Zeit gedauert, aber wir machen stetig Fortschritte und hoffen, das Tempo mithalten zu können. Es ist motivierend, endlich zu sehen, wie sich die Dinge fügen!
Ich habe Monate für diesen Beitrag @varkor erwartet :) Ich bin kein Rust-Zauberer, aber ich würde gerne einen der FIXME
s tackle angehen
Herzlichen Glückwunsch @jplatte , @yodaldevoid und @varkor. Dies ist ein großer Schritt, um benutzerdefinierte Array-like
Merkmale in mathematischen Bibliotheken loszuwerden.
Cross-Posting...
@varkor In Bezug auf Tests wäre es vielleicht hilfreich zu wissen, was voraussichtlich funktioniert. Ich war zum Beispiel überrascht, dass das nicht funktioniert: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=d84ffd15226fcffe02c102edb8ae5cf1
Auch als Referenz für FIXME-Interessierte: https://oli-obk.github.io/fixmeh/
@mark-im versuche {}
um FOO
. Auf diese Weise wird es funktionieren.
Zitat von https://github.com/rust-lang/rfcs/blob/master/text/2000-const-generics.md :
Wenn ein Ausdruck als const-Parameter (außer bei Arrays) angewendet wird, der kein Identitätsausdruck ist, muss der Ausdruck in einem Block enthalten sein. Diese syntaktische Einschränkung ist erforderlich, um zu vermeiden, dass beim Parsen eines Ausdrucks innerhalb eines Typs ein unendlicher Lookahead erforderlich ist.
{expression}
sollte nur notwendig sein, wenn der Ausdruck kein Bezeichner oder Literal ist, das ist ein Fehler.
Zitat des gleichen RFC:
Identitätsausdruck: Ein Ausdruck, der nicht weiter ausgewertet werden kann, außer indem er durch Namen im Gültigkeitsbereich ersetzt wird. Dies beinhaltet alle Literale sowie alle Idents - zB 3, "Hello, world", foo_bar.
:+1: Danke. Ich habe gerade https://github.com/rust-lang/rust/blob/ef9a876f8297e26859469b004047e0946ed10565/src/libsyntax/parse/parser.rs#L6056 gefunden
@mark-im Ja, derzeit können wir beim ersten Parsen nicht den Unterschied zwischen Idents für Typen und Konstanten erkennen. Wir haben die Entscheidung getroffen, wie wir all das in Zukunft handhaben. Ich nehme an, jetzt könnte die Zukunft sein.
Für einen kleinen Hintergrund haben wir über zwei Möglichkeiten gesprochen, an die ich mich erinnern kann, wie man damit umgeht. Die erste Möglichkeit besteht darin, die Benutzer zu zwingen, const-Argumente zu markieren (entweder durch Eingabe von const
vor ihnen oder auf andere Weise). Dies ist aus ergonomischer Sicht nicht großartig, aber aus Sicht des Parsens einfach. Die zweite Möglichkeit besteht darin, alle generischen Argumente gleich zu behandeln, bis wir später während der Kompilierung beginnen, sie mit generischen Parametern zu paaren. Dies ist wahrscheinlich die Methode, die wir wählen möchten, und es wurde bereits daran gearbeitet, dies zu ermöglichen, wir haben nur den letzten Schritt noch nicht getan.
Erstaunliche Arbeit. Ich helfe gerne, indem ich einige der FIXME
's repariere, wenn ich kann. Ich habe überhaupt nicht viel Erfahrung mit der rustc-Codebasis, also beginne ich mit dem FIXME https://github.com/rust-lang/rust/blob/master/src/librustc_mir/monomorphize/item.rs# L397 , wie es scheint, wäre es einfach.
Was ist mit folgendem Code:
trait Foo {
const N: usize;
fn foo() -> [u8; Self::N];
}
Derzeit führt dies zu einem Kompilierungsfehler "kein zugeordnetes Element namens N
für Typ Self
im aktuellen Bereich gefunden". Wird ein solcher Code akzeptiert, nachdem FIXME
s beendet wurden, oder erfordert die Implementierung zusätzlichen Aufwand?
@yodaldevoid
Kurze Frage, bitte entschuldigt, falls dies schon besprochen wurde.
Die zweite Möglichkeit besteht darin, alle generischen Argumente gleich zu behandeln, bis wir später während der Kompilierung beginnen, sie mit generischen Parametern zu paaren. Dies ist wahrscheinlich die Methode, die wir wählen möchten, und es wurde bereits daran gearbeitet, dies zu ermöglichen, wir haben nur den letzten Schritt noch nicht getan.
Läuft dies nicht eher gegen den Strich nach dem Prinzip, dass Rust Funktionssignaturen explizit macht, um Fehler zu vermeiden, die sich auf die Implementierung einer Funktion beziehen? Vielleicht verstehe ich dies jedoch völlig falsch und Sie sprechen davon, generische Parameter innerhalb des Funktionskörpers zu analysieren.
Läuft dies nicht eher gegen den Strich nach dem Prinzip, dass Rust Funktionssignaturen explizit macht, um Fehler zu vermeiden, die sich auf die Implementierung einer Funktion beziehen? Vielleicht verstehe ich dies jedoch völlig falsch und Sie sprechen davon, generische Parameter innerhalb des Funktionskörpers zu analysieren.
Hier geht es darum zu bestimmen, ob Bezeichner, die als generische Parameter an eine Funktion übergeben werden, Konstanten oder Typen sind.
Beispiel:
fn greet<const NAME:&'static str>(){
println!("Hello, {}.",NAME);
}
const HIS_NAME:&'static str="John";
greet::<HIS_NAME>();
greet::<"Dave">();
Beachten Sie, dass Sie beim Definieren der Funktion angeben müssen, dass NAME eine Konstante ist, aber beim Aufrufen muss nicht angegeben werden, dass HIS_NAME eine Konstante innerhalb des Turbofish-Operators ( ::< >
) ist.
@zesterer Wenn dieser Lösungsvorschlag für den Benutzer transparent gemacht wurde (z. B. bei der Definition einer Funktion gab es keinen Unterschied zwischen Parametertypen), liegen Sie richtig. Die Idee hinter der vorgeschlagenen Lösung besteht jedoch nicht darin, das Benutzerverhalten zu ändern, sondern nur die Implementierung zu ändern. Der Benutzer würde immer noch Funktionssignaturen mit expliziten type-, const- und life-time-Parametern schreiben, und generische Argumente müssten immer noch mit einem entsprechenden Parameter übereinstimmen, nur wie wir sie im Compiler parsen, würde sich ändern.
@newpavlov Ich bin überrascht, dass der Code nicht funktioniert. Wenn Sie eine separate Ausgabe einreichen könnten, wäre dies willkommen.
@yodaldevoid @robarnold
Ach, hab dich. Ich habe fälschlicherweise angenommen, dass es sich um Typ- / Funktionssignaturen handelt.
Für die Verwendung von const-Generika für integrierte Array-Typen habe ich #60466 geöffnet, um die Meinungen anderer dazu zu sehen.
Impl-Blöcke scheinen im Moment komplett kaputt zu sein. Ist dies im aktuellen Status der Funktion zu erwarten oder sollte ich ein anderes Problem dazu eröffnen?
Impl-Blöcke scheinen im Moment komplett kaputt zu sein. Ist dies im aktuellen Status der Funktion zu erwarten oder sollte ich ein anderes Problem dazu eröffnen?
Sie müssen {}
s um die N
, impl<const N: usize> Dummy<{N}> {}
hinzufügen.
Aber ja, dann erhalten Sie eine Fehlermeldung über den nicht eingeschränkten Parameter.
Ah danke. Die Sache mit den Zahnspangen vergessen!
Ich bin nicht überrascht, dass es fehlschlägt, sobald das gelöst ist, da dies ein stark minimierter Testfall war.
...aber ja, das sollte wohl funktionieren:
#![feature(const_generics)]
trait Dummy {}
struct Vector<const N: usize> {
data: [f32; N],
}
impl<const N: usize> Dummy for Vector<{N}> {}
...und es schlägt immer noch mit E0207 fehl:
warning: the feature `const_generics` is incomplete and may cause the compiler to crash
--> src/lib.rs:1:12
|
1 | #![feature(const_generics)]
| ^^^^^^^^^^^^^^
error[E0207]: the const parameter `N` is not constrained by the impl trait, self type, or predicates
--> src/lib.rs:9:12
|
9 | impl<const N: usize> Dummy for Vector<{N}> {}
| ^ unconstrained const parameter
error: aborting due to previous error
For more information about this error, try `rustc --explain E0207`.
error: Could not compile `small-matrix`.
To learn more, run the command again with --verbose.
... und ich gehe davon aus, dass
#![feature(const_generics)]
fn dummy<const N: usize>() -> [f32; N] {
[0.; N]
}
->
warning: the feature `const_generics` is incomplete and may cause the compiler to crash
--> src/lib.rs:1:12
|
1 | #![feature(const_generics)]
| ^^^^^^^^^^^^^^
error: internal compiler error: cat_expr Errd
--> src/lib.rs:3:44
|
3 | pub fn dummy<const N: usize>() -> [f32; N] {
| ____________________________________________^
4 | | [0.; N]
5 | | }
| |_^
error: internal compiler error: cat_expr Errd
--> src/lib.rs:4:5
|
4 | [0.; N]
| ^^^^^^^
error: internal compiler error: QualifyAndPromoteConstants: Mir had errors
--> src/lib.rs:3:1
|
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | | [0.; N]
5 | | }
| |_^
error: internal compiler error: broken MIR in DefId(0/0:3 ~ small_matrix[5b35]::dummy[0]) ("return type"): bad type [type error]
--> src/lib.rs:3:1
|
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | | [0.; N]
5 | | }
| |_^
error: internal compiler error: broken MIR in DefId(0/0:3 ~ small_matrix[5b35]::dummy[0]) (LocalDecl { mutability: Mut, is_user_variable: None, internal: false, is_block_tail: None, ty: [type error], user_ty: UserTypeProjections { contents: [] }, name: None, source_info: SourceInfo { span: src/lib.rs:3:1: 5:2, scope: scope[0] }, visibility_scope: scope[0] }): bad type [type error]
--> src/lib.rs:3:1
|
3 | / pub fn dummy<const N: usize>() -> [f32; N] {
4 | | [0.; N]
5 | | }
| |_^
thread 'rustc' panicked at 'no errors encountered even though `delay_span_bug` issued', src/librustc_errors/lib.rs:356:17
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
error: internal compiler error: unexpected panic
note: the compiler unexpectedly panicked. this is a bug.
note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports
note: rustc 1.36.0-nightly (cfdc84a00 2019-05-07) running on x86_64-unknown-linux-gnu
note: compiler flags: -C debuginfo=2 -C incremental --crate-type lib
note: some of the compiler flags provided by cargo are hidden
@HadrienG2
Siehe #60619 und #60632.
Ich denke, dieser Code sollte bauen:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e45b7b5e881732ad80b7015fc2d3795c
aber es fehler derzeit:
error[E0119]: widersprüchliche Implementierungen von Trait std::convert::TryFrom<[type error]>
für Typ MyArray<_, _>
:
Leider ist das eine Einschränkung von Try{From,Into}
und hat nichts mit const-Generika zu tun. Sie können in vielen Fällen nicht generisch implementiert werden: Laufstall
@oberien in Ihrem Beispiel ist T
ein fremder Typ, und es ist nicht bekannt, ob T: Into<Foo<T>>
gilt oder nicht.
In meinem Beispiel [T; N] ist ein in libcore definierter Typ und ist #[fundamental]
(ich weiß nicht einmal, was das bedeutet), also denke ich, dass der Trait-Resolver tatsächlich weiß, dass [T; N]: Into<MyArray<T, {N}>>
nicht gilt, und da sollte kein Konflikt sein, denke ich?
[...] und ist
#[fundamental]
[...]
Dies ist das Problem, fundamental
hängt damit zusammen, dass nachgeschaltete Benutzer Trait-Implementierungen definieren können, wenn sie einen lokalen Typ in den grundlegenden Wrapper-Typ einschließen. In diesem Playground können Sie sehen, dass Ihre Implementierung zwischen nicht fundamentalen Typen funktioniert, aber fehlschlägt, wenn der Typ from
mit fundamental
markiert ist (mit einer viel besseren Fehlermeldung).
@varkor @yodaldevoid Also, ich landete gerade in einer Situation, in der S<{N == 0}>
(wobei S
einen const bool
Parameter und N
einen const usize
) ist aus Sicht des Compilers weder S<{true}>
noch S<{false}>
(in dem Sinne, dass er nicht die gleichen Eigenschaften wie beide implementiert) und ich bin mir nicht sicher, ob dies ein Fehler oder ein Fehler ist erwartete Einschränkung des aktuellen Prototyps. Können Sie mir eine kurze Auffrischung der aktuellen Vereinigungslogik geben?
Als minimiertes Beispiel ist dies...
// Machinery for type level if-then-else
struct Test<const B: bool>;
trait IfFn<S, Z> { type Out; }
impl<S, Z> IfFn<S, Z> for Test<{true }> { type Out = S; }
impl<S, Z> IfFn<S, Z> for Test<{false}> { type Out = Z; }
// Returns an u8 if B is true, else an u16
fn should_be_ok<const B: bool>() -> <Test<{B}> as IfFn<u8, u16>>::Out {
0
}
... erzeugt folgenden Fehler:
error[E0277]: the trait bound `Test<B>: IfFn<u8, u16>` is not satisfied
--> src/lib.rs:32:1
|
32 | / fn should_be_ok<const B: bool>() -> <Test<{B}> as IfFn<u8, u16>>::Out {
33 | | 0
34 | | }
| |_^ the trait `IfFn<u8, u16>` is not implemented for `Test<B>`
|
= help: the following implementations were found:
<Test<false> as IfFn<S, Z>>
<Test<true> as IfFn<S, Z>>
Ich weiß es nicht genau, aber ich denke, das Problem könnte sein, dass der Compiler nicht die Logik hat, um zu entscheiden, ob das Literal 0
ein u8
oder ein u16
. Probiere etwas wie: 0u8 as _
Das ist es nicht, die Fehlermeldung bleibt die gleiche.
Wenn Sie jedoch eine Version wünschen, die keine Inferenz vom Ganzzahltyp enthält, ist hier eine dümmere Minimierung:
struct Test<const B: bool>;
trait Not { const B: bool; }
impl Not for Test<{true }> { const B: bool = false; }
impl Not for Test<{false}> { const B: bool = true; }
fn should_be_ok<const B: bool>() -> bool {
<Test<{B}> as Not>::B
}
Scheitert immer noch, weil Test<{B}>
angeblich Not
nicht implementiert hat.
Hi ! Ich bin mir nicht wirklich sicher, ob die Vereinheitlichung auf konstanten Werten funktionieren soll oder nicht, aber es ist ein neues Vereinheitlichungssystem in Arbeit ("Kreide"), an dem gearbeitet wird, aber nicht das ist, was rustc derzeit verwendet.
Also, was Sie versuchen zu tun, muss _vielleicht_ warten, bis die Kreide fertig ist. Und selbst dann bin ich mir nicht sicher, ob es auch mit Kreide funktioniert oder funktionieren soll.
Informationen zum Fortschritt finden Sie unter
@HadrienG2 könnten Sie dafür ein separates Problem
@carado Unification wird auf consts durchgeführt oder nichts davon würde funktionieren. Const-Generika werden nicht auf Kreide blockiert.
@HadrienG2 sobald wir die Spezialisierung für const-Generika haben, können Sie so etwas tun wie
struct Test<const B: bool>;
trait Not { const B: bool; }
impl<const B: bool> Not for Test<B> { const B: bool = false; }
impl Not for Test<{false}> { const B: bool = true; }
fn should_be_ok<const B: bool>() -> bool {
<Test<{B}> as Not>::B
}
Was Sie tun möchten, ist eine Art Vollständigkeitsprüfung, wie sie es für ein Match gibt, das gerade auf das Eigenschaftensystem erweitert wurde. Weiß nicht, dass es in irgendeinem Thread erwähnt wird.
@yodaldevoid Abgelegt als https://github.com/rust-lang/rust/issues/61537 .
@carado Chalk betrifft nicht die tatsächlichen "Typsystem-Primitive", von denen dies eine ist.
Das heißt, rustc
noch (auch heute, da Chalk bereits teilweise integriert ist) den Teil der Vereinheitlichung implementieren, der sich mit für Chalk undurchsichtigen Entitäten befasst (zB zwei verschiedene Konstantenausdrücke).
Wenn wir das implementieren (wofür wir sowieso keine Pläne für die nahe Zukunft haben), wird es sich nicht viel ändern, selbst nachdem Chalk das Eigenschaftssystem ersetzt hat (was sein Hauptzweck ist, nicht die Vereinheitlichung).
Oh, mein Böses dann! Ich glaube, ich weiß nicht wirklich, wovon ich rede.
Eines der großartigen Dinge, die const generics bieten würde, sind kompilierte einfache Funktionen, zum Beispiel faktorielle.
Beispiel:
fn factorial<const X: i32>() -> Option<i32> {
match X {
i if i < 0 => None,
0 => Some(1),
1 => Some(1),
i => Some(factorial::<i - 1>().unwrap() + i)
}
}
Soweit ich weiß, gibt es eine begrenzte Unterstützung für const-Generika in Nightly? Wenn ja, gibt es einen Ort, der besser organisiert ist als diese Ausgabe, an dem ich finde, wie man ihn verwendet und so?
@dancojocaru2000 const
Funktionen sollten der bevorzugte Weg für die Berechnung auf Wertebene zur Kompilierzeit sein, zB https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=4994b7ca9cda0bfc44f5359443431378 , was passiert nicht funktionieren, weil sie noch nicht vollständig implementiert sind. Aber auch const
Generika sind es auch nicht.
Ihr Snippet könnte problematisch sein, weil ich nicht sicher bin, ob match
für const
Argumente funktionieren soll. Sie haben im Code auch Addition mit Multiplikation gemischt, aber das spielt keine allzu große Rolle.
Ich denke, Sie können mit einer Peano-Codierung bereits zur Kompilierzeit Fakultäten durchführen, die oft als Demo für andere funktionale Sprachen gezeigt wird.
Kompilierzeit berechnete einfache Funktionen
Ich denke das passendere Feature wäre const fn .
Theoretisch würde Ihr Code fast so funktionieren, wie er ist
#![feature(const_generics)]
fn factorial<const X: i32>() -> Option<i32> {
match X {
i if i < 0 => None,
0 => Some(1),
1 => Some(1),
i => Some(factorial::<{X - 1}>().unwrap() + i)
}
}
fn main() {
println!("{:?}", factorial::<10>());
}
Aber es geht nicht:
warning: the feature `const_generics` is incomplete and may cause the compiler to crash
--> src/main.rs:1:12
|
1 | #![feature(const_generics)]
| ^^^^^^^^^^^^^^
error: internal compiler error: src/librustc_codegen_ssa/mir/operand.rs:79: unevaluated constant in `OperandRef::from_const`
Obwohl Sie wirklich einen unsignierten Typ verwenden sollten:
#![feature(const_generics)]
fn factorial<const X: u32>() -> u32 {
match X {
0 => 1,
1 => 1,
_ => factorial::<{ X - 1 }>() + X,
}
}
fn main() {
println!("{:?}", factorial::<10>());
}
Gibt es einen Ort, der besser organisiert ist als diese Ausgabe, wo ich finde, wie man es benutzt und so?
Das wird normalerweise im instabilen Buch behandelt , aber jetzt gibt es dort nichts Nützliches. Wenn Sie Teile entdecken, könnten Sie vielleicht darüber nachdenken, einige Inhalte dafür zu skizzieren?
Fakultäten zur Kompilierzeit mit einer Peano-Kodierung,
Ein Beispiel dafür finden Sie in der typenum-Kiste
Theoretisch würde Ihr Code fast so funktionieren, wie er ist
Beim Aufrufen von factorial::<0>
das _ => factorial::<{X - 1}>() * X
zu kodiert sein, oder? Dies würde jedoch zu einem Integer-Unterlauf führen.
Kann ich erwarten, dass Code wie dieser in Zukunft kompiliert wird?
#![feature(const_generics)]
trait NeedsDrop<const B: bool> { }
impl<T> NeedsDrop<std::mem::needs_drop<T>()> for T { }
fn foo<D: NeedsDrop<false>>(d: D) { }
Dies kreuzt sich mit einer kürzlich durchgeführten Implementierung einiger [T; N]
Arrays für N <= 32
als konstante Generika, aber der folgende Code kompiliert nicht auf einem neuesten ( 4bb6b4a5e 2019-07-11
) Abend mit verschiedenen Fehlern the trait `std::array::LengthAtMost32` is not implemented for `[u64; _]`
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BigintRepresentation<
const N: usize
>(pub [u64; N])
where [u64; N]: std::array::LengthAtMost32,
[u64; N*2]: std::array::LengthAtMost32;
obwohl folgendes gilt:
#[derive(Copy, Clone, PartialEq, Eq)]
pub struct BigintRepresentation<
const N: usize
>(pub [u64; N])
where [u64; N]: std::array::LengthAtMost32;
Anscheinend ist N*2
keine gültige Konstante oder kein gültiger Qualifizierer für eine solche Einschränkung
@shamatar N*2
sollte mit geschweiften Klammern wie `[u64; {N*2}]
Ich denke, die Fehler "Trait nicht implementiert" kommen von den Ableitungsmakros, die die Grenze für [u64; {N*2}]
nicht hinzufügen, aber wenn die Ableitungen entfernt werden, gibt es derzeit einen ICE:
```Fehler: interner Compilerfehler: Konstante im Typ hatte einen ignorierten Fehler: TooGeneric
--> src/main.rs:5:1
|
5 | / pub struct BigintRepresentation<
6 | | const N: verwenden
7 | | >(Pub [u64; N])
8 | | wobei [u64; N]: std::array::LengthAtMost32,
9 | | [u64; {N*2}]: std::array::LengthAtMost32;
| |_____________________________________________^
Thread 'rustc' geriet in Panik bei 'keine Fehler aufgetreten, obwohl delay_span_bug
ausgegeben wurde', src/librustc_errors/lib.rs:366:17
Hinweis: Führen Sie mit der Umgebungsvariablen RUST_BACKTRACE=1
, um einen Backtrace anzuzeigen.
Dieser Code kompiliert nicht:
#![feature(const_generics)]
struct Foo<const X: usize>([u8; X]);
impl<const X: usize> Foo<X> {
fn new() -> Self {
Self([0u8; X])
}
}
warning: the feature `const_generics` is incomplete and may cause the compiler to crash
--> src/lib.rs:1:12
|
1 | #![feature(const_generics)]
| ^^^^^^^^^^^^^^
error[E0573]: expected type, found const parameter `X`
--> src/lib.rs:5:26
|
5 | impl<const X: usize> Foo<X> {
| ^
| |
| not a type
| help: try using the variant's enum: `regex_syntax::ast::HexLiteralKind`
error[E0107]: wrong number of const arguments: expected 1, found 0
--> src/lib.rs:5:22
|
5 | impl<const X: usize> Foo<X> {
| ^^^^^^ expected 1 const argument
error[E0107]: wrong number of type arguments: expected 0, found 1
--> src/lib.rs:5:26
|
5 | impl<const X: usize> Foo<X> {
| ^ unexpected type argument
error: aborting due to 3 previous errors
@npmccallum musst du Foo<{X}>
@pengowen123 Ja, es kompiliert (stürzt den Compiler mit constant in type had an ignored error: TooGeneric
) ohne derive
, aber dann kann es eine separate Frage sein, ob eine solche "doppelte Einschränkung" effektiv N <= 32
ist oder nicht N <= 16
vom Compiler nicht angewendet.
Ausdrücke, die Parameter erwähnen, werden wahrscheinlich noch eine Weile nicht funktionieren, [T; N]
und Foo<{N}>
werden so geschrieben, dass sie keine Ausdrücke mit der Erwähnung von N
, sondern direkt auf den Parameter N
verweisen und das größere Problem umgehen.
Die Verwendung von Self
führt zu einem Absturz.
Selbst wenn Sie es gegen Value<{C}>
austauschen, stürzt es immer noch ab.
#![feature(const_generics)]
struct Value<const C: usize>;
impl<const C: usize> Value<{C}> {
pub fn new() -> Self {
unimplemented!()
}
}
pub fn main() {
let value = Value::new();
}
Dein Snippet stürzt beim Laufstall nicht ab: https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=d3fda06d2e8b3eb739afa99d5da84a33
Wie bei #61338 ist die Ursache des Problems die inkrementelle Kompilierung.
Kompilierzeit berechnete einfache Funktionen
Ich denke das passendere Feature wäre const fn .
Theoretisch würde Ihr Code fast so funktionieren, wie er ist
#![feature(const_generics)] fn factorial<const X: i32>() -> Option<i32> { match X { i if i < 0 => None, 0 => Some(1), 1 => Some(1), i => Some(factorial::<{X - 1}>().unwrap() + i) } } fn main() { println!("{:?}", factorial::<10>()); }
Aber es geht nicht:
warning: the feature `const_generics` is incomplete and may cause the compiler to crash --> src/main.rs:1:12 | 1 | #![feature(const_generics)] | ^^^^^^^^^^^^^^ error: internal compiler error: src/librustc_codegen_ssa/mir/operand.rs:79: unevaluated constant in `OperandRef::from_const`
Obwohl Sie wirklich einen unsignierten Typ verwenden sollten:
#![feature(const_generics)] fn factorial<const X: u32>() -> u32 { match X { 0 => 1, 1 => 1, _ => factorial::<{ X - 1 }>() + X, } } fn main() { println!("{:?}", factorial::<10>()); }
Diese Funktion ist auch für mich nützlich, const fn
ist nicht genug. Ich möchte es für ein N-dimensionales Array mit einer Abschlussbedingung von dimensions=0 verwenden.
Ich kann eine Struktur erstellen:
#![feature(const_generics)]
struct MyArray<T: Default, const len: usize> {
real_array: [T; len]
}
impl<T: Default, const len: usize> MyArray<T, {len}> {
fn new() -> Self {
return MyArray {
real_array: [Default::default(); len]
}
}
}
Dies schlägt fehl, weil ich das Array aus folgenden Gründen nicht initialisieren kann:
error: array lengths can't depend on generic parameters
--> src/main.rs:9:46
|
9 | real_array: [Default::default(); len]
|
Ich habe versucht, es zu umgehen, indem ich const-Werte auf den generischen Wert gesetzt habe.
Das geht über den obigen Fehler hinaus, aber der Compiler stürzt dann ab.
error: internal compiler error: src/librustc/ty/subst.rs:597: const parameter `height/#0` (Const { ty: usize, val: Param(height/#0) }/0) out of range when substituting substs=[]
Die Sprache benötigt dringend grundlegende Konstanten-Generika, damit die Standardbibliothek nicht jede Funktion für jede Array-Größe definieren muss. Dies ist der einzige Grund, warum ich keinen Rost verwende. Brauchen wir wirklich komplette Kompilierzeitfunktionen?
Ich könnte mich irren, aber einfache Integer-Ausdrücke sollten ausreichen und ich hoffe, dass niemand seine Zeit damit verschwendet, sicherzustellen, dass diese verrückten Beispiele funktionieren.
Die Sprache benötigt dringend grundlegende konstante Generics, damit die Standardbibliothek nicht jede Funktion für jede Array-Größe definieren muss
Da gibt es schon einige Bemühungen https://github.com/rust-lang/rust/issues/61415.
Ich hoffe, niemand verschwendet seine Zeit damit, dass diese verrückten Beispiele funktionieren.
Manche Leute tun das, ich kann kein Problem damit sehen ;)
Dies ist der einzige Grund, warum ich keinen Rost verwende.
Dies ist der am wenigsten interessante Grund, den ich für jemanden zitiert habe, der keinen Rost verwendet. Sind Sie sicher, dass Sie die Wahrheit sagen?
aber einfache ganzzahlige Ausdrücke sollten ausreichen
Selbst diesen reduzierten Umfang zu realisieren ist unglaublich schwer. Probieren Sie es aus, wir haben wirklich fähige Leute, die das machen und es gibt einen Grund, warum es so lange dauert.
Leider konnte ich in letzter Zeit nicht viel Zeit auf die Behebung von Fehlern in Const-Generika (oder allgemeiner auf Rust) verwenden. Wenn sich jemand daran beteiligen möchte, const-Generika voranzutreiben, gebe ich gerne Ratschläge, um die offenen Probleme anzugehen und Fehlerkorrekturen zu überprüfen, obwohl es wahrscheinlich eine Weile dauern wird, bis ich mich wieder darauf konzentrieren kann.
Ich hoffe, niemand verschwendet seine Zeit damit, dass diese verrückten Beispiele funktionieren.
Niemand ist es, miri
ist bereits viel mächtiger als C++ constexpr
.
Und niemand arbeitet an etwas Ausgefallenem wie der Ableitung von N = M
aus N + 1 = M + 1
.
Die meisten dieser Fehler betreffen nicht die Ausdrücke in ihnen, sondern das Typsystem und wie const
Generika mit allen anderen Funktionen interagieren.
Sie wären immer noch da, wenn Sie nur const
Generika und Integer-Literale hätten.
Übrigens, ich denke, der Fehler "Array-Längen können nicht von generischen Parametern abhängen" für [expr; N]
Ausdrücke wird nicht benötigt. Wir könnten den gleichen Trick wie für [T; N]
Typen verwenden und ziehen N
ohne es als Ausdruck auszuwerten.
Ich möchte das zwar versuchen, aber ich bin mir nicht sicher, ob ich die richtige Person bin. Ich habe wenig Rost verwendet und kenne sehr wenig Compilertheorie. Ich brauche vielleicht ein bisschen Coaching, aber ich bin auf jeden Fall bereit. 😄
Bearbeiten: Ich habe jedoch ein bisschen Erfahrung mit Software im Allgemeinen.
@varkor , ich habe auf der Suche nach etwas Nützlichem für rustc
gesucht und würde gerne einsteigen und helfen. Vielen Dank auch für Ihre Offenheit, dass Sie sich Zeit dafür nehmen!
@varkor , ich
Eine Sache, die wir jetzt tun könnten, ist mir gerade klar geworden, dass Foo::<{...}>
/ [expr; ...]
Ausdrücke auf generische Parameter verweisen dürfen (im ...
Teil).
Dies liegt daran, dass Ausdrücke irgendwo innerhalb eines Bodys verschachtelt werden müssen , was dazu neigt , zyklische Abhängigkeiten zu vermeiden.
Allerdings mache ich mir Sorgen, dass zB [(); [0; 1][0]]
in einer Signatur kaputt gehen würde, so dass wir wahrscheinlich sowieso einen Kraterlauf brauchen
Für jeden, der daran interessiert ist, bei const-Generika zu helfen, würde ich empfehlen, einen Blick auf die Liste der offenen Probleme mit const-Generika zu werfen und zu untersuchen, was für Sie interessant ist. Einige haben bereits eine Untersuchung durchgeführt, die in den Kommentaren stehen sollte. Die meisten Probleme werden wahrscheinlich ein wenig Nachforschung erfordern. Es ist hilfreich, einen Kommentar zu hinterlassen, wenn Sie vorhaben, etwas zu untersuchen, damit wir keine doppelten Anstrengungen unternehmen (aber Sie können den Problembevollmächtigten oft ignorieren, wenn es für eine Weile keine Aktivitäten gab). Wenn Sie Fragen haben, können Sie diese in den Kommentaren zum Thema oder auf Discord oder Zulip stellen. @eddyb , @yodaldevoid , @oli-obk und ich kennen viele der relevanten Bereiche und sind gute Ansprechpartner. Vielen Dank für Ihr Interesse!
cc @hameerabbasi , @ranweiler
Fragen zu const Generika:
const
Parameters beliebigen Typs?const
Generika sind nicht implementiert / stürzen den Compiler ab?PS (Mitwirkende:) Vielen Dank für die Arbeit an dieser Funktion.
const-generics befindet sich noch in der Entwicklung, daher gibt es noch keine offiziellen Dokumente dazu. Bei den anderen beiden Fragen bin ich mir nicht ganz sicher. Als ich das letzte Mal const-Generics verwendet habe, sind sie abgestürzt, als ich einige const-Generic-Parameter angegeben habe, aber es ist fast einen Monat her, seit ich das letzte Mal etwas damit gemacht habe, also können sich die Dinge seitdem geändert haben.
Und niemand arbeitet an etwas Ausgefallenem wie der Ableitung von
N = M
ausN + 1 = M + 1
.
Dies wäre sehr nützlich, um einen Solver für solche Typbeschränkungen zu haben. Wenn beispielsweise eine Addition mit zwei N-Bit-Zahlen implementiert wird, sollte die Rückgabegröße (N + 1) sein, um ein Überlaufbit zu berücksichtigen. Wenn (zum Beispiel) zwei 5-Bit-Zahlen angegeben werden, sollte der Löser überprüfen, ob N + 1 = 6
. Hoffentlich kann dies später auf const-Generika geschraubt werden :)
@flip111 Ja, ich denke, der Plan ist, dies später hinzuzufügen, aber diese Art von allgemeinen Ausdruckseinschränkungen sind sehr komplex und schwer zu implementieren. Also werden wir sie vielleicht für ein paar Jahre nicht sehen.
Um ehrlich zu sein, klingt die Lösung dieser Art von Constraint-basierten Problemen sehr nach einem Job für Prolog. Vielleicht ist Huckepack auf der Chalk-Engine eine Option? Afaik löst es jedoch Eigenschaften, oder?
Übrigens, ich liebe das Thema, obwohl ich es mir nicht leisten kann, bei diesem Geldautomaten zu helfen. Vielen Dank an alle, die an Const Generics arbeiten, für Ihre Zeit und Ihr Engagement. 💐
Ein kleines Update: Ich arbeite mich den Rust Tree hoch. Ich habe vor, einen Beitrag zu leisten, möchte aber ein Selbststudium an dem Ort machen, an dem ich kein übermäßiges Coaching brauche.
Ich kann eine Struktur erstellen:
#![feature(const_generics)] struct MyArray<T: Default, const len: usize> { real_array: [T; len] } impl<T: Default, const len: usize> MyArray<T, {len}> { fn new() -> Self { return MyArray { real_array: [Default::default(); len] } } }
Dies schlägt fehl, weil ich das Array aus folgenden Gründen nicht initialisieren kann:
error: array lengths can't depend on generic parameters --> src/main.rs:9:46 | 9 | real_array: [Default::default(); len] |
Ich bin auf das gleiche Problem gestoßen, habe aber eine Problemumgehung mit
MaybeUninit
:
https://play.rust-lang.org/?version=nightly&mode=release&edition=2018&gist=3100d5f7a4efd844954a6fa5e8b8c526
Es ist offensichtlich nur ein Workaround, um richtig initialisierte Arrays zu erhalten, aber für mich reicht dies aus, bis ein geeigneter Weg zur Verfügung gestellt wird.
Hinweis: Ich denke, der Code sollte immer wie beabsichtigt funktionieren, aber wenn jemand einen Fehler bei der Verwendung des unsicheren findet, würde ich ihn gerne beheben.
@raidwas Sie =
, um nicht initialisierten Speicher zu initialisieren. Tun Sie dies stattdessen,
@KrishnaSannasi danke, guter Fang: D (technisch gesehen habe ich keinen nicht initialisierten Speicher
Technisch gesehen ist das Fallenlassen sogar von Floats undefiniert, aber es ist derzeit nicht ausnutzbar.
Technisch gesehen ist das Fallenlassen sogar von Floats undefiniert, aber es ist derzeit nicht ausnutzbar.
Ich hätte erwartet, dass das Löschen eines beliebigen Copy
Typs, auch nicht initialisierter, ein No-Op ist. Vielleicht lohnt es sich, das zu ändern.
Technisch gesehen ist das Fallenlassen sogar von Floats undefiniert, aber es ist derzeit nicht ausnutzbar.
Ich hätte erwartet, dass das Löschen eines beliebigen
Copy
Typs, auch nicht initialisierter, ein No-Op ist. Vielleicht lohnt es sich, das zu ändern.
Es ist immer noch technisch UB. Während das sichere Löschen von definierten Copy
Werten ein No-Op ist, kann der Compiler entscheiden, einige unerwartete Optimierungen vorzunehmen, wenn Sie versuchen, nicht initialisierten Speicher jeglicher Art zu löschen (zB den gesamten Code entfernen, der diesen Wert jemals berührt). könnte Dinge kaputt machen. Das ist mein Verständnis davon.
Um nicht unhöflich zu sein, aber ≥ 60 Leute werden über Kommentare zu diesem Thread benachrichtigt, daher sollten wir die Diskussion außerhalb des Themas wahrscheinlich auf ein Minimum beschränken. Nutzen wir das lieber, um die Arbeit an const-Generika zu koordinieren, denn das wollen wir alle.
Absturz, wenn ich _Aliases verwendet habe, die auf Typ mit const-param_ von einer anderen Kiste zeigen (Abhängigkeit).
Zum Beispiel:
Pub-Typ Alias
pub struct Struct
- crate B
externe Kiste crate_a;
benutze crate_a::Alias;
pub fn inner_fn(v: Alias
```
Absturzprotokoll (44580).txt
@fzzr- klingt wie #64730
Klingt auch wie https://github.com/rust-lang/rust/issues/61624
Ich habe etwas Code auf Const-Generika umgestellt und es scheint, als gäbe es wirklich zwei verschiedene Anwendungsfälle. Ich bin mir nicht sicher, ob sie zusammengeführt werden sollten oder ob wir besser mit zwei verschiedenen Syntaxen für die Anwendungsfälle bedient sind.
Viele meiner Verwendungen sind nicht wirklich für Typen gedacht, bei denen ein konstanter Wert eine Rolle bei der Bestimmung der Typen spielt, sondern um Funktionen zu nutzen, die für nicht konstante / literale Werte gesperrt sind (noch nicht vollständig unterstützt, z. aber letztendlich muss es sein).
IMHO sollten wir formell "const arguments" neben const generics landen, damit die Leute keinen mutierten Code schreiben, der tausend "const generics" (eine für jedes Argument) einführt, um den Compiler dazu zu bringen, bestimmte Variablen als Literale / Konstanten auszuwerten.
@mqudsi Könnten Sie ein Beispiel geben? Es gibt bereits Pläne und laufende Grundlagenarbeit, um const eval leistungsfähiger zu machen. Das ist jedoch orthogonal zu const-Generika.
Was ich meine ist, wenn Sie typischen Parser-Code wie den folgenden zur Wiederverwendung umgestalten möchten:
let mut result: u32 = 0;
let mut digits = 0;
while digits < max_digits && src.has_remaining() {
match src.get_u8() {
d<strong i="6">@b</strong>'0'..=b'9' => {
result = result*10 + (d - b'0') as u32;
digits += 1;
},
b'>' => match result {
0..=191 => break, // valid range
_ => return Err(DecoderError::OutOfRange),
},
_ => return Err(DecoderError::Malformed)
}
}
Normalerweise würden Sie es in eine Funktion verschieben, außer dass Folgendes nicht funktioniert, da bestimmte Werte, die beim Mustervergleich verwendet werden, zu Variablen geworden sind:
#[inline(always)]
fn read_str_digits<B: bytes::buf::Buf>(src: &mut B, stop_word: u8,
max_digits: u8, min_value: u32, max_value: u32) -> Result<u32, DecoderError> {
let mut result: u32 = 0;
let mut digits = 0;
while digits < max_digits && src.has_remaining() {
match src.get_u8() {
d<strong i="10">@b</strong>'0'..=b'9' => {
result = result*10 + (d - b'0') as u32;
digits += 1;
},
stop_word => match result {
min_value..=max_value => break, // valid range
_ => return Err(DecoderError::OutOfRange),
},
_ => return Err(DecoderError::Malformed)
}
}
...
}
Wenn const generisches vor const-Argumenten landet, kann ich const generisches plötzlich missbrauchen, um Folgendes zu erreichen:
#[inline(always)]
fn read_str_digits<const MinValue: u32, const MaxValue: u32, const StopWord: u8, B: bytes::buf::Buf>
(src: &mut B, max_digits: u8) -> Result<u32, DecoderError> {
let mut result: u32 = 0;
let mut digits = 0;
while digits < max_digits && src.has_remaining() {
match src.get_u8() {
d<strong i="14">@b</strong>'0'..=b'9' => {
result = result*10 + (d - b'0') as u32;
digits += 1;
},
StopWord => match result {
MinValue..=MaxValue => break, // valid range
_ => return Err(DecoderError::OutOfRange),
},
_ => return Err(DecoderError::Malformed)
}
}
...
}
Ab heute funktioniert selbst dieser Code nicht, weil der Compiler die generischen const-Werte nicht als Konstanten erkennt, die für die Verwendung in einem Muster gültig sind, aber das ist offensichtlich falsch und muss behandelt werden, bevor RFC 2000 landen kann. Versteh mich nicht falsch, ich kämpfe seit Jahren für generische Konstanten und habe PRs bereit für ein Dutzend große Kisten (ich kann es kaum erwarten, die Zeitzone in chrono
einer konstanten generischen und alle verschiedenen DateTime
Typen vereinen), aber imho, wenn es möglich wird, const-Generika zu missbrauchen, um const-Argumente zu fälschen (wobei mit "const" wirklich "wörtlich" gemeint ist), dann werden Sie weit verbreitet sein Missbrauch davon.
Es ist nicht unbedingt das Ende der Welt, aber ohne zu tief in die Materie einzudringen, scheint es, als ob eine ordnungsgemäße und vollständige Implementierung von const-Generika sowieso notwendigerweise alle Installationen für const-Argumente umfasst, also können wir uns genauso gut die zusätzliche Zeit nehmen um die Syntax/ux/story für const-Argumente fertigzustellen, während wir schon dabei sind, und eine unglückliche Ära des Code-Gestanks zu vermeiden. Ja, das oben Genannte kann immer noch mit Makros durchgeführt werden, aber die Ergonomie von const-Generika ist tausendmal einfacher.
fwiw, so stelle ich mir die const-Argumentversion vor:
#[inline(always)]
fn read_str_digits<B: Bytes>(src: &mut B, min_value: const u32,
max_value: const u32, stop_word: const u8, max_digits: u8) -> Result<u32, ()> {
let mut result: u32 = 0;
let mut digits = 0;
while digits < max_digits && src.has_remaining() {
match src.get_u8() {
d<strong i="23">@b</strong>'0'..=b'9' => {
result = result*10 + (d - b'0') as u32;
digits += 1;
},
stop_word => match result {
min_value..=max_value => break, // valid range
_ => return Err(()),
},
_ => return Err(())
}
}
...
}
Es wäre praktisch identisch mit const-Generika, aber für die Semantik.
Normalerweise würden Sie es in eine Funktion verschieben, außer dass Folgendes nicht funktioniert, da bestimmte Werte, die beim Mustervergleich verwendet werden, zu Variablen geworden sind:
Dies scheint ein Anwendungsfall zu sein, der leichter angegangen werden kann, indem die Werte gebundener Variablen in Mustern (nicht nur in consts) mit einer neuen Syntax verwendet werden können. Die Tatsache, dass Konstanten derzeit in Mustern erlaubt sind, ist kontraintuitiv und meines Wissens historisch.
@mqudsi Verzeihen Sie mir, wenn dies eine dumme Frage ist, aber ich sehe nichts Falsches an dem von Ihnen angegebenen Beispiel. Es sieht für mich nach einem vollkommen gültigen Anwendungsfall für const-Generika aus: eine Definition zu haben, die so verallgemeinert ist, dass sie mit beliebigen Werten als Max/Min arbeitet. Ich sehe nicht wirklich die Vorteile von const-Argumenten gegenüber const-Generika. Sie scheinen mir gleichwertig zu sein; das heißt, const-Argumente könnten einfach als Entzuckerung zu const-Generika implementiert werden. Könnten Sie näher erläutern, was mit diesem Designmuster nicht stimmt?
Hier ist eine Zusammenfassung der Arbeiten an const-Generika seit dem letzten Update . Beim letzten Mal drehte sich alles um die Kernimplementierung: sicherzustellen, dass alles zusammenpasst und einige der grundlegenden Testfälle bestanden. Seitdem konzentrierten sich die Bemühungen darauf, const-Generika zuverlässiger zu machen: Fälle zu beheben, die den Compiler abstürzten oder unerwartet nicht funktionierten, oder die Diagnose zu verbessern. Wir nähern uns etwas, das zuverlässig funktioniert, obwohl es noch ein Weg vor uns ist.
Darüber hinaus wurden durch andere Arbeiten am Compiler einige der anderen Probleme im Zusammenhang mit const-Generika behoben.
Wir haben auch damit begonnen, const-Generika innerhalb des Compilers selbst zu verwenden: Array-Trait-Implementierungen verwenden jetzt dank der Arbeit von @crlf0710 und @scottmcm (https://github.com/rust-lang/rust/pull/60466, https ://github.com/rust-lang/rust/pull/62435), die zu Leistungsverbesserungen geführt hat und die es uns auch ermöglichen wird, Array-Trait-Implementierungen in Zukunft (wenn const-Generika stabilisiert ist) einzuschränken. @Centril hat den gleichen Ansatz verwendet, um VecDeque
zu verbessern (https://github.com/rust-lang/rust/pull/63061).
Viele der besonders häufigen Fehler bei const-Generika wurden in den letzten Monaten behoben. @nikomatsakis untersucht die faule Normalisierung , die eine Vielzahl der verbleibenden Probleme beheben sollte, während @jplatte versucht , die Disambiguierung von const-Parametern von Typparametern zu beheben (damit Sie nicht mehr {X}
eingeben müssen für a const-Argument).
Ich möchte @eddyb auch für all ihr Mentoring und ihre Überprüfung für const-Generika während der gesamten Entwicklung danken, was von unschätzbarem Wert war.
Es gibt noch eine ganze Reihe anderer Probleme zu lösen und wie zuvor könnten wir jede Hilfe gebrauchen, die wir bekommen können – wenn Sie daran interessiert sind, eines der verbleibenden Probleme anzugehen, können Sie mich gerne zu diesem Problem anpingen oder weiter Discord oder Zulip für Hinweise zum Einstieg.
@mqudsi @mark-im Wäre es angebracht, die aktuelle syntaktische Alternative für Generika, impl Trait
in Argumentposition, auf const-Generika zu erweitern? Dies ist die Syntax @mqudsi , die für diesen Anwendungsfall vorgeschlagen wird.
Persönlich denke ich, dass dies die Lesbarkeit solcher Anwendungsfälle verbessern würde, aber ich sehe ein Problem: normale Generics werden verwendet, um Daten zu übergeben, also sind sie an ein Argument gebunden. Dies ist bei const-Generika nicht der Fall. Wie würden Sie sie also bestehen? So tun, als wären es Argumente?
Im Fall von impl Trait
in Argumentposition kann es auch nicht mit der Turbofish-Syntax verwendet werden. Für const
im Argument wäre es umgekehrt. Dies kann verwirrend sein.
Ich bin mir nicht sicher, ob die Vorteile die "Seltsamkeit" hier überwiegen, aber ich wollte trotzdem meine Gedanken teilen.
Ich habe mich vage an eine Diskussion darüber erinnert und jetzt habe ich sie gefunden: https://internals.rust-lang.org/t/pre-rfc-const-function-arguments/6709
@PvdBerg1998 das scheint eine sehr schlechte Idee zu sein. Ich verstehe wirklich nicht ganz, dass <…>
eine _harte Syntax_ zum Lesen ist. Hier zwei Möglichkeiten:
where
Klausel zu setzen?Vergleichen Sie nun mit dem oben verlinkten Pre-RFC. Es würde ein _tag_ (im Sinne des Typsystems) direkt an der Position von Funktionsargumenten, dh Variablenbindungen für den Rumpf einer Funktion, einführen.
Mit anderen Worten: Es ist sehr verwirrend und wird, wie mehrere Leute für Impl Trait in Arg-Position angegeben haben, nicht benötigt. Bitte machen Sie den gleichen Fehler nicht zweimal, um eine Funktion hinzuzufügen, die sich nicht lohnt. Vor allem, wenn Sie „so tun, als wären es Argumente“. Rust würde hier in die falsche Richtung gehen. Die Syntax sollte klarer sein, ja, aber nicht verwirrender. Bitte hör auf.
@phaazon
Ich persönlich stimme dem verlinkten Vorschlag voll und ganz zu, dass das Zulassen von Konstanten in der Argumentposition ein wirklich guter ergonomischer Schub sein wird. Es geht nicht nur um Funktionssignaturen (wohl where
Klauseln "lösen" das Problem überhaupt nicht, sondern erschweren das Verständnis von Signaturen, da Sie 3 Stellen statt nur einer analysieren müssen), sondern auch um wie diese Funktionen verwendet werden. Vergleichen:
let r = _mm_blend_ps::<{2 * C}>(a, b);
let r = _mm_blend_ps(a, b, 2 * C);
Die zweite Zeile ist meiner Meinung nach viel natürlicher als die zweite. Ein weiteres Beispiel wäre die Matrixerstellung: MatrixF32::new(3, 3)
vs. MatrixF32::new::<3, 3>()
. Ich bin nicht der Meinung, dass diese Funktionalität "sehr verwirrend" sein wird. Für den Benutzer ist es nichts weiter als eine zusätzliche Anforderung für ein Funktionsargument. Wir können bereits const-Argumente über Makros emulieren (siehe SIMD-Intrinsik in std
), aber das ist ziemlich hässlich und ineffizient.
Was impl Trait
in der Argumentationsposition angeht, war ich anfangs auch gegen dieses Feature, aber nach einiger Zeit habe ich meine Meinung geändert und glaube, dass es am Ende eine gute Entscheidung war. Der einzige unvollständige Teil ist derzeit die Interaktion mit expliziten Typparametern, die über Turbofish bereitgestellt werden. Ich denke, eine gute Lösung wäre, impl Trait
Argumente für Turbofish unsichtbar zu machen, dh Benutzer sollten es verwenden, wenn sie sicher sind, dass keine explizite Typ-Disambiguierung erforderlich ist. Dies würde es uns ermöglichen, in einigen Szenarien den Bedarf an _
in Turbofish erheblich zu reduzieren.
Ich denke, eine Diskussion über const-Argumente ist hier nicht Thema, daher sollte sie hier wahrscheinlich nicht fortgesetzt werden.
Eine Sache, die const
Argumente von impl Trait
Argumenten (die @PvdBerg1998 erwähnte) unterscheidet, ist, dass es sich um Argumente handelt, mit allem, was für Typrückschlüsse impliziert wird.
Sie können einen Typ selbst nicht als Argument übergeben, aber Sie können einen Wert übergeben, und es ist völlig vernünftig, eine Funktion ihre konstanten generischen Argumente aus ihren normalen Argumenten in Klammern ableiten zu lassen.
Dies ist in C++ ziemlich üblich, zum Beispiel:
template <typename T, size_t N>
size_t length(T (&array)[N]) {
return N;
}
Dies unterscheidet sich auf subtile Weise davon, einfach einen konstanten Parameter zu akzeptieren und ihn in einen generischen konstanten Parameter zu entzuckern ( fn foo(const N: usize)
-> fn foo<const N: usize>()
). Es ist näher, einen Parameter mit einem generischen const-Parameter einzuschränken und dann das generische const-Argument aus dem Argument abzuleiten. Das obige Matrixbeispiel könnte also so aussehen:
impl<const W: usize, const H: usize> MatrixF32<W, H> {
fn new(w: usize<W>, h: usize<H>) -> Self { .. }
}
...wobei usize<X>
eine erfundene Syntax für "ein usize
mit dem Wert X
" ist. (Analog können Sie usize<X..Y>
für Bereichstypen verwenden, die im Zusammenhang mit der Verallgemeinerung von NonZero
besprochen wurden.)
Wenn Sie Roping in Bereichstypen vermeiden möchten, könnten Sie sich abhängige typisierte Sprachen ansehen, die es ermöglichen, Parameter direkt in Typen zu verwenden, mit ein wenig Anpassung des Bereichs:
fn new(const W: usize, const H: usize) -> MatrixF32<W, H> { .. }
Sie sind keine Argumente. Sie sind Generika . Auf Typsystemebene bedeutet dies, dass ihre _Werte_ zur Kompilierzeit leben, während die Werte eines Funktionsarguments zur Laufzeit leben. Beides zu mischen ist extrem irreführend.
Da mir Typensysteme wichtig sind, verstößt dieser Vorschlag gegen viele vernünftige und solide Prinzipien. Sie möchten Werte und Typen nicht vermischen. Denn, ja, ein N
(beachte, ich habe nicht usize
gesagt, ich sagte N
), obwohl du denkst, dass es ein Wert ist, ist viel eher ein Typ als ein Wert. Als Grund hat Haskell keine const-Generika, aber es hat DataKinds
, was es ermöglicht, reguläre Datenkonstruktoren als Typen zu heben:
foo :: Integer -> Integer
foo 0 -- 0 has type Integer
-- but
data P (a :: Integer)
type MyP = P 10 -- 10 has kind Integer, which “value” is the 10 type
So:
fn foo<const X: usize>()
Hier entspricht X
eher einem Typ als einem Wert.
Außerdem interessiere ich mich für Monomorphisierung. Wenn ich das lese:
let x = foo(12, "Hello, world!", None);
Wenn wir uns bei Ihrem Vorschlag die Definition von foo
ansehen, ist es schwer zu erkennen, welche Argumente foo
monorisieren werden. Denn jedes Mal, wenn Sie einen anderen Wert für ein const generisches übergeben, erstellen Sie eine komplett neue Funktion.
Vielleicht fühlt es sich für _Sie_ und _Ihre Gründe_ intuitiver an, aber ich habe auch Gründe zu sagen, dass es in Bezug auf die Typkorrektheit überhaupt nicht intuitiv ist. Zu sagen, dass es sich um Argumente handelt, ist so, als ob parametrierte Funktionen in der Mathematik ihre Parameterargumente haben. Sie verwechseln Parameter und Argumente und zeigen, wie schlecht diese Idee ist.
Sie haben mich vielleicht falsch verstanden, ich habe ausdrücklich gesagt " const
Argumente ... sind Argumente " und nicht " const
Generika ". Mein erstes Rust-Beispiel ist auch genau äquivalent zu DataKinds ( usize<N>
ist die Version auf Typebene von N
). Hier gibt es kein Problem in Bezug auf die Typkorrektheit - ich argumentiere nicht in Bezug auf die Intuition, sondern in Analogie zu bestehenden, etablierten und soliden Typsystemen.
Was Ihre Bedenken hinsichtlich der Monomorphisierung betrifft - das ist nicht anders als heute! Jedes Argument kann potentiell eine neue Monomorphisierung verursachen. Ich möchte auch hinzufügen, dass die Lösung für dieses Problem darin besteht, die Kosten aller Generika zu senken, indem nicht-abhängigen Code zwischen Instanzen geteilt wird, einschließlich und insbesondere durch Konvertieren von const-Generika in Laufzeitwerte, wenn möglich.
(Und ich bin völlig verwirrt von Ihrer Behauptung, dass ich Parameter und Argumente verwechsle, ich habe sie sorgfältig unterschieden.)
Ich verstehe, was du meinst, aber ich bin immer noch sehr besorgt. Da das _Argument_ mit _const_ gekennzeichnet ist, bedeutet dies, dass Sie es nicht _einen Wert_ übergeben können, was zur Laufzeit spricht. Sie müssen ihm einen _konstanten Wert_ übergeben, der dem _konstanten generischen_ zugeordnet wird. Übrigens, Sie haben es hier nicht erwähnt, aber das ist nur eine Anwendung eines _singleton type_, wenn Sie 10
als Typ betrachten: Sein einziger möglicher Wert ist 10
.
Da ich in der Argumentposition gegen Impl Trait war, bin ich auch dagegen (und Sie werden verstehen, warum wir hier vergleichen können). Mehrere Punkte:
<…>
schwieriger ist als (…)
, insbesondere wenn man bedenkt, dass struct
vs. fn
. Derzeit hat struct
nur <…>
und fn
hat beides, da auf beide Parameter gesetzt werden können.foo<A, B>
lesen, wissen Sie, dass es zwei Parameter gibt, die zu einer Funktionsdefinition und -implementierung führen.Ich spüre, was du hier tun willst. foo(1, 3)
fühlt sich für dich besser an als foo<3>(1)
, aber nicht für mich. Der Grund dafür ist, dass foo(1, 3)
das zweite Argument als laufzeitbasiert akzeptieren sollte und der Vorschlag, den Sie geben, dies verbietet. Ich kann zwar Anwendungen sehen, insbesondere mit Arrays, aber ich mag derzeit nicht, wie es aussieht. Ich wäre viel mehr für eine Lösung, die sagt: „Wenn ein Argument const
, können wir eine Typvariable / const generisch ableiten.
fn foo<const N: usize>(a: usize, b: usize | N);
Das ist nur eine imaginäre Syntax, die ich mir ausgedacht habe, um meinen Standpunkt zu erklären: Wenn Sie b
als konstanten Wert übergeben, N = b
und Sie erhalten Ihre schöne Syntax:
foo(1, 3)
Wenn b
kein konstanter Wert ist, müssen Sie N
explizit übergeben:
foo<3>(1, x)
Wie wäre die Syntax dafür mit Ihrem Vorschlag:
fn foo<const N: usize>(x: [f32; N]);
fn new(const W: usize, const H: usize) -> MatrixF32<W, H> { .. }
Es führt uns zu Fragen nach assoziierten Elementen, kurz gesagt,
new()
ist eine verbundene Funktion und muss mit einem konkreten Typ verbunden sein, konstante generische Elemente haben eine Semantik wie "Dieser Typ hat diesen Parameter...", dh einen abhängigen Typ. Also solltenew()
im Kontext keine Argumente haben. Die explizite Verwendung sollte wie folgt aussehen:Type<32>::new()
. Und natürlich sollten alle const generischen Argumente eidierbar sein.
Ich spüre, was du hier tun willst. foo(1, 3) fühlt sich für dich besser an als foo<3>(1), aber nicht für mich. Der Grund dafür ist, dass foo(1, 3) das zweite Argument als laufzeitbasiert akzeptieren sollte und der Vorschlag, den Sie machen, dies verbietet.
Das ist weder meine Motivation noch meine Absicht - ich spreche nicht darüber, was sich "besser anfühlt" und ich glaube nicht, dass die Unterscheidung zwischen Kompilieren und Laufzeit mit der Unterscheidung zwischen Typ und Wertebene verschmolzen werden muss. Die beiden sind bereits getrennt - const
Elemente (Wert oder Fn) sind sicherlich keine Typen!
Alles, worauf ich hinaus will, ist die semantische Verbindung, die die Typinferenz zwischen Parametern auf Wertebene und Parametern auf Typebene herstellt. Es gibt keine Beziehung zu impl Trait
- mein Punkt war wieder const Argumente zu unterscheiden impl Trait
, genau diese Art von unproduktiver Angst , den Kopf ab.
Wie wäre die Syntax dafür mit Ihrem Vorschlag:
fn foo<const N: usize>(x: [f32; N]);
Diese Syntax würde sich nicht ändern! Ich schlage nicht vor, dass wir const-Parameter aus der Typebene entfernen (syntaktisch oder semantisch), sondern nur, dass wir sie der Wertebene hinzufügen, teilweise um die Inferenz der Typebene zu erleichtern.
Auf der cppcon gab es eine Diskussion über constexpr-Funktionsparameter, die dem von @PvdBerg1998 ähnlich zu sein scheint und wie der interne Thread aussieht, mit dem @mark-im verlinkt ist. Es scheint dort gut angekommen zu sein.
Es scheint eine gute Idee zu sein, aber es sollte ausgearbeitet werden, nachdem wir die erste Implementierung für const-Generics abgeschlossen haben
Bearbeiten: Dies wurde in diesem Thread erwähnt, hier ist das zugehörige Papier für constexpr-Funktionsparameter
https://github.com/davidstone/isocpp/blob/master/constexpr-parameters.md
Ich möchte darum bitten, dass wir uns die Diskussion einer solch bedeutenden syntaktischen Änderung entweder für einen Thread zu Interna oder ein Problem (oder eine Änderung der PR) im RFCs-Repo vorbehalten. Der Kommentarbereich dieser Ausgabe ist bereits ziemlich lang und die fragliche Syntax scheint für ein brauchbares MVP von const-Generika unnötig zu sein.
Wenn wir Kommentare verschieben könnten, würden wir das wahrscheinlich tun. Vielleicht möchten wir das Problem für @rust-lang-Teammitglieder sperren und alle Offtopic-Kommentare ausblenden?
Alle Designdiskussionen sollten im RFC-Repository stattfinden und alle Fehlerberichte sollten in separaten Ausgaben enthalten sein.
cc @rust-lang/moderation Gibt es dafür einen Präzedenzfall?
Sie können einen Kommentar in ein Problem _konvertieren_, aber keine Kommentare verschieben. Das Sperren ist in Ordnung, und ich würde Kommentare außerhalb des Themas einfach ausblenden.
Zu Beginn des neuen Jahres dachte ich, es wäre gut, ein weiteres Update darüber zu geben, wo wir jetzt mit const-Generika stehen. Es ist etwas mehr als 2 Jahre her, dass die const Generika RFC akzeptiert wurde. Im ersten Jahr (ungefähr 2018) wurde eine Reihe umfangreicher Refactoring-Anstrengungen unternommen, um const generisches zu erleichtern, indem der Umgang mit generischen Parametern im Allgemeinen im gesamten Compiler verbessert wurde. Im zweiten Jahr (ungefähr 2019) begann die Arbeit an der Implementierung von const-Generika selbst: Zuerst wurde das Nötigste zum Laufen gebracht und dann die Verbesserung der Integrität des Features verlangsamt. Die Leute begannen auch mit der Verwendung von const-Generika im Compiler selbst zu experimentieren. Genauere Beschreibungen dieser Bemühungen finden sich in den ersten beiden Update-Beiträgen: [1] , [2] .
In den letzten Monaten seit dem letzten Update gab es gute Fortschritte.
{}
(https://github.com/rust-lang/rust/pull/66104)structural_match
Prüfung, um zu verhindern, dass Typen mit benutzerdefinierter Gleichheitsprüfung als const-Generika verwendet werden (https://github.com/rust-lang/rust/pull/65627)Ein großes Dankeschön an alle, die mit const Generika geholfen haben!
Was kommt als nächstes? Der größte Blocker für const-Generika ist derzeit die Lazy Normalization , die für bestimmte Arten von const-generischen Grenzen (wie in Arrays ) erforderlich ist. @skinny121 untersucht derzeit die faule Normalisierung und setzt ihre fantastischen Bemühungen fort, die großen Fehler in const-Generika zu beseitigen . Dies würde viele der gegenwärtigen Probleme mit const-Generika lösen.
Abgesehen von der faulen Normalisierung gibt es immer noch eine beträchtliche Anzahl von Fehlern und Papierschnitten, die wir
Gibt es eine Untermenge von const-Generika, die keine faule Normalisierung erreicht und nicht viele Fehler und Papierschnitte aufweist, die aber eine gute Menge an nützlichem Code ausdrücken können? Ich stelle fest, dass die Impls vieler Eigenschaften von std für Arrays gut zu funktionieren scheinen. Vielleicht gibt es eine Einschränkung, die es anderen Crates ermöglicht, die Art von Impls zu schreiben, die wir in std für ihre eigenen Eigenschaften haben, obwohl sie nicht alle ausgefalleneren Funktionen unterstützen?
Wir haben jetzt mehr als die Hälfte des Jahres hinter uns, daher fasse ich kurz die Arbeit zusammen, die im Gange ist. In den letzten 6 Monaten waren viele Leute daran beteiligt, den Support von const-Generika zu verbessern. Vielen Dank also an alle, die in irgendeiner Weise geholfen haben! Ich möchte besonders @skinny121 und @lcnr danken , die beide einige große Funktionen in Angriff genommen haben, die const vermissen : faule Normalisierung für Konstanten und Unterstützung von const-Argumenten in Methodenaufrufen sowie das Beheben zahlreicher schwieriger Fehler. Ihre Bemühungen sind aus der folgenden Zusammenfassung ersichtlich.
Const-Generika wird jetzt an mehreren Stellen im Compiler verwendet, und es gibt bereits Experimente mit Merkmalen und Funktionen, die Const-Generika verwenden, z. B. slice::array_chunks
und IntoIterator
und FromIterator
für [T; N]
(https://github.com/rust-lang/rust/pull/65819, https://github.com/rust-lang/rust/pull/69985). Die Beschränkung der Länge 32 für Implementierungen von Array-Eigenschaften wird auch endlich für die Entfernung vorbereitet .
Wir diskutieren derzeit die Stabilisierung einer Teilmenge von const-Generika , die eine breite Palette von Anwendungsfällen abdecken sollten (obwohl es noch einige Probleme zu lösen gibt, bevor const-Generika vollständig stabilisiert werden können).
unused_braces
hinzugefügt (https://github.com/rust-lang/rust/pull/70081)dyn Trait
für const generische Parameter verboten (https://github.com/rust-lang/rust/pull/71038)Obwohl Const Generics bereits einen langen Weg zurückgelegt hat, gibt es immer noch Bugs und scharfe Kanten . Wenn Sie daran interessiert sind, eines der verbleibenden Probleme anzugehen , können @lcnr oder @eddyb für das Problem oder Zulip anpingen, um
Hilfreichster Kommentar
Hier ist eine Zusammenfassung der bisherigen Fortschritte bei const-Generika.
Bevor die Arbeit an const-Generika richtig beginnen konnte, mussten einige Refactorings durchgeführt werden. @jplatte hat die Aufgabe mit https://github.com/rust-lang/rust/pull/45930 übernommen. @jplatte begann dann
@yodaldevoid und ich haben dort weitergemacht, wo @jplatte aufgehört hat, stellten aber schnell fest, dass der Fortschritt durch die Art und Weise, wie generische Parameter im Allgemeinen gehandhabt wurden, behindert wurde. So begann eine Reihe von Änderungen, um die Handhabung von Generika in der gesamten Codebasis zu überarbeiten: https://github.com/rust-lang/rust/pull/48149 , https://github.com/rust-lang/rust/pull /48452 , https://github.com/rust-lang/rust/pull/48523 , https://github.com/rust-lang/rust/pull/51880.
Damit könnte die Implementierung von const-Generika ernsthaft beginnen. Seitdem haben @yodaldevoid und ich langsam aber sicher schrittweise Unterstützung für const-Generika hinzugefügt: https://github.com/rust-lang/rust/pull/58191 , https://github.com/rust-lang/rust /pull/58503 , https://github.com/rust-lang/rust/pull/58581 , https://github.com/rust-lang/rust/pull/58583 , https://github.com/rust -lang/rust/pull/59170 , https://github.com/rust-lang/rust/pull/59355 , https://github.com/rust-lang/rust/pull/59415 , https://github .com/rust-lang/rust/pull/60058 , https://github.com/rust-lang/rust/pull/60280 , https://github.com/rust-lang/rust/pull/60284 und die meisten vor kurzem https://github.com/rust-lang/rust/pull/59008. (Diese wurden größtenteils aus der Haupt-Pull-Anfrage von
Wie ist der Stand jetzt? Einige konst Generika Tests jetzt 🎉However arbeiten, gibt es noch diejenigen , die dies nicht tun, und es gibt eine Reihe von
FIXME
s über den gesamten Code , dass wir noch arbeiten müssen. Wir nähern uns jetzt jedoch näher und wir sind an einem Punkt angelangt, an dem es einige niedrig hängende Früchte gibt, wenn Sie helfen möchten.FIXME(const_generics)
die über den Code verstreut sind. Wir planen, uns durch sie zu arbeiten, aber @yodaldevoid und ich haben nur so viel Zeit. t doppelter Aufwand).Ich habe im oberen Post einen Überblick über einige der verbleibenden Implementierungsprobleme geschrieben, bevor const generics für die richtigen Tests bereit ist, um eine grobe Vorstellung davon zu geben, was noch zu tun ist.
Es hat seine Zeit gedauert, aber wir machen stetig Fortschritte und hoffen, das Tempo mithalten zu können. Es ist motivierend, endlich zu sehen, wie sich die Dinge fügen!