Rust: problème de suivi const fn (RFC 911)

Créé le 6 avr. 2015  ·  274Commentaires  ·  Source: rust-lang/rust

https://github.com/rust-lang/rust/issues/57563 | nouveau problème de méta-suivi

Ancien contenu

Problème de suivi pour rust-lang/rfcs#911.

Ce problème a été clos au profit de problèmes plus ciblés :

Choses à faire avant de stabiliser:

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

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

Commentaire le plus utile

Veuillez ignorer cela si je suis complètement à côté de la plaque.

Le problème que je vois avec cette RFC est qu'en tant qu'utilisateur, vous devez marquer autant de fonctions const fn que possible car ce sera probablement la meilleure pratique. La même chose se produit actuellement en C++ avec contexpr. Je pense que c'est juste une verbosité inutile.

D n'a pas const fn mais il permet à n'importe quelle fonction d'être appelée au moment de la compilation (à quelques exceptions près).

par exemple

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

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

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

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

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

Remarque, je ne suis pas vraiment un utilisateur de Rust et je n'ai lu la RFC qu'il y a quelques minutes, il est donc possible que j'aie mal compris quelque chose.

Tous les 274 commentaires

Est-ce fermé par #25609 ?

@Munksgaard Cela ajoute simplement la prise en charge du compilateur AFAIK. Il y a beaucoup de fonctions dans la stdlib qui doivent être changées en const fn et testées pour la rupture. Je ne sais pas où en sont les progrès.

J'espère que cela sera implémenté sur std::ptr::null() et null_mut() afin que nous puissions les utiliser pour initialiser static mut *MyTypeWithDrop sans recourir à 0usize as *mut _

EDIT : Supprimé car hors sujet

Pour être clair, la question ici ne porte pas principalement sur l'utilité de la fonctionnalité mais plutôt sur la meilleure façon de la formuler (ou le meilleur cadre pour la formuler). Voir la discussion RFC.

C'est maintenant le problème de suivi pour une éventuelle stabilisation.

https://github.com/rust-lang/rust/issues/29107 a été fermé.

Je ne suis pas d'accord avec le fait que "l'intégration avec des modèles", ou toute modification de la bibliothèque standard, devrait bloquer cela. Ceci est très utile même sans ces modifications, et ces modifications peuvent être effectuées ultérieurement. En particulier, j'aimerais bientôt commencer à utiliser const fn dans mon propre code.

En conséquence, l'état de stabilisation de celui-ci pourrait-il être réévalué ?

Je ne doute pas que const fn même dans sa forme limitée actuelle serait une fonctionnalité utile à avoir, mais ce que j'aimerais vraiment, idéalement avant d'aller plus loin dans cette voie, ce serait pour ceux qui sont en faveur de "la const fn approche" pour réfléchir et articuler leur fin de partie préférée. Si nous continuons à ajouter progressivement des fonctionnalités apparemment utiles de la manière la plus évidente, il me semble très probable que nous finirons par copier plus ou moins l'intégralité de la conception constexpr de C++. Est-ce quelque chose avec lequel nous sommes à l'aise? Même si nous disons oui, je préférerais de loin que nous choisissions cette voie de manière lucide, au lieu d'y reculer à petits pas dans le temps, comme la voie de moindre résistance, jusqu'à ce qu'elle devienne inévitable.

(Étant donné que la sémantique du code Rust sûr devrait être entièrement définissable, il semble probable qu'éventuellement au moins chaque fonction qui ne dépend pas (transitivement) de unsafe devrait pouvoir être marquée comme const . Et étant donné que unsafe est censé être un détail de mise en œuvre, je parie que les gens feront également pression pour assouplir cette restriction. histoire bien intégrée pour la mise en scène et le calcul au niveau du type.)

@glaebhoerl

Je ne doute pas que const fn, même dans sa forme limitée actuelle, serait une fonctionnalité utile à avoir, mais ce que j'aimerais vraiment, idéalement avant d'aller plus loin dans cette voie, ce serait pour ceux qui sont en faveur de "l'approche const fn" pour réfléchir et articuler leur fin de partie préférée... il me semble très probable que nous finirons par copier plus ou moins l'intégralité de la conception constexpr de C++.

Ce que j'aimerais personnellement, même plus que ça, c'est qu'on ait une vision assez claire de la façon dont on va l'implémenter, et quelle partie du langage on va couvrir. Cela dit, cela est très étroitement lié à la prise en charge des constantes associées ou des génériques sur les entiers dans mon esprit.

@eddyb et moi avons récemment fait quelques croquis sur un schéma qui pourrait permettre une évaluation constante d'une très large bande de code. Fondamentalement, abaisser toutes les constantes à MIR et l'interpréter (dans certains cas,
l'interprétation abstraite, s'il y a des génériques que vous ne pouvez pas encore évaluer, c'est là que les choses deviennent les plus intéressantes pour moi).

Cependant, bien qu'il semble assez facile de prendre en charge une très grande partie du "langage intégré", le code réel se heurte en pratique à la nécessité d'allouer très rapidement de la mémoire. En d'autres termes, vous voulez utiliser Vec ou un autre conteneur. Et c'est là que tout ce schéma d'interprétation commence à se compliquer dans mon esprit.

Cela dit, @glaebhoerl , j'aimerais aussi vous entendre articuler votre fin de partie alternative préférée. Je pense que vous avez esquissé certaines de ces pensées dans la RFC const fn , mais je pense qu'il serait bon de l'entendre à nouveau, et dans ce contexte. :)

Le problème avec l'allocation est qu'elle s'échappe au moment de l'exécution.
Si nous pouvons d'une manière ou d'une autre interdire de franchir cette barrière de compilation/d'exécution, alors je pense que nous pourrions avoir un liballoc fonctionnel avec const fn .
Il ne serait pas plus difficile de gérer ces types d'allocations que de traiter des valeurs adressables par octet sur une pile interprétée.

Alternativement, nous pourrions générer du code d'exécution pour allouer et remplir les valeurs chaque fois que cette barrière doit être franchie, bien que je ne sois pas sûr du type de cas d'utilisation.

Gardez à l'esprit que même avec une évaluation de type constexpr à part entière, const fn _serait toujours_ pur : l'exécuter deux fois sur des données 'static donnerait exactement le même résultat et non Effets secondaires.

@nikomatsakis Si j'en avais un, je l'aurais mentionné. :) Je ne vois que des inconnues connues. Le tout avec const s dans le cadre du système générique faisait bien sûr partie de ce que j'ai compris comme étant la conception C++. En ce qui concerne les paramètres génériques const s et const associés, étant donné que nous avons déjà des tableaux de taille fixe avec const s dans leur type et que nous aimerions faire abstraction de eux, je serais surpris s'il y avait une bien meilleure façon - par opposition à simplement plus générale - de le faire. La partie const fn des choses semble plus séparable et variable. Il est facile d'imaginer aller plus loin et avoir des choses comme const impl s et const Trait limites dans les génériques, mais je suis sûr qu'il existe un art antérieur pour ce genre de chose générale qui a déjà compris les choses et nous devrions essayer de le trouver.

Parmi les principaux cas d'utilisation du langage Rust, ceux qui nécessitent principalement un contrôle de bas niveau, comme les noyaux, semblent déjà raisonnablement bien servis, mais un autre domaine dans lequel Rust pourrait avoir beaucoup de potentiel concerne les choses qui nécessitent principalement des performances élevées, et dans cette prise en charge puissante de l'espace (sous une forme ou une autre) pour le calcul par étapes (dont const fn est déjà une instance très limitée) semble pouvoir changer la donne. (Juste au cours des dernières semaines, je suis tombé sur deux tweets distincts de personnes qui ont décidé de passer de Rust à un langage avec de meilleures capacités de mise en scène.) Je ne sais pas si l'une des solutions existantes dans les langages "proches de nous" -- Le constexpr C++, le CTFE ad hoc de D, nos macros procédurales -- sont vraiment assez inspirants et puissants/complets pour ce genre de choses. (Les macros procédurales semblent être une bonne chose à avoir, mais plus pour l'abstraction et les DSL, pas autant pour la génération de code axée sur les performances.)

Quant à ce qui _serait_ inspirant et assez bon... Je ne l'ai pas encore vu, et je ne connais pas assez tout l'espace pour savoir précisément où chercher. Bien sûr, d'après ce qui précède, nous pourrions au moins jeter un coup d'œil sur Julia et Terra, même s'ils semblent être des langues assez différentes de Rust à bien des égards. Je sais qu'Oleg Kiselyov a fait beaucoup de travail intéressant dans ce domaine. Le travail de Tiark Rompf sur Lancet et Lightweight Modular Staging pour Scala semble vraiment intéressant à regarder. Je me souviens avoir vu une présentation de @kmcallister à un moment donné sur ce à quoi pourrait ressembler un Rust typé de manière dépendante (ce qui pourrait au moins être plus général que de coller const partout), et je me souviens aussi avoir vu quelque chose d'Oleg à l'effet que les types eux-mêmes sont une forme de mise en scène (ce qui semble naturel étant donné que la séparation de phase entre la compilation et l'exécution ressemble beaucoup à des étapes) ... de nombreuses connexions potentielles passionnantes dans de nombreuses directions différentes, c'est pourquoi cela ressemblerait à un raté opportunité si nous devions simplement nous engager sur la première solution qui nous vient à l'esprit. :)

(Ce n'était qu'un braindump et j'ai presque sûrement imparfaitement caractérisé beaucoup de choses.)

Cependant, bien qu'il semble assez facile de prendre en charge une très grande partie du "langage intégré", le code réel se heurte en pratique à la nécessité d'allouer très rapidement de la mémoire. En d'autres termes, vous souhaitez utiliser Vec ou un autre conteneur. Et c'est là que tout ce schéma d'interprétation commence à se compliquer dans mon esprit.

Je ne suis pas d'accord avec cette caractérisation de "vrai code dans la pratique". Je pense que Rust suscite un grand intérêt car il permet de réduire le besoin d'allocation de mémoire de tas. Mon code, en particulier, fait un effort concret pour éviter l'allocation de tas autant que possible.

Être capable de faire plus que cela serait _bien_ mais être capable de construire des instances statiques de types non triviaux avec des invariants imposés par le compilateur est essentiel. L'approche C++ constexpr est extrêmement limitante, mais c'est plus que ce dont j'ai besoin pour mes cas d'utilisation : j'ai besoin de fournir une fonction qui peut construire une instance de type T avec des paramètres x , y et z sorte que la fonction garantit que x , y et z sont valides (par exemple, x < y && z > 0 ), de sorte que le résultat peut être une variable static , sans utiliser de code d'initialisation qui s'exécute au démarrage.

@briansmith FWIW une autre approche qui a une chance de résoudre les mêmes cas d'utilisation serait si les macros avaient une hygiène de confidentialité , ce que je crois (j'espère) que nous prévoyons de leur faire avoir.

@briansmith FWIW une autre approche qui a une chance de résoudre les mêmes cas d'utilisation serait si les macros avaient une hygiène de confidentialité, ce que je crois (j'espère) que nous prévoyons de leur faire avoir.

Je suppose que si vous utilisez les macros procédurales, vous pouvez évaluer x < y && z > 0 au moment de la compilation. Mais, il semble qu'il faudrait de très nombreux mois avant que les macros procédurales puissent être utilisées dans Rust stable, si jamais elles le sont. const fn est intéressant car il peut être activé pour Rust stable _now_, pour autant que je comprenne l'état des choses.

@glaebhoerl Je ne retiendrais pas mon souffle pour une hygiène stricte, il est tout à fait possible que nous ayons un mécanisme d'échappement (tout comme les vrais LISP), donc vous ne le voudrez peut-être pas à des fins de sécurité.

@glaebhoerl il y a aussi https://anydsl.github.io/, qui utilise même
Syntaxe de type Rust ;) ils ciblent essentiellement le calcul par étapes et dans
évaluation partielle particulière.

Le sam. 16 janvier 2016 à 18 h 29, Eduard-Mihai Burtescu <
[email protected]> a écrit :

@glaebhoerl https://github.com/glaebhoerl Je ne retiendrais pas mon souffle pendant
hygiène stricte, il est fort possible que nous ayons un mécanisme d'évacuation
(tout comme les vrais LISP), donc vous ne le voudrez peut-être pas pour tout type de sécurité
fins.


Répondez directement à cet e-mail ou consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment -172271960.

29525 doit être fixé avant la stabilisation

Étant donné que la sémantique du code Rust sûr devrait être entièrement définissable, il semble probable qu'éventuellement au moins chaque fonction qui ne dépend pas (transitivement) de unsafe devrait pouvoir être marquée comme const . Et étant donné que unsafe est censé être un détail d'implémentation, je parie que les gens feront également pression pour assouplir cette restriction.

Juste une pensée: si jamais nous définissons formellement le modèle de mémoire de Rust , alors même le code unsafe pourrait potentiellement être évalué en toute sécurité et de manière sensée au moment de la compilation en l'interprétant de manière abstraite/symbolique - c'est-à-dire que l'utilisation de pointeurs bruts ne serait pas t se transformer en accès directs à la mémoire comme au moment de l'exécution, mais plutôt quelque chose (juste à titre d'exemple pour l'illustration) comme une recherche dans un hashmap d'adresses allouées, ainsi que leurs types et valeurs, ou similaire, avec chaque étape vérifiée pour la validité - donc que toute exécution dont le comportement n'est pas défini serait _strictement_ une erreur signalée par le compilateur, au lieu d'une faille de sécurité dans rustc . (Cela peut également être lié à la situation de gestion isize et usize au moment de la compilation symboliquement ou d'une manière dépendante de la plate-forme.)

Je ne sais pas où cela nous mène en ce qui concerne const fn . D'une part, cela ouvrirait probablement un code beaucoup plus utile disponible au moment de la compilation - Box , Vec , Rc , tout ce qui utilise unsafe pour l'optimisation des performances - ce qui est bien. Une restriction arbitraire de moins. D'un autre côté, la limite de "ce qui peut éventuellement être un const fn " serait désormais essentiellement déplacée vers _tout ce qui n'implique pas le FFI_. Donc, ce que nous suivrions _réellement_ dans le système de type, sous l'apparence de const ness, c'est ce que les choses dépendent (transitivement) du FFI et ce qui ne le fait pas. Et que quelque chose utilise ou non le FFI est _toujours_ quelque chose que les gens considèrent à juste titre comme un détail d'implémentation interne, et cette restriction (contrairement à unsafe ) _vraiment_ ne semble pas possible de lever. Et dans ce scénario, vous auriez probablement beaucoup plus fn éligibles à const que ceux qui ne le seraient pas.

Ainsi, vous auriez toujours const ness tournant autour d'une restriction arbitraire exposant l'implémentation, et vous finiriez également par devoir écrire const presque partout. Cela ne semble pas très attrayant non plus...

c'est-à-dire que l'utilisation de pointeurs bruts ne se transformerait pas en accès directs à la mémoire comme lors de l'exécution, mais plutôt quelque chose ... comme une recherche dans une table de hachage des adresses allouées,

@glaebhoerl Eh bien, c'est à peu près le modèle que j'ai décrit et que le miri de @tsion implémente.

Je pense que la distinction FFI est très importante en raison de la pureté, qui est _requise_ pour la cohérence.
Vous _ne pourriez même pas_ utiliser GHC pour Rust const fn s car il a unsafePerformIO .

Je n'aime pas trop le mot-clé const moi-même, c'est pourquoi je suis d'accord avec const fn foo<T: Trait> au lieu de const fn foo<T: const Trait> (pour exiger un const impl Trait for T ).

Tout comme Sized , nous avons probablement les mauvaises valeurs par défaut, mais je n'ai vu aucune autre proposition qui puisse fonctionner de manière réaliste.

@eddyb Je pense que vous vouliez faire un lien vers https://internals.rust-lang.org/t/mir-constant-evaluation/3143/31 (commentaire 31, pas 11).

@tsion fixe, merci !

Veuillez ignorer cela si je suis complètement à côté de la plaque.

Le problème que je vois avec cette RFC est qu'en tant qu'utilisateur, vous devez marquer autant de fonctions const fn que possible car ce sera probablement la meilleure pratique. La même chose se produit actuellement en C++ avec contexpr. Je pense que c'est juste une verbosité inutile.

D n'a pas const fn mais il permet à n'importe quelle fonction d'être appelée au moment de la compilation (à quelques exceptions près).

par exemple

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

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

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

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

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

Remarque, je ne suis pas vraiment un utilisateur de Rust et je n'ai lu la RFC qu'il y a quelques minutes, il est donc possible que j'aie mal compris quelque chose.

@MaikKlein il y a eu beaucoup de discussions sur CTFE dans la discussion RFC

Je ne vois aucun commentaire récent expliquant les bloqueurs ici, et l'opération n'est pas très éclairante. Quel est le statut. Comment pouvons-nous déplacer cela jusqu'à la ligne d'arrivée ?

Ceci est utilisé par Rocket : https://github.com/SergioBenitez/Rocket/issues/19#issuecomment -269052006

Voir https://github.com/rust-lang/rust/issues/29646#issuecomment -271759986. Nous devons également reconsidérer notre position sur l'explicitation puisque miri repousse la limite des "effets secondaires globaux" ( @solson et @nikomatsakis en parlaient justement sur IRC).

Le problème que je vois avec cette RFC est qu'en tant qu'utilisateur, vous devez marquer autant de fonction const fn que possible car ce sera probablement la meilleure pratique.

Bien que nous puissions rendre des fonctions arbitraires appelables, si ces fonctions accèdent au code C ou à la statique, nous ne pourrons pas les calculer. Comme solution, je suggère un lint qui avertira des fonctions publiques qui pourraient être const fn.

Je suis d'accord pour la peluche. Il est similaire aux peluches intégrées existantes missing_docs , missing_debug_implementations et missing_copy_implementations .

Il y a une sorte de problème avec le fait d'avoir la peluche par défaut, cependant ... cela avertirait des fonctions que vous ne voulez pas explicitement être const , par exemple, parce que vous prévoyez de modifier ultérieurement la fonction de sorte qu'elle ne peut pas être const et vous ne voulez pas valider votre interface sur const (la suppression const est une modification avec rupture).

Je suppose que #[allow(missing_const)] fn foo() {} pourrait fonctionner dans ces cas ?

@eddyb @nikomatsakis Mon point "supprimer const est un changement radical" suggère que nous voudrons avoir le mot-clé après tout, car c'est une promesse en aval que le fn restera _remain_ const jusqu'à la prochaine version majeure.

Ce sera dommage de voir combien de const devront être saupoudrés dans std et d'autres bibliothèques, mais je ne vois pas comment vous pouvez l'éviter, à moins que ce ne soit requis que sur public- face à des objets, et cela semble être une règle déroutante.

à moins que cela ne soit requis que sur les éléments destinés au public, et cela semble être une règle déroutante.

J'aime celui-ci... Je ne pense pas que ce serait déroutant. Votre interface publique est protégée car vous ne pouvez pas créer une fonction not-const appelée par un const fn

Techniquement, il serait préférable d'annoter les fonctions comme notconst , car je m'attends à ce qu'il y ait bien plus const fn que l'inverse.

notconst serait également plus cohérent avec la philosophie de conception de Rust. (c'est-à-dire " mut , pas const ")

à moins que cela ne soit requis que sur les éléments destinés au public, et cela semble être une règle déroutante.

J'aime celui-ci... Je ne pense pas que ce serait déroutant.

Je bascule sur cette idée. Cela a ses avantages ( ne pensez qu'à const fn lorsque vous prenez des décisions d'interface publique) mais j'ai pensé à une autre façon de prêter à confusion :

Votre interface publique est protégée car vous ne pouvez pas créer une fonction not-const appelée par un const fn

C'est vrai, et malheureusement cela impliquerait que lorsqu'un auteur de bibliothèque marque une fonction publique const , alors il marque implicitement toutes les fonctions appelées transitivement par cette fonction const également, et il y a une chance ils marquent involontairement les fonctions qu'ils ne veulent pas, les empêchant ainsi de réécrire ces fonctions internes en utilisant des fonctionnalités non const à l'avenir.


Je m'attends à ce qu'il y ait bien plus const fn que l'inverse.

J'ai pensé de cette façon pendant un moment, mais ce ne sera vrai que pour les caisses de bibliothèque Rust pures. Il ne sera pas possible de créer des const fns basés sur FFI (même s'ils ne sont que transitivement basés sur FFI, ce qui est beaucoup de choses), donc le montant de const fn peut ne pas être aussi mauvais comme vous et moi le pensions.


Ma conclusion actuelle : tout const fn non explicite semble problématique. Il n'y a peut-être pas de bon moyen d'éviter d'écrire beaucoup le mot-clé.

De plus, pour mémoire, notconst serait un changement radical.

@solson Un très bon point.

Gardez à l'esprit que le mot-clé devient encore plus poilu si vous essayez de l'utiliser avec des méthodes de trait. Le limiter à la définition de trait n'est pas assez utile et annoter impls entraîne des règles imparfaites "const fn parametrim".

J'ai l'impression que ce compromis a été assez discuté lorsque nous avons adopté const fn en premier lieu. Je pense que l'analyse de @solson est également correcte. Je suppose que la seule chose qui a changé, c'est peut-être que le pourcentage d'agents fns a augmenté, mais je ne pense pas qu'il soit suffisant pour changer le compromis fondamental ici. Il va être ennuyeux de devoir progressivement ajouter const fn dans vos interfaces publiques et ainsi de suite, mais c'est la vie.

@nikomatsakis Ce qui me trouble, c'est la combinaison de ces deux faits :

  • nous ne pouvons pas tout vérifier à l'avance, le code unsafe peut être "dynamiquement non constant"
  • quoi que nous fassions pour les génériques et les impls de traits, ce sera un compromis entre "correct" et flexible

Étant donné que les "effets secondaires globaux" sont la principale chose qui empêche le code d'être const fn , n'est-ce pas le "système d'effets" que Rust avait et a été supprimé ?
Ne devrait-on pas parler de « stabilité d'effet » ? Semble similaire au code en supposant qu'une bibliothèque ne panique jamais à l'OMI.

@eddyb absolument const est un système d'effets et oui, il comporte tous les inconvénients qui nous ont donné envie de les éviter autant que possible ... Il est plausible que si nous allons endurer la douleur d'ajouter dans un système d'effets, nous pouvons envisager une syntaxe que nous pouvons adapter à d'autres types d'effets. Par exemple, nous payons un prix similaire avec unsafe (également un effet), bien que je ne sois pas sûr qu'il soit logique de penser à les unifier.

Le fait que des violations puissent se produire dynamiquement semble être une raison de plus pour faire cet opt-in, non ?

Que dis-tu de ça:

En général, je pense que const fn s ne devrait être utilisé que pour les constructeurs ( new ) ou lorsque cela est absolument nécessaire.

Cependant, vous souhaiterez parfois utiliser d'autres méthodes afin de créer facilement une constante. Je pense que nous pourrions résoudre ce problème dans de nombreux cas en faisant de la constness la valeur par défaut , mais uniquement pour le module de définition . De cette façon, les dépendants ne peuvent pas supposer la constance à moins d'être explicitement garantis avec const , tout en ayant la possibilité de créer des constantes avec des fonctions sans que tout soit const .

@torkleyy Vous pouvez déjà le faire en ayant des assistants qui ne sont pas exportés.

Je ne vois pas d'argument solide selon lequel les fonctions d'assistance privées ne devraient pas être implicitement const , lorsque cela est possible. Je pense que @solson disait que rendre const explicite, même pour les fonctions d'assistance, oblige le programmeur à faire une pause et à se demander s'il veut s'engager dans cette fonction étant const . Mais si les programmeurs sont déjà obligés d'y penser pour les fonctions publiques, n'est-ce pas suffisant ? Cela ne vaudrait-il pas la peine de ne pas avoir à écrire const partout ?

Sur IRC , @eddyb a proposé de diviser cette porte de fonctionnalité afin que nous puissions stabiliser les appels à const fns avant de déterminer les détails de leur déclaration et de leurs corps. Cela vous semble-t-il une bonne idée ?

@durka Cela me semble très bien, en tant qu'utilisateur de Rust qui ne connaît pas grand-chose aux composants internes du compilateur.

Excusez mon incompréhension ici, mais qu'est-ce que cela signifie de stabiliser l'appel à une fonction const sans stabiliser la déclaration.

Sommes-nous en train de dire que le compilateur saura d'une manière ou d'une autre ce qui est et n'est pas constant par certains moyens, mais laissera cette partie ouverte à la discussion/implémentation pour le moment ?

Comment alors les appels peuvent-ils être stabilisés si le compilateur peut plus tard changer d'avis sur ce qui est constant ?

@nixpulvis Certains const fn s existent déjà dans la bibliothèque standard, par exemple UnsafeCell::new . Cette proposition permettrait d'appeler de telles fonctions dans des contextes constants, par exemple l'initialiseur d'un élément static .

@nixpulvis Ce que je voulais dire, c'était des appels à des fonctions const fn définies par du code instable (comme la bibliothèque standard), à partir de contextes constants, et non de fonctions régulières définies dans du code Rust stable.

Bien que je sois tout à fait favorable à la stabilisation des appels vers const fn si cela peut se produire plus rapidement, je ne vois pas clairement ce qui bloque la stabilisation de toutes les fonctionnalités const fn . Quelles sont les préoccupations restantes aujourd'hui ? Quelle serait la voie pour y remédier ?

@SimonSapin C'est plus que nous ne sommes pas clairs sur la conception de la déclaration const fn s aujourd'hui, et nous ne sommes pas sûrs des interactions entre eux et les traits et de la flexibilité qu'il devrait y avoir.

Je pense que je suis enclin à stabiliser les utilisations de const fn. Cela semble être une victoire en termes d'ergonomie et d'expressivité et je ne peux toujours pas imaginer une meilleure façon de gérer l'évaluation constante au moment de la compilation que de simplement pouvoir "écrire du code normal".

stabiliser les usages de const fn.

Cela stabilise également certaines fonctions dans la bibliothèque standard comme étant const , l'équipe de la bibliothèque devrait au moins faire un audit.

J'ai soumis un PR https://github.com/rust-lang/rust/issues/43017 pour stabiliser les invocations, ainsi qu'une liste de fonctions à auditer par @petrochenkov.

J'ai une question/un commentaire sur la façon dont cela pourrait être utilisé dans certaines situations de trait/impl. Supposons que nous ayons une bibliothèque mathématique avec un trait Zero :

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

Ce trait ne nécessite pas que la méthode zero soit const , car cela l'empêcherait d'être implémentée par un type BigInt soutenu par un Vec . Mais pour les scalaires machine et autres types simples, ce serait beaucoup plus pratique si la méthode était const .

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

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

Le trait ne nécessite pas que la méthode soit const , mais elle devrait toujours être autorisée, car const ajoute une restriction à l'implémentation et n'en ignore pas une. Cela empêche d'avoir une version normale et une version const de la même fonction pour certains types. Ce que je me demande, c'est que cela a déjà été abordé?

Pourquoi voudriez-vous que différentes implémentations du trait se comportent différemment ? Vous ne pouvez pas l'utiliser dans un contexte générique. Vous pouvez simplement créer une implémentation locale sur le scalaire avec un const fn.

@Daggerbot C'est la seule façon que je vois pour const fn dans les traits - avoir le trait exigeant que tous les impls soient const fn est beaucoup moins courant que d'avoir effectivement " const impl s" .

@jethrogb Vous pourriez, bien que cela nécessite que la constance soit une propriété de l'impl.
Ce que j'ai à l'esprit, c'est qu'un const fn générique avec une limite, par exemple T: Zero , nécessitera le impl de Zero pour le T s il est appelé avec pour ne contenir que des méthodes const fn , lorsque l'appel provient d'un contexte constant lui-même (par exemple un autre const fn ).

Ce n'est pas parfait mais aucune alternative supérieure n'a été proposée - IMO le plus proche de cela serait "autoriser tous les appels et erreurs en profondeur à partir de la pile d'appels si quelque chose d'impossible au moment de la compilation est tenté", ce qui n'est pas aussi mauvais que ça peut sembler à première vue - la plupart des préoccupations à ce sujet sont liées à la rétrocompatibilité, c'est-à-dire que le marquage d'une fonction const fn garantit que le fait est enregistré et que l'exécution d'opérations non valides au moment de la compilation nécessiterait de ne pas le faire const fn .

Cela ne résoudrait-il pas le problème ?

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

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

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

Le passe-partout pourrait être diminué avec des macros.

Outre l'inconvénient mineur d'avoir deux traits distincts ( Zero et ConstZero ) qui font presque exactement la même chose, je vois un problème potentiel lors de l'utilisation d'une implémentation globale :

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

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

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

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

L'erreur disparaîtrait si nous supprimions la couverture impl. Dans l'ensemble, c'est probablement le plus facile à implémenter dans un compilateur car il ajoute le moins de complexité au langage.

Mais si nous pouvions ajouter const à une méthode implémentée là où ce n'est pas nécessaire, nous pouvons éviter cette duplication, bien que pas encore parfaitement :

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

IIRC, C++ autorise quelque chose comme ça lorsque vous travaillez avec constexpr . L'inconvénient ici est que ce const ne serait applicable que si <T as Zero>::zero est également const . Cela devrait-il être une erreur, ou le compilateur devrait-il ignorer ce const lorsqu'il n'est pas applicable (comme C++) ?

Aucun de ces exemples ne résout parfaitement le problème, mais je ne peux pas vraiment penser à une meilleure façon.

Edit : la suggestion de @andersk rendrait le premier exemple possible sans erreurs. Ce serait probablement la solution la meilleure/la plus simple en ce qui concerne l'implémentation du compilateur.

@Daggerbot Cela ressemble à un cas d'utilisation de la règle du "treillis" proposée vers la fin de la RFC 1210 (spécialisation) . Si vous écrivez

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

alors bien que 1 chevauche 3, leur intersection est couverte précisément par 4, donc cela serait autorisé en vertu de la règle du treillis.

Voir aussi http://smallcultfollowing.com/babysteps/blog/2016/09/24/intersection-impls/.

C'est un système incroyablement complexe, que nous voulons éviter.

Ouais, la règle du treillis serait nécessaire.

@eddyb qu'est-ce que vous considérez comme complexe ?

@Kixunil Dupliquer presque tous les traits de la bibliothèque standard, au lieu de "simplement" marquer certains impl s comme const fn .

On s'égare ici. Actuellement, le problème concerne la stabilisation des utilisations de const fn . Autoriser les méthodes de trait const fn ou const impl Trait for Foo sont orthogonaux entre eux et aux RFC acceptés.

@oli-obk Ce n'est pas le nouveau RFC mais le problème de suivi pour const fn .

Je viens de remarquer et j'ai édité mon commentaire.

@eddyb oui, mais c'est plus simple pour le compilateur (moins la spécialisation, mais nous voudrons probablement de toute façon la spécialisation) et permet également aux gens de se limiter à ConstTrait .

Quoi qu'il en soit, je ne suis pas opposé au fait de marquer impls comme const. J'imagine également que le compilateur génère automatiquement ConstTrait: Trait .

@Kixunil Ce n'est pas beaucoup plus simple, surtout si vous pouvez le faire avec une spécialisation.
Le compilateur n'aurait pas à générer automatiquement quoi que ce soit comme ConstTrait: Trait , et le système de traits n'a pas besoin de savoir quoi que ce soit de tout cela, il suffit de parcourir les implémentations (soit un concret impl ou une limite where ) que le système de traits fournit et vérifiez-les.

Je me demande si const fns devrait interdire les accès à UnsafeCell . Il est probablement nécessaire d'autoriser un comportement vraiment const :

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

Jusqu'à présent, j'ai vu que set n'est pas const . La question est de savoir si cela restera pour toujours. En d'autres termes : le code unsafe peut-il compter sur le fait que lors de l'exécution de la même fonction const sur des données immuables, il renverra toujours le même résultat aujourd'hui et dans chaque version future du langage/de la bibliothèque ?

const fn ne signifie pas immuable, cela signifie qu'il peut être appelé au moment de la compilation.

Je vois. J'apprécierais beaucoup si je pouvais en quelque sorte garantir qu'une fonction renvoie toujours la même chose lorsqu'elle est appelée plusieurs fois sans utiliser les traits unsafe , si c'est possible d'une manière ou d'une autre.

@jethrogb Merci pour le lien !

J'ai remarqué que mem::size_of est implémenté comme un const fn tous les soirs. Serait-ce possible pour mem::transmute et les autres ? Les intrinsèques de Rust fonctionnent à l'intérieur du compilateur, et je ne suis pas assez familier pour apporter les modifications appropriées pour permettre cela. Sinon, je serais heureux de le mettre en œuvre.

Malheureusement, opérer sur des valeurs est un peu plus difficile que d'en créer par magie. transmute nécessite miri. Une première étape vers l'intégration de miri dans le compilateur est déjà en cours : #43628

Alors! Tout intérêt à stabiliser const *Cell::new , mem::{size,align}_of , ptr::null{,_mut} , Atomic*::new , Once::new et {integer}::{min,max}_value ? Allons-nous avoir des FCP ici ou créer des problèmes de suivi individuels ?

Oui.

Je ne fais partie d'aucune équipe qui a un pouvoir de décision à ce sujet, mais mon opinion personnelle est que tous ces éléments, à l'exception mem::{size,align}_of , sont suffisamment insignifiants pour pouvoir être stabilisés maintenant sans passer par les mouvements d'un tampon en caoutchouc FCP.

En tant qu'utilisateur, j'aimerais utiliser mem::{size,align}_of dans les expressions const dès que possible, mais j'ai lu @nikomatsakis exprimer des inquiétudes quant à leur insta-const-stable lorsqu'ils ont été créés const fn s . Je ne sais pas s'il y a des préoccupations spécifiques ou juste une mise en garde générale, mais IIRC c'est pourquoi des portes de fonctionnalités par fonction ont été ajoutées. J'imagine que les préoccupations de ces deux-là seraient suffisamment similaires pour qu'ils puissent partager un FCP. Je ne sais pas si @rustbot peut gérer des FCP séparés dans le même thread GitHub, il est donc probablement préférable d'ouvrir des problèmes séparés.

@durka pouvez-vous ouvrir un seul problème de suivi pour stabiliser la constance de toutes ces fonctions ? Je proposerai FCP une fois qu'il sera en place.

Pour suivre une piste dans une discussion sur const fns sur alloc::Layout :
La panique peut-elle être autorisée dans un const fn et traitée comme une erreur de compilation ? C'est similaire à ce qui se fait maintenant avec les expressions arithmétiques constantes, n'est-ce pas ?

Oui, c'est une fonctionnalité super triviale une fois que miri est fusionné

Est-ce le bon endroit pour demander des fonctions std supplémentaires devenant const ? Si c'est le cas, Duration:: { new , from_secs , from_millis } devraient tous être sûrs pour faire const .

@remexre Le moyen le plus simple d'y parvenir est probablement de faire un PR et de demander l'examen de l'équipe libs là-bas.

PR comme https://github.com/rust-lang/rust/pull/47300. J'ai aussi ajouté const aux constructeurs instables pendant que j'y étais.

Des réflexions sur la possibilité de déclarer d'autres fonctions std const ? Plus précisément, mem::uninitialized et mem::zeroed ? Je pense que ces deux éléments sont des candidats appropriés pour des fonctions const supplémentaires. Le seul inconvénient auquel je peux penser est le même inconvénient de mem::uninitialized , où la création de structures implémentant Drop est créée et écrite sans ptr::write .

Je peux également joindre un PR si cela semble approprié.

Quelle est la motivation pour cela ? Cela semble être un footgun inutile pour permettre de créer des modèles de bits invalides qui ne peuvent pas être écrasés (car ils sont dans un const), mais peut-être que je néglige l'évidence.

mem::uninitialized est absolument une arme à pied, qui tire aussi à travers vos mains si elle est mal dirigée. Sérieusement, je ne peux pas exagérer à quel point l'utilisation de cette fonction peut être incroyablement dangereuse, malgré son marquage comme unsafe .

La motivation derrière la déclaration de ces fonctions supplémentaires const découle de la nature de ces fonctions, car l'appel mem::uninitialized<Vec<u32>> renverra le même résultat à chaque fois, sans effets secondaires. Évidemment, s'il n'est pas initialisé, c'est une chose terrible à faire. Par conséquent, le unsafe est toujours présent.

Mais pour un cas d'utilisation, considérons une minuterie globale, celle qui suit le début d'une fonction. Son état interne sera déterminé ultérieurement, mais nous avons besoin d'un moyen de le présenter comme une structure globale statique créée à l'exécution.

use std::time::Instant;

pub struct GlobalTimer {
    time: UnsafeCell<Instant>
}

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

Ce code ne se compile pas, car Instant::now() n'est pas une fonction const . Remplacer Instant::now() par mem::uninitialized::<Instant>()) résoudrait ce problème si mem::uninitialized était un const fn . Idéalement, le développeur initialisera cette structure une fois que le programme commencera à s'exécuter. Et bien que ce code soit considéré comme une rouille non idiomatique (l'état global est généralement très mauvais), ce n'est qu'un des nombreux cas où les structures statiques globales sont utiles.

Je pense que cet article donne une bonne base pour l'avenir du code Rust exécuté au moment de la compilation. Les structures statiques globales au moment de la compilation sont une fonctionnalité avec certains cas d'utilisation importants (les systèmes d'exploitation viennent également à l'esprit) qui manquent actuellement à la rouille. De petits pas peuvent être faits vers cet objectif en pensant à ajouter lentement const aux fonctions de bibliothèque jugées appropriées, telles que mem::uninitialized et mem::zeroed , malgré leurs marquages unsafe .

Edit : oublié le const dans la signature de la fonction de TimeManager::init()

Hmm, ce code compile donc il me manque toujours la motivation exacte ici ... si vous pouviez écrire du code tel que

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

Mais les fns const sont actuellement si restreints que vous ne pouvez même pas écrire cela ...

J'apprécie la justification théorique, mais const n'est pas la même chose que pure et je ne pense pas que nous devrions faire quoi que ce soit pour encourager l'utilisation de ces fonctions si ce n'est pas nécessaire pour un cas d'utilisation convaincant .

Je pense qu'il y a des fruits à portée de main beaucoup plus bas qui pourraient être stabilisés en premier. Sans miri, les intrinsèques non initialisés et mis à zéro n'ont de toute façon guère de sens. J'aimerais bien les voir un jour pourtant. On pourrait même les stabiliser initialement et exiger que toutes les constantes produisent un résultat initialisé, même si les calculs intermédiaires peuvent être non initialisés.

Cela dit, avec les unions et le code non sécurisé, vous pouvez de toute façon émuler non initialisé ou mis à zéro, il n'y a donc pas grand intérêt à les garder non const

Avec l'aide de union s, le code précédent compile maintenant . C'est absolument terrifiant 😅.

Tous les bons points aussi. Ces fonctions intrinsèques sont assez basses sur la liste des cas d'utilisation, mais elles sont toujours des candidats appropriés pour une éventuelle const -ness.

C'est terriblement incroyable.

Alors ... pourquoi préconisez-vous exactement de constifier mem :: uninitialized, comme
opposé à, disons, Instant::now ? :)

La nécessité d'avoir des initialiseurs constants pour les structures avec des valeurs non constantes
interiors est réel (voir : Mutex). Mais je ne pense pas faire ce malarkey
plus facile est la bonne façon d'obtenir cela!

Le jeu. 25 janvier 2018 à 02h21, Stephen Fleischman <
[email protected]> a écrit :

Avec l'aide des syndicats, le code précédent compile maintenant
https://play.rust-lang.org/?gist=be075cf12f63dee3b2e2b65a12a3c854&version=nightly .
C'est absolument terrifiant 😅.


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-360382201 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAC3n-HyWD6MUEbfHkUUXonh9ORGPSRoks5tOCtegaJpZM4D66IA
.

Instant::now ne peut pas être const. Que retournerait cette fonction ? Le temps de la compilation ?

Quelqu'un peut-il résumer ce qui doit être fait pour stabiliser cela? Quelle décision doit être prise ? Que ce soit pour stabiliser cela du tout?

Intégration avec des modèles (par exemple https://gist.github.com/d0ff1de8b6fc15ef1bb6)

J'ai déjà commenté l'essentiel, mais étant donné que const fn ne peut actuellement pas être comparé dans un modèle, cela ne devrait pas bloquer la stabilisation, n'est-ce pas ? Nous pourrions toujours l'autoriser par la suite si cela a du sens.

Instant::now ne peut pas être const. Que retournerait cette fonction ? Le temps de la compilation ?

Mais il peut y avoir Instant::zero() ou Instant::min_value() qui est const.

Quelqu'un peut-il résumer ce qui doit être fait pour stabiliser cela? Quelle décision doit être prise ? Que ce soit pour stabiliser cela du tout?

Je pense que la seule question ouverte est de savoir si nos contrôles const fn sont suffisamment stricts pour ne pas autoriser/stabiliser accidentellement quelque chose que nous ne voulons pas à l'intérieur de const fn.

Pouvons-nous faire une intégration avec des modèles via rust-lang/rfcs#2272 ? Les schémas sont déjà douloureux tels qu'ils le sont actuellement, ne les rendons pas plus douloureux.

Je pense que la seule question ouverte est de savoir si nos contrôles const fn sont suffisamment stricts pour ne pas autoriser/stabiliser accidentellement quelque chose que nous ne voulons pas à l'intérieur de const fn.

Corrigez-moi si je me trompe, mais ces vérifications ne sont-elles pas identiques à celles actuellement autorisées dans le corps d'une rvalue const ? J'avais l'impression que const FOO: Type = { body }; et const FOO: Type = foo(); const fn foo() -> Type { body } sont identiques dans ce qu'ils autorisent pour tout corps arbitraire

@sgrif Je pense que le problème concerne les arguments, que const fn ont, mais pas const .
De plus, il n'est pas certain qu'à long terme nous souhaitions conserver le système const fn d'aujourd'hui.

Suggérez-vous plutôt des génériques const (dans les deux sens) ? (par exemple <const T> + const C<T> alias const C<const T> ?)

J'aimerais vraiment avoir une macro try_const! qui essaiera d'évaluer n'importe quelle expression au moment de la compilation, et de paniquer si ce n'est pas possible. Cette macro sera capable d'appeler des fns non const (en utilisant miri ?), nous n'avons donc pas à attendre que chaque fonction de std ait été marquée const fn. Cependant, comme son nom l'indique, il peut échouer à tout moment, donc si une fonction est mise à jour et ne peut plus être const, elle arrêtera la compilation.

@ Badel2 Je comprends pourquoi vous voudriez une telle fonctionnalité, mais je soupçonne que son utilisation généralisée serait vraiment mauvaise pour l'écosystème des caisses. Parce que de cette façon, votre caisse peut finir par dépendre d'une fonction dans une autre caisse évaluable au moment de la compilation, puis l'auteur de la caisse change quelque chose n'affectant pas la signature de la fonction mais empêchant la fonction d'être évaluable au moment de la compilation.

Si la fonction était marquée const fn en premier lieu, alors l'auteur de la caisse aurait repéré le problème directement en essayant de compiler la caisse et vous pouvez vous fier à l'annotation.

Si seulement cela fonctionnait sur le terrain de jeu... https://play.rust-lang.org/?gist=6c0a46ee8299e36202f959908e8189e6&version=stable

Il s'agit d'un moyen non portable (en fait, tellement non portable qu'il fonctionne sur mon système mais pas sur le terrain de jeu - pourtant ils sont tous les deux sous Linux) d'inclure le temps de construction dans le programme construit.

La manière portable serait d'autoriser SystemTime :: now () dans l'évaluation const.

(Il s'agit d'un argument pour const/compile-time-eval de TOUTE fonction/expression, qu'elle soit const fn ou non.)

Cela me semble être un argument pour interdire les chemins absolus dans include_bytes 😃

Si vous autorisez SystemTime :: now dans const fn, const FOO: [u8; SystemTime::now()] = [42; SystemTime::now()]; produirait une erreur aléatoire en fonction des performances de votre système, du planificateur et de la position de Jupiter.

Encore pire:

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

Cela ne signifie pas que la valeur de TIME est la même sur tous les sites d'utilisation, en particulier dans les compilations avec incréments et dans les caisses.

Et encore plus fou, vous pouvez bousiller foo.clone() de manière très malsaine, car vous pourriez finir par sélectionner le clone impl à partir d'un tableau de longueur 3, mais le type de retour pourrait être un tableau de longueur 4.

Ainsi, même si nous autorisions l'appel de fonctions arbitraires, nous n'autoriserions jamais SystemTime::new() à revenir avec succès, tout comme nous n'autoriserions jamais les vrais générateurs de nombres aléatoires

@SoniEx2 Je suppose que c'est un peu hors sujet ici, mais vous pouvez implémenter quelque chose comme ça dès aujourd'hui en utilisant un fichier cargo build.rs . Voir Build Scripts dans le Cargo Book, en particulier la section sur l'étude de cas de la génération de code.

@oli-obk Je pense que ce n'est pas complètement le même problème car l'un concerne la sécurité des API tandis que l'autre concerne l'environnement de construction, mais je conviens qu'ils peuvent tous deux entraîner une rupture de l'écosystème s'ils ne sont pas appliqués avec précaution.

Veuillez ne pas autoriser l'obtention de l'heure actuelle dans un const fn ; nous n'avons pas besoin d'ajouter des moyens plus simples pour rendre les builds non reproductibles.

Nous ne pouvons autoriser aucun type de non-déterminisme (comme les nombres aléatoires, l'heure actuelle, etc.) dans const fn - ce qui conduit à un système de type instable puisque rustc suppose que les expressions constantes évaluent toujours le même résultat donné la même entrée. Voir ici pour un peu plus d'explications.

Une future méthode pour gérer les cas comme dans https://github.com/rust-lang/rust/issues/24111#issuecomment -376352844 serait d'utiliser une simple macro procédurale qui obtient l'heure actuelle et l'émet sous forme de nombre simple ou jeton de chaîne. Les macros procédurales sont un code plus ou moins totalement illimité qui peut obtenir l'heure par l'une des méthodes portables habituelles que le code Rust non constant utiliserait.

@rfcbot fusion fcp

Je propose de fusionner ceci, car c'est une option quelque peu sensée, ce n'est pas un changement cassant, empêche les changements cassants accidentels (changer une fonction d'une manière qui la rend non const évaluable alors que d'autres caisses utilisent la fonction dans des contextes const) et le seul La mauvaise chose à ce sujet est que nous devons lancer const avant un tas de déclarations de fonctions.

@rfcbot fusion fcp au nom de @oli-obk - cela vaut la peine de penser à la stabilisation et de discuter des problèmes

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

  • [x] @aturon
  • [x] @cramertj
  • [ ] @eddyb
  • [x] @joshtriplett
  • [ ] @nikomatsakis
  • [x] @cnrc
  • [ ] @pnkfelix
  • [x] @scottmcm
  • [x] @sansbateaux

Préoccupations :

  • conception (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588)
  • parallel-const-traits résolus par https://github.com/rust-lang/rust/issues/24111#issuecomment -377133537
  • priorité (https://github.com/rust-lang/rust/issues/24111#issuecomment-376652507)
  • adresses de pointeur d'exécution (https://github.com/rust-lang/rust/issues/24111#issuecomment-386745312)

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

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

@rfcbot concerne la priorité

Nous voudrions peut-être mettre cela de côté après l'édition, car je ne pense pas que nous ayons la bande passante nécessaire pour faire face aux retombées.

@rfcbot concerne tout-const

Nous nous retrouvons un peu dans un monde C++ où il y a une incitation à rendre chaque fonction que vous pouvez const .

Un résumé d'une courte discussion avec @oli-obk :

  1. À l'avenir, presque toutes les fonctions pourraient être marquées const . Par exemple, tout sur Vec pourrait être const . Dans ce monde, il pourrait être judicieux de se débarrasser complètement du mot-clé const : presque tout peut être const , et il faudra faire tout son possible pour changer une fonction de const à non -const, donc les risques de rétrocompatibilité concernant la constance inférée ne seraient probablement pas très élevés.

  2. Cependant, se débarrasser de const aujourd'hui n'est pas faisable. Le miri d'aujourd'hui ne peut pas tout interpréter, et il n'est pas vraiment testé en profondeur en production.

  3. Il est en fait rétrocompatible d'exiger const aujourd'hui, puis de déprécier ce mot-clé et de passer à la constance déduite à l'avenir.

En rassemblant 1, 2 et 3, cela semble être une bonne option pour stabiliser le mot-clé const aujourd'hui, plutôt que d'étendre l'ensemble des fonctions à évaluation constante dans les futures versions. Après un certain temps, nous aurons un évaluateur constant de blaireau qui a fait ses preuves, qui peut tout évaluer. À ce stade, nous pouvons passer à la const inférée.

Wrt les dangers des retombées : const fn a été largement utilisé tous les soirs, en particulier sur l'embarqué. De plus, le vérificateur const fn est le même que celui utilisé pour les initialiseurs statiques et les constantes (à l'exception de certains contrôles spécifiques statiques et des arguments de fonction).

Le principal inconvénient que je vois est que nous préconisons essentiellement de pulvériser const généreusement sur les caisses (pour l'instant, voir le post de @matklad pour de futures idées

@rfcbot concerne les traits constants parallèles

On dirait que la stabilisation de cela entraînera immédiatement un tas de caisses créant une hiérarchie de traits parallèles avec Const à l'avant : ConstDefault , ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto , etc. et demander ConstIndex et autres. Ce n'est pas terrible - nous avons certainement un peu cela avec Try aujourd'hui, bien que la stabilisation de TryFrom aidera - mais je pense qu'il serait bien d'avoir au moins une esquisse du plan pour le résoudre plus agréablement. (Est-ce https://github.com/rust-lang/rfcs/pull/2237 ? Je ne sais pas).

( @nrc : il semble que le bot n'ait enregistré qu'une de vos préoccupations)

Parallel-const-traits a la solution triviale dans la future version hypothétique const-all-the-things. Ils fonctionneraient tout simplement.

Dans le monde const fn, vous ne vous retrouveriez pas avec une duplication de traits, tant que nous n'autorisons pas les méthodes de traits const fn (ce que nous n'autorisons pas), simplement parce que vous ne le pouvez pas. Vous pouvez bien sûr créer des constantes associées (on nightly), ce qui est un peu la situation dans laquelle se trouvait libstd il y a un an, où nous avions un tas de constantes pour initialiser divers types à l'intérieur de statiques/constantes, sans exposer leurs champs privés. Mais c'est quelque chose qui aurait pu se produire depuis un certain temps et qui ne s'est pas produit.

Pour être clair, ConstDefault est déjà possible aujourd'hui sans const fn , et le reste de ces exemples ( ConstFrom , ConstInto , ConstClone , ConstTryFrom , ConstTryInto ) ne sera pas possible même avec cette fonctionnalité stabilisée, car elle n'ajoute pas de méthodes de trait const comme @oli-obk l'a mentionné.

( ConstDefault est possible en utilisant un const associé plutôt qu'un const fn associé, mais c'est équivalent en puissance pour autant que je sache.)

@scottmcm const fn dans les définitions de traits n'est pas possible aujourd'hui (oh @solson l'a déjà mentionné).

Idée aléatoire @eddyb : et si nous rendions possible const impl un trait au lieu d'ajouter const fn dans les définitions de traits ? (Ces deux ne sont pas non plus mutuellement exclusifs.)

@whitequark https://github.com/rust-lang/rfcs/pull/2237 couvre cette idée, grâce à une combinaison de const impl s'étendant à const fn sur chaque fn dans le impl , et permettant à un impl avec toutes les méthodes const de satisfaire une limite T: const Trait , sans marquer aucune des méthodes const dans la définition de trait elle-même.

@rfcbot concerne la conception

Nous nous sommes toujours efforcés de stabiliser un système const fn spécifique pour plusieurs raisons :

  • l'actuel ne prend pas en charge les trait s qui nécessitent des méthodes const fn , ni les traits impl s qui fournissent des méthodes const fn (voir https://github. com/rust-lang/rfcs/pull/2237 pour quelques façons de le faire)
  • dans le même ordre d'idées, il y a le problème de demander une méthode utilisée via un T: Trait lié à const fn sans avoir de traits séparés, et de préférence uniquement lorsqu'il est utilisé au moment de la compilation (par exemple Option::map fonctionnerait de la même manière à l'exécution mais nécessiterait une fermeture const-callable dans CTFE)
  • avec beaucoup d'algorithmes, de collections et d'abstractions potentiellement évaluables par const, il pourrait y avoir des caisses entières qui utiliseraient const fn partout ( libcore me vient à l'esprit)

Il existe différents choix de conception qui atténueraient la plupart ou de tous ces problèmes (au prix d'en introduire d'autres), par exemple, voici quelques-uns de ceux qui sont apparus :

  • ne nécessitant aucune annotation et émettant simplement des erreurs de compilation et miri ne parvient pas à évaluer

    • avantages : bases de code plus propres, l'évaluation constante peut déjà échouer en fonction des valeurs impliquées

    • Inconvénients : pas de documentation sur le comportement au niveau du langage et de limite de semver, vous pouvez lancer le code de n'importe qui d'autre sur miri et observer toute modification mineure qu'il a apportée, par exemple, la journalisation du débogage à l'exécution est ajoutée dans une version de correctif de l'une de vos dépendances ; aussi, la promotion de rvalue est plus difficile à faire

  • un moyen d'opter pour le comportement ci-dessus par fonction/impl/module/etc.

    • les corps de ces fonctions se comporteraient (au moins en termes de const fn ) comme des macros

    • pour : ne pas avoir les implications semver, limiter la portée de certaines analyses

    • Inconvénients : la documentation n'est toujours pas excellente, le comportement à affecter n'est pas clair ( juste const-évaluabilité ?), tout changement dans le corps pourrait être considéré comme une rupture de semver

Parce que de cette façon, votre caisse peut finir par dépendre d'une fonction dans une autre caisse évaluable au moment de la compilation, puis l'auteur de la caisse change quelque chose n'affectant pas la signature de la fonction mais empêchant la fonction d'être évaluable au moment de la compilation.

@leoschwarz n'est-ce pas déjà un problème avec les traits automatiques ? Peut-être que la solution à cela est d'intégrer le rust-semverver à la cargaison pour détecter ce type de bris involontaire.

Cela dit, je ne sais pas ce qui se passe si miri a une limite de temps d'évaluation que vous (en tant qu'auteur de bibliothèque) dépassez accidentellement, provoquant un échec de compilation en aval.

@nrc Je pense que "tout-const" est vrai, mais pas un problème. Oui, nous finirons par marquer une énorme quantité de choses const .

Je veux juste souligner que je ne suis pas sûr de vouloir que tout soit déduit
const. Il s'agit de décider si le temps d'exécution ou le temps de compilation est plus
important. Parfois, je pense que le compilateur fait assez de calcul à
temps de compilation !

Le mercredi 28 mars 2018 à 14h49, Josh Triplett [email protected]
a écrit:

@nrc https://github.com/nrc Je pense que "tout-const" est vrai, mais pas
un problème. Oui, nous finirons par marquer une énorme bande de choses const.


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-376914220 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAC3ny9Wm9JK6p-fXf6gbaEgjFtBpMctks5ti6LigaJpZM4D66IA
.

délai d'évaluation

cette limite est bientôt dépassée.

Je veux juste souligner que je ne suis pas sûr de vouloir que tout ce qui est inféré soit const.

Oh non, nous n'allons pas calculer les choses au hasard au moment de la compilation. Autorisez simplement le calcul de choses aléatoires dans le corps des statiques, des constantes, des discriminants de variantes d'énumération et des longueurs de tableau

@rfcbot a résolu les traits constants parallèles

Merci pour les corrections les amis !

cette limite est bientôt dépassée.

Impressionnant. Dans ce cas, auto-const-fn (en combinaison avec une certaine intégration de rust-semverver ou similaire pour donner des informations sur la casse) semble génial, bien que "ajouter une journalisation et provoquer une casse" puisse être problématique. Bien que vous puissiez augmenter le numéro de version, je suppose, ce n'est pas comme s'ils étaient finis.

La journalisation et l'impression sont des effets secondaires "fins" dans mon modèle de constantes. Nous pourrions trouver une solution à cela si tout le monde est d'accord. Nous pourrions même écrire dans des fichiers (pas vraiment, mais agir comme si nous le faisions et tout jeter).

Je suis vraiment préoccupé par le fait de jeter en silence les effets secondaires.

Nous pourrons en discuter une fois que nous aurons créé un RFC autour d'eux. Pour l'instant, vous ne pouvez pas avoir d'"effets secondaires" dans les constantes. Le sujet est orthogonal à la stabilisation const fn

Je suis un peu inquiet à propos de l'approche "juste un avertissement semver" pour déduire
constance. Si un auteur de caisse qui n'a jamais pensé à la constance voit
"Attention : la modification que vous venez d'effectuer rend impossible l'appel de foo() dans
contexte const, ce qui était auparavant possible », verront-ils simplement cela comme un
non séquentiel et le faire taire ? De toute évidence, les personnes concernées par ce problème pensent souvent
sur quelles fonctions peuvent être const. Et ce serait bien si plus de gens le faisaient
cela (une fois que const_fn est stable). Mais est-ce que les avertissements improvisés sont le bon
moyen d'encourager cela?

Le jeudi 29 mars 2018 à 4h36, Oliver Schneider [email protected]
a écrit:

Nous pourrons en discuter une fois que nous aurons créé un RFC autour d'eux. Pour l'instant tu viens
ne peut pas avoir d'"effets secondaires" dans les constantes. Le sujet est orthogonal à
stabilisation const fn


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-377164275 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAC3n3MtmvrDF42Iy0nhZ2q8xC-QGcvXks5tjJ0ggaJpZM4D66IA
.

Je pense que const fn explicite peut être ennuyeux et encombrer de nombreuses API, mais je pense que l'alternative de supposer implicitement a beaucoup trop de problèmes pour être réalisable:

  • Le fait de jeter des effets secondaires peut conduire à ce que le code se comporte différemment (disons écrire puis lire un fichier) s'il est appelé const ou non const.
  • L'appel de fonctions externes signifie qu'il peut toujours y avoir des effets secondaires, donc un code non sûr ne pourrait probablement jamais être déduit const fn.
  • Quand const fn peut-il être déduit pour du code générique ? Cela serait-il fait au moment de la monomorphisation ?

Cependant, je vois vraiment que le plus gros problème de ne pas le rendre explicite est que quelqu'un peut accidentellement casser beaucoup de code avec un seul changement sans même s'en rendre compte. Ceci est particulièrement préoccupant avec les longs graphes de dépendance courants dans l'écosystème Rust. Si cela nécessite une modification explicite de la signature de la fonction, on saura plus facilement qu'il s'agit d'une modification avec rupture.

Peut-être qu'une telle fonctionnalité pourrait être implémentée en tant qu'indicateur de configuration au niveau de la caisse qui peut être ajouté à la racine de la caisse, #![infer_const_fn] ou quelque chose comme ça, et rester opt-in pour toujours. Si le drapeau est ajouté, const fn serait déduit si possible dans la caisse et également reflété dans la documentation (et cela nécessiterait que les fonctions appelées soient également const fn), si un auteur de caisse ajoute ce drapeau, il s'engage en quelque sorte à être prudent à propos de la gestion des versions et peut-être que Rust-Semverver pourrait même être forcé.

Et si on le faisait à l'envers ?

Plutôt que d'avoir const fn, ayez side fn.

C'est toujours explicite (vous devez mettre side fn pour appeler side fn, rompant explicitement la compatibilité) et supprime l'encombrement. (Certains) intrinsèques et tout ce qui contient asm serait un côté fn.

Ce n'est pas rétrocompatible, même si je suppose qu'il peut être ajouté dans une édition ?

Le 30 mars 2018 à 02:43:06 GMT+08:00, "Soni L." [email protected] a écrit :

Et si on le faisait à l'envers ?

Plutôt que d'avoir const fn, ayez side fn.

C'est toujours explicite (il faut mettre side fn pour appeler side fn,
rompant explicitement la compatibilité) et supprime l'encombrement. (Quelque)
les intrinsèques et tout ce qui a asm serait un côté fn.

--
Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail ou consultez-le sur GitHub :
https://github.com/rust-lang/rust/issues/24111#issuecomment -377333542

--
Envoyé depuis mon appareil Android avec K-9 Mail. Veuillez excuser ma brièveté.

Je pense que le plus gros problème est que ce serait un vrai choc pour les débutants, car ce n'est pas ce que font la plupart des langages de programmation.

@whitequark Je ne suis pas d'accord avec tout ce qui fait juste ça ("jeter les effets secondaires"), je pense que @oli-obk parlait d'une future extension mais d'après les discussions auxquelles j'ai participé, je sais ce qui suit

  • on peut distinguer les "effets secondaires déterministes" (qui ne vous renvoient pas de données impures)
  • nous pourrions avoir des API spéciales si nous voulions par exemple "enregistrer les données au moment de la compilation"

    • c'est assez difficile selon le niveau de déterminisme extérieur que vous voulez, car la compilation incrémentielle à la demande n'entraînera pas nécessairement une sortie cohérente

  • concrètement, on ne toucherait à rien de ce qui existe actuellement et utilise globals/C FFI

EDIT : juste pour que la discussion ne déraille pas, par exemple :

Le fait de jeter des effets secondaires peut conduire à ce que le code se comporte différemment (disons écrire puis lire un fichier) s'il est appelé const ou non const.

Nous pouvons (probablement ?) Tous supposer que @oli-obk s'est mal exprimé concernant le rejet d'effets secondaires comme celui-là.

Peut-être qu'une telle fonctionnalité pourrait être implémentée en tant qu'indicateur de configuration au niveau de la caisse qui peut être ajouté à la racine de la caisse

C'est un sous-ensemble du deuxième exemple de suggestions passées, de https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588.
Si nous avons un "drapeau de configuration" étendu, l'utilisateur devrait pouvoir choisir des étendues plus fines IMO.

Et si on le faisait à l'envers ?
Plutôt que d'avoir const fn, ayez side fn.
C'est toujours explicite (vous devez mettre side fn pour appeler side fn, rompant explicitement la compatibilité) et supprime l'encombrement. (Certains) intrinsèques et tout ce qui contient asm serait un côté fn.

Dans https://github.com/rust-lang/rust/issues/24111#issuecomment -376829588, j'ai essayé de souligner que des bibliothèques entières pouvaient être "toutes const fn " ou "toutes side fn ".
Si ce n'était pas sur les déclarations de fonction, mais plutôt sur la portée, cela pourrait peut -être fonctionner dans une future édition.
Cependant, sans sémantique "inférer du corps", vous devez concevoir les interactions de trait même pour l'opt-in side fn , donc vous ne gagnez rien et vous introduisez des frictions potentiellement massives.

La section 3.3 de la rédaction de Kenton Varda "Les singletons considérés comme nuisibles" semble pertinente ici (honnêtement, tout cela vaut la peine d'être lu).

Qu'en est-il de la journalisation de débogage ?

En pratique, tout le monde reconnaît que la journalisation de débogage devrait être disponible pour chaque morceau de code. Nous faisons une exception pour cela. La base théorique exacte de cette exception, pour ceux qui s'en soucient, peut être fournie de plusieurs façons.

Du point de vue de la sécurité, la journalisation de débogage est un singleton bénin. Il ne peut pas être utilisé comme canal de communication car il est en écriture seule. Et il est clairement impossible de causer des dommages en écrivant dans un journal de débogage, car la journalisation de débogage n'est pas un facteur d'exactitude du programme. Même si un module malveillant "spam" le journal, les messages de ce module peuvent facilement être filtrés, car les journaux de débogage identifient normalement exactement quel module a produit chaque message (parfois ils fournissent même une trace de la pile). Par conséquent, il n'y a aucun problème à le fournir.

Des arguments analogues peuvent être avancés pour montrer que la journalisation de débogage ne nuit pas à la lisibilité, à la testabilité ou à la maintenabilité.

Une autre justification théorique de la journalisation de débogage indique que la fonction de journal de débogage n'est en réalité qu'un non-op qui se trouve être observé par le débogueur. Lorsqu'aucun débogueur n'est en cours d'exécution, la fonction ne fait rien. Le débogage en général brise évidemment tout le modèle de capacité objet, mais c'est aussi évidemment une opération privilégiée.

Ma déclaration sur "nous pouvons trouver une solution [pour le débogage]" faisait en effet référence à une future API potentielle, qui peut être appelée à partir de consts, mais qui a une certaine forme d'impression. L'implémentation aléatoire d'opérations d'impression spécifiques à la plate-forme (juste pour que nous puissions faire en sorte que le code existant avec des instructions print/debug soit const) n'est pas quelque chose qu'un évaluateur const devrait faire. Ce serait purement opt-in, n'ayant explicitement pas de comportement observable différent (par exemple, des avertissements dans const eval et une sortie de ligne de commande/fichier au moment de l'exécution). La sémantique exacte est laissée aux futures RFC et doit être considérée comme entièrement orthogonale à const fn en général

Y a-t-il des inconvénients significatifs à const impl Trait et T: const Trait ?

À part encore plus de pulvérisation autour de const , seules certaines méthodes de trait pourraient être const, ce qui nécessiterait un contrôle plus fin. Je ne connais pas de syntaxe soignée pour spécifier cela cependant. Peut-être where <T as Trait>::some_method is const ( is pourrait être un mot-clé contextuel).

Encore une fois, c'est orthogonal à const fn.

[u8; SizeOf<T>::Output]

Si const et side fns sont séparés, nous devons tenir compte des considérations de conception réelles. Le moyen le plus simple de les séparer est de faire de const fns une extension de quelque chose que nous avons aujourd'hui - le système de type turing-complete.

Alternativement, faites en sorte que const fns soit vraiment const : tout ce qui est const fn doit être évalué comme si chaque paramètre était un const générique.

Cela les rend beaucoup plus faciles à raisonner, car je ne peux pas personnellement raisonner sur les const fns tels qu'ils sont actuellement. Je peux raisonner sur les types complets de turing, les macros, les fns normaux, etc., mais je trouve impossible de raisonner sur const fn, car même des détails mineurs changent complètement leur signification.

car même des détails mineurs changent complètement leur signification.

Pourriez-vous élaborer? Voulez-vous dire des extensions comme les pointeurs const const fn les limites de trait const, ... ? Parce que je ne vois aucun détail mineur dans la simple proposition const fn .

Alternativement, faites en sorte que const fns soit vraiment const : tout ce qui est const fn doit être évalué comme si chaque paramètre était un const générique.

C'est ce que nous faisons au moment de la compilation. Juste qu'à l'exécution, la fonction est utilisée comme n'importe quelle autre fonction.

Le problème est que n'importe quel détail mineur peut transformer une évaluation const en une évaluation d'exécution. Cela peut ne pas sembler énorme, au début, mais cela peut l'être .

Disons que l'appel de la fonction est vraiment long car tout est const fns? Et vous voulez le diviser en plusieurs lignes.

Donc, vous ajoutez quelques let s.

Maintenant, votre programme prend 20 fois plus de temps à s'exécuter.

@SoniEx2 La taille des tableaux ( $N in [u8; $N] ) est toujours évaluée au moment de la compilation. Si cette expression n'est pas const , la compilation échouera. Inversement, let x = foo() appellera foo au moment de l'exécution, qu'il s'agisse ou non d'un const fn (modulo l'intégration et la propagation constante de l'optimiseur, mais c'est entièrement distinct de const fn ). Si vous voulez nommer le résultat de l'évaluation d'une expression au moment de la compilation, vous avez besoin d'un élément const .

Maintenant, votre programme prend 20 fois plus de temps à s'exécuter.

Ce n'est pas du tout comme ça que const fn fonctionne!

Si vous déclarez une fonction const fn et que vous ajoutez une liaison let à l'intérieur, votre code arrête de compiler.

Si vous supprimez const d'un const fn , il s'agit d'un changement de rupture et cassera toutes les utilisations de cette fonction à l'intérieur, par exemple const , static ou les longueurs de tableau. Votre code qui était du code d'exécution et exécutait un const fn , ne s'exécuterait jamais au moment de la compilation. C'est juste un appel de fonction d'exécution normal, donc ça ne ralentit pas.

Edit : @SimonSapin m'a devancé :D

Const fn est évalué au moment de la compilation si possible.

C'est-à-dire,

const fn random() -> i32 {
    4
}

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

Disons maintenant que vous avez un const fn qui prend des arguments. Cela serait évalué au moment de la compilation :

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

Cela entraînerait l'évaluation de const_fn_with_1_arg au moment de l'exécution :

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

@eddyb Je me demande si le problème de conception peut être résolu par l'observation que le "minimal const fn" est compatible avec toutes les futures extensions potentielles? Autrement dit, je crois comprendre que nous voulons stabiliser

marquer les fonctions libres et les méthodes inhérentes comme const, leur permettant d'être appelées dans des contextes constants, avec des arguments constants.

Cela semble trivialement entièrement compatible avec toutes les conceptions "d'effets constants pour les traits". Il est également compatible avec la conception "inferred const", car nous pouvons rendre const facultatif plus tard.

Existe-t-il d'autres conceptions futures incompatibles avec la proposition actuelle de "const fn minimal" ?

@nrc

Notez que rfcbot n'a pas enregistré votre problème everything-const (un problème par commentaire !) Cependant, il semble que ce soit un sous-ensemble du problème de conception, qui est traité par mon commentaire précédent (TL; DR : proposition minimale actuelle est entièrement compatible avec tout, nous pourrions rendre le mot-clé const facultatif à l'avenir).

En ce qui concerne le problème de priorité/retombées, j'aimerais documenter ce dont nous avons discuté de toutes parts, et ce que nous n'avons pas encore documenté :

  • const fn foo(x: i32) -> i32 { body } est un ajout relativement mineur par rapport à const FOO: i32 = body; , donc le risque de retombées est faible. Autrement dit, la plupart du code qui implémente réellement const fn travaille déjà dur dans le compilateur stable (avertissement : c'est quelque chose que j'ai entendu de @oli-obk, j'ai peut-être mal entendu).

  • le groupe de travail intégré veut const fn mal :)

De plus, notez que la non stabilisation const fn conduit à la prolifération d'API sous-optimales dans les bibliothèques, car elles doivent utiliser des astuces comme ATOMIC_USIZE_INIT pour contourner le manque de const fns.

soit je = aléatoire(); // le RHS de cette liaison est évalué au moment de la compilation, il n'y a pas d'appel à random au moment de l'exécution.

Non, cela ne se produit pas du tout. Cela pourrait se produire (et llvm le fait probablement), mais vous ne pouvez pas vous attendre à ce que des optimisations du compilateur qui dépendent de l'heuristique se produisent réellement. Si vous voulez que quelque chose soit calculé au moment de la compilation, collez-le dans un const et vous obtenez cette garantie.

donc const fn n'est évalué que dans un const, et c'est fondamentalement inutile sinon?

pourquoi ne pas avoir const et non-const fn strictement séparés alors?

voir la sémantique est un gâchis car ils mélangent intentionnellement des trucs de compilation et d'exécution.

donc const fn n'est évalué que dans un const, et c'est fondamentalement inutile sinon?

Ils ne sont pas inutiles, ils sont exécutés à l'exécution comme toute autre fonction sinon. Cela signifie que vous n'avez pas à utiliser différents "sous-langages" de Rust selon que vous êtes dans une évaluation const ou non.

pourquoi ne pas avoir const et non-const fn strictement séparés alors?

Toute la motivation pour const fn est de ne pas avoir cette séparation. Sinon, nous aurions besoin de dupliquer toutes sortes de fonctions : AtomicUsize::new() + AtomicUsize::const_new() , même si les deux corps sont identiques.

Voulez-vous vraiment avoir à écrire 90 % de libcore deux fois, une fois pour const eval et une fois pour l'exécution ? Il en va probablement de même pour beaucoup d'autres caisses.

Je pensais AtomicUsize::Of<value> . Et oui, je préférerais tout écrire deux fois plutôt que d'avoir des garanties inconnues. (De plus, cela ne se comporterait pas différemment selon que quelque chose est évalué ou non.)

Pouvez-vous déclarer consts dans const fn pour garantir l'évaluation const (pour const fn récursif)? Ou avez-vous besoin de passer par des génériques const? Etc.

@SoniEx2 comme exemple de la façon dont votre exemple doit être écrit pour tirer parti de const fn et se transformer en une erreur de temps de compilation si l'une ou l'autre fonction devient non- const :

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

(exemple de fonctionnement complet sur l'aire de jeux)

Légèrement moins ergonomique car il n'y a pas d'inférence de type, mais qui sait, cela peut changer à l'avenir.

que d'avoir des garanties inconnues.

pourriez-vous être si gentil et donner quelques exemples de cas où vous pensez que quelque chose n'est pas clair ?

Pouvez-vous déclarer consts dans const fn pour garantir l'évaluation const (pour const fn récursif)? Ou avez-vous besoin de passer par des génériques const? Etc.

Le but de const fn n'est pas d'évaluer magiquement les choses au moment de la compilation. C'est pour pouvoir évaluer les choses au moment de la compilation.

L'évaluation magique des choses au moment de la compilation se produit déjà puisque rustc était basé sur llvm. Alors ... exactement quand il a cessé d'être implémenté dans ocaml. Je ne pense pas que quiconque veuille supprimer la propagation constante de rustc.

const fn n'influence en aucune façon la propagation constante. Si vous aviez une fonction qui pouvait accidentellement être propagée par const, et que llvm le faisait, et que vous modifiiez cette fonction de manière à ce qu'elle ne soit plus propagée par const, llvm cesserait de le faire. Ceci est complètement indépendant de l'attachement de const à une fonction. Pour llvm, il n'y a pas de différence entre un const fn et un fn .

En même temps, rustc ne change pas du tout son comportement lorsque vous attachez const à un fn (en supposant que la fonction est un const fn valide et donc toujours compilée après cela). Il vous permet uniquement d'appeler cette fonction dans des constantes à partir de maintenant .

Je ne pense pas à LLVM, je pense à la rouille. LLVM n'a pas d'importance pour moi ici.

@SoniEx2

Const fn est évalué au moment de la compilation si possible.

Ce n'est pas correct. const fn , lorsqu'il est appelé dans un contexte particulier, SERA évalué au moment de la compilation. Si ce n'est pas possible, il y a une erreur matérielle. Dans tous les autres contextes, la partie const n'a aucune importance.

Des exemples de tels contextes qui nécessitent const fn sont les longueurs de tableau. Vous pouvez écrire [i32; 15] . Vous pouvez également écrire [i32; 3+4] car le compilateur peut calculer le 7 . Vous ne pouvez pas écrire [i32; read_something_from_network()] , car comment cela aurait-il un sens ? Avec cette proposition, vous POUVEZ écrire [i32; foo(15)] si foo est const fn , ce qui garantit qu'il s'agit plus d'un ajout que d'un accès au réseau.

Il ne s'agit pas du tout de code qui peut s'exécuter ou s'exécutera lors de l'exécution du programme. Il n'y a pas de "peut-être évaluer au moment de la compilation". Il y a juste "à évaluer au moment de la compilation ou à abandonner la compilation".

Veuillez également lire le RFC : https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md

Au lieu d'avoir une annotation const fn , et s'il s'agissait d'une propriété déduite ? Il ne serait pas explicite dans le code source, mais pourrait être automatiquement étiqueté comme tel dans la documentation générée automatiquement. Cela permettrait un éventuel élargissement de ce qui est considéré comme const , sans que les auteurs de la bibliothèque aient besoin de changer leur code. Au début, l'inférence pourrait être limitée à tout ce que les const fn s supportent actuellement (fonctions pures et déterministes sans aucune liaison let ?).

Selon cette approche, l'évaluation se produirait au moment de la compilation si le résultat est lié à une variable const , et au moment de l'exécution sinon. Cela semble plus souhaitable, car cela donne à l'appelant (plutôt qu'à l'appelé) le contrôle du moment où la fonction est évaluée.

Cela a déjà été discuté de manière assez approfondie. L'inconvénient de cette approche est qu'il est plus facile d'aller accidentellement dans l'autre sens - quelqu'un pourrait utiliser une fonction de bibliothèque dans un contexte const , mais l'auteur de la bibliothèque pourrait alors faire en sorte qu'elle ne soit plus const sans même s'en rendre compte.

Hum, oui c'est un problème...

Modifier : la seule solution à laquelle je peux penser, sans aller jusqu'à const fn , serait d'avoir une annotation de désactivation, afin que l'auteur de la bibliothèque puisse se réserver le droit de casser const ness. Je ne suis pas sûr que ce soit mieux que de saupoudrer const fn partout, cependant. Le seul avantage réel serait l'adoption plus rapide d'une définition élargie de const .

Edit 2: Je suppose que cela casserait la rétrocompatibilité, donc c'est un non-démarreur. Désolé pour le détour.

Donc... la discussion s'est éteinte. Résumons :

le commentaire rfcbot est https://github.com/rust-lang/rust/issues/24111#issuecomment -376649804

Préoccupations actuelles

  • Ce n'est pas une priorité, et la version 2018 en a déjà mis assez dans nos assiettes
  • Nous commençons à tout marquer const , ce qui est ennuyeux
  • Est-ce la conception sur laquelle nous voulons nous engager ?

Je ne peux pas vraiment parler de la priorité + du problème des retombées, sauf que const fn a cuit la nuit depuis longtemps

Les deux autres points sont étroitement liés. La conception de @eddyb (https://github.com/rust-lang/rust/issues/24111#issuecomment-376829588 si j'ai bien compris) est de ne pas avoir const fn , mais plutôt un #[const] Attribut

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

et cela va récursivement dans tout ce qui est marqué avec, donc toute fonction à l'intérieur d'un module #[const] est un #[const] fn . Une fonction déclarée dans un #[const] fn est aussi un #[const] fn .

Cela réduit le nombre d'annotations nécessaires, car certaines caisses se contenteront de taper un #![const] dans le lib.rs et d'en finir.

Problèmes que je vois avec cette conception (mais ces problèmes existent aussi souvent dans const fn ):

  • peut nécessiter une prise en charge de l'opt-out, car vous voudrez peut-être pouvoir déclarer quelques fonctions non constantes au plus profond d'une arborescence de modules autrement #[const] .
  • les pointeurs de fonction vers #[const] fn dans la position du type argument/retour doivent-ils être #[const] fn ?

    • encore une fois, l'opt-out serait nécessaire

Nous devons réfléchir à ces choses, donc nous ne concevons pas un système qui sera incompatible avec une future version où nous voulons pouvoir appeler des fonctions via des pointeurs de fonction lors d'un eval const.

Notez que je n'ai pas proposé un certain design, mais simplement énuméré quelques directions plausibles connues.
L'idée originale était un attribut généralisé "exposer le corps de la fonction", non limité à const , mais il existe de nombreuses variantes possibles, et certaines d'entre elles pourraient même être bonnes .

EDIT : (ne voulez pas oublier cela) @solson me montrait comment Lean a des attributs comme @pattern qui dérivent automatiquement diverses choses du corps d'une fonction.

@oli-obk Je pense que nous ne devrions pas utiliser d'attributs, car unsafe n'utilise pas d'attribut.
De plus, async ne le fait pas non plus actuellement. Et si nous introduisons try fn donnés try { .. } blocs, alors nous avons une autre chose qui n'est pas basée sur les attributs. Je pense que nous devrions essayer de rester aussi cohérents que possible avec des choses qui ressemblent à des effets. en utilisant ou non des attributs. #[target_feature(..)] met une ride dans la cohérence globale.

PS : Vous pouvez utiliser const mod { .. } pour obtenir le même effet que #![const] plus ou moins. Cela pourrait également s'appliquer à try mod , async mod , unsafe mod .

Je pencherai toujours pour faire des choses avec des types spéciaux.

struct SizeOf<T>;

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

il est plus facile d'apprendre de nouveaux types en utilisant la syntaxe existante que d'apprendre de nouveaux concepts avec une nouvelle syntaxe.

et nous pouvons plus tard prendre en charge le système de type lors de l'exécution.

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

types au moment de la compilation, les génériques const soit au moment de la compilation, soit à l'exécution.

Donc... je suggère d'ignorer le fait qu'il pourrait y avoir une meilleure conception là-bas, parce que nous avons une conception qui est

  1. Facile à raisonner
  2. Compatible avec toutes les conceptions plus permissives
  3. Historiquement utilisé dans le code instable avec un grand succès, le principal reproche étant le manque de fonctionnalités.
  4. Peut être ligné pour suggérer d'ajouter l'annotation à des fonctions non encore annotées qui ont un corps qui permet de l'ajouter.
  5. Personnellement, comme indiqué dans la demande de fusion, je considère que la seule conception que nous pouvons stabiliser sans une autre période d'essai de plusieurs années.
  6. Permet de partager le code entre le runtime et le code d'évaluation const, au lieu d'exiger un code personnalisé pour chacun

et nous pouvons plus tard prendre en charge le système de type lors de l'exécution.

C'est le typage dépendant, qui est loin , alors que l'appel d'un const fn à l'exécution fonctionne très bien aujourd'hui.

@oli-obk Qu'en est-il des traits ? Je ne veux pas stabiliser const fn sans avoir une idée de ce que nous allons faire pour les méthodes de trait qui sont const fn dans seulement certains des impl du trait.

@eddyb semble que je devrais alors accélérer l'écriture des nouvelles limites et méthodes const. :)

@Centril Mon point est que la proposition d'attribut (qu'elle utilise ou non un mot-clé) entraînerait une approche beaucoup plus différente pour traiter les méthodes de trait, et nous devons comparer cela .
L'approche const fn actuelle peut sembler simple et extensible, mais pas lorsqu'elle est réellement étendue.

consts génériques + const génériques :

intrinsic const SizeOf<T>: usize;

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

@eddyb J'ai différentes solutions en tête qui sont entièrement compatibles avec la conception const fn. Je vais l'écrire.

Woah, je viens de voir que ça fait deux ans que ça a commencé. Une date de stabilisation est-elle prévue ? J'ai une caisse qui est presque disponible sur stable car elle attend que cette extension soit stabilisée. :)

@rfcbot concerne les adresses de pointeur d'exécution

Sur un autre sujet, la question de savoir si nous voulons une transparence référentielle à partir de const fn s'est posée, et le problème de l'utilisation d'adresses de pointeurs bruts comme oracle de non-déterminisme est apparu : https://github.com/rust- lang/rust/issues/49146#issuecomment -386727325. Il y a une solution décrite ici, mais elle implique de faire quelques opérations de pointeur brut unsafe (je ne sais pas combien d'entre elles sont même autorisées aujourd'hui), avant la stabilisation.

@eddyb E0018 ne s'appliquerait-il pas également aux const fn ?

Le C Way est que les pointeurs d'objet sont autorisés à tous être 0 sauf s'ils sont relatifs (c'est-à-dire à l'intérieur d'un objet) et suivis au moment de l'exécution d'une manière ou d'une autre.

Je ne sais pas si rust prend en charge les règles d'aliasing de C.

@sgrif De nombreuses erreurs émises à propos des constantes vont disparaître tôt ou tard - miri ne se soucie pas du type d'une valeur, un emplacement abstrait qui se trouve dans une usize est toujours un emplacement abstrait (et le lancer sur un pointeur vous restitue le pointeur d'origine).

Je viens de vérifier et pour l'instant , la conversion de pointeurs sur des entiers et les opérateurs de comparaison entre pointeurs sont interdits dans des contextes constants. Cependant, c'est exactement ce à quoi nous pensions , j'ai toujours peur.

@eddyb Assez juste. Cependant, je m'attendrais à ce que toutes vos préoccupations qui touchent const fn touchent déjà des blocs de const aujourd'hui.

@sgrif La différence est const (même les const associés qui dépendent des paramètres de type générique) sont entièrement évalués au moment de la compilation, sous miri, tandis que const fn est un non- const fn pour les appels d'exécution.
Donc, si nous voulons vraiment la transparence référentielle, nous devons nous assurer que nous n'autorisons pas (dans le code sécurisé, au moins) les choses qui peuvent provoquer un non-déterminisme à l'exécution même si elles vont bien sous miri .
Ce qui signifie probablement que l'obtention des bits flottants est également un problème, car, par exemple, les charges utiles NaN.

Choses que vous devriez envisager de faire dans miri :

Tous les pointeurs valent 0 sauf s'ils sont relatifs. Par exemple:

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

Ensuite, vous les suivez lors de l'évaluation Miri. Certaines choses seraient UB, comme passer du pointeur à l'utilisation de retour au pointeur, mais celles-ci sont faciles à interdire (interdire les conversions d'utilisation en pointeur, puisque vous suivriez déjà les pointeurs au moment de l'exécution/de l'évaluation).

En ce qui concerne les bits flottants, la normalisation NaN ?

Je pense que les deux rendraient le tout déterministe.

Ce qui signifie probablement que l'obtention des bits flottants est également un problème, car, par exemple, les charges utiles NaN.

Je pense que pour une transparence référentielle totale, nous devrions rendre toutes les opérations flottantes dangereuses.

le déterminisme en virgule flottante est difficile

Le point le plus important ici est que l'optimiseur de LLVM peut modifier et modifie l'ordre des opérations flottantes ainsi que ~ effectue ~ les opérations de fusion pour lesquelles il a un opcode combiné. Ces changements affectent le résultat de l'opération, même si la différence réelle est légère. Cela affecte la transparence référentielle car miri exécute le mir non optimisé pour llvm tandis que la cible exécute le code optimisé pour llvm et éventuellement réorganisé et donc doté d'une sémantique différente de celle du code natif.

J'accepterais une première fonctionnalité const fn stable sans flotteurs pour l'instant jusqu'à ce qu'il y ait une décision sur l'importance de la transparence référentielle, mais je ne voudrais pas que const fn soit ralenti ou bloqué par cette discussion.

Le point le plus important ici est que l'optimiseur de LLVM peut modifier et modifie l'ordre des opérations flottantes ainsi qu'effectuer des opérations de fusion pour lesquelles il a un opcode combiné. Ces changements affectent le résultat de l'opération, même si la différence réelle est légère.

Les opérations de fusion (je suppose que vous vous référez à mul-add) ne sont pas autorisées/ne sont pas effectuées sans indicateurs de maths rapides précisément parce qu'elles modifient l'arrondi des résultats. LLVM est très attentif à préserver la sémantique exacte des opérations en virgule flottante lorsqu'il cible du matériel conforme à la norme IEEE, dans les limites fixées par "l'environnement en virgule flottante par défaut".

Deux choses que LLVM n'essaie pas de préserver sont les charges utiles NaN et la signalisation des NaN, car ces deux éléments ne peuvent pas être observés dans l'environnement fp par défaut - uniquement en inspectant les bits des flottants, que nous devrions donc interdire. (Et même si LLVM était plus prudent à ce sujet, le matériel varie également dans son traitement des charges utiles NaN, vous ne pouvez donc pas épingler cela sur LLVM.)

L'autre cas majeur que je connais où la décision du compilateur peut faire une différence pour les résultats en virgule flottante est l'emplacement des débordements et des rechargements dans le code x87 (pré-SSE). Et c'est surtout un problème car x87 arrondit par défaut à 80 bits pour les résultats intermédiaires et arrondit à 32 ou 64 bits sur les magasins. Définir correctement le mode d'arrondi avant chaque instruction FPU pour obtenir des résultats correctement arrondis est possible, mais ce n'est pas vraiment pratique et donc (je crois) LLVM ne le prend pas en charge.

Sur la Transparence Référentielle Complète

Mon point de vue est que nous devrions opter pour une transparence référentielle totale, car cela correspond au message général de Rust consistant à choisir la sécurité/l'exactitude plutôt que la commodité/l'exhaustivité .

La transparence référentielle ajoute de nombreux avantages au raisonnement, comme l'activation du raisonnement équationnel (jusqu'au fond, mais un raisonnement rapide et lâche est moralement correct ).

Cependant, il y a bien sûr des inconvénients. perdre sur l'exhaustivité wrt. CTFE. J'entends par là qu'un mécanisme const fn référentiellement transparent ne serait pas capable d'évaluer autant au moment de la compilation qu'un schéma const fn non transparent aux références. À ceux qui proposent de ne pas s'en tenir à la transparence référentielle, je demanderais qu'ils fournissent autant de cas d'utilisation concrets que possible contre cette proposition afin que nous puissions évaluer les compromis.

D'accord, il semble que vous ayez raison sur le point LLVM, il semble en effet éviter les opérations de calcul erronées à moins que vous n'activiez le mode mathématique rapide.

Cependant, il existe encore un tas d' états dont dépendent les opérations en virgule flottante, comme la précision interne. L'évaluateur flottant CTFE connaît-il la valeur de précision interne au moment de l'exécution ?

De plus, lors du déversement de valeurs dans la mémoire, nous convertissons la valeur interne au format ieee754 et modifions ainsi la précision. Cela peut également affecter le résultat, et l'algorithme par lequel les compilateurs effectuent le déversement n'est pas spécifié, n'est-ce pas ?

@ est31 Notez que j'ai supposé que nous ne nous soucions pas de savoir si le comportement au moment de la compilation et de l'exécution diffère, seulement que les appels répétés (avec des graphiques d'objets figés) sont cohérents et sans effets secondaires globaux.

Donc, si nous voulons vraiment la transparence référentielle, nous devons nous assurer que nous n'autorisons pas (dans le code sécurisé, au moins) les choses qui peuvent provoquer un non-déterminisme à l'exécution même si elles vont bien sous miri.

Donc, le but ici est de garantir qu'un const fn est déterministe et sans effet secondaire au moment de l'exécution, même si miri se tromperait à ce sujet pendant l'exécution , au moins si le const fn est entièrement sûr code? Je n'ai jamais considéré que c'était important, TBH. C'est une exigence assez forte, et en effet nous devrions au moins rendre dangereux ptr-to-int et float-to-bits , ce qui sera difficile à expliquer. OTOH, nous devrions alors également rendre la comparaison de pointeurs bruts dangereuse et j'en serais heureux :D

Quelle est la motivation pour cela ? On dirait que nous essayons de réintroduire la pureté et un système d'effets, qui sont des choses que Rust avait et a perdues, probablement parce qu'ils ne portaient pas leur poids (EDIT : ou parce qu'il n'était tout simplement pas assez flexible pour faire tous les différents les choses pour lesquelles les gens voulaient l'utiliser, voir https://mail.mozilla.org/pipermail/rust-dev/2013-April/003926.html).

ptr-to-int est sûr s'il convertit depuis le début d'un objet et que vous autorisez int-to-ptr à échouer. ce serait 100% déterministe. et chaque appel produirait les mêmes résultats.

ptr-to-int est sûr, int-to-ptr n'est pas sûr. ptr-to-int devrait être autorisé à obtenir des résultats "inattendus" dans l'évaluation const .

et vous devriez vraiment utiliser la canonisation NaN dans un interpréteur/VM. (un exemple de ceci est LuaJIT, qui fait que tous les résultats NaN sont le NaN canonique, et tous les autres NaN sont un NaN compressé)

ptr-to-int est sûr s'il convertit depuis le début d'un objet et que vous autorisez int-to-ptr à échouer. ce serait 100% déterministe. et chaque appel produirait les mêmes résultats.

Comment suggérez-vous que nous implémentions cela au moment de l'exécution ? (&mut *Box::new(...)) as usize est une fonction non déterministe en ce moment et devrait être tout const fn . Alors, comment suggérez-vous que nous nous assurons qu'il est "référentiellement transparent au moment de l'exécution" dans le sens de toujours renvoyer la même valeur ?

Box :: new doit renvoyer un pointeur suivi dans la VM.

La conversion donnerait 0, au moment de la compilation (c'est-à-dire dans une évaluation const). Dans une évaluation non constante, cela renverrait n'importe quoi.

Un pointeur suivi fonctionne comme ceci :

Vous allouez un pointeur, et vous l'assignez. Lorsque vous l'attribuez, la machine virtuelle définit la valeur du pointeur sur 0, mais prend l'adresse mémoire du pointeur et l'attache à une table de recherche. Lorsque vous utilisez le pointeur, vous parcourez la table de recherche. Lorsque vous obtenez la valeur du pointeur (c'est-à-dire les octets qui le composent), vous obtenez 0. En supposant qu'il s'agisse du pointeur de base d'un objet.

Box :: new doit renvoyer un pointeur suivi dans la VM.

Nous parlons ici de comportement à l'exécution. Comme dans, que se passe-t-il lorsque cela est exécuté dans le binaire. Il n'y a pas de machine virtuelle.

miri peut déjà gérer tout cela très bien et de manière entièrement déterministe.


Aussi, pour en revenir à la préoccupation concernant const fn et la détermination du temps d'exécution : si nous voulons cela (ce que je ne suis pas sûr de faire, attendant toujours que certains m'expliquent pourquoi nous nous en soucions :D ), nous pourrions simplement déclarer ptr-to-int et float-to-bits sont des opérations non constantes. Y a-t-il un problème avec ça?

@RalfJung J'ai lié https://github.com/rust-lang/rust/issues/49146#issuecomment -386727325 déjà dans le commentaire de préoccupation, peut-être que cela s'est perdu sous d'autres messages - il inclut une description d'une solution proposée par @Centril ( faire des opérations unsafe et mettre en liste blanche quelques "utilisations correctes", par exemple offset_of pour ptr-to-int et NaN-normalized float-to-bits).

@eddyb bien sûr, il existe différentes solutions ; ce que je demande, c'est la motivation. Je ne l'ai pas trouvé là-bas non plus.

De plus, en me citant d'IRC: je pense que nous pouvons même légitimement affirmer que CTFE est déterministe, même lorsque la conversion ptr-to-int est autorisée. Il faut toujours du code non-CTFE (par exemple, extraire des bits d'un ptr converti en int) pour observer réellement tout non-déterminisme.

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

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

est X == Y ?

avec ma suggestion, X == Y.

@SoniEx2 qui casse foo as *const _ as usize as *const _ ce qui est totalement un noop en ce moment

Quelle motivation :
Personnellement, je ne vois pas de problème avec le comportement d'exécution différent du comportement au moment de la compilation ou même const fn étant non déterministe au moment de l'exécution. La sécurité n'est pas le problème ici, donc dangereux est totalement le mauvais mot-clé imo. C'est juste un comportement surprenant que nous captons totalement lors de l'évaluation au moment de la compilation. Vous pouvez déjà le faire dans les fonctions normales, donc pas de surprises là-bas. Const fn consiste à marquer les fonctions existantes comme évaluables au moment de la compilation sans affecter l'exécution. Ce n'est pas une question de pureté, du moins c'est l'ambiance que j'ai eue quand les gens parlent de "l'enfer pur" (je n'étais pas là, donc j'ai peut-être mal interprété)

@SoniEx2 Oui, X == Y toujours.

Cependant, si vous avez :

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

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

Ensuite, main peut paniquer lors de l'exécution.

@RalfJung

ou parce qu'il n'était tout simplement pas assez flexible pour faire toutes les différentes choses pour lesquelles les gens voulaient l'utiliser [..]

Quelles étaient ces choses ? Il est difficile d'évaluer cela sans concrétisation.

Je pense que nous pouvons même affirmer à juste titre que CTFE est déterministe, même lorsque la conversion ptr-to-int est autorisée.
[...]
Il faut toujours du code non-CTFE (par exemple, extraire des bits d'un ptr converti en int) pour observer réellement tout non-déterminisme.

Oui, const fn est toujours déterministe lorsqu'il est exécuté au moment de la compilation, mais je ne pense pas qu'il soit déterministe au moment de l'exécution car il y aura invariablement des non- const fn (juste fn ) si le résultat de const fn doit être utile pour quoi que ce soit au moment de l'exécution, puis ce code fn observera les effets secondaires du const fn exécuté.

Tout le point de séparation entre le code pur et non pur dans Haskell est que vous pouvez prendre la décision "y aura-t-il des effets secondaires" local où vous n'avez pas à penser aux effets secondaires possibles à l'échelle mondiale. C'est-à-dire étant donné :

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

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

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

Vous savez que le résultat de revLine dans let revLine = reverse line ne peut dépendre que de l'état passé dans reverse qui est line . Cela offre des avantages de raisonnement et une séparation nette.

Je me demande à quoi ressemblait le système d'effets à l'époque ... Je pense que nous en avons besoin d'un étant donné const fn , async fn , etc. de toute façon pour le faire proprement et obtenir la réutilisation du code (quelque chose https:// github.com/rust-lang/rfcs/pull/2237 mais avec quelques changements...) et la paramétricité semble être une bonne idée pour cela.

Dans les mots immortels de Phil Wadler et Conor McBride :

Serai-je pur ou impur ?
—Philip Wadler [60]

Nous disons 'Oui' : la pureté est un choix à faire localement

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

@oli-obk

Je n'envie pas la personne qui doit documenter ce comportement surprenant que le résultat de l'exécution peut différer de const fn s'il est exécuté à l'exécution ou à la compilation avec les mêmes arguments.

Comme option conservatrice, je propose que nous retardions la décision sur la transparence/pureté référentielle en stabilisant const fn comme référentiellement transparent, ce qui en fait unsafe (ou non- const ) à violer mais nous ne garantissons pas réellement la transparence référentielle, donc les gens ne peuvent pas non plus le supposer.

Plus tard, quand nous aurons acquis de l'expérience, nous pourrons alors prendre la décision.
Acquérir de l'expérience inclut des personnes qui essaient d'utiliser le non-déterminisme mais échouent, puis le signalent et nous parlent de leurs cas d'utilisation.

Oui, mais ce n'est pas une évaluation const.

Convertir int en ptr dans const ne semble pas être une énorme perte. Obtenir des décalages de champ semble plus important, et c'est ce que j'essaie de préserver.

Je ne voudrais pas modifier silencieusement le comportement lors de l'ajout const à une fonction @SoniEx2 et c'est essentiellement ce que ferait votre suggestion.

@centril Je suis d'accord, faisons la chose conservatrice maintenant. nous ne les rendons donc pas dangereux, mais instables. de cette façon, la libstd peut l'utiliser, mais nous pourrons ensuite décider des détails.

Un problème que nous avons est que les utilisateurs peuvent toujours bricoler toute analyse que nous faisons en introduisant des unions et en faisant des conversions fantaisistes, donc nous devrions faire un tel contournement des conversions UB afin que nous soyons autorisés à le changer plus tard.

ce serait toujours le même au moment de l'exécution, le const ne changerait les choses qu'au moment du const, ce qui... ne serait même pas possible de faire sans le const.

c'est bien que const fns soit un sous-langage avec différentes règles d'alias de pointeur.

@Centril @est31 Je ne sais pas ce que le chlorotrifluoroéthylène a à voir avec quoi que ce soit (pouvons-nous éviter d'utiliser un tas d'acronymes sans les définir, s'il vous plaît ?)

@sgrif CTFE (évaluation de la fonction au moment de la compilation) est utilisé depuis longtemps pour Rust (y compris dans les parties anciennes de ce fil). C'est l'un de ces termes qui devrait déjà être défini quelque part au centre.

@sgrif lol désolé la plupart du temps je suis la personne qui se demande "quels mots bizarres utilisent-ils maintenant?". Je pense que je parlais à @eddyb sur IRC quand il a mentionné "CTFE" et j'ai dû lui demander ce que cela signifiait :p.

@eddyb ctrl+f + CTFE sur cette page ne mène à rien qui la définisse. Quoi qu'il en soit, je pars principalement de l'hypothèse que nous ne voulons pas forcer les gens à creuser beaucoup pour participer aux discussions ici. "Vous devriez déjà savoir ce que cela signifie" est assez exclusif à l'OMI.

@Centril @est31 merci :coeur:

Alors... des pensées sur

Un problème que nous avons est que les utilisateurs peuvent toujours bricoler toute analyse que nous faisons en introduisant des unions et en faisant des conversions fantaisistes, donc nous devrions faire un tel contournement des conversions UB afin que nous soyons autorisés à le changer plus tard.

Les accès au champ union à l'intérieur de const fn devraient-ils donc également être instables?

ctrl+f + CTFE sur cette page ne mène à rien qui la définisse.

Désolé, ce que je voulais dire par là, c'est que son utilisation dans la conception et le développement de Rust est antérieure à ce problème antérieur à la version 1.0.
Il devrait y avoir un document central pour ces termes, mais malheureusement, le glossaire de référence fait vraiment défaut.
HRTB et NLL sont deux exemples d'autres acronymes plus récents mais non expliqués en ligne.

Je ne veux pas que les discussions soient exclusives, mais je ne pense pas qu'il soit juste de demander à @Centril ou à @est31 de définir le CTFE, car aucun d'eux n'a introduit l'acronyme en premier lieu.
Il existe une solution qui, malheureusement, ne fonctionnerait pas nécessairement sur GitHub, que j'ai vue dans un certain subreddit, où un bot créera un commentaire de haut niveau et le maintiendra à jour , avec une liste d'extensions pour tous les acronymes couramment utilisés de ce subreddit apparaissant dans la discussion.

De plus, je suis curieux de savoir si je suis dans une bulle Google, puisque les deux articles de wikipedia pour CTFE ("Chlorotrifluoroéthylène" et "Compile time function execution") sont les deux premiers résultats pour moi. Même dans ce cas, le premier commence par "Pour la fonctionnalité du compilateur, voir l'exécution de la fonction de compilation.".

( J'ai mis le lien wiki CTFE en haut ; maintenant pouvons-nous revenir à const fn s ? :P)

Quelqu'un pourrait-il ajouter CTFE au glossaire du guide rustc (je suis sur mobile) ?

De plus, je pense que nous devrions stabiliser le strict minimum et voir ce que les gens rencontrent souvent dans la pratique, qui envoient l'approche actuelle avec les expressions const if et match.

@Centril

Je n'envie pas la personne qui doit documenter ce comportement surprenant que le résultat de l'exécution peut différer pour const fn s'il est exécuté à l'exécution ou à la compilation avec les mêmes arguments.

Le even que vous avez écrit ci-dessus échouerait s'il était exécuté au moment du CTFE car il inspecte les bits d'un pointeur. (Bien qu'en fait nous puissions dire de manière déterministe que c'est même à cause de l'alignement, et si le test d'égalité est effectué via des opérations sur les bits, "full miri" le ferait correctement. Mais supposons que vous testiez l'octet le moins significatif, pas seulement le dernier morceau.)
Le cas dont nous parlons ici est une fonction qui se trompe avec une erreur d'interpréteur au moment de la compilation, mais qui réussit au moment de l'exécution. La différence réside dans le fait que la fonction termine même son exécution. Je ne pense pas que ce soit difficile à expliquer.

Je suis d'accord que si CTFE réussit avec un result , alors la version d'exécution de la fonction devrait également être garantie de réussir avec le même résultat. Mais c'est une garantie beaucoup plus faible que ce dont nous parlons ici, n'est-ce pas?

@oli-obk

Je suis d'accord, faisons la chose conservatrice maintenant. nous ne les rendons donc pas dangereux, mais instables. de cette façon, la libstd peut l'utiliser, mais nous pourrons ensuite décider des détails.

J'ai perdu le contexte, qu'est-ce que "ceux-ci" ici ?

À l'heure actuelle, heureusement, CTFE miri refuse carrément de faire quoi que ce soit avec les valeurs de pointeur - arithmétique, comparaison, toutes les erreurs. Il s'agit d'une vérification effectuée au moment du CTFE sur la base des valeurs réellement utilisées dans le calcul, elle ne peut pas être contournée par les unions et de toute façon le code qui serait nécessaire pour faire l'arithmétique/comparaison n'existe tout simplement pas . Par conséquent, je suis à peu près sûr que nous satisfaisons à la garantie que j'ai énoncée ci-dessus.

Je pourrais imaginer des problèmes si CTFE renvoyait une valeur de pointeur, mais comment une valeur de pointeur calculée au moment de la compilation aurait-elle un sens n'importe où? Je suppose que nous vérifions déjà que tout ce que miri calcule ne contient pas de valeurs de pointeur, car nous devons le transformer en bits ?

Nous pourrions soigneusement ajouter des opérations à CTFE miri, et en fait tout ce dont nous avons besoin pour @eddyb 's offset_of [1] est une soustraction de pointeur. Ce code existe en "full miri", et il ne réussit que si les deux pointeurs se trouvent dans la même allocation, ce qui est suffisant pour maintenir la garantie ci-dessus. Ce qui ne fonctionnerait pas, c'est le assert que @eddyb a ajouté comme protection.
Nous pourrions également autoriser les opérations sur les bits sur les valeurs du pointeur si les opérations n'affectent que la partie alignée du pointeur, c'est toujours déterministe et le code existe déjà en "full miri".

EDIT : [1] Pour référence, je fais référence à sa macro dans ce fil auquel nous ne pouvons pas établir de lien car elle a été marquée hors sujet, voici donc une copie :

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

EDIT2 : En fait, il l'a également posté ici .

"ceux-ci" sont float -> conversion de bits et pointeur -> conversions usize

Je suis d'accord que si CTFE réussit avec un résultat, alors la version d'exécution de la fonction devrait également être garantie de réussir avec le même résultat. Mais c'est une garantie beaucoup plus faible que ce dont nous parlons ici, n'est-ce pas?

Ainsi, une fonction appelée avec des arguments au moment de l'exécution n'est garantie de pureté que si elle se termine réellement si elle est évaluée au moment de la compilation avec les mêmes arguments ?

Cela rendrait nos vies un million de fois plus faciles, d'autant plus que je ne vois pas de moyen d'empêcher le non-déterminisme sans laisser d'échappatoires ou paralyser const fn de manière radicale.

Ainsi, une fonction appelée avec des arguments au moment de l'exécution n'est garantie de pureté que si elle se termine réellement si elle est évaluée au moment de la compilation avec les mêmes arguments ?

Oui, c'est ce que je propose.


En y réfléchissant un peu plus (et en lisant la réponse de @ oli-obk qui est apparue pendant que j'écrivais ceci), j'ai l'impression que vous voulez une garantie supplémentaire du type "un coffre-fort const fn ne se trompera pas à Temps CTFE (autre que les paniques) lorsqu'il est appelé avec des arguments valides". Une sorte de garantie de "sécurité constante". Avec la garantie que j'ai énoncée ci-dessus concernant le CTFE réussi coïncidant avec le comportement d'exécution, cela fournirait une garantie qu'un const fn sûr sera déterministe au moment de l'exécution, car il correspond à l'exécution réussie du CTFE.

Je suis d'accord que c'est une garantie plus difficile à obtenir. Pour le meilleur ou pour le pire, Rust a diverses opérations sûres que CTFE miri ne peut pas garantir de toujours s'exécuter avec succès tout en maintenant le déterminisme, comme la variante de oracle de @Centril qui teste l'octet le moins significatif pour être 0. Du point de vue de CTFE dans ce cadre, les « valeurs valides de type usize » ne constituent que des valeurs qui sont PrimVal::Bytes , tandis qu'un PrimVal::Ptr ne devrait pas être autorisé [1]. C'est comme si nous avions un système de type légèrement différent dans le contexte const - je ne propose pas de changer ce que fait miri, je propose de changer ce que les différents types de Rust "signifient" lorsqu'ils sont attachés à un const fn . Un tel système de type garantirait que toutes les opérations arithmétiques et binaires sûres ne peuvent pas se tromper dans CTFE : pour les entrées valides CTFE, la soustraction d'entiers ne peut jamais échouer dans CTFE car les deux côtés sont PrimVal::Bytes . Bien sûr, ptr-to-int doit être une opération non sécurisée dans ce paramètre car sa valeur de retour a le type usize mais n'est pas un CTFE valide usize .

S'il s'agit d'une garantie qui nous tient à cœur, il ne me paraît pas déraisonnable de rendre plus strict le système de typage lors de la vérification des fonctions CTFE ; après tout, nous voulons l'utiliser pour faire plus de choses (en cochant "const safety"). Je pense qu'il serait alors très logique de déclarer des casts ptr-to-int unsafe dans le contexte const , en arguant que cela est nécessaire parce que le contexte const fait garanties supplémentaires.

Tout comme notre "sécurité d'exécution" normale peut être renversée par un code non sécurisé, la "sécurité constante" peut également l'être, et c'est très bien. Je ne vois aucun problème à ce que le code non sécurisé puisse toujours effectuer des transtypages ptr-to-int via les unions - c'est du code non sécurisé , après tout. Dans ce monde, les obligations de preuve pour le code non sécurisé dans un contexte const sont plus fortes que celles dans un contexte non-const ; si votre fonction renvoie un entier, vous devez prouver que ce sera toujours un PrimVal::Bytes et jamais un PrimVal::Ptr .

La macro de @eddyb aurait besoin d'un bloc non sécurisé, mais elle serait toujours sûre à utiliser car elle n'utilise que ces "fonctionnalités const unsafe" (ptr-to-usize puis soustraction du résultat, ou simplement en utilisant le pointeur soustraction intrinsèque directement) de manière à ne pas générer d'erreur CTFE.

Le coût d'un tel système serait qu'un coffre-fort d'ordre supérieur const fn doive prendre une fermeture const fn pour pouvoir garantir que l'exécution de la fermeture ne violera pas, elle-même, la "const safety". Tel est le prix à payer pour obtenir des garanties appropriées sur const fn en toute sécurité.

[1] J'ignore complètement les flotteurs ici car je ne sais pas trop où le non-déterminisme surviendrait. Quelqu'un peut-il fournir un exemple de code à virgule flottante qui se comporterait différemment au moment du CTFE qu'au moment de l'exécution? Serait-il suffisant par exemple de faire une erreur miri si, lors d'une opération en virgule flottante, l'un des opérandes est un NaN de signalisation (pour obtenir la première garantie, celle de mon post précédent), et de dire que le système de type CTFE ne permet pas de signaler les NaN au type f32 / f64 (pour obtenir la "const safety") ?

Ce qui ne fonctionnerait pas, c'est l'assertion que @eddyb a ajoutée comme protection.

Bien sûr, mais vous pouvez réécrire assert!(condition); en [()][!condition as usize]; pour l'instant.

Bien sûr, mais vous pouvez réécrire assert!(condition); à [()][!condition as usize] ; pour le moment.

Ce n'est pas l'affirmation à laquelle je pensais, c'est le test d'égalité du pointeur dans votre condition. L'égalité des pointeurs est un mal et je préférerais qu'on ne puisse pas l'autoriser au CTFE.

EDIT: Peu importe, je viens de réaliser que le assert teste le décalage. Donc, en fait, il ne peut jamais échouer au moment du CTFE car si les pointeurs ne sont pas dans le même bloc lors de l'exécution wrapping_sub , miri générera une erreur.

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

comme je l'ai déjà dit, utilisez des pointeurs virtuels dans miri, plutôt que de vrais pointeurs. il peut fournir un déterminisme constant au temps constant. si la fonction est écrite correctement, le comportement à l'exécution et à la compilation devrait produire les mêmes résultats, que l'exécution soit non déterministe alors que la compilation est déterministe. vous pouvez avoir un comportement déterministe dans des environnements non déterministes si vous codez pour cela.

obtenir un décalage de champ est déterministe. avec des pointeurs virtuels, il reste déterministe. avec de vrais pointeurs, c'est toujours déterministe.

obtenir la régularité du 14e bit d'un pointeur n'est pas déterministe. avec des pointeurs virtuels, il devient déterministe. avec de vrais pointeurs, ce n'est pas déterministe. c'est bien, car l'un se produit au moment de la compilation (un environnement déterministe), tandis que l'autre se produit au moment de l'exécution (un environnement non déterministe).

const fn doit être aussi déterministe que l'environnement dans lequel il est utilisé.

@SoniEx2

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

En effet nous ne pouvons pas. Je pense que je pourrais vivre avec une version de comparaison de pointeurs bruts qui se trompe si l'un ou l'autre pointeur n'est pas actuellement déréférencable dans le sens d'être dans un objet alloué (mais ce n'est pas ce que "full miri" implémente actuellement). Cependant, cela rendrait toujours is_eq non "const safe" car si T est de taille nulle, il pourrait pointer un après la fin d'un objet même si nous ne considérons que le code sûr.

C++ permet de comparer des pointeurs un après la fin pour produire un résultat indéterminé (pensez : non déterministe). C et C++ permettent tous deux de comparer un pointeur pendant pour produire un résultat indéterminé. Ce que LLVM garantit n'est pas clair, mais je préfère ne pas parier sur des garanties qui dépassent ce qu'ils doivent garantir pour C/C++ (le plus faible des deux, s'ils diffèrent). C'est un problème si nous voulons garantir le déterminisme d'exécution pour tout ce qui s'exécute avec succès dans CTFE, ce que je pense que nous faisons.

@RalfJung

La différence réside dans le fait que la fonction termine même son exécution.

L'avocat des diables : "retourner ⊥" dans un cas revient au même que d'avoir des résultats différents.

Je ne pense pas que ce soit difficile à expliquer.

Personnellement, je considère cela comme un comportement surprenant; Vous pouvez l'expliquer, et je comprendrai peut-être (mais je ne suis pas représentatif...), mais cela ne correspond pas à mon intuition.

@oli-obk

Ainsi, une fonction appelée avec des arguments au moment de l'exécution n'est garantie de pureté que si elle se termine réellement si elle est évaluée au moment de la compilation avec les mêmes arguments ?

Personnellement, je ne trouve pas cette garantie suffisante. Je pense que nous devrions d'abord voir jusqu'où nous pouvons aller avec la pureté et ce n'est que lorsque nous savons qu'elle est paralysante dans la pratique que nous devons nous diriger vers des garanties plus faibles.

@RalfJung

cela fournirait une garantie qu'un fn const sûr sera déterministe au moment de l'exécution, car il correspond à l'exécution CTFE réussie.

D'ACCORD; Tu m'as perdu; Je ne vois pas comment vous êtes arrivé à cette garantie "safe const fn is deterministic" étant donné les deux prémisses; pourriez-vous préciser le raisonnement?

S'il s'agit d'une garantie qui nous tient à cœur, il ne me paraît pas déraisonnable de rendre plus strict le système de typage lors de la vérification des fonctions CTFE ; après tout, nous voulons l'utiliser pour faire plus de choses (en cochant "const safety"). Je pense qu'il serait alors très logique de déclarer les casts ptr-to-int dangereux dans le contexte const, en arguant que cela est nécessaire parce que le contexte const offre des garanties supplémentaires.

Tout comme notre "sécurité d'exécution" normale peut être renversée par un code non sécurisé, la "sécurité constante" peut également l'être, et c'est très bien. Je ne vois aucun problème à ce que le code non sécurisé puisse continuer à effectuer des transtypages ptr-to-int via les unions - c'est du code non sécurisé, après tout. Dans ce monde, les obligations de preuve pour le code non sécurisé dans un contexte const sont plus fortes que celles dans un contexte non-const ; si votre fonction renvoie un entier, vous devez prouver que ce sera toujours un PrimVal::Bytes et jamais un PrimVal::Ptr .

Ces paragraphes sont de la musique à mes oreilles ! ❤️ Cela semble-t-il garantir le déterminisme ("pureté") ? et c'est précisément ce que j'avais en tête plus tôt. Je pense que la sécurité const est aussi un terme formidable !

Pour référence future, permettez-moi d'appeler cette garantie "Fiabilité CTFE": Si CTFE ne génère pas d'erreur, son comportement correspond à l'exécution - les deux divergent ou les deux se terminent avec la même valeur. (J'ignore entièrement les valeurs de retour d'ordre supérieur ici.)

@Centril

L'avocat des diables : "retourner ⊥" dans un cas revient au même que d'avoir des résultats différents.

Eh bien, c'est clairement une question de définition. Je pense que vous avez compris la garantie de solidité du CTFE que je décrivais et vous convenez que c'est une garantie que nous voulons; si c'est tout ce que nous voulons est à discuter :)

D'ACCORD; Tu m'as perdu; Je ne vois pas comment vous êtes arrivé à cette garantie "safe const fn is deterministic" étant donné les deux prémisses; pourriez-vous préciser le raisonnement?

Disons que nous avons un appel à foo(x)foo est une fonction const sûre et x est une valeur const-valide (c'est-à-dire, ce n'est pas &y as *const _ as usize ). Ensuite, nous savons que foo(x) s'exécutera dans CTFE sans générer d'erreur, par sécurité const. Par conséquent, selon la solidité CTFE, au moment de l'exécution, foo(x) se comportera de la même manière que dans CTFE.

Essentiellement, je pense que j'ai décomposé votre garantie en deux parties - l'une garantissant qu'un const fn sûr n'essaiera jamais de faire quelque chose que CTFE ne prend pas en charge (comme lire à partir de stdin ou déterminer si l'octet le moins significatif d'un pointeur est 0), et un garantissant que tout ce que CTFE prend en charge correspond à l'exécution.

Ces paragraphes sont de la musique à mes oreilles ! cœur Cela ne semble-t-il pas assurer le déterminisme (« pureté ») ? et c'est précisément ce que j'avais en tête plus tôt. Je pense que la sécurité const est aussi un terme formidable !

Content que tu aimes ça. :) Cela signifie que je comprends enfin de quoi nous parlons ici. « pureté » peut signifier tant de choses différentes, je me sens souvent un peu mal à l'aise lorsque le terme est utilisé. Et le déterminisme n'est pas une condition suffisante pour la sécurité const, le critère pertinent est de savoir si l'exécution en CTFE lève une erreur. (Un exemple d'une fonction déterministe non-const-safe est ma variante de votre orcale multiplié par 0. Ce n'est pas correct de le faire même en utilisant du code non sécurisé car miri produira une erreur lors de l'inspection des octets d'un pointeur, même si les octets n'ont finalement pas d'importance. C'est comme si l'opération qui extrait l'octet le moins significatif d'un pointeur est "const-UB" et n'est donc pas autorisée même dans un code const non sécurisé.)

oui, un pointeur un après la fin d'un élément de tableau pointerait probablement vers l'élément de tableau suivant. et alors? ce n'est pas vraiment non déterministe ? déterministe au moment de la compilation est tout ce qui compte de toute façon. autant que je m'en soucie, l'évaluation de l'exécution pourrait segfault pour l'épuisement de la pile.

@RalfJung

Le coût d'un tel système serait qu'un const fn d'ordre supérieur sûr doive prendre une fermeture const fn pour pouvoir garantir que l'exécution de la fermeture ne violera pas, elle-même, la "sécurité const". Tel est le prix à payer pour obtenir des garanties appropriées sur const fn en toute sécurité.

Je pense que cela peut être sérieusement atténué pour prendre en charge la transformation de la plupart des codes existants en introduisant ?const où vous pouvez écrire des fonctions d'ordre supérieur dont le résultat peut être lié à un const si et seulement si la fonction fournie est ?const fn(T) -> U et où is_const(x : T) ; Donc tu as :

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

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

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

Je rédigerai une RFC proposant quelque chose à cet effet (et plus) dans une semaine environ.

@Centril à ce stade, vous développez un système d'effets avec polymorphisme d'effet. Je sais que cela a toujours été votre programme secret (?), Juste pour vous faire savoir que cela devient flagrant. ^^

@RalfJung J'ai déjà révélé le secret sur https://github.com/rust-lang/rfcs/pull/2237 l'année dernière mais je vais devoir le réécrire ;)
Presque domaine public maintenant ^,-

@SoniEx2

oui, un pointeur un après la fin d'un élément de tableau pointerait probablement vers l'élément de tableau suivant. et alors? ce n'est pas vraiment non déterministe ? déterministe au moment de la compilation est tout ce qui compte de toute façon. autant que je m'en soucie, l'évaluation de l'exécution pourrait segfault pour l'épuisement de la pile.

Le problème est dans des situations comme les suivantes (en C++):

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

Les compilateurs C veulent (et font !) optimiser cette comparaison à false . Après tout, un pointeur pointe vers x et un autre vers y , il n'est donc pas possible qu'ils soient jamais égaux.
Sauf, bien sûr, que les adresses sont égales sur la machine car un pointeur pointe juste à la fin de x , qui est la même adresse que (le début de) y ! Donc, si vous obscurcissez suffisamment le code pour que le compilateur ne voie plus d'où viennent les adresses, vous pouvez dire que la comparaison est évaluée à true . La norme C++ permet donc aux deux résultats de se produire de manière non déterministe, justifiant à la fois l'optimisation (qui indique false ) et la compilation en assemblage (qui indique true ). La norme C ne le permet pas, ce qui rend les compilateurs LLVM (et GCC) non conformes, car les deux effectueront ce type d'optimisations.

J'ai écrit un résumé de mes idées sur la sécurité const, la solidité const, etc. qui sont apparues ici dans ce fil et/ou dans une discussion connexe sur IRC : https://www.ralfj.de/blog/2018/07/19/ const.html

Cette question ici est devenue quelque peu difficile à démêler parce que tant de choses ont été discutées. @oli-obk a utilement créé un dépôt pour les problèmes de const-eval, donc un bon endroit pour discuter de sous-problèmes spécifiques est probablement le traqueur de problèmes de https://github.com/rust-rfcs/const-eval.

@Centril a suggéré de stabiliser une version minimale compatible avec toutes les futures extensions :

  • pas d'arguments génériques avec des limites de trait
  • pas d'arguments ou de types de retour de pointeur fn ou de type dyn Trait

    • vérifié récursivement sur le type d'argument afin que les champs d'arguments puissent également ne pas être de ces types

  • pas de code dangereux (parce que nous ne savons pas s'il y a des choses qui posent problème)

    • Personnellement, je pense que c'est bien, sauf pour union s qui sont déjà derrière une porte de fonctionnalité supplémentaire et les derefs de pointeur bruts (généralement interdits dans toute constante en ce moment). Tout autre code non sécurisé doit passer par d'autres const fns ou const intrinsèques non sécurisés, qui nécessitent leur propre discussion par rapport à la stabilisation.

(nit: ma suggestion comprenait également une vérification récursive pour les pointeurs fn ou dyn Trait sur le type de retour de const fn s)

pas d'arguments génériques avec des limites de trait

Pour clarifier, quelque chose comme ça serait-il accepté ou non ?

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

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

La limite ne fait pas partie du const fn lui-même.

Également pour clarifier : la proposition de stabiliser ces choses et de laisser le reste derrière la porte des fonctionnalités OU de les stabiliser et de faire du reste une erreur ?

@mark-im le reste resterait derrière une porte de fonctionnalité.

@oli-obk quel est le problème avec le code non sécurisé ? Nous autorisons unsafe dans const X : Ty = ... , qui a tous les mêmes problèmes. Je pense que les corps const fn devraient être vérifiés exactement comme les corps const .

Je pense que nous voulons rester conservateurs autour des opérations "unconst" - des opérations sûres mais pas const-safe (essentiellement tout sur les pointeurs bruts) - mais celles-ci sont déjà entièrement interdites dans le contexte const, n'est-ce pas?

La borne ne fait pas partie de la const fn elle-même.

Non, les limites sur le bloc impl ne seraient pas non plus autorisées dans le cadre de cette proposition

quel est le problème avec le code non sécurisé ?

Je ne vois aucun problème comme indiqué dans mon commentaire. Chaque fonctionnalité/fonction const unsafe doit de toute façon passer par sa propre stabilisation.

@RalfJung Je pense que le problème est " @Centril est nerveux que nous ayons raté quelque chose. Ceux-ci sont déjà entièrement interdits dans le contexte const". ;) Mais nous devons stabiliser unsafe { .. } dans const fn à un moment donné, donc si vous êtes sûr qu'il n'y a pas de problèmes et que nous avons détecté toutes les opérations unconst, alors faisons-le ?

De plus, si nous avons raté quelque chose, nous sommes déjà un peu foutus car les gens peuvent l'utiliser sur const .

J'ai toujours l'intention d'écrire un PR remplissant les parties sécurité/promotion const du dépôt RFC const fn ; ce serait le moment de vérifier soigneusement si nous avons tout couvert (et si nous avons des cas de test).

Une autre chose qui est apparue sur Discord concerne les opérations FP : nous ne pouvons actuellement pas garantir qu'elles correspondent au matériel réel. CTFE suivra exactement IEEE, mais LLVM/hardware pourrait ne pas l'être.

Cela s'applique également aux éléments const , mais ceux-ci ne seront jamais exécutés au moment de l'exécution -- contrairement const fn . Il semble donc prudent de ne pas stabiliser les opérations de PF en const fn .

OTOH, nous promouvons déjà les résultats des opérations PF ? Nous avons donc déjà cette inadéquation entre l'exécution et la compilation observable sur stable. Cela vaudrait-il la peine d'effectuer une course au cratère pour voir si nous pouvons annuler cela ?

Pour référence future, l'article suivant est pertinent en ce qui concerne les virgules flottantes et le déterminisme :

@RalfJung

Cela vaudrait-il la peine d'effectuer une course au cratère pour voir si nous pouvons annuler cela ?

Je serais surpris si nous pouvions le faire, mais cela vaut au moins la peine d'essayer. :)

@RalfJung , il pourrait y avoir une entrée intéressante plus loin dans ce fil https://github.com/rust-lang/rust/issues/24111#issuecomment -386764565

En supposant que nous voulions conserver la forme de mot-clé const fn , je pense que stabiliser quelque chose maintenant , c'est assez limité, est une solution assez décente (comment ne l'ai-je pas vu avant ?!)

Lorsque nous procédons à ces stabilisations au coup par coup, je veux juste enregistrer un
demande une liste claire des restrictions et de leurs justifications. C'est
va être frustrant lorsque les utilisateurs font un changement apparemment inoffensif et
rencontrer une erreur de compilation, donc nous pouvons au moins réparer les erreurs. je
reconnaître que le raisonnement est réparti dans de nombreuses discussions, qui ne sont pas toutes
J'ai suivi, mais je pense qu'il devrait y avoir un tableau dans les docs (ou le
référence, ou le nomicon, au moins) répertoriant chaque opération interdite, la
problème qu'il pourrait causer, et les perspectives de stabilisation (par exemple "jamais",
"si RFC XYZ est implémenté", "après avoir défini cette partie de la spécification").

Le lun. 20 août 2018 à 13:44 Eduard-Mihai Burtescu <
[email protected]> a écrit :

En supposant que nous voulions conserver le formulaire de mot-clé const fn, je pense que la stabilisation
quelque chose maintenant , qui est assez limité, est une solution assez décente (comment
je ne l'ai pas vu avant ?!)


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-414403036 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAC3n80yVxIa3agsJP4wXZFkgyHVmAsjks5uSvWXgaJpZM4D66IA
.

@est31 Comme @rkruppe l' a déjà écrit, ces fusibles seraient illégaux sans -ffast-math - et je pense que LLVM gère cela correctement.

D'après ce que je me souviens, l'arithmétique de base n'est même pas si problématique (sauf sur x86 32 bits car x87...), mais les fonctions transcendantales le sont. Et ce ne sont pas const fn , n'est-ce pas ? Donc, j'espère qu'à la fin, nous pourrons avoir la parité entre les articles const , la promotion et const fn à cet égard également.

@RalfJung Je ne suis toujours pas convaincu que, par exemple, le renverser puis le recharger dans les registres FPU entre certaines opérations donne les mêmes résultats que le même calcul sans ce renversement.

D'après ce que je me souviens, l'arithmétique de base n'est même pas si problématique (sauf sur x86 32 bits car x87...), mais les fonctions transcendantales le sont.

En quoi les fonctions transcendantales signifieraient-elles un problème ?

IIRC, alors que certaines fonctions transcendantales sont prises en charge par les processeurs x86 courants, ces fonctions sont lentes et évitées, et uniquement incluses pour l'exhaustivité et la compatibilité avec les implémentations existantes. Ainsi, presque partout, les fonctions transcendantales sont exprimées en termes de combinaisons de fonctions arithmétiques de base. Cela signifie qu'il n'y a pas de différence dans leur transparence référentielle par rapport à celle des fonctions arithmétiques. Si les fonctions de base sont "sûres", alors tout ce qui est construit dessus l'est, y compris les fonctions transcendantales. La seule source "d'intransparence référentielle" ici pourrait être différentes approximations (implémentations) de ces fonctions transcendantales en termes de ces fonctions arithmétiques de base. Est-ce la source du problème ?

@est31 Alors que la plupart des fonctions transcendantales ne sont finalement que du code de bibliothèque composé d'opérations primitives entières et flottantes, ces implémentations ne sont pas standardisées et, en pratique, un programme Rust peut interagir avec peut-être trois implémentations différentes tout au long de sa durée de vie, dont certaines varient également selon l'hôte ou la cible. Plate-forme:

  • Il y a l'implémentation d'exécution que le programme utilise sur la cible (par exemple, la plate-forme cible libm ou les instructions matérielles dans certaines circonstances)
  • Il y a le dossier constant utilisé par LLVM (apparemment, ce n'est que la plate-forme hôte C libm)
  • Il y a tout ce que MIRI ferait avec ces opérations (par exemple, quelque chose dans rustc_apfloat , ou l'interprétation d'une implémentation Rust telle que https://github.com/japaric/libm/)

Si l'un d'entre eux n'est pas d'accord, vous pouvez obtenir des résultats différents selon le moment où une expression est évaluée.

@rfcbot annuler

Il est peu probable que nous stabilisions le mois entier dans un proche avenir.
Au lieu de cela, j'aimerais développer un consensus pour un sous-ensemble plus minimal (comme décrit à peu près dans https://github.com/rust-lang/rust/issues/24111#issuecomment-414310119) que nous espérons pouvoir stabiliser à court terme .
Ce sous-ensemble est suivi dans #53555. Une description plus détaillée y est disponible.

Proposition de @Centril annulée.

@rkruppe y a-t-il une raison d'abaisser les fonctions transcendantales aux intrinsèques llvm? Ne pouvons-nous pas simplement éviter tout le problème en les ramenant à des implémentations bien connues, réservées à la rouille, que nous contrôlons et qui sont les mêmes sur toutes les plates-formes ?

y a-t-il une raison pour abaisser les fonctions transcendantales à des intrinsèques llvm ?

Outre la simplicité de ne pas implémenter une libm multiplateforme entière, les intrinsèques ont un avantage dans l'optimiseur et le codegen de LLVM qu'une fonction de bibliothèque ordinaire n'obtiendra pas. Évidemment, le pliage constant (et les choses connexes comme l'analyse de la plage de valeurs) est un problème dans ce contexte, mais c'est très utile autrement. Il existe également des identités algébriques (appliquées par la passe SimplifyLibCalls). Enfin, quelques fonctions (principalement sqrt et sa réciproque, qui ne sont pas transcendantales mais peu importe) ont un support spécial de génération de code pour par exemple générer sqrtss sur x86 avec SSE.

Ne pouvons-nous pas simplement éviter tout le problème en les ramenant à des implémentations bien connues, réservées à la rouille, que nous contrôlons et qui sont les mêmes sur toutes les plates-formes ?

Même en ignorant tout ce qui précède, ce n'est pas une excellente option à l'OMI. Si la plate-forme cible a une libm C, il devrait être possible de l'utiliser, soit parce qu'elle est plus optimisée, soit pour éviter les ballonnements.

les intrinsèques ont un avantage dans l'optimiseur et le codegen de LLVM qu'une fonction de bibliothèque ordinaire n'obtiendra pas.

J'ai pensé que de telles optimisations ne sont activées que si les mathématiques rapides sont activées?

Si la plate-forme cible a une libm C, il devrait être possible de l'utiliser, soit parce qu'elle est plus optimisée, soit pour éviter les ballonnements.

Sûr. Il devrait toujours y avoir une option pour échanger cette propriété plutôt académique - la transparence référentielle - en faveur de choses qui comptent plus comme une vitesse améliorée du binaire compilé ou des binaires plus petits. Par exemple, en utilisant la plate-forme libm ou en activant le mode mathématique rapide. Ou suggérez-vous que nous devrions interdire le mode mathématique rapide de toute éternité ?

IMO, nous ne devrions pas interdire f32::sin() dans les contextes constants, du moins pas si nous autorisons + , - etc. Une telle interdiction obligera les gens à créer et à utiliser des caisses qui fournissent des implémentations compatibles const.

J'ai pensé que de telles optimisations ne sont activées que si les mathématiques rapides sont activées?

L'évaluation constante de ces fonctions et l'émission de sqrtss est facile à justifier sans -ffast-math, car elle peut être correctement arrondie.

Ou suggérez-vous que nous devrions interdire le mode mathématique rapide de toute éternité ?

Je ne suggère rien, et je n'ai pas non plus d'opinion (atm) sur la question de savoir si une telle propriété devrait être garantie. Je signale simplement des contraintes.

Impossible de trouver un problème ouvert pour ICE causé par Vec dans le contexte const fn.

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

Dois-je ouvrir un nouveau sujet pour cela ?

@Voultapher ouais ça ressemble à un nouvel ICE.

Très bien ouvert # 55063.

Si le compilateur est capable de vérifier si une fonction peut être appelée dans un constexpr au moment de la compilation lorsqu'un utilisateur l'annote avec const fn , pourquoi ne pas simplement effectuer automatiquement la vérification sur toutes les fonctions ? (similaire aux traits automatiques) ? Je ne vois pas d'effets négatifs réels, et l'avantage évident est que nous n'avons pas à dépendre d'un jugement humain sujet à l'erreur.

L'inconvénient majeur est qu'il devient un détail public, donc une mise en œuvre
le changement dans une fonction qui n'est pas censée être const est maintenant en panne.

De plus, nous n'avons pas besoin de nous fier au jugement humain. Nous pouvons avoir une peluche clippy qui nous indique quand une fonction non annotée pourrait être const fn : https://github.com/rust-lang/rust-clippy/issues/2440

Ceci est similaire à la façon dont nous ne déduisons pas la mutabilité des variables locales, mais au lieu de cela, le compilateur nous indique où ajouter ou supprimer mut .

@remexre const fn agit comme une spécification d'interface. Je ne connais pas très bien les petits détails de cette fonctionnalité (et peut-être que ce qui suit ici est déjà pensé), mais deux cas où je peux penser au compilateur indiquant quand une fonction est mal annotée comme const est pour échouer la compilation si une telle fonction prend un &mut comme paramètre ou si elle appelle d'autres fonctions non const . Donc, si vous modifiez l'implémentation d'un const fn et que vous brisez ces contraintes, le compilateur vous arrêtera. Ensuite, vous pouvez choisir d'implémenter les bits non const dans (a) des fonctions distinctes ou de casser l'API si c'était un changement prévu.

Il y a un autre point médian dont je n'ai pas vu parler et c'est la possibilité d'introduire un opposé à ce marqueur et une sorte d '"inférence de pureté de fonction" lorsqu'elle n'est pas explicitement définie. Ensuite, la documentation afficherait le marqueur réel, mais avec une sorte d'avertissement sur le fait de ne pas garantir la stabilité de ce marqueur s'il s'agit d'un const . Le problème est que cela pourrait inciter à être paresseux et à le faire presque à chaque fois, ce qui n'est pas son but.

un const fn devrait-il être capable de produire une sortie? pourquoi &mut devrait-il être interdit ?

@aledomu Mon commentaire était destiné à @AGaussman ; Je parle du cas où un auteur de bibliothèque expose une fonction qui n'est pas « destinée à être » const (en ce sens que la const-ness n'est pas destinée à faire partie de l'API) ; si const devait être déduit, ce serait un changement radical de rendre ladite fonction non const.

@SoniEx2 const fn est une fonction qui peut être évaluée au moment de la compilation, ce qui n'est le cas que pour toute fonction pure.

@remexre Si ce n'est pas censé être une partie stable de l'API, ne le marquez pas.

Pour le bit d'inférence que j'ai commenté, c'est pourquoi j'ai mentionné la nécessité d'un avertissement sur les documents de caisse.

quelle est la différence? absolument aucun !

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

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

J'ai signalé des problèmes ciblés pour :

Les problèmes ciblés suivants existent déjà :

S'il y a d'autres domaines, qui ne sont pas déjà suivis par d'autres problèmes, cela doit être discuté wrt. const eval et const fn , je suggère que les gens fassent de nouveaux problèmes (et m'y mettent en copie + @oli-obk).

Ceci conclut l'utilité de ce numéro, qui est clos par la présente.

Je n'ai pas toutes les spécificités à l'esprit, mais n'y a-t-il pas beaucoup plus qui sont pris en charge par miri mais pas encore activés dans min_const_fn ? Par exemple des pointeurs bruts.

@SimonSapin Ouais, bonne prise. Il y a d'autres problèmes existants pour cela. J'ai mis à jour le commentaire + la description du problème. S'il y a quelque chose qui n'est pas couvert, faites de nouveaux problèmes s'il vous plaît.

Je pense qu'il n'est pas approprié de clore un problème de suivi de méta lorsqu'il n'est pas du tout clair que ce qu'il couvre est couvert de manière exhaustive par des problèmes plus spécifiques.

Lorsque je supprime #![feature(const_fn)] dans Servo, les messages d'erreur sont :

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

(Ces const fn sont tous des constructeurs triviaux pour les types avec des champs privés. L'ancien message est sur le constructeur de struct Guard<T: Clone + Copy> , même si Clone n'est pas utilisé dans le constructeur. ce dernier sert à initialiser Option<fn()> (simplifié) à None ou Some(name_of_a_function_item) .)

Cependant, ni les traits ni les types de pointeurs de fonction ne sont mentionnés dans la description de ce problème.

Je ne veux pas dire que nous devrions avoir juste deux problèmes plus spécifiques pour ce qui précède. Je veux dire que nous devrions rouvrir celui-ci jusqu'à ce que nous nous assurions d'une manière ou d'une autre que tout ce qui se trouve derrière la porte de fonctionnalité const_fn (qui pointe toujours ici dans les messages d'erreur) a un problème de suivi. Ou jusqu'à ce que const_fn soit complètement stabilisé.

@SimonSapin

Je pense qu'il n'est pas approprié de clore un problème de suivi de méta lorsqu'il n'est pas du tout clair que ce qu'il couvre est couvert de manière exhaustive par des problèmes plus spécifiques.

Ce problème a la saveur de https://github.com/rust-lang/rust/issues/34511 qui est l'un des plus gros gâchis en ce qui concerne les problèmes de suivi. Ce problème est également un problème général depuis un certain temps, il n'agit donc pas comme un méta-problème pour le moment. Pour de tels accès libres, veuillez utiliser http://internals.rust-lang.org/ à la place.

Cependant, ni les traits ni les types de pointeurs de fonction ne sont mentionnés dans la description de ce problème.

Je ne veux pas dire que nous devrions avoir juste deux problèmes plus spécifiques pour ce qui précède.

C'est exactement ce que je pense qu'il faut faire. Du point de vue du triage T-Lang, il est avantageux d'avoir des problèmes ciblés et exploitables .

Je veux dire que nous devrions rouvrir celui-ci jusqu'à ce que nous nous assurions d'une manière ou d'une autre que tout ce qui se trouve derrière la porte de fonctionnalité const_fn (qui pointe toujours ici dans les messages d'erreur) a un problème de suivi. Ou jusqu'à ce que const_fn soit complètement stabilisé.

Ce n'est même pas clair pour moi ce que const_fn la porte de fonctionnalité constitue même ou que tout sera stabilisé à un moment donné. Tout, à part les limites et les pointeurs de fonction de la RFC d'origine, a des problèmes ouverts et plus encore.

Ce n'est même pas clair pour moi ce que const_fn la porte de fonctionnalité constitue même

C'est exactement pourquoi nous ne devrions pas le fermer tant que nous n'avons pas compris cela, OMI.

Tout

Est-ce vraiment tout, cependant?

Est-ce que quelqu'un sait ce qui est arrivé à la fonctionnalité const_string_new ? Y a-t-il un problème de suivi pour cela? Le livre instable ne fait que des liens ici.

@phansch C'est parce que tous les rustc_const_unstable pointent ici. (cc @oli-obk pouvons-nous résoudre ce problème ?)

Le problème devrait alors être ouvert. C'est juste insultant en tant qu'utilisateur d'être pointé du doigt
à un problème clos.

Le mer. 9 janvier 2019, 04:05 Mazdak Farrokhzad < [email protected]
a écrit:

@phansch https://github.com/phansch C'est parce que tout
point rustc_const_unstable ici.


Vous recevez ceci parce que vous avez été mentionné.
Répondez directement à cet e-mail, consultez-le sur GitHub
https://github.com/rust-lang/rust/issues/24111#issuecomment-452622097 ,
ou couper le fil
https://github.com/notifications/unsubscribe-auth/AAC3n7JhzsZZpizmWlp0Nww5bcfIqH2Vks5vBbC8gaJpZM4D66IA
.

@durka : Il y aura toujours une fenêtre possible où quelque chose est fermé la nuit et la résolution n'a toujours pas atterri dans stable. En quoi c'est insultant ?

J'ai résisté aux commentaires ici, et peut-être devrions-nous déplacer cette conversation vers un fil de discussion sur les internes (y en a-t-il déjà un ?) Mais...

La décision de fermer cela n'a aucun sens pour moi. C'est un problème de suivi, car il apparaît dans les messages d'erreur du compilateur, et ce n'est pas le seul, voir ce post pour d'autres exemples : https://internals.rust-lang.org/t/psa-tracking-for-gated -fonctionnalités-de-langue/2887. Pour moi, clore ce dossier implique de la stabilité, ce qui n'est évidemment pas encore le cas.

Franchement, je ne vois pas d'argument pour fermer ceci ... Je suis heureux qu'un problème plus ciblé existe maintenant, afin que la mise en œuvre puisse aller de l'avant, avec, espérons-le, une nouvelle discussion et une nouvelle orientation, mais je ne vois pas de moyen clair d'associer le compilateur messages avec ceux-ci.

Encore une fois, si cela nécessite (ou a déjà) un fil de discussion sur les internes, peut-être déplaçons-nous cette conversation là-bas ?

EDIT : Ou le problème est-il simplement que le livre est obsolète ? Essayer l'exemple de la RFC (il manque quelques #[derive(...)] s) semble fonctionner sans erreur sur Rust rustc 1.31.1. Y a-t-il toujours un message d'erreur du compilateur pointant ici ? Ce serait bien d'avoir un endroit pour lier les erreurs comme:

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

Si nous voulons qu'ils soient liés à des problèmes spécifiques, ce serait peut-être une amélioration.

Ok, alors il devrait y avoir des preuves solides que ce problème reste ouvert. Autant que je puisse dire ceci:

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

est la seule fonctionnalité active qui pointe vers un problème résolu.

Dans un monde idéal, je pense que ce genre de discussions devrait vraiment être automatisée, car comme nous l'avons découvert, les gens ont des opinions et des idées différentes sur la façon dont les choses devraient fonctionner. Mais ce n'est vraiment pas une conversation pour ce fil...

Si nous voulons qu'ils soient liés à des problèmes spécifiques, ce serait peut-être une amélioration.

Oui, c'est la bonne solution, et ce que @Centril a déjà suggéré .

Le commentaire initial a également été modifié pour rediriger les gens vers les problèmes spécifiques qui arrivent ici dans la "fenêtre" mentionnée par @ErichDonGubler .

https://github.com/rust-lang/rust/issues/57563 a maintenant été ouvert pour suivre les fonctionnalités const instables restantes.

Quelqu'un pourrait-il modifier le corps du problème ici pour mettre en évidence le lien vers # 57563 alors ?

@glaebhoerl fait :)

Salut, je suis arrivé ici parce que j'ai obtenu error[E0658]: const fn is unstable (see issue #24111) lors de la compilation de ncurses-rs. Que dois-je faire? Améliorer la rouille ? j'ai

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

EDIT: a fait brew uninstall rust et a suivi les instructions d'installation de rustup , maintenant rustc --version est rustc 1.33.0 (2aa4c46cf 2019-02-28) et cette erreur a disparu.

Oui, pour pouvoir utiliser const fn sur stable, vous devrez mettre à jour votre compilateur.

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