Rust: [Stabilization] Pin APIs

Créé le 7 nov. 2018  ·  213Commentaires  ·  Source: rust-lang/rust

@rfcbot fcp fusionner
Nom de la fonctionnalité: pin
Objectif de stabilisation: 1.32.0
Problème de suivi: # 49150
RFC connexes: rust-lang / rfcs # 2349

Il s'agit d'une proposition visant à stabiliser la fonctionnalité de bibliothèque pin , en effectuant un "épinglage"
API pour manipuler la mémoire épinglée utilisable sur stable.

(J'ai essayé d'écrire cette proposition sous la forme d'un "rapport de stabilisation" complet.)

Fonctionnalité ou API stabilisées

[std|core]::pin::Pin

Cela stabilise le type Pin dans le sous-module pin de std / core . Pin est
un wrapper fondamental et transparent autour d'un type générique P , qui est destiné
être un type de pointeur (par exemple, Pin<&mut T> et Pin<Box<T>> sont tous les deux
conceptions valides et prévues). Le wrapper Pin modifie le pointeur en "pin"
la mémoire à laquelle il se réfère en place, empêchant l'utilisateur de déplacer des objets
de cette mémoire.

La façon habituelle d'utiliser le type Pin est de construire une variante épinglée de certains
type de pointeur propriétaire ( Box , Rc , etc.). La bibliothèque std possède tous des pointeurs
fournissez un constructeur pinned qui renvoie ceci. Ensuite, pour manipuler le
valeur à l'intérieur, tous ces pointeurs fournissent un moyen de dégrader vers Pin<&T>
et Pin<&mut T> . Les pointeurs épinglés peuvent déréférencer, vous donnant &T , mais pas
déréf mutuellement en toute sécurité: cela n'est possible qu'en utilisant le unsafe get_mut
fonction.

En conséquence, toute personne faisant muter des données via une épingle sera tenue de respecter le
invariant qu'ils ne sortent jamais de ces données. Cela permet à un autre code de
supposons en toute sécurité que les données ne sont jamais déplacées, ce qui leur permet de contenir (pour
exemple) auto-références.

Le type Pin aura ces API stabilisées:

impl<P> Pin<P> where P: Deref, P::Target: Unpin

  • fn new(pointer: P) -> Pin<P>

impl<P> Pin<P> where P: Deref

  • unsafe fn new_unchecked(pointer: P) -> Pin<P>
  • fn as_ref(&self) -> Pin<&P::Target>

impl<P> Pin<P> where P: DerefMut

  • fn as_mut(&mut self) -> Pin<&mut P::Target>
  • fn set(&mut self, P::Target);

impl<'a, T: ?Sized> Pin<&'a T>

  • unsafe fn map_unchecked<U, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
  • fn get_ref(self) -> &'a T

impl<'a, T: ?Sized> Pin<&'a mut T>

  • fn into_ref(self) -> Pin<&'a T>
  • unsafe fn get_unchecked_mut(self) -> &'a mut T
  • unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>

impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin

  • fn get_mut(self) -> &'a mut T

Trait impls

La plupart des attributs de trait sur Pin sont assez rote, ces deux sont importants pour
son fonctionnement:

  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }
  • impl<P: DerefMut> DerefMut for Pin<P> where P::Target: Unpin { }

std::marker::Unpin

Unpin est une caractéristique automatique sûre qui exclut les garanties d'épinglage. Si la
cible d'un pointeur épinglé implémente Unpin , il est prudent de changer
y faire référence. Unpin types
être déplacé d'un Pin .

Cela rend aussi ergonomique de traiter une référence épinglée à quelque chose qui
ne contient pas d'auto-références comme il le serait pour traiter un non-épinglé
référence. Les garanties de Pin n'ont d'importance que pour les types de cas spéciaux comme
structures auto-référentielles: ces types n'implémentent pas Unpin , donc ils ont
les restrictions de type Pin .

Implémentations notables de Unpin dans std:

  • impl<'a, T: ?Sized> Unpin for &'a T
  • impl<'a, T: ?Sized> Unpin for &'a mut T
  • impl<T: ?Sized> Unpin for Box<T>
  • impl<T: ?Sized> Unpin for Rc<T>
  • impl<T: ?Sized> Unpin for Arc<T>

Ceux-ci codifient la notion selon laquelle l'épinglage n'est pas transitif entre les pointeurs. Cette
est, a Pin<&T> épingle uniquement le bloc de mémoire réel représenté par T dans a
endroit. Les utilisateurs ont parfois été déroutés par cela et s'attendaient à ce qu'un type
comme Pin<&mut Box<T>> épingle les données de T en place, mais cela ne fait que
mémoire à laquelle la référence épinglée fait référence: dans ce cas, les Box
représentation, qui un pointeur dans le tas.

std::marker::Pinned

Le type Pinned est un ZST qui n'implémente pas Unpin ; ça te permet de
supprime l'implémentation automatique de Unpin sur stable, où !Unpin impls
ne serait pas encore stable.

Constructeurs de pointeurs intelligents

Des constructeurs sont ajoutés aux pointeurs intelligents std pour créer des références épinglées:

  • Box::pinned(data: T) -> Pin<Box<T>>
  • Rc::pinned(data: T) -> Pin<Rc<T>>
  • Arc::pinned(data: T) -> Pin<Arc<T>>

Remarques sur l'épinglage et la sécurité

Au cours des 9 derniers mois, les API d'épinglage ont subi plusieurs itérations comme
nous avons étudié leur pouvoir expressif et aussi la solidité de leur
garanties. Je dirais maintenant avec confiance que les API d'épinglage se sont stabilisées ici
sont solides et assez proches des maxima locaux en ergonomie et
expressivité; c'est-à-dire prêt pour la stabilisation.

L'un des problèmes les plus délicats de l'épinglage est de déterminer quand il est sûr de fonctionner
une projection pin : c'est-à-dire passer d'un Pin<P<Target = Foo>> à un
Pin<P<Target = Bar>> , où Bar est un champ de Foo . Heureusement, nous avons
été en mesure de codifier un ensemble de règles qui peuvent aider les utilisateurs à déterminer si un tel
la projection est sûre:

  1. Il n'est sûr d'épingler un projet que si (Foo: Unpin) implies (Bar: Unpin) : cela
    est, si ce n'est jamais le cas que Foo (le type contenant) est Unpin tandis que
    Bar (le type projeté) n'est pas Unpin .
  2. Ce n'est sûr que si Bar n'est jamais déplacé lors de la destruction de Foo ,
    signifiant que soit Foo n'a pas de destructeur, soit le destructeur est soigneusement
    vérifié pour s'assurer qu'il ne sort jamais du champ sur lequel il est projeté.
  3. Ce n'est sûr que si Foo (le type contenant) n'est pas repr(packed) ,
    car cela entraîne le déplacement des champs pour les réaligner.

En outre, les API std ne fournissent aucun moyen sûr d'épingler des objets à la pile.
En effet, il n'y a aucun moyen de l'implémenter en toute sécurité à l'aide d'une API de fonction.
Cependant, les utilisateurs peuvent épingler des éléments à la pile de manière non sécurisée en garantissant qu'ils
ne déplacez plus jamais l'objet après avoir créé la référence épinglée.

Le pin-utils crate sur crates.io contient des macros pour aider avec les deux piles
épinglage et projection de broche. La macro d'épinglage de la pile épingle en toute sécurité les objets
empiler en utilisant une astuce impliquant l'ombre, alors qu'une macro pour la projection existe
ce qui est dangereux, mais vous évite d'avoir à écrire le passe-partout de projection dans
lequel vous pourriez éventuellement introduire un autre code dangereux incorrect.

Modifications de la mise en œuvre avant la stabilisation

  • [] Exportez Unpin du prélude, supprimez la réexportation de pin::Unpin

En règle générale, nous ne réexportons pas les éléments de plusieurs endroits dans std à moins que
l'un est un supermodule de la définition réelle (par exemple, raccourcir
std::collections::hash_map::HashMap à std::collections::HashMap ). Pour ça
raison, la réexportation de std::marker::Unpin de std::pin::Unpin est hors de
endroit.

Dans le même temps, d'autres traits de marqueur importants tels que l'envoi et la synchronisation sont inclus
dans le prélude. Donc au lieu de réexporter Unpin depuis le module pin , en
en mettant en prélude, nous rendons inutile l'importation de std::marker::Unpin ,
la même raison pour laquelle il a été placé dans pin .

  • [] Changer les fonctions associées en méthodes

Actuellement, une grande partie de la fonction associée de Pin n'utilise pas la syntaxe de méthode.
En théorie, c'est pour éviter d'entrer en conflit avec des méthodes internes déréférables. cependant,
cette règle n'a pas été appliquée de manière cohérente et, d'après notre expérience,
vient de rendre les choses plus gênantes. Les pointeurs épinglés implémentent uniquement immuables
deref, non mutable deref ou deref par valeur, ce qui limite la capacité de deref
en tous cas. De plus, beaucoup de ces noms sont assez uniques (par exemple map_unchecked )
et peu susceptible d'entrer en conflit.

Au lieu de cela, nous préférons donner aux méthodes Pin leur priorité; utilisateurs qui
besoin d'accéder à une méthode intérieure peut toujours utiliser UFCS, comme ils le seraient
requis pour accéder aux méthodes Pin si nous n'avons pas utilisé la syntaxe de méthode.

  • [] Renommez get_mut_unchecked en get_unchecked_mut

L'ordre actuel n'est pas cohérent avec les autres utilisations de la bibliothèque standard.

  • [] Supprimer impl<P> Unpin for Pin<P>

Cet impl n'est pas justifié par notre justification standard pour unpin impls: il n'y a pas de direction de pointeur entre Pin<P> et P . Son utilité est couverte par les impls pour les pointeurs eux-mêmes.

Ce futur impl devra changer pour ajouter une liaison P: Unpin .

  • [] Marquer Pin comme repr(transparent)

La broche doit être un wrapper transparent autour du pointeur à l'intérieur, avec la même représentation.

Fonctionnalités connectées et jalons plus importants

Les API pin sont importantes pour manipuler en toute sécurité des sections de mémoire qui peuvent
être assuré de ne pas être déménagé. Si les objets de cette mémoire ne
implémentez Unpin , leur adresse ne changera jamais. Ceci est nécessaire pour
création de générateurs auto-référentiels et de fonctions asynchrones. Par conséquent,
le type Pin apparaît dans la bibliothèque standard des API future et le sera bientôt
apparaissent également dans les API des générateurs (# 55704).

Stabiliser le type Pin et ses API est un précurseur nécessaire à la stabilisation
les API Future , qui sont elles-mêmes un précurseur nécessaire pour stabiliser le
async/await syntaxe et déplacement de tout l'écosystème d'E / S asynchrones futures 0.3
sur Rust stable.

cc @cramertj @RalfJung

T-libs finished-final-comment-period

Commentaire le plus utile

Quelqu'un a-t-il intérêt à écrire un chapitre sur les futurs nomicon? Cela semble incroyablement nécessaire compte tenu de la subtilité de cela. En particulier, je pense que des exemples, en particulier des exemples d'implémentations boguées / mauvaises et comment elles ne sont pas valables, seraient éclairants.

Tous les 213 commentaires

@rfcbot fcp fusionner

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

  • [x] @Kimundi
  • [] @SimonSapin
  • [x] @alexcrichton
  • [x] @dtolnay
  • [] @sfackler
  • [x] @sans bateaux

Préoccupations:

  • box-pinnned-vs-box-pin (https://github.com/rust-lang/rust/issues/55766#issuecomment-443371976)
  • amélioration-de-la-documentation (https://github.com/rust-lang/rust/issues/55766#issuecomment-443367737)
  • naming-of-Unpin résolu par https://github.com/rust-lang/rust/issues/55766#issuecomment -445074980
  • auto-méthodes (https://github.com/rust-lang/rust/issues/55766#issuecomment-443368794)

Une fois que la majorité des examinateurs approuvent (et au plus 2 approbations sont en attente), cela entrera dans sa période de commentaires finale. 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 marqués peuvent me donner.

Merci pour la rédaction détaillée ici @withoutboats! J'ai aussi été historiquement confus par les différentes garanties de Pin , et j'ai actuellement quelques questions sur les API en cours de stabilisation ici par rapport aux garanties de sécurité. Pour aider à régler cela dans ma propre tête, j'ai pensé que j'essaierais d'écrire ces choses.

En essayant de commencer à écrire cela, je continue de me heurter à un mur de "qu'est-ce que Unpin ?" Je suis un peu confus par ce que c'est et les diverses garanties qui l'entourent. Pouvez-vous redire ce que cela signifie pour T to et aussi de ne pas implémenter Unpin ? De plus, si Unpin est un trait sûr à implémenter, il semble naïvement qu'il pourrait être utilisé pour saper facilement les garanties dangereuses de Pin<T> , mais il me manque sûrement quelque chose

si j'ai bien compris, chaque type qui n'est pas auto-référentiel (c'est-à-dire pas un générateur) est Unpin

Ce n'est pas seulement l'auto-référentialité, il existe d'autres cas d'utilisation d'adresses mémoire stables que Pin peut également prendre en charge. Ils sont cependant relativement peu nombreux et espacés.

Comment je comprends que Unpin être implémenté en toute sécurité, c'est qu'en l'implémentant, vous pouvez violer un invariant requis d'un autre code dangereux que vous avez écrit (surtout, vous seul pouvez écrire ce code dangereux, aucun code externe ne peut se fier à la question de savoir si vous avez implémenté Unpin ). Il n'y a rien que vous puissiez faire avec l'API sûre de Pin qui causera des problèmes, que vous ayez implémenté ou non Unpin . En optant pour l'utilisation de certaines des API non sécurisées de Pin , vous garantissez que vous n'implémenterez Unpin lorsque cela est sûr. Cela est couvert par le point 1 de la section «Remarques sur l'épinglage et la sécurité» ci-dessus.

Hm je ne comprends toujours pas vraiment Unpin . J'essaie d'abord de comprendre ce que signifie implémenter ou ne pas impulser Unpin .

Tout d'abord, il est probablement utile de savoir quels types implémentent automatiquement Unpin ! Il est mentionné ci-dessus que les types de pointeurs courants (Arc / Rc / Box / references) implémentent Unpin , mais je pense que c'est tout? S'il s'agit d'un trait automatique, cela signifie-t-il qu'un type MyType implémente automatiquement Unpin s'il ne contient que des pointeurs? Ou est-ce qu'aucun autre type n'implémente automatiquement Unpin ?

Je continue d'essayer de résumer ou de dire ce que Unpin garantit et autres, mais je trouve vraiment difficile de le faire. Quelqu'un peut-il aider en réitérant ce que signifie implémenter Unpin ainsi que ce que signifie ne pas implémenter Unpin ?

Je pense que je comprends les garanties de Pin<P> où vous ne pouvez sortir d'aucun des membres en ligne de P::Target , mais est-ce vrai?

@alexcrichton Merci pour les questions, je suis sûr que les API d'épinglage peuvent être un peu déroutantes pour les personnes qui n'ont pas fait partie du groupe qui se concentre sur elles.

Tout d'abord, il est probablement utile de savoir quels types implémentent automatiquement Unpin!

Unpin est une caractéristique automatique comme Envoyer ou Sync, donc la plupart des types l'implémentent automatiquement. Les générateurs et les types de fonctions asynchrones sont !Unpin . Les types qui pourraient contenir un générateur ou un corps de fonction asynchrone (en d'autres termes, des types avec des paramètres de type) ne sont pas automatiquement Unpin sauf si leurs paramètres de type le sont.

L'implémentation explicite pour les types pointeurs est de rendre Unpin même si leurs paramètres de type ne le sont pas. J'espère que la raison en sera plus claire à la fin de ce commentaire.

Quelqu'un peut-il aider en réitérant ce que signifie implémenter Unpin ainsi que ce que signifie ne pas implémenter Unpin?

Voici le genre d'idée fondamentale des API d'épinglage. Tout d'abord, étant donné un type de pointeur P , Pin<P> agit comme P sauf qu'à moins que Pin doit maintenir:

  • Si vous n'obtenez pas en toute sécurité un &mut P::Target d'un Pin<P> , vous ne devez jamais déplacer P::Target .
  • Si vous pouvez construire un Pin<P> , il doit être garanti que vous ne pourrez jamais obtenir un pointeur non épinglé vers les données vers lesquelles le pointeur pointe jusqu'à ce que le destructeur s'exécute.

L'implication de tout cela est que si vous construisez un Pin<P> , la valeur pointée par P ne bougera plus jamais, ce qui est la garantie dont nous avons besoin pour les structures auto-référentielles et intrusives collections. Mais vous pouvez désactiver cette garantie en implémentant simplement Unpin pour votre type.

Donc, si vous implémentez Unpin pour un type, vous dites que le type exclut toute garantie de sécurité supplémentaire de Pin - il est possible de déréférencer mutuellement les pointeurs pointant vers lui. Cela signifie que vous dites que le type n'a pas besoin d'être inamovible pour être utilisé en toute sécurité.

Déplacer un type de pointeur comme Rc<T> ne déplace pas T car T est derrière un pointeur. De même, épingler un pointeur sur un Rc<T> (comme dans Pin<Box<Rc<T>> ) n'épingle pas réellement T , il épingle seulement ce pointeur particulier. C'est pourquoi tout ce qui garde ses génériques derrière un pointeur peut implémenter Unpin même si leurs génériques ne le font pas.

De plus, si Unpin est un trait sûr à mettre en œuvre, il semble naïvement qu'il pourrait être utilisé pour saper facilement les garanties dangereuses de Pin, mais il me manque sûrement quelque chose

C'était l'une des parties les plus délicates de l'API d'épinglage, et nous nous sommes trompés au début.

Unpin signifie "même une fois que quelque chose a été inséré dans une épingle, il est sûr d'obtenir une référence mutable." Il existe aujourd'hui un autre trait qui vous donne le même accès: Drop . Donc, ce que nous avons compris, c'est que puisque Drop est sûr, Unpin doit également l'être. Cela mine-t-il tout le système? Pas assez.

Pour implémenter réellement un type auto-référentiel, il faudrait un code non sécurisé - en pratique, les seuls types auto-référentiels dont tout le monde se soucie sont ceux que le compilateur génère pour vous: les générateurs et les machines à états de fonctions asynchrones. Ceux-ci disent explicitement qu'ils n'implémentent pas Unpin et qu'ils n'ont pas d'implémentation Drop , donc vous savez, pour ces types, une fois que vous avez un Pin<&mut T> , ils le feront n'obtient jamais réellement une référence mutable, car il s'agit d'un type anonyme dont nous savons qu'il n'implémente pas Unpin ou Drop.

Le problème se pose une fois que vous avez une structure contenant l'un de ces types anonymes, comme un futur combinateur. Pour passer d'un Pin<&mut Fuse<Fut>> à un Pin<&mut Fut> , vous devez effectuer une "projection pin". C'est là que vous pouvez rencontrer des problèmes: si vous épinglez un projet sur le champ futur d'un combinateur, mais que vous implémentez ensuite Drop pour le combinateur, vous pouvez sortir d'un champ censé être épinglé.

Pour cette raison, la projection des broches n'est pas sûre! Afin d'effectuer une projection de broche sans violer les invariants d'épinglage, vous devez garantir que vous ne faites jamais plusieurs choses, que j'ai énumérées dans la proposition de stabilisation.

Ainsi, le tl; dr: Drop existe, donc Unpin doit être sûr. Mais cela ne gâche pas le tout, cela signifie simplement que la projection d'épingle est unsafe et quiconque veut épingler un projet doit respecter un ensemble d'invariants.

générateurs et machines à états de fonction asynchrone. Ceux-ci disent explicitement qu'ils n'implémentent pas Unpin et qu'ils n'ont pas d'implémentation Drop, donc vous savez, pour ces types, une fois que vous avez un Pin <& mut T>, ils n'obtiendront jamais une référence mutable, car ils un type anonyme que nous savons n'implémente pas Unpin ou Drop.

Une machine à états asynchrone ne devrait-elle pas avoir une implémentation Drop ? Les choses qui sont sur la "pile" de la fonction asynchrone (qui équivaut probablement à des champs dans la machine à états) doivent être détruites lorsque la fonction async se termine ou est annulée. Ou cela se produit-il autrement?

Je suppose que ce qui importe dans ce contexte est de savoir si un impl Drop for Foo {…} élément existe, ce qui irait à code avec &mut Foo qui pourrait par exemple utiliser mem::replace à « exfiltrer » et déplacer le Foo Valeur

Ce n'est pas la même chose que "drop glue", qui peut être appelé via ptr::drop_in_place . Drop glue pour un type Foo appellera Drop::drop s'il est implémenté, puis appelera récursivement le drop glue pour chaque champ. Mais ces appels récursifs n'impliquent jamais &mut Foo .

De plus, même si un générateur (et donc une machine à états asynchrone) a une colle de suppression personnalisée, il s'agit simplement de supprimer le jeu de champs correct en fonction de l'état actuel, il promet de ne jamais déplacer aucun des champs pendant la suppression.

La terminologie que j'utilise (bien que je ne pense pas qu'il y ait de norme): "drop glue" est la marche récursive des champs générée par le compilateur, appelant leurs destructeurs; «Drop implementation» est une implémentation du trait Drop , et «destructor» est la combinaison du drop glue et de l'implémentation de Drop. Le drop glue ne déplace jamais rien, donc nous ne nous soucions que des implémentations Drop.

Quelqu'un a-t-il intérêt à écrire un chapitre sur les futurs nomicon? Cela semble incroyablement nécessaire compte tenu de la subtilité de cela. En particulier, je pense que des exemples, en particulier des exemples d'implémentations boguées / mauvaises et comment elles ne sont pas valables, seraient éclairants.

@Gankro Bien sûr, vous pouvez me

Merci pour l'explication à tous!

Personnellement, je suis super nouveau dans async Rust et les API Pin, mais j'ai joué un peu avec ces derniers jours (https://github.com/rust-lang-nursery/futures-rs/pull/1315 - où J'ai essayé d'exploiter l'épinglage pour les collections intrusives en code asynchrone).

Au cours de l'expérimentation, j'ai eu quelques problèmes avec ces API:

  • Le concept d'épinglage semble très difficile à comprendre, même si l'on sait que des adresses mémoire stables sont nécessaires. Quelques défis auxquels j'ai été confronté:

    • Messages d'erreur comme:

      > dans impl core::future::future::Future+futures_core::future::FusedFuture , le trait std::marker::Unpin n'est pas implémenté pour std::marker::Pinned

      Celui-ci semble assez récursif et contradictoire (pourquoi quelque chose qui est épinglé ne devrait-il pas l'être?)

    • Comment puis-je accéder aux champs mutables dans ma méthode qui prend une épingle comme paramètre. Il a fallu un peu de recherche, apprendre de nouveaux termes ( Pin projection), essayer de copier les méthodes de pin-utils , jusqu'à ce que j'arrive à une solution qui utilise 2 méthodes dangereuses pour faire ce dont j'avais besoin .

    • Comment puis-je réellement utiliser mon avenir dans une méthode async et un cas select . Il s'est avéré que j'avais besoin de pin_mut!() pour la pile à terme. Ce qui n'est pas vraiment une pile, ce qui le rend déroutant.

    • Pourquoi ma méthode drop() elle à nouveau &mut self , au lieu d'un Pin .

    • Dans l'ensemble, c'était un peu le sentiment d'utiliser au hasard des API liées à l'épinglage non sécurisées dans l'espoir d'obtenir des choses à compiler, ce qui ne devrait certainement pas être la situation idéale. Je soupçonne que d'autres pourraient essayer de faire des choses similaires.

  • Le concept se propage beaucoup dans la base de code, ce qui semble forcer beaucoup de gens à comprendre ce que sont réellement Pin et Unpin . Cela semble être un peu une fuite de détails d'implémentation pour certains cas d'utilisation plus spéciaux.
  • La broche semble être quelque peu liée à la durée de vie, mais aussi quelque peu orthogonale. Fondamentalement, une épingle nous dit que nous pourrions revoir un objet avec exactement la même adresse jusqu'à ce que drop() soit appelé. Ce qui lui donne une sorte de durée de vie virtuelle entre la portée actuelle et 'static . Il semble déroutant d'avoir 2 concepts qui sont liés, mais toujours complètement différents. Je me suis demandé s'il pouvait être modélisé par quelque chose comme 'pinned .

Lors de la compilation des choses, j'ai souvent pensé à C ++, où la plupart de ces problèmes sont évités: les types peuvent être déclarés inamovibles en supprimant le constructeur de déplacement et l'opérateur d'affectation. Lorsqu'un type n'est pas déplaçable, les types qui contiennent ce type ne le sont pas non plus. Ainsi, la propriété et les exigences circulent nativement dans la hiérarchie des types et sont vérifiées par le compilateur, au lieu de transmettre la propriété à l'intérieur de certains appels (pas tous, par exemple pas drop() ). Je pense que c'est beaucoup plus facile à comprendre. Mais peut-être pas applicable à Rust, puisque les Future doivent souvent être déplacés avant que quelque chose ne commence à les interroger? Mais d'un autre côté, cela pourrait être réparable avec une nouvelle définition avec ce trait, ou en séparant le mouvement et la phase de sondage.

Concernant Alex Unpin commentaire: Je saisis lentement ce que signifie Unpin , mais je suis d'accord que c'est difficile à comprendre. Peut-être qu'un autre nom peut aider, mais je ne peux pas en trouver un concis pour le moment. Quelque chose avec ThingsInsideDontBreakIfObjectGetsMoved , DoesntRequireStableAddress , PinDoesntMatter , etc.

Ce que je n'ai pas encore complètement compris, c'est pourquoi obtenir &mut self partir de Pin<&mut self> ne peut pas être sûr pour tous les types. Si Pin signifie simplement que l'adresse de l'objet lui-même est stable, cela devrait être vrai pour la plupart des types Rust typiques. Il semble qu'il y ait un autre problème intégré à Pin, à savoir que les types auto-référentiels à l'intérieur ne se cassent pas. Pour ces types, les manipuler après avoir reçu un Pin est toujours dangereux. Mais je pense que dans la plupart des cas, ceux-ci seront générés par le compilateur, et des pointeurs insécurité ou même bruts devraient être bien. Pour les futurs combinateurs qui doivent transférer des appels épinglés vers des sous-champs, il faudrait évidemment des appels non sécurisés pour créer des broches vers les champs avant de les interroger. Mais dans ce cas, je vois l'insécurité plus liée à l'endroit où cela compte réellement (la broche est-elle toujours valide?) Qu'à l'accès à d'autres champs pour lesquels cela n'a aucune importance.

Une autre pensée que j'ai eue est de savoir s'il est possible d'obtenir un système plus simple si certaines limitations sont appliquées. Par exemple, si les éléments nécessitant un épinglage ne pouvaient être utilisés que dans des méthodes asynchrones et non avec de futurs combinateurs. Peut-être que cela pourrait simplifier les choses à l'un des Pin ou Unpin . Considérant que pour beaucoup de code, les méthodes async / await avec prise en charge de select / join peuvent être favorables aux combinateurs, ce n'est peut-être pas la perte la plus énorme. Mais je n'ai pas vraiment réfléchi suffisamment à la question de savoir si cela aide vraiment.

Du côté positif: être capable d'écrire du code asynchrone / en attente avec des emprunts "stack" est plutôt cool et indispensable! Et la possibilité d'utiliser l'épinglage pour d'autres cas d'utilisation, comme les collections intrusives, améliorera les performances ainsi que les cibles telles que les systèmes embarqués ou les noyaux. J'ai donc vraiment hâte de trouver une solution à ce sujet.

Petite note du rapport de stabilisation:

fn get_unchecked_mut(self) -> &'a mut T

Je suppose que c'est en fait un unsafe fn ?

@ Matthias247 vous ne pouvez pas obtenir en toute sécurité & mut T à partir de Pin<P<T>> si T n'est pas Unpin parce que vous pourriez alors mem::swap pour déplacer T hors du Pin, ce qui irait à l'encontre du but d'épingler des choses.

Une chose que j'ai du mal à m'expliquer est ce qui rend Future fondamentalement différent des autres traits, de sorte que Pin doit faire partie de son API. Je veux dire, je sais intuitivement que c'est parce que async / await nécessite un épinglage, mais est-ce que cela dit quelque chose de spécifiquement sur les Futures qui est différent de, disons, les itérateurs?

Le sondage pourrait-il prendre & mut self et implémenter Future uniquement pour les types Pin<P> ou les types qui sont Unpin?

Comment puis-je accéder aux champs mutables dans ma méthode qui prend une épingle comme paramètre.

Cela me fait me demander s'il y a quelques méthodes manquantes sur Pin ,

impl<'a, T: ?Sized> Pin<&'a T> {
    fn map<U: Unpin, F: FnOnce(&T) -> &U>(self, f: F) -> Pin<&'a U>
}

impl<'a, T: ?Sized> Pin<&'a mut T> {
    fn map<U: Unpin, F: FnOnce(&mut T) -> &mut U>(self, f: F) -> Pin<&'a mut U>
}

Ceux-ci peuvent être utilisés dans la macro pin_utils::unsafe_unpinned! .

J'essaie de comprendre pourquoi cette macro prétend être dangereuse. Si nous sommes !Unpin et avons un champ Unpin , pourquoi serait-il dangereux de projeter dans ce champ?

La seule situation où je peux voir que ce n'est pas sain est l'implémentation d'un type personnalisé !Unpin , en prenant un pointeur brut vers un champ Unpin de self (et en comptant sur lui ayant une adresse stable / pointant vers le même instance), puis en acquérant un &mut dans le même champ et en le passant à une fonction externe. Cela semble relever du même raisonnement sur la raison pour laquelle la mise en œuvre de Unpin est sûre, en prenant un pointeur brut vers un champ d'un !Unpin vous désactivez la possibilité d'appeler certains des coffres-forts. Apis.

Pour rendre la situation précédente plus sûre, il pourrait y avoir un wrapper construit sur Pinned pour le marquage qui normalement Unpin champs d'une structure sont en fait !Unpin au lieu d'ajouter simplement un Pinned à la structure dans son ensemble:

pub struct MustPin<T: Unpin>(T, Pinned);

impl<T: Unpin> MustPin<T> {
    pub const fn new(t: T) -> Self { ... }
    pub fn get(self: Pin<&Self>) -> *const T { ... }
    pub fn get_mut(self: Pin<&mut Self>) -> *mut T { ... }
}

Tout cela semble rétrocompatible avec l'API actuelle, cela pourrait supprimer une partie de l'insécurité des combinateurs futures-rs (par exemple, cela donnerait un accès sécurisé à des champs supplémentaires comme celui-ci ), mais n'est pas nécessaire pour les cas d'utilisation actuels. Nous pourrions probablement expérimenter certaines API comme celle-ci pour implémenter des types !Unpin (comme des collections intrusives) et les ajouter plus tard.

@ Nemo157 Ces fonctions de carte ne sont pas sûres car je pourrais mem::swap sur le &mut T la fonction me passe avant de renvoyer un &mut U . (bien le mutable est, l'immuable n'est peut-être pas dangereux)

EDIT: De plus, la macro pin-utils est différente, unsafe_unpinned n'a pas à voir avec le type de cible étant Unpin , c'est juste une "projection non épinglée" - une projection vers un &mut Field . Son utilisation est sûre tant que vous n'épinglez jamais de projet à ce champ, même si le champ est !Unpin .

Une chose que j'ai du mal à m'expliquer est ce qui rend Future fondamentalement différent des autres traits, de sorte que Pin doit faire partie de son API. Je veux dire, je sais intuitivement que c'est parce que async / await nécessite un épinglage, mais est-ce que cela dit quelque chose de spécifiquement sur les Futures qui est différent de, disons, les itérateurs?

Il n'y a pas de différence théorique, mais il y en a des pragmatiques. D'une part, Iterator est stable. Mais en conséquence, à moins que nous ne trouvions quelque chose de très intelligent, vous ne pourrez jamais exécuter une boucle for sur un générateur auto-référentiel sans double indirection, même si cela devrait être complètement sûr sans lui ( car la boucle for consommera et ne déplacera jamais le générateur).

Une autre différence pragmatique importante est que les modèles de code entre Iterator et Future sont assez différents. Vous ne pouvez pas passer 10 minutes sans vouloir emprunter sur un point d'attente dans le futur, c'est pourquoi sur les futures 0.1 vous voyez ces Arc apparaître partout afin que vous puissiez utiliser la même variable dans deux and_then appels. Mais nous sommes allés assez loin sans pouvoir exprimer du tout des itérateurs auto-référentiels, ce n'est tout simplement pas si important qu'un cas d'utilisation.

Ces fonctions de carte ne sont pas sûres car je pourrais mem::swap sur le &mut T la fonction me passe avant de renvoyer un &mut U

Ah, putain, j'ai oublié cette partie: froncer les sourcils:

fn as_mut(&mut self) -> Pin<&mut P::Target> fn into_ref (self) -> Pin <& 'a T> `

Faut-il les appeler as_pinned_mut et into_pinned_ref , pour éviter de les confondre avec des méthodes qui renvoient réellement des références?

Implémentations notables de Unpin dans std:

Nous avons également impl<P> Unpin for Pin<P> {} . Pour les types que nous utilisons, cela n'a aucun effet et cela semble un peu plus sûr?
Qu'à cela ne tienne, vous l'avez sur votre liste. ;)

Garantie de chute

Je pense qu'il faut codifier la garantie Drop avant la stabilisation, sinon il sera trop tard:

Il est illégal d'invalider le stockage d'un objet épinglé (la cible d'une référence Pin ) sans appeler drop sur l'objet.

«Invalidate» ici peut signifier «désallocation», mais aussi «réaffectation»: lors du passage de x de Ok(foo) à Err(bar) , le stockage de foo est invalidé .

Cela a pour conséquence, par exemple, qu'il devient illégal de désallouer un Pin<Box<T>> , Pin<Rc<T>> ou Pin<Arc<T>> sans appeler au préalable drop::<T> .

Réutilisation Deref , DerefMut

Je me sens toujours un peu mal à l'aise sur la façon dont cela réutilise le trait Deref pour signifier "c'est un pointeur intelligent". Nous utilisons Deref pour d'autres choses également, par exemple pour "l'héritage". C'est peut-être un anti-pattern, mais c'est encore assez courant et, franchement, c'est utile. :RÉ

Je pense que ce n’est pas un problème de solidité.

Comprendre Unpin

Plug sans vergogne: j'ai écrit deux articles de blog à ce sujet, qui pourraient aider ou non selon à quel point vous aimez lire la syntaxe (semi-) formelle. ;) Les API ont changé depuis, mais l'idée de base n'a pas changé.

Projections de broches sûres

@ Matthias247 Je pense qu'un problème que vous rencontrez est que la construction d'abstractions qui impliquent l'épinglage nécessite presque toujours un code non sécurisé. Utiliser ces abstractions est bien, mais par exemple, définir un futur combinateur en code sécurisé ne fonctionnera pas. La raison en est que les "projections de broches" ont des contraintes que nous ne pourrions vérifier en toute sécurité qu'avec les modifications du compilateur. Par exemple, vous demandez

Pourquoi ma méthode drop () prend-elle à nouveau & mut self, au lieu d'un Pin.

Eh bien, drop() est vieux - il existe depuis Rust 1.0 - donc nous ne pouvons pas le changer. Nous aimerions simplement que cela prenne Pin<&mut Self> , puis les types Unpin pourraient obtenir leurs &mut comme ils le font maintenant, mais c'est un changement non rétrocompatible.

Pour rendre l'écriture des futurs combinateurs sûre, nous aurions besoin de projections de broches sûres, et pour cela nous devons changer le compilateur, cela ne peut pas être fait dans une bibliothèque. Nous pourrions presque le faire dans un derive proc_macro, sauf que nous aurions besoin d'un moyen d'affirmer "ce type n'a pas d'implémentation de Drop ou Unpin ".

Je pense que cela vaudrait la peine de trouver comment obtenir une API aussi sûre pour les projections de broches. Cependant, cela ne doit pas bloquer la stabilisation: l'API que nous stabilisons ici devrait être compatible avec les projections de goupille de sécurité. ( Unpin pourrait devoir devenir un élément lang pour implémenter l'assertion ci-dessus, mais cela ne semble pas trop mal.)

L'épinglage n'est pas !

J'ai souvent pensé à C ++, où la plupart de ces problèmes sont évités: les types peuvent être déclarés inamovibles en supprimant le constructeur de déplacement et l'opérateur d'affectation. Lorsqu'un type n'est pas déplaçable, les types qui contiennent ce type ne le sont pas non plus.

Il y a eu plusieurs tentatives pour définir des types immuables pour Rust. Cela se complique très vite.

Une chose importante à comprendre est que l'épinglage ne fournit pas de types inamovibles! Tous les types restent mobiles dans Rust. Si vous avez un T , vous pouvez le déplacer où vous le souhaitez. Au lieu de types inamovibles, nous utilisons la capacité de Rust à définir de nouvelles API encapsulées, car nous définissons un type de pointeur à partir duquel vous ne pouvez pas sortir . Toute la magie est dans le type pointeur ( Pin<&mut T> ), pas dans la pointee ( T ). Il n'y a aucun moyen pour un type de dire "je ne peux jamais être déplacé". Cependant, il existe un moyen pour un type de dire " Si je suis épinglé , ne me déplacez plus". Ainsi , un MyFuture que je possède sera toujours mobile, mais Pin<&mut MyFuture> est un pointeur vers une instance de MyFuture qui ne peut se déplacer plus.

C'est un point subtil, et nous devrions probablement passer un peu de temps à étoffer la documentation ici pour aider à éviter ce malentendu très courant.

Mais nous sommes allés assez loin sans pouvoir exprimer du tout des itérateurs auto-référentiels, ce n'est tout simplement pas si important qu'un cas d'utilisation.

En effet, jusqu'à présent, tous les itérateurs sont définis à l'aide d'un type et d'un bloc impl Iterator for … . Lorsque l'état doit être conservé entre deux itérations, il n'y a pas d'autre choix que de le ranger dans des champs de type itérateur et de travailler avec &mut self .

Cependant, même si ce n'est pas la principale motivation pour inclure des générateurs dans le langage, ce serait très bien de pouvoir éventuellement utiliser la syntaxe du générateur yield pour définir quelque chose qui peut être utilisé avec une for boucle. Dès que cela existe, emprunter sur yield parce que tout aussi important pour les générateurs-itérateurs que pour les générateurs-futurs.

( Unpin devra peut-être devenir un élément lang pour implémenter l'assertion ci-dessus, mais cela ne semble pas trop mal.)

Unpin et Pin doivent déjà être des éléments lang pour prendre en charge des générateurs immobiles sûrs.

Ok merci pour les explications à tous! Je suis d'accord avec @Gankro qu'un chapitre de style nomicon sur Pin et tel serait extrêmement utile ici. Je soupçonne qu'il y a beaucoup d'histoire de développement pour expliquer pourquoi diverses méthodes sûres existent et pourquoi des méthodes dangereuses sont dangereuses et autres.

Dans un effort pour m'aider à comprendre cela, je voulais essayer à nouveau d'écrire pourquoi chaque fonction est sûre ou pourquoi c'est unsafe . Avec la compréhension d'en haut, j'ai tout ce qui suit, mais il y a quelques questions ici et là. Si d'autres peuvent m'aider à remplir cela, ce serait génial! (ou aidez à préciser où ma pensée est fausse)

  • fn new(P) -> Pin<P> where P: Deref, P::Target: Unpin

    • C'est une méthode sûre pour construire Pin<P> , et alors que l'existence de
      Pin<P> signifie normalement que P::Target ne sera plus jamais déplacé dans le
      programme, P::Target implémente Unpin qui se lit "les garanties de
      Pin ne tient plus ". Par conséquent, la sécurité ici est qu'il n'y a pas
      garanties à respecter, afin que tout puisse se dérouler comme d'habitude.
  • unsafe fn new_unchecked(P) -> Pin<P> where P: Deref

    • Contrairement à la précédente, cette fonction est unsafe car P ne le fait pas
      implémentent nécessairement Unpin , donc Pin<P> doit respecter la garantie que
      P::Target ne bougera plus jamais après la création de Pin<P> .

    Un moyen simple de violer cette garantie, si cette fonction est sûre, semble
    comme:

    fn foo<T>(mut a: T, b: T) {
        Pin::new_unchecked(&mut a); // should mean `a` can never move again
        let a2 = mem::replace(&mut a, b);
        // the address of `a` changed to `a2`'s stack slot
    }
    

    Par conséquent, c'est à l'utilisateur de garantir que Pin<P> signifie bien
    P::Target n'est plus jamais déplacé après la construction, donc c'est unsafe !

  • fn as_ref(&Pin<P>) -> Pin<&P::Target> where P: Deref

    • Étant donné Pin<P> nous avons la garantie que P::Target ne bougera jamais. C'est
      partie du contrat de Pin . En conséquence, cela signifie trivialement que
      &P::Target , un autre "pointeur intelligent" vers P::Target fournira la même chose
      garantie, donc &Pin<P> peut être traduit en toute sécurité en Pin<&P::Target> .

    C'est une méthode générique pour passer de Pin<SmartPointer<T>> à Pin<&T>

  • fn as_mut(&mut Pin<P>) -> Pin<&mut P::Target> where P: DerefMut

    • Je pense que la sécurité ici est à peu près la même que celle des as_ref ci-dessus.
      Nous ne distribuons pas d'accès mutable, juste un Pin , donc rien ne peut être facilement
      violé encore.

    Question : qu'en est-il d'un impl DerefMut "malveillant"? C'est un moyen sûr
    pour appeler un DerefMut fourni par l'utilisateur qui crée &mut P::Target nativement,
    probablement lui permettant de le modifier également. Comment est-ce sûr?

  • fn set(&mut Pin<P>, P::Target); where P: DerefMut

    • Question : Étant donné Pin<P> (et le fait que nous ne savons rien sur
      Unpin ), cela ne devrait-il pas garantir que P::Target ne bouge jamais? Si nous pouvons
      réinitialisez-le avec un autre P::Target , cependant, cela ne devrait-il pas être dangereux?
      Ou est-ce en quelque sorte lié aux destructeurs et à tout ça?
  • unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>

    • C'est une fonction unsafe donc la question principale ici est "pourquoi n'est-ce pas
      safe "? Un exemple de non-respect des garanties si cela était sûr ressemble à:

    ...

    Question : quel est le contre-exemple ici? Si c'était sûr, qu'est-ce
    l'exemple qui montre la violation des garanties de Pin ?

  • fn get_ref(Pin<&'a T>) -> &'a T

    • La garantie de Pin<&T> signifie que T ne bougera jamais. Retour &T
      n'autorise pas la mutation de T , il devrait donc être prudent de le faire tout en respectant
      cette garantie.

    Un "peut-être eucha" ici est la mutabilité intérieure, et si T était
    RefCell<MyType> ? Ceci, cependant, ne viole pas les garanties de
    Pin<&T> car la garantie s'applique uniquement à T dans son ensemble, pas à
    champ intérieur MyType . Alors que la mutabilité intérieure peut se déplacer
    internes, il ne peut toujours pas déplacer la structure entière derrière un
    & référence.

  • fn into_ref(Pin<&'a mut T>) -> Pin<&'a T>

    • Pin<&mut T> signifie que T ne bougera jamais. En conséquence, cela signifie Pin<&T>
      offre la même garantie. Cette conversion ne devrait pas poser beaucoup de problème,
      il s'agit principalement de changer de type.
  • unsafe fn get_unchecked_mut(Pin<&'a mut T>) -> &'a mut T

    • Pin<&mut T> signifie que T ne doit jamais bouger, donc c'est trivialement unsafe
      car vous pouvez utiliser mem::replace sur le résultat pour déplacer T (en toute sécurité). le
      unsafe here "bien que je vous donne &mut T , vous n'êtes jamais autorisé à le faire
      déplacer T ".
  • unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(Pin<&'a mut T>, f: F) -> Pin<&'a mut U>

    • Je pense que le unsafe ici est fondamentalement au moins le même que ci-dessus, nous sommes
      distribuer &mut T toute sécurité (ne peut pas exiger une fermeture dangereuse) qui pourrait
      facilement utilisable avec mem::replace . Il y a probablement d'autres risques ici aussi
      avec la projection, mais il semble raisonnable que ce soit au moins dangereux
      à cause de ça.
  • fn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin

    • En implémentant Unpin un type dit " Pin<&mut T> n'a aucune garantie, c'est
      juste un wrapper newtype de &mut T ". Par conséquent, sans aucune garantie
      maintenez, nous pouvons retourner &mut T toute sécurité
  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }

    • Cela peut être implémenté en toute sécurité avec as_ref suivi de get_ref , donc le
      la sécurité de cet impl résulte de ce qui précède.
  • impl<P: DerefMut> DerefMut for Pin<P> where T::Target: Unpin { }

    • Cela peut être implémenté en toute sécurité avec as_mut suivi de get_mut , donc le
      la sécurité de cet impl résulte de ce qui précède.
  • impl<T: ?Sized> Unpin for Box<T> (et autres implémentations liées aux pointeurs)

    • Si aucune autre action n'était entreprise, Box<T> implémenterait le trait Unpin
      seulement si T implémenté Unpin . Cette implémentation codifie ici que même
      si T n'implémente pas explicitement Unpin , Box<T> implémente Unpin .

    Question : Quel est un exemple de ce que cela est fondamentalement habilitant?

    Par exemple, quel coffre-fort pourrait autrement exiger unsafe si cela impl

    n'existait pas.


@ Matthias247 vous ne pouvez pas obtenir & mut T en toute sécurité depuis Pin

> si T n'est pas Unpin parce que vous pourriez alors mem :: swap pour déplacer T hors du Pin, ce qui irait à l'encontre du but d'épingler des choses.

Merci! Oui, puisque swap n'est pas une méthode dangereuse, cela pose évidemment problème. Mais cela pourrait-il être résolu en ajoutant un Unpin lié à swap() ? Puisque tout le code jusqu'à présent devrait être Unpin ou n'est pas sûr de toute façon, cela ne devrait rien casser.

Je suppose que l'une des choses qui me confond le plus est que Pin<T> codifie plusieurs garanties: que l'adresse du T est stable, ainsi que certaines garanties sur son état interne (c'est un peu figé / immuable dans certains situations, mais pas vraiment).

Il m'a semblé que déplacer le code / les projections non sécurisés uniquement aux endroits où d'autres appels basés sur Pin sont nécessaires (par exemple dans poll s sur les champs) pourrait être préférable de traiter ces projections tous les cas. Cependant, je me suis maintenant rendu compte qu'il y avait un autre problème avec cela: le code qui a accès à la référence mutable peut déplacer le champ librement, et lorsque drop() est alors appelé en toute sécurité sur ce champ, il peut se briser car il adresse stockée ailleurs. Pour cela, la surcharge drop(Pin<T>) dont il a été question serait nécessaire.

@RalfJung Merci pour les explications! Je suis d'accord sur le fait que j'ai certainement essayé de faire quelque chose de dangereux en général, et par conséquent, cela devrait être bien d'exiger de moi une compréhension supplémentaire. Je suis plus préoccupé par les gens qui veulent écrire des combinateurs futurs plus généralement sûrs, mais qui pourraient maintenant être confrontés à tous ces termes. S'ils peuvent écrire des combinateurs sans avoir à comprendre du tout Unpin et épingler les projections, et n'obtenir que des combinateurs qui fonctionnent de manière réduite (uniquement sur les futurs Unpin ), cela semble préférable. Comme je ne l'ai pas essayé, je ne peux pas dire si c'est le cas actuellement. Je pense qu'il faut encore ajouter au moins Unpin limites manuellement.

Je comprends également le fait que les types non mobiles sont différents du type à broches. Cependant, je suis actuellement plus concentré sur les cas d'utilisation que sur la différence. Et pour le cas d'utilisation des collections intrusives, les types non mobiles fonctionnent très bien sans introduire trop de complexité. Pour les futurs, des recherches seraient évidemment nécessaires, car il manque un type mobile à un type non mobile. Si cette méthode n'était pas plus ergonomique que les API Pin, il n'y aurait pas non plus de victoire.

Ajouter T: Unpin à mem::swap signifierait qu'il ne pourrait pas être utilisé sur certains types, même s'ils ne sont pas à l'intérieur d'une épingle.

Mais cela pourrait-il être résolu en ajoutant un Unpin lié à swap ()? Étant donné que tout le code jusqu'à présent devrait être Unpin ou est de toute façon dangereux, cela ne devrait rien casser.

Cela briserait tout le code générique: Si vous écrivez une fonction dans la rustine stable d'aujourd'hui pour quelque T sans contraintes, vous pouvez appeler swap dessus. Cela doit continuer à fonctionner.

les types non mobiles fonctionnent très bien sans introduire trop de complexité.

Personne n'a démontré un moyen d'ajouter des types non mobiles à Rust d'une manière rétrocompatible sans que la complexité dépasse de manière significative ce qui est proposé pour la stabilisation ici. Vous devriez trouver certaines de ces anciennes propositions et discussions dans le référentiel RFC. Voir https://github.com/rust-lang/rfcs/pull/1858 pour un exemple.

La RFC à https://github.com/rust-lang/rfcs/pull/2349 ainsi que la série de blogs de bateau commençant ici devraient vous aider à vous donner un aperçu et une impression des autres conceptions qui ont été considérées. (Notez également les dates, cette conception est en cours depuis près de 10 mois!)

Aussi mem :: swap est un hareng rouge, car ce n'est pas du tout une fonction intéressante. C'est littéralement juste

let temp = *a; 
*a = *b; 
*b = temp;

@Gankro n'utilise-

Edit: Je suppose qu'une autre façon de penser à cela est que l'ajout de T: Unpin à mem::swap change en fait la définition de la sécurité au niveau de la langue. Cela briserait tous les mycrate::swap fns dans la nature.

Ajouter T: Unpin à mem :: swap signifierait qu'il ne pourrait pas être utilisé sur certains types même s'ils ne sont pas à l'intérieur d'une épingle.

Si Unpin est automatiquement dérivé (de la même manière que Sync / Send ), j'ai pensé que cela ne devrait pas être un problème.

Cela briserait tout le code générique: si vous écrivez une fonction dans la rustine stable d'aujourd'hui pour certains T sans contraintes, vous pouvez appeler swap dessus. Cela doit continuer à fonctionner.

Mais celui-ci l'est évidemment. Je n'ai pas pensé au fait que les limites des traits doivent être explicitement propagées dans Rust.

Personne n'a démontré un moyen d'ajouter des types non mobiles à Rust d'une manière rétrocompatible sans que la complexité dépasse de manière significative ce qui est proposé pour la stabilisation ici. Vous devriez trouver certaines de ces anciennes propositions et discussions dans le référentiel RFC. Voir rust-lang / rfcs # 1858 pour un exemple.

Merci, je vais lire un peu plus sur les travaux précédents à ce sujet si je trouve du temps. De toute évidence, beaucoup de réflexions et d'efforts ont déjà été consacrés à cela, et je ne veux certainement pas bloquer les choses. Je voulais simplement faire part de mes préoccupations et de mes questions en essayant de travailler avec cela.

@ Matthias247

Si Unpin est automatiquement dérivé (de la même manière que Sync / Send), j'ai pensé que cela ne devrait pas être un problème.

Je ne suis pas sûr d'avoir été clair. Pour clarifier, il est parfaitement sûr de se déplacer dans un type !Unpin et donc parfaitement sûr de les mem::swap . Il n'est pas sûr de déplacer un type T: !Unpin après son épinglage, c'est-à-dire à l'intérieur d'un Pin<P<T>> .

Par exemple, dans le code async / await, vous pouvez vous déplacer autant que vous le souhaitez dans les futurs renvoyés par une fonction async. Vous ne pouvez plus les déplacer qu'une fois que vous les pin_mut! ou les mettre dans un Pin<Box<..>>> ou autre chose.

J'ai quelques questions variées à ce sujet:

  1. Compte tenu de l'importance de Pin<T> pour async et pour les générateurs et le potentiel de défaut lors de l'interaction avec d'autres parties de Rust (par exemple swap & replace ), vérification formelle (par exemple par @jhjourdan ou @RalfJung) a-t-elle été faite de la variante de l'API d'épinglage proposée ici?

  2. Cette API donne-t-elle de nouvelles garanties sur la machine abstraite / la sémantique opérationnelle de Rust? C'est-à-dire que si nous oublions le cas d'utilisation comme mécanisme de support pour async & await / générateurs, pourrions-nous le mettre dans une caisse dans l'écosystème et cela fonctionnerait simplement étant donné les garanties actuelles que nous donner?

  3. Quels types d'API ou d'ajouts de système de type deviennent impossibles suite à la stabilisation de l'API d'épinglage? (cette question est une extension de 2.)

  4. Quelles sont les possibilités de l'API à stabiliser en termes de faire évoluer un langage fourni de type &pin T pour améliorer la projection de champ et autres (ce qui ne semble pas génial avec l'API proposée).

J'ai quelques notes assorties:

  1. La documentation de la bibliothèque standard semble assez rare. :(

  2. Je suis d'accord avec d'autres pour dire que la construction d'épinglage est assez exigeante mentalement / complexe.

  3. L'exemple Unmovable dans la documentation semble trop compliqué et implique unsafe ; cela semble sous-optimal. Avec une initialisation progressive telle qu'élaborée dans un projet de RFC dans le langage (c'est-à-dire en améliorant NLL), cela pourrait plutôt nous donner:

struct Unmovable<'a> {
    data: String,
    slice: &'a str,
}

let um: Unmovable<'_>;
um.data = "hello".to_string();
um.slice = &um.data; // OK! we borrow self-referentially.

drop(um); // ERROR! `um.slice` is borrowing `um.data` so you cannot move `um`.

// You won't be able to take a &mut reference to `um` so no `swap` problems.

Cela n'implique aucun danger et il est assez facile pour l'utilisateur de gérer cela.

En outre, les API std ne fournissent aucun moyen sûr d'épingler des objets à la pile.
En effet, il n'y a aucun moyen de l'implémenter en toute sécurité à l'aide d'une API de fonction.

Qu'en est-il de l'API comme ça?

pub fn using_pin<T, R, F>(value: T, f: F) -> R
where F: for<'a> FnOnce(Pin<&'a mut T>) -> R {
    pin_mut!(value);    // Actual implementation inlines this but the point is this API is safe as long as pin_mut! is safe.
    f(value)
}

Je n'ai pas suivi de trop près le développement des API d'épinglage, donc cela aurait pu être mentionné ou expliqué ailleurs et je n'ai pas pu le trouver, excuses si tel est le cas:

Le type Pinned est un ZST qui n'implémente pas Unpin ; ça te permet de
supprime l'implémentation automatique de Unpin sur stable, où !Unpin impls
ne serait pas encore stable.

Y a-t-il une explication quelque part sur la raison pour laquelle !Unpin impls n'a pas pu être stabilisé?

Ce n'est sûr que si Foo (le type contenant) n'est pas repr (emballé),
car cela entraîne le déplacement des champs pour les réaligner.

L'emballage signifie-t-il que les champs peuvent être déplacés dynamiquement? C'est un peu effrayant. Sommes-nous sûrs que llvm ne générera jamais de code pour déplacer des champs dans d'autres circonstances? De même, est-il possible que les valeurs épinglées sur la pile soient déplacées par llvm?

Malgré la subtilité, cela semble être une très belle API. Bon travail!

@Centril Ralf a écrit sur l'épinglage sur son blog .

Les API pin n'impliquent aucun changement de langue et sont entièrement implémentées dans la bibliothèque standard en utilisant des fonctionnalités de langage préexistantes. Il n'a aucun impact sur la langue Rust et n'empêche aucune autre fonctionnalité linguistique.

Pin n'est en réalité qu'une implémentation intelligente de l'une des fonctionnalités les plus précieuses et les plus classiques de Rust: la possibilité d'introduire des invariants dans une API en marquant une partie de l'API unsafe . Pin encapsule un pointeur pour rendre une opération ( DerefMut ) dangereuse, exigeant que les personnes qui la font respectent certains invariants (qu'elles ne sortent pas de la référence), et autorisant un autre code à supposons que cela ne se produira jamais. Un exemple similaire, beaucoup plus ancien de cette même astuce est String , ce qui rend dangereux de mettre des octets non UTF8 dans une chaîne, permettant à un autre code de supposer que toutes les données dans le String sont UTF8.

Y a-t-il une explication quelque part sur pourquoi! Unpin impls n'a pas pu être rendu stable?

Les impls négatifs sont actuellement instables, totalement indépendants de ces API.

Les impls négatifs sont actuellement instables.

🤦‍♂️ Cela a du sens, j'aurais dû y penser un peu plus longtemps. Merci.

@alexcrichton Voilà une excellente analyse de cette API, nous devrions essayer de la préserver à un endroit mieux qu'un commentaire qui se perdra!

Certains commentaires:

as_mut : Question: qu'en est-il d'un impl DerefMut "malveillant"? C'est un moyen sûr
pour appeler un DerefMut fourni par l'utilisateur qui crates & mut P :: Target nativement,
probablement lui permettant de le modifier également. Comment est-ce sûr?

Fondamentalement, lorsque vous appelez new_unchecked , vous faites une promesse sur les implémentations Deref et DerefMut pour ce type.

set : Pin donné

(et le fait que nous ne savons rien sur
Unpin), cela ne devrait-il pas garantir que P :: Target ne bouge jamais? Si nous pouvons
réinitialisez-le avec un autre P :: Target, cependant, cela ne devrait-il pas être dangereux?
Ou est-ce en quelque sorte lié aux destructeurs et à tout ça?

Cela supprime l'ancien contenu du pointeur et y met un nouveau contenu. "Épinglé" ne signifie pas "jamais abandonné", cela signifie "jamais déplacé jusqu'à ce qu'il soit abandonné". Donc, le laisser tomber ou le remplacer en appelant drop est très bien. Appeler drop est crucial, c'est la garantie de chute que j'ai mentionnée ci-dessus.

Où voyez-vous quelque chose bouger ici?

map_unchecked : Question :: quel est le contre-exemple ici? Si cela était sûr, quel est l'exemple qui montre une violation des garanties de Pin?

Un exemple serait de commencer par un Pin<&&T> et d'utiliser cette méthode pour obtenir un Pin<&T> . L'épinglage ne se «propage pas à travers» les références.

get_ref : Un "peut-être gotcha" ici est la mutabilité intérieure, et si T l'était
RefCell?

En effet, c'est un piège, mais comme vous l'avez observé, pas un problème de solidité. Ce qui ne serait pas valable, c'est d'avoir une méthode qui va de Pin<RefCell<T>> à Pin<&[mut] T> . Fondamentalement, ce qui se passe, c'est que RefCell ne propage pas l'épinglage, nous pourrions impl<T> Unpin for RefCell<T> .

une vérification formelle (par exemple par @jhjourdan ou @RalfJung) a-t-elle été faite de la variante de l'API d'épinglage proposée ici?

Non, pas de notre côté. Les articles de blog que j'ai mentionnés ci-dessus contiennent quelques réflexions sur la façon dont je commencerais à officialiser cela, mais nous ne l'avons pas vraiment fait. Si vous me donnez une machine à remonter le temps ou un doctorant intéressé, nous le ferons. ;)

si nous oublions le cas d'utilisation comme mécanisme de support pour async & await / generators, pourrions-nous le mettre dans une caisse dans l'écosystème et cela fonctionnerait simplement compte tenu des garanties actuelles que nous donnons?

Telle est l'intention.

Quels types d'API ou d'ajouts de système de type deviennent impossibles suite à la stabilisation de l'API d'épinglage? (cette question est une extension de 2.)

Euh, je ne sais pas comment répondre à cette question avec confiance. L'espace des ajouts possibles est tout simplement trop grand et trop dimensionnel pour que j'ose faire une déclaration universelle à ce sujet.

Quelles sont les possibilités de l'API à stabiliser en termes de faire évoluer un langage fourni et de type pin T pour améliorer la projection de champ et autres (ce qui ne semble pas génial avec l'API proposée).

Je ne suis plus sûr de &pin T , il ne correspond pas très bien au nouveau Pin<T> générique. En termes de gestion des projections, nous aurions besoin d'un hack pour dire " Unpin et Drop n'ont pas été implémentés pour ce type", alors nous pourrions le faire en toute sécurité avec une macro. Pour une ergonomie supplémentaire, nous voudrions probablement une capacité générique de "projection de champ" dans le langage, qui couvrirait également le passage de &Cell<(A, B)> à &Cell<A> .

Y a-t-il une explication quelque part sur pourquoi! Unpin impls n'a pas pu être rendu stable?

Les impls négatifs AFAIK ont des limitations de longue date, et si vous leur ajoutez des limites génériques, ils ne fonctionneront souvent pas comme vous le pensez. (Les limites génériques sont parfois simplement ignorées, ou alors.)

Peut-être que Chalk corrige tout cela, peut-être juste une partie, mais de toute façon, nous ne voudrions probablement pas bloquer cela sur Chalk.

L'emballage signifie-t-il que les champs peuvent être déplacés dynamiquement? C'est un peu effrayant.

C'est le seul bon moyen d'appeler drop sur un champ compact, étant donné que drop attend une référence alignée.

Sommes-nous sûrs que llvm ne générera jamais de code pour déplacer des champs dans d'autres circonstances? De même, est-il possible que les valeurs épinglées sur la pile soient déplacées par llvm?

La copie d'octets LLVM autour ne devrait pas poser de problème, car elle ne peut pas modifier le comportement du programme. Il s'agit d'un concept de plus haut niveau de "déplacement" des données, d'une manière qui est observable dans Rust (par exemple parce que les pointeurs vers les données ne pointent plus vers elles). LLVM ne peut pas simplement déplacer des données ailleurs vers lesquelles nous pouvons avoir des pointeurs. De même, LLVM ne peut pas simplement déplacer des valeurs sur la pile dont l'adresse est prise.

Ok merci @RalfJung ,

Lorsque vous dites "jamais déplacé jusqu'à ce qu'il soit abandonné", cela signifie que Drop peut être appelé là où &mut self a quitté l'adresse de Pin<&mut Self> ? Les destructeurs ne peuvent pas compter sur la précision des pointeurs intérieurs, n'est-ce pas?

Mon souci en regardant set était de savoir comment les paniques seraient gérées, mais je pense que ce n'est pas un problème après avoir creusé un peu plus le codegen.

Lorsque vous dites "jamais déplacé jusqu'à ce qu'il soit abandonné", cela signifie que Drop peut être appelé là où &mut self a quitté l'adresse de Pin<&mut Self> ? Les destructeurs ne peuvent pas compter sur la précision des pointeurs intérieurs, n'est-ce pas?

@alexcrichton ma compréhension était que cela signifie ne jamais bouger avant le retour de Drop::drop . Sinon, certains cas d'utilisation (collections intrusives et tampons DMA alloués à la pile au moins) deviennent impossibles.

Les destructeurs peuvent compter sur quelque chose qui n'a jamais été déplacé s'ils peuvent prouver qu'il était auparavant dans un Pin . Par exemple, si une machine à états ne peut entrer dans un état que via une API qui nécessite son épinglage, le destructeur peut supposer qu'elle a été épinglée avant d'être supprimée si elle est dans cet état.

Le code ne peut pas supposer que les destructeurs non locaux ne déplacent pas les membres, mais vous pouvez évidemment supposer que vos propres destructeurs ne déplacent pas les choses parce que c'est vous qui les écrivez.

Quand vous dites "jamais déplacé jusqu'à ce qu'il soit abandonné", cela signifie que Drop peut être appelé là où & mut self s'est déplacé de l'adresse de Pin <& mut Self>? Les destructeurs ne peuvent pas compter sur la précision des pointeurs intérieurs, n'est-ce pas?

Je veux dire que les données ne bougeront jamais (dans le sens de ne pas aller ailleurs, y compris de ne pas être désallouées) jusqu'à ce que drop soit appelé. Et oui, drop peut compter sur l'exécution sur l'emplacement épinglé, même si son type ne peut pas l'exprimer. drop devrait prendre Pin<&mut self> (pour tous les types), mais hélas, trop tard pour cela.

Après l'appel de drop , les données ne sont que des octets sans signification, vous pouvez faire n'importe quoi avec eux: mettre de nouvelles choses là-dedans, désallouer, peu importe.

Cela permet, par exemple, une liste chaînée intrusive où le destructeur d'un élément le désenregistre en ajustant les pointeurs voisins. Nous savons que la mémoire ne disparaîtra pas sans l'appel de ce destructeur. (L'élément peut toujours être divulgué , puis il restera pour toujours dans cette liste chaînée. Mais dans ce cas, il restera une mémoire valide, il n'y a donc pas de problème de sécurité.)

J'ai lu tout ce que je peux trouver sur Pin , Unpin , et la discussion qui a mené à tout cela, et même si je pense que je comprends maintenant ce qui se passe, il y a aussi beaucoup de subtilité autour de ce que signifient les différents types, ainsi que du contrat que les réalisateurs et les utilisateurs doivent respecter. Malheureusement, je vois relativement peu de discussions à ce sujet dans la documentation pour std::pin ou sur Pin et Unpin particulier. En particulier, j'aimerais voir une partie du contenu de ces commentaires:

incorporé dans la documentation. Plus précisément:

  • Ce Pin ne protège que "un niveau de profondeur".
  • Cela Pin::new_unchecked impose des restrictions sur l'impl pour Deref et DerefMut .
  • L'interaction entre Pin et Drop .
  • Comment !Unpin n'est pas autorisé à se déplacer une fois placé dans un Pin .
  • La raison pour laquelle Pin<Box<T>>: Unpin where T: !Unpin . Cela renvoie à la restriction "un niveau profond" ci-dessus, mais je pense que cet exemple concret avec une explication appropriée serait utile aux lecteurs.

J'ai trouvé les contre-exemples de @alexcrichton assez utiles aussi. Donner au lecteur une idée de ce qui peut mal tourner pour les différentes méthodes unsafe au-delà de la prose, je pense que cela aiderait beaucoup (du moins cela a certainement fait pour moi). En général, en raison de la subtilité de cette API, j'aimerais voir les sections Sécurité des différentes méthodes non sécurisées développées, et éventuellement également référencées à partir de la documentation au niveau du module std::pin .

Je n'ai pas encore beaucoup utilisé cette API moi-même, alors je vais faire confiance au jugement technique selon lequel elle est en bon état maintenant. Cependant, je pense que les noms Pin , Pinned et Unpin sont trop similaires et inexpressifs pour des types / traits très différents et une API relativement complexe, ce qui rend la compréhension plus difficile. (comme en témoignent quelques commentaires sur ce fil).

Ils semblent suivre les conventions de dénomination des traits marqueurs, donc je ne peux pas vraiment me plaindre, mais je me demande si nous pourrions faire un compromis entre la verbosité et les noms auto-explicatifs:

  • Pinned - un peu déroutant car c'est le même mot que Pin . Étant donné qu'il est utilisé comme PhantomData pour augmenter une structure avec des méta-informations qui manqueraient autrement, que diriez-vous de PinnedData , PhantomPinned , PhantomSelfRef ou même DisableUnpin ? Quelque chose qui indique le modèle d'utilisation et / ou l'effet qu'il va avoir.
  • Unpin - Comme dans https://github.com/rust-lang/rust/issues/55766#issuecomment -437266922, je suis confus par le nom Unpin , car "un-pin "peut être compris de multiples manières ambiguës. Quelque chose comme IgnorePin , PinNeutral peut-être?

En général, j'échoue et je trouve moi-même de bons noms alternatifs ...

PhantomPin et PinNeutral me semblent particulièrement sympathiques.

Juste pour fournir un contrepoint, j'ai trouvé Unpin intuitif (une fois que je l'ai compris). Pinned est plus difficile à suivre, étant donné que je ne l'ai pas utilisé dans mon propre code. Pourquoi ne pas changer Pinned en NotUnpin ?

Je suis d'accord pour dire que trouver de meilleurs noms peut être un exercice de bikeshedding intéressant afin de rendre l'épinglage plus facile à parler. Je propose:

  • Pin -> Pinned : Quand on vous donne un Pin , c'est vraiment une promesse que ce qui vous est donné a été épinglé et _remain_ épinglé pour toujours. Je ne me sens pas trop fortement à ce sujet cependant, car vous pourriez aussi parler du fait de recevoir une "broche d'une valeur". Pinned<Box<T>> me lit juste mieux, d'autant plus que seul le Box est épinglé, pas le contenu qu'il contient.
  • Unpin -> Repin : pour les autres traits marqueurs, nous parlons généralement de ce que vous pouvez faire avec quelque chose qui a ce trait marqueur. C'est vraisemblablement la raison Unpin laquelle Unpin peut être épinglé, puis ré-épinglé ailleurs, sans conséquence ni considération. J'aime aussi la suggestion de @ mark-im de PinNeutral , même si elle est un peu plus verbeuse.
  • Pinned -> PermanentPin : Je ne pense pas que Pinned soit un bon nom, car quelque chose qui contient un Pinned n'est pas vraiment épinglé. n'est tout simplement Unpin . PhantomPin a un problème similaire en ce sens qu'il fait référence à Pin , alors que Pin n'est pas vraiment ce à quoi vous voulez arriver. NotUnpin a un double négatif, ce qui rend difficile de raisonner. La suggestion de @Kimundi de PhantomSelfRef devient assez proche, même si je pense toujours que c'est un peu "complexe", et cela lie la propriété "ne peut pas être déplacée une fois épinglée" avec une instance où c'est le cas avoir une référence personnelle). Ma suggestion pourrait également être formulée comme suit: PermanentlyPinned ; Je ne sais pas quelle forme est la moins mauvaise.

Je pense que Pinned devrait finir par être NotXX est ce que Unpin finit par être nommé. Le seul travail de Pinned est de faire en sorte que le type englobant n'implémente pas Unpin . (Edit: et si nous changeons Unpin en autre chose, vraisemblablement le double négatif n'est pas un problème)

Repin n'a pas de sens pour moi, car "Ce type peut être épinglé ailleurs" est juste un effet secondaire de "Ce type peut être déplacé hors d'une épingle".

@tikue Dans un certain sens, je ressens la même chose, mais en sens inverse. Je pense que Unpin devrait être formulé dans le négatif "ceci n'est _pas_ contraint par Pin ", alors que Pinned devrait être formulé dans le positif "ce _est_ contraint par Pin ". Surtout juste pour éviter le double négatif. Mais c'est un détail; J'aime l'idée qu'il y ait une dualité. Peut-être: s/Unpin/TemporaryPin , s/Pinned/PermanentPin ?

EDIT: Ouais, je vois votre point sur Repin étant un effet secondaire de Unpin . Je voulais communiquer le fait que Pin est "sans importance" pour un type qui est Unpin , ce que je ne pense pas que Unpin fait très bien. D'où la suggestion ci-dessus de TemporaryPin .

@jonhoo Je pense que la principale raison pour laquelle je préfère le contraire est que Pinned empêche l'implémentation d'un trait, c'est donc, dans le sens le plus clair pour moi, le vrai négatif.

Edit: qu'en est-il:

Unpin -> Escape
Pinned -> NoEscape

Intéressant .. J'essaie de voir comment cela s'intégrerait dans la documentation. Quelque chose comme:

En général, quand on vous donne un Pin<P> , cela vient avec une garantie que la cible de P ne bougera pas tant qu'elle ne sera pas abandonnée. L'exception à cela est si la cible de P est Escape . Les types marqués comme Escape promettent qu'ils restent valides même s'ils sont déplacés (par exemple, ils ne contiennent pas d'auto-références internes), et ils sont donc autorisés à "échapper" à un Pin . Escape est un auto-trait, donc tous les types qui consistent entièrement en des types qui sont Escape sont eux-mêmes aussi Escape . Les développeurs peuvent désactiver cette option tous les soirs en utilisant impl !Escape for T {} , ou en incluant le marqueur NoEscape de std::phantom .

Cela semble assez décent, bien que le lien avec le mot «échapper» semble un peu ténu. Séparément, écrire ce qui précède m'a aussi fait réaliser que Pin<P> ne garantit pas _ vraiment_ que la cible de P ne sera pas déplacée (précisément à cause de Unpin ). Au lieu de cela, cela garantit que _ ni l'un ni l'autre_ n'a d'importance si la cible de P se déplace _ou_ P . Je ne sais pas comment utiliser cela pour informer un meilleur choix de noms cependant ... Mais c'est probablement quelque chose qui devrait figurer dans la documentation d'une manière ou d'une autre.

Personnellement, j'ai aussi une grande aversion pour Unpin comme nom, peut-être parce qu'il est le plus souvent vu comme impl !Unpin qui lit "not-un-pin" et nécessite plusieurs cycles cérébraux (je 'suis un modèle plus ancien) pour conclure que cela signifie "d'accord, celui-ci sera épinglé pour toujours une fois qu'il sera épinglé la première fois", donc je ne peux même pas optimiser le double négatif.
En général, les humains ont tendance à avoir plus de mal à penser en termes négatifs que positifs (sans source directe, mais consultez le travail de Richard Hudson en cas de doute).
Btw Repin me semble vraiment sympa.

Pin est difficile à expliquer car cela ne rend pas toujours la valeur épinglée immobile. Pinned est déroutant car il n'épingle en fait rien. Cela empêche juste d'échapper un Pin .

Pin<P<T>> pourrait être expliqué comme une valeur épinglée: les valeurs épinglées ne peuvent pas être déplacées sauf si la valeur est un type qui peut échapper à une épingle.

Quelques recherches rapides sur Google semblent montrer qu'en lutte, quand on est épinglé, sortir d'une épingle s'appelle s'échapper .

J'aime aussi le terme escape au lieu de Unpin mais j'irais pour EscapePin .

Beaucoup de bonnes pensées ici!

Une chose générale: pour moi en tant que locuteur non natif, Pin et Unpin sont principalement des verbes / actions. Bien que Pin sens, puisque l'objet est épinglé à un emplacement mémoire à la fois, je ne peux pas voir la même chose pour Unpin . Une fois que je reçois une référence de Pin<&mut T> , T sera toujours épinglé dans le sens où son emplacement mémoire est stable, que ce soit Unpin ou non. Il n'est pas possible de vraiment désépingler un objet en tant qu'action. La différence est que les types Unpin ne nécessitent pas les garanties d'épinglage pour être respectées lors d'interactions ultérieures. Ils ne sont pas autoréférentiels et leur adresse mémoire n'est par exemple pas envoyée à un autre objet et y est stockée.

Je suis d'accord avec @tikue pour Unpin , mais c'est difficile à quantifier. Ce n'est pas simplement que ces types sont mobiles, et ce n'est pas non plus uniquement leur manque d'auto-référentialité? C'est peut-être quelque chose à propos de "aucun pointeur dans tout l'espace mémoire n'est invalidé lorsque l'objet est déplacé". Alors quelque chose comme StableOnMoveAfterPin , ou juste StableMove pourrait être une option, mais cela ne semble pas vraiment génial.

Repin pour moi a les mêmes complications que Unpin , c'est-à-dire que cela implique qu'une chose est d'abord détachée - ce qui, à mon avis, ne se produit pas.

Puisque le trait définit principalement ce qui se passe après avoir vu un Pin du type, je trouve que des choses comme PinNeutral , ou PinInvariant pas trop mal.

Concernant Pin vs Pinned , je pense que je préférerais Pinned , car c'est l'état du pointeur au moment où on le voit.

@ Matthias247 Je ne pense pas que vous ayez la garantie que l'adresse de P::Target est stable si P: Unpin ? Je pourrais me tromper à ce sujet?

@ Matthias247

Une fois que je reçois une référence de Pin <& mut T>, T sera toujours épinglé dans le sens où son emplacement mémoire est stable, que ce soit unpin ou non.

Pouvez-vous clarifier ce que vous entendez par là? Étant donné un T: Unpin vous pouvez écrire ce qui suit:

let pin_t: Pin<&mut T> = ...
let mut other_t: T = ...
mem::replace(Pin::get_mut(pin_t), &mut other_t);
// Now the value originally behind pin_t is in other_t

@jonhoo En fait, une bonne question. Mon raisonnement était que les contrats à terme étaient encadrés, puis leur méthode poll() sera appelée sur la même adresse mémoire. Mais cela ne s'applique évidemment qu'à la tâche / future de niveau supérieur, et les couches intermédiaires peuvent déplacer des futures quand elles sont Unpin . Il semble donc que vous ayez raison.

Wrt le dernier bikeshedding:

  • Unpin : qu'en est-il de MoveFromPin ? Si je ne manque toujours pas de subtilités, je pense que cela indique directement la capacité que le trait permet réellement: si le type est dans un Pin , vous pouvez toujours le déplacer.

    Fondamentalement, cela dit la même chose que Unpin , mais sous la forme d'une assertion positive, donc au lieu du double négatif !Unpin nous avons maintenant !MoveFromPin . Je pense que je trouve cela plus facile à interpréter, au moins ... c'est euh, des types que vous ne pouvez pas sortir d'une épingle.

    (Il y a une certaine marge de manœuvre sur l'idée de base: MoveOutOfPin , MoveFromPinned , MoveWhenPinned , etc.)

  • Pinned : cela peut alors devenir NoMoveFromPin , et son effet est de créer un type !MoveFromPin . Je pense que cela semble assez simple.

  • Pin lui-même: celui-ci n'est pas connecté aux deux autres et n'est pas non plus aussi important, mais je pense qu'il pourrait y avoir de la place pour une légère amélioration ici aussi.

    Le problème est que Pin<&mut T> (par exemple) ne signifie pas que le &mut est épinglé, cela signifie que le T est (une confusion que je pense avoir vue à au moins un commentaire récent). Puisque la partie Pin agit comme une sorte de modificateur sur le &mut , je pense qu'il serait préférable de l'appeler Pinning .

    Il existe un précédent indirect à cela: si nous voulons modifier la sémantique de débordement d'un type entier pour s'enrouler au lieu de paniquer, nous disons Wrapping<i32> plutôt que Wrap<i32> .

Tous ces éléments sont plus longs que les originaux, mais étant donné la délicatesse du raisonnement autour de ceux-ci, cela pourrait être un investissement intéressant pour plus de clarté.

Re: Repin , je m'attendrais à ce que ce soit quelque chose comme

unsafe trait Repin {
    unsafe fn repin(from: *mut Self, to: *mut Self);
}

qui pourrait être utilisé pour prendre en charge les types !Unpin dans une collection semblable à Vec qui déplace occasionnellement son contenu (ce n'est pas une proposition pour ajouter un tel trait maintenant ou jamais, juste ma première impression du nom du trait).

Bikshedding également les noms:

  • Pin<P> -> Pinned<P> : la valeur vers laquelle pointe le pointeur P est épinglée en mémoire pour sa durée de vie (jusqu'à ce qu'elle soit supprimée).
  • Unpin -> Moveable : la valeur n'a pas besoin d'être épinglée et peut être déplacée librement.
  • Pinned (struct) -> Unmoveable : les besoins doivent être Pinned et ne peuvent pas être déplacés.

Je ne pense pas que Pin ou Unpin devrait changer, les alternatives ajoutent toutes de la verbosité sans clarté à mon avis, ou sont même assez trompeuses. De plus, nous avons déjà eu cette conversation, avons décidé d'utiliser Pin et Unpin , et aucun des arguments évoqués dans ce fil n'est nouveau.

Cependant, Pinned été ajouté depuis la discussion précédente, et je pense qu'il est logique de faire en sorte que PhantomPinned soit clair, c'est un type de marqueur fantôme comme PhantomData .

Personnellement, j'ai aussi une grande aversion pour Unpin en tant que nom, peut-être parce qu'il est le plus souvent considéré comme impl! Unpin qui lit "not-un-pin" et nécessite plusieurs cycles cérébraux (je suis un modèle plus ancien) pour conclure cela signifie "d'accord, celui-ci sera épinglé pour toujours une fois qu'il sera épinglé la première fois", donc je ne peux même pas optimiser le double négatif.

C'est complètement le contraire de mon expérience, implémenter manuellement !Unpin signifie que vous implémentez une structure auto-référentielle à la main en utilisant un code non sécurisé, un cas d'utilisation extrêmement niche. En revanche, tout ce qui garde une structure potentiellement désépinglée derrière un pointeur a une implication positive de Unpin . Tous les impls de Unpin dans std sont de polarité positive, par exemple.

@withoutboats pourriez-vous fournir un lien vers la discussion précédente autour de ces noms où les arguments soulevés ici ont déjà été débattus?

Voici un fil, bien qu'il ait certainement également été discuté sur le fil RFC et le problème de suivi https://internals.rust-lang.org/t/naming-pin-anchor-move/6864

(les types de ce thread appelés ancre et épingle sont maintenant appelés Pin<Box<T>> et Pin<&'a mut T> )

Unpin est-il censé être lu comme l'abréviation de Unpinable? Impossible d'épingler, car vous ne pouvez pas réellement garder la valeur épinglée, même si elle est à l'intérieur d'une épingle. (Est-ce même une compréhension correcte de ma part?)

J'ai parcouru certains des documents et des fils de commentaires et je n'ai vu aucune référence à Unpinnable en particulier.

Unpin n'est pas censé être court pour quoi que ce soit. Une chose que je pense n'est pas évidente pour beaucoup d'utilisateurs, mais qui est vraie, c'est que le guide de style standard de la bibliothèque est de préférer les verbes comme noms de trait, pas les adjectifs - d'où Send , pas Sendable . Cela n'a pas été appliqué avec une cohérence parfaite, mais c'est la norme. Unpin est comme dans "pour désépingler", car il est possible de détacher ce type du Pin avec lequel vous l'avez épinglé.

Les noms comme Move (pas "déplaçable", rappelez-vous) sont moins clairs que Unpin car ils impliquent que cela a à voir avec la possibilité de le déplacer du tout, plutôt que de connecter le comportement au type de broche. Vous pouvez déplacer les types !Unpin , car vous pouvez déplacer n'importe quelle Sized dans Rust.

Les noms qui sont des phrases entières, comme je l'ai vu, seraient très unidiomatiques pour std.

Ce n'est peut-être pas court, mais c'est exactement ainsi que je le lis. Puisque les traits sont des verbes, vous devez le transformer manuellement en adjectif si vous voulez l'utiliser pour décrire un type plutôt qu'une opération sur un type; std::iter::Iterator est implémenté par quelque chose qui est itérable, std::io::Seek est implémenté par quelque chose qui peut être recherché, std::pin::Unpin est implémenté par quelque chose qui n'est pas épinglable.

@withoutboats un problème spécifique avec certains des autres noms qui n'ont pas de problème de verbe, par exemple Escape ou EscapePin ? J'ai compris que cette discussion a eu lieu auparavant, mais probablement beaucoup plus d'yeux sont maintenant, donc je ne suis pas sûr que ce soit une répétition complètement redondante ...

Une chose que je pense est vraie, c'est qu'il est malheureux que Pin et Unpin puissent être lus comme une paire (certains types sont "pin" et d'autres "unpin"), quand Pin est censé être un nom , pas un verbe . Espérons que le fait que Pin ne soit pas un trait clarifie les choses. L'argument en faveur de Pinning sens, mais ici nous nous heurtons au problème de la longueur du nom. D'autant que les receveurs de méthodes devront répéter deux fois self , on se retrouve avec beaucoup de caractères: self: Pinning<&mut Self> . Pas convaincu que Pinning<P> est un ensemble de quatre caractères d'une valeur de clarté sur Pin<P> .

@tikue la terminologie "escape" est beaucoup plus surchargée que l'épinglage je pense, en conflit avec des concepts comme l'analyse d'échappement.

De plus, nous avons déjà eu cette conversation, avons pris la décision d'utiliser Pin and Unpin, et aucun des arguments évoqués dans ce fil n'est nouveau.

Cela me frotte dans le mauvais sens - le rapport d'expérience de la communauté est-il indésirable? Personnellement, je n'ai pas vu de problème clair avec Unpin jusqu'à certains des autres commentaires de ce fil, ainsi que la juxtaposition avec Pinned double négatif.

Rust n'effectue pas d'analyse d'échappement, donc je ne suis pas sûr de voir cela comme un vrai problème.

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

J'aimerais toujours voir les améliorations de la documentation comme indiqué dans https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891 avant que cela n'arrive :)

J'ai ouvert https://github.com/rust-lang/rust/pull/55992 pour ajouter la documentation suggérée ci-dessus et renommer Pinned en PhantomPinned .

Je pense que Pinned (et PhantomPinned ) encourage la conceptualisation d'une valeur "épinglée" comme une valeur qui ne peut pas sortir d'un Pin , ce qui signifie que de nombreuses valeurs dans un Pin (ceux dont les types impliquent Unpin ) ne sont

Cela semble déroutant. Je trouve plus facile de conceptualiser toutes les valeurs dans un Pin comme épinglées alors qu'elles sont dans le Pin , et que l'épinglage soit permanent ou non, c'est ce que l'on appelait auparavant Pinned contrôles. Un nom séparé de Pin* empêcherait la fusion de deux concepts distincts.

PhantomNotUnpin : P

Personnellement, j'ai aussi une grande aversion pour Unpin en tant que nom, peut-être parce qu'il est le plus souvent considéré comme impl! Unpin qui se lit "not-un-pin" et nécessite plusieurs cycles cérébraux

Merci! J'ai également été dérangé par Unpin pendant un certain temps, mais je n'ai pas été en mesure de déterminer (heh) pourquoi. Maintenant je crois comprendre: c'est la double négation.

C'est complètement le contraire de mon expérience, la mise en œuvre manuelle! Unpin signifie que vous implémentez une structure auto-référentielle à la main en utilisant du code non sécurisé, un cas d'utilisation extrêmement niche. En revanche, tout ce qui conserve une structure potentiellement désépinglée derrière un pointeur a une implication positive de Unpin. Tous les impls de Unpin dans std sont de polarité positive, par exemple.

Ce n'est pas seulement une question de mise en œuvre, mais aussi de discussion. impl !Sync est assez rare (pas seul car il est instable), mais parler des types Sync et !Sync est assez courant. De même, !Unpin été soulevé beaucoup dans les discussions sur cette fonctionnalité, du moins celles que j'ai eues.

Moi aussi, je préférerais quelque chose qui exprime positivement une propriété ( MoveFromPin environ). Je ne suis pas entièrement convaincu par l'ergonomie, car contrairement à Pin on ne devrait pas avoir à écrire ce trait lié assez souvent.

Rust n'effectue pas d'analyse d'échappement, donc je ne suis pas sûr de voir cela comme un vrai problème.

LLVM le fait, donc l'analyse d'échappement est toujours très pertinente pour Rust.

La façon de résoudre ce problème est de choisir des mots qui inversent la signification de Pin / Unpin . Par exemple, renommez Unpin en Relocate . Alors !Unpin devient !Relocate . Cela a un sens beaucoup plus intuitif pour moi - je le lis comme "Oh, les objets de ce type ne peuvent pas être déplacés". Un autre concurrent est Movable .

Je ne sais pas quel est le mot opposé qui pourrait remplacer Pin , ou si nous en avons même besoin. Mais je pourrais certainement imaginer les documents disant quelque chose comme ceci:

Un objet Pinned peut être modifié directement via DerefMut si et seulement si l'objet peut être déplacé en mémoire. Relocate est un trait automatique - il est ajouté par défaut. Mais s'il y a des pointeurs directs vers la mémoire contenant vos valeurs, désactivez Relocate en ajoutant impl !Relocate sur votre type.

impl<T: Relocate> DerefMut for Pin<T> { ... }

Cela me semble beaucoup plus intuitif que Unpin .

J'ai ouvert # 55992 pour ajouter la documentation suggérée ci-dessus

Cela n'ajoute qu'une partie de ce qui a été suggéré dans https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891, cependant.

J'aime la suggestion MoveFromPin . Relocate est également bon, mais n'est peut-être pas assez associé à Pin . On pourrait à nouveau le comprendre comme un type non mobile (ce qui n'est pas le cas). RelocateFromPin serait encore une fois bien.

Escape ing est une chose qui est également associée par exemple dans Swift avec des fermetures, et si elles sont appelées à l'intérieur ou à l'extérieur de la chaîne d'appels actuelle. Cela semble trompeur.

Je ne vois aucun problème avec des noms plus longs tant que cela contribue à la clarté.

FWIW Je voudrais également lancer un vote pour renommer Unpin en quelque chose de "positif" comme Relocate ou MoveFromPin (ou encore plus verbeux mais peut-être légèrement plus précis MayMoveFromPin ).

Je suis d'accord que le double négatif de !Unpin ou juste de Unpin m'a historiquement dérouté, et quelque chose d'encadré positif "cela peut bouger même si c'est à l'intérieur Pin " aiderait à atténuer une certaine confusion!

FWIW J'ai d'abord pensé la même chose à propos de Unpin , mais quand je suis allé l'utiliser IMO, cela avait du sens - ce n'est pas vraiment une double négation, puisque l'opération que vous recherchez est la capacité de Unpin (prendre quelque chose dans et hors de Pin librement) pas la possibilité de garder les choses dans une épingle. C'est la même chose que MoveFromPin , simplement formulé différemment. Je préfère un nom qui ne fait pas croire aux gens que ce n'est "pas Pin " ou quelque chose comme ça, mais IMO MoveFromPin et d'autres sont beaucoup trop verbeux. UndoPin ? ( FreePin pour la foule des haskell?)

Je pense toujours que !Unpin lit bizarre - j'ai entraîné ma voix intérieure à la traiter plus comme "Ne détachez pas ça!" au lieu de l'habituel "N'implémente pas Unpin ", mais cela a demandé un certain effort.

Et pour !Pluck ?

@runiq

au lieu de l'habituel "N'implémente pas la suppression de l'épinglage"

Je l'ai mentionné dans mon commentaire précédent, mais je pense que c'est en fait une bonne façon de le dire: Unpin est l'opération qui consiste à le faire entrer et sortir de Pin<C<_>> . Les choses qui n'implémentent pas Unpin ne fournissent pas cette capacité.

@cramertj J'aime assez UndoPin

@cramertj Je suis d'accord que je ne suis pas un grand fan des alternatives proposées jusqu'à présent, mais je préférerais personnellement le mot verbeux MoveFromPin à Unpin . C'est un bon point que ce n'est pas un double négatif, mais en le lisant (en tant que quelqu'un qui n'a pas encore travaillé avec une tonne), cela ne cesse de me faire trébucher en disant que c'est un double négatif. J'essaie de lire le préfixe "Un" comme négatif ...

Je suis curieux, mais @cramertj ou d'autres avez-vous l'impression qu'il y a une bonne idée de la quantité de Unpin liée ergonomiquement? Est-ce super rare? Est-ce super commun? Assez commun pour être une douleur si c'est une douleur à taper?

Pour un nom court et doux, j'aime personnellement l'idée Relocate , mais pour un nom plus long et plus verbeux-mais-ok-parce que-vous-ne-tapez-pas-autant-j'aime bien MoveFromPin . Personnellement, je pense que quelle que soit l'ergonomie de la saisie du nom, les deux valent mieux que Unpin

Je suis curieux, mais @cramertj ou d'autres avez-vous l'impression qu'il y a une bonne idée de la façon dont la reliure Unpin est ergonomique? Est-ce super rare? Est-ce super commun? Assez commun pour être une douleur si c'est une douleur à taper?

D'après mon expérience, il y a deux cas où Unpin apparaît en fait dans le code utilisateur:

  1. Unpin limites de Unpin apparaissent parce que vous devez réellement déplacer un futur pendant que vous l'interrogez (par exemple, des choses comme certaines API sélectionnées).
  2. Plus communément, si votre futur est générique uniquement sur autre chose qu'un futur, vous voulez généralement ajouter une implémentation inconditionnelle de Unpin , car peu importe si ces autres types sont Unpin puisque vous n'avez jamais épingler le projet sur eux.

Un exemple du second cas est si vous avez une sorte de type de tampon générique sur (par exemple T: AsRef<[u8]> ). Vous n'avez pas besoin de l'épingler pour en extraire la tranche, donc vous ne vous souciez pas de savoir si elle implémente Unpin ou non, donc vous voulez juste dire que votre type implémente inconditionnellement Unpin afin que vous puissiez implémenter Future ignorant l'épinglage.

Unpin est assez courant à voir comme une borne - select! , StreamExt::next et d'autres combinateurs exigent tous que les types sur lesquels ils opèrent soient Unpin .

@withoutboats Je suis curieux de connaître votre point 2 ici; Pensons-nous que impl Unpin est quelque chose que les gens vont devoir se rappeler de mettre en œuvre souvent? Semblable à la façon dont les auteurs de bibliothèques oublient souvent aujourd'hui de #[derive(Debug)] ou impl std::error::Error pour leurs types personnalisés, ce qui rend plus difficile l'utilisation de ces bibliothèques?

Unpin est une caractéristique automatique. Le seul moment où vous aurez un type qui n'implémente pas unpin, c'est lorsque ce type en sort explicitement. (Ou contient un champ qui choisit de ne pas épingler).

Je comprends que c'est le cas. Et j'ai supposé que ce serait de loin le cas le plus courant, c'est pourquoi j'ai été surpris que @withoutboats ait même mentionné le deuxième point. Cela me suggère que c'est peut-être plus courant que je ne le pensais à l'origine (mais peut-être seulement lors de la mise en œuvre de votre propre avenir), alors je suis curieux de connaître la fréquence de ces types de cas d'utilisation :)

@alexcrichton Je suis curieux, mais @cramertj ou d'autres avez-vous l'impression qu'il y a une bonne idée de la façon dont la reliure Unpin se pose ergonomiquement? Est-ce super rare? Est-ce super commun? Assez commun pour être une douleur si c'est une douleur à taper?

J'avais écrit un long article sur mon expérience de portage de mon code sur Futures 0.3 (qui utilise Pin ). Vous n'avez pas besoin de le lire, le résumé est:

La plupart du temps, vous n'avez pas du tout à vous soucier de Unpin , car Unpin est implémenté automatiquement pour presque tous les types.

Donc, le seul moment où vous devez vous soucier de Unpin est:

  1. Vous avez un type générique par rapport aux autres types (par exemple struct Foo<A> ).

  2. Et vous voulez implémenter une API d'épinglage (par exemple Future / Stream / Signal ) pour ce type.

Dans ce cas, vous devez utiliser ceci:

impl<A> Unpin for Foo<A> where A: Unpin {}

Ou ca:

impl<A> Unpin for Foo<A> {}

impl<A> Future for Foo<A> where A: Unpin { ... }

C'est généralement la seule situation où Unpin est nécessaire. Comme vous pouvez le voir, cela signifie généralement qu'il faut utiliser Unpin ~ 2 fois par type.

Dans le cas des non-combinateurs, ou dans le cas des types qui n'implémentent pas Future / Stream / Signal , vous n'avez pas besoin d'utiliser Unpin du tout.

Donc, je dirais que Unpin apparaît très rarement, et cela ne se produit vraiment que dans la situation de création Signal combinateurs Future / Stream / Signal .

Je suis donc fortement en faveur d'un nom comme MoveFromPin . L'épinglage est une fonctionnalité de niche que la plupart des gens n'ont jamais besoin de gérer, nous ne devrions donc pas sur-optimiser la longueur du nom.

Je pense que les avantages cognitifs (éviter la double négation) sont bien plus importants que de sauver quelques personnages dans des situations rares.

Surtout parce que l'épinglage est déjà assez difficile à comprendre! Alors ne compliquons pas les choses inutilement.

@jonhoo pense- impl Unpin est quelque chose que les gens vont devoir se rappeler de mettre en œuvre souvent? Semblable à la façon dont les auteurs de bibliothèques oublient souvent aujourd'hui de #[derive(Debug)] ou impl std::error::Error pour leurs types personnalisés, ce qui rend plus difficile l'utilisation de ces bibliothèques?

Je ne pense pas qu'il soit possible d'oublier impl Unpin , car si l'auteur oublie, il obtiendra une erreur de compilation, ce qui empêchera la publication de sa caisse en premier lieu. Ce n'est donc pas du tout #[derive(Debug)] .

La situation dont parle combinateurs Future / Stream / Signal , cela n'affecte personne d'autre (en particulier, cela n'affecte pas utilisateurs en aval de la bibliothèque).

(Je pense que la double négation en fait partie, et peut-être simplement plus de négation que strictement nécessaire l'est aussi, mais je pense que ce n'est pas toute l'histoire (essayer d'introspecter ici) ... "Détacher" est un peu , métaphorique? Indirect? Cela a du sens quand on l'explique, et comme «épingler» lui-même est déjà une métaphore, on ne sait pas pourquoi mon cerveau aurait un problème à approfondir cette métaphore, mais, néanmoins, mon cerveau trouve le sens de "détacher" pour une raison obscure et difficile à verrouiller fermement.)

Je suppose que vous avez raison @cramertj , ce n'est pas en fait une double négation au sens habituel, mais comme @alexcrichton et @glaebhoerl, je continue à me faire trébucher. « Un- » comme préfixe a un sentiment très négation-y à elle ( « dangereux », « lettre morte » et ainsi de suite comment je rencontre habituellement ce préfixe), et il est ici épinglant niant, si seulement comme un verbe.

@withoutboats Je suis curieux de connaître votre point 2 ici; pensons-nous que impl Unpin est quelque chose que les gens vont devoir se rappeler de mettre en œuvre souvent? Similaire à la façon dont les auteurs de bibliothèques oublient souvent aujourd'hui de # [derive (Debug)] ou d'implémenter std :: error :: Error pour leurs types personnalisés, ce qui rend plus difficile l'utilisation de ces bibliothèques?

Absolument pas! Si un utilisateur implémente manuellement un Future qui est générique sur un non-futur, il voudra probablement pouvoir muter l'état dans sa future implémentation sans écrire de code non sécurisé - c'est-à-dire pour traiter Pin<&mut Self> comme &mut self . Ils obtiendront des erreurs indiquant que MyFuture<T: AsRef<[u8]>> n'implémente pas Unpin . La meilleure solution à ce problème est alors d'implémenter Unpin . Mais le seul impact que cela a est sur l'utilisateur essayant d'implémenter Future , et il est impossible pour eux d'oublier car leur code ne se compilera pas.

La situation dont parle

Je parle spécifiquement des contrats à terme manuels génériques non combinateurs , qui devraient juste avoir des impls généraux de Unpin même si leurs génériques ne sont pas Unpin .

J'ai créé un organigramme pour la question "Que dois-je faire à propos de l'épinglage lorsque j'implémente un futur / flux manuel?"

pinning-flowchart

Pour faire du vélo un peu plus, aujourd'hui, au déjeuner, j'ai trouvé LeavePin . Il a le même ton que escape sans la sémantique trompeuse implicite.

Existe-t-il des interactions intéressantes entre la spécialisation en implémentation et les broches, comme c'est le cas avec les durées de vie?

La sous-chaîne "specializ" apparaît un peu dans la discussion sur les problèmes de suivi , mais elle n'est pas concluante. La RFC Pin ou la RFC de spécialisation doit mentionner explicitement cette interaction, soit comme «il est vérifié que c'est OK» ou «des recherches supplémentaires sont nécessaires pour juger qu'elle est sûre».

@vi non seulement il n'y a pas d'interaction de solidité, mais il n'est pas possible qu'il y ait une interaction de solidité. Les API pin sont strictement définies dans la bibliothèque standard et n'impliquent aucune nouvelle fonctionnalité de langage, un utilisateur peut les définir tout aussi facilement dans des bibliothèques tierces. Si une fonctionnalité de langage est défectueuse face à ce code de bibliothèque existant, ce n'est pas une période saine, car n'importe quel utilisateur peut écrire ce code de bibliothèque aujourd'hui et il compilera bien.

Si une fonctionnalité de langage est défectueuse face à ce code de bibliothèque existant, ce n'est pas une période saine, car n'importe quel utilisateur peut écrire ce code de bibliothèque aujourd'hui et il compilera bien.

Non pas que cela devrait avoir une incidence sur pin ... mais je ne pense pas que ce soit aussi simple que cela.

Si une fonctionnalité de bibliothèque, lors de l'utilisation de unsafe , utilise des constructions de langage dont le comportement n'est pas encore spécifié (par exemple &packed.field as *const _ , ou faisant diverses hypothèses sur l'ABI) alors si des changements de langage supplémentaires invalident les hypothèses de ces bibliothèques, je pense que ce sont les bibliothèques qui sont défectueuses et non les changements de langue. D'un autre côté, si les changements de langue rendent le comportement défini défectueux, c'est la faute des changements de langue. Une compilation fine n'est donc pas une condition suffisante pour la solidité d'une bibliothèque face à des changements de langue et de danger.

+1 à MoveFromPin ou similaire

Si vous posez la question "Quand dois-je désimplémenter Unpin pour mon propre type?", La réponse est beaucoup plus claire si vous demandez plutôt "Quand dois-je désimplémenter MoveFromPin pour mon propre type?"

Idem avec "Dois-je ajouter Détacher comme trait lié ici?" vs "dois-je ajouter MoveFromPin comme trait lié ici?"

L'épinglage n'est pas!

Désolé si cela a été mentionné quelque part, mais je n'ai fait qu'écumer la grande quantité de discussions qui ont eu lieu autour de Pin ici, dans le problème de l'implémentation et dans le numéro RFC.

La rouille aura-t-elle jamais! Je peux certainement voir des cas d'utilisation pour une telle chose (diable, je suis venu chercher Pin parce que je cherchais un moyen de ne pas me tirer une balle dans le pied avec des types qui ne peuvent pas être déplacés). Si la réponse est oui, comment cela interagirait-il avec Pin? L'existence de Pin rendrait-il plus difficile qu'il ne l'est déjà d'ajouter!

La période de commentaires finale, avec une disposition à fusionner , conformément à l' examen ci - terminée .

Il devrait y avoir une préoccupation non résolue concernant la dénomination de Unpin.

Comme @RalfJung l'a souligné , # 55992 n'ajoute également qu'une petite quantité de la documentation supplémentaire demandée dans https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891 et ailleurs. Je ne sais pas que c'est une raison pour ne pas encore fusionner.

Pourquoi ma méthode drop () prend-elle à nouveau & mut self, au lieu d'un Pin.

Eh bien, drop () est vieux - il existe depuis Rust 1.0 - donc nous ne pouvons pas le changer. Nous aimerions simplement que cela prenne Pin <& mut Self>, puis les types Unpin pourraient obtenir leur & mut comme ils le font maintenant, mais c'est un changement non rétrocompatible.

Je me demandais s'il serait possible de mettre en œuvre ce changement d'une manière rétrocompatible. AIUI jusqu'à ce que nous ajoutions Unpin (et les gens peuvent spécifier !Unpin ) tous les types implémentent Unpin . Nous pourrions donc ajouter un trait:

trait DropPinned {
    fn drop(Pin<&mut> self);
}

puis implémentez ce trait pour tous les types Unpin , qui, jusqu'à ce que les gens puissent se désinscrire, sont tous types. Quelque chose comme:

impl<T> PinDrop for T where T:Unpin + Drop {
    fn drop(Pin<&mut T> self) {
        Drop::drop(self.get_mut());
    }
}

Ensuite, le compilateur insère des appels à DropPinned::drop au lieu de Drop::drop . Essentiellement, le trait DropPinned devient l'élément de langue plutôt que Drop . AFAICS ce serait rétrocompatible si et seulement si ce mécanisme était introduit en même temps que Unpin .

Il devrait y avoir une préoccupation non résolue concernant la dénomination de Unpin.

@tikue Aucun membre de l'équipe libs n'a Unpin était nouveau ou nouveau pour ce fil, donc généralement notre processus prendrait en compte l'actuel nommage à finaliser. Évidemment, si quelqu'un a des inquiétudes, il devrait s'exprimer, mais le but d'avoir des cases à cocher d'équipe suivies de FCP est de s'assurer que tout le monde est à l'aise pour stabiliser l'API telle que proposée.

@cramertj Je suis un peu confus. Plusieurs personnes ont pris la parole. Quand j'ai demandé des références sur les autres endroits où les arguments sur la dénomination de Unpin avaient été soulevés et résolus, on m'a dirigé vers https://internals.rust-lang.org/t/naming-pin-anchor- move / 6864 , qui, pour autant que je également des gens se plaignant du nom de Unpin , et aucun véritable contre-argument. La RFC originale dans https://github.com/rust-lang/rfcs/pull/2349 n'a pas non plus beaucoup de raisons pour lesquelles les alternatives proposées à Unpin sont pires. Même dans ce fil, il semble que les seuls contre-arguments qui apparaissent vraiment sont "c'est plus court" et "c'est techniquement correct". Êtes-vous en mesure d'indiquer une discussion concrète où des noms alternatifs plus faciles à comprendre (tels que MoveFromPin ) sont discutés et rejetés?

J'ai expliqué dans les commentaires précédents pourquoi je crois que de nouvelles perspectives ont été évoquées dans ce fil. J'ai suivi d'assez près les discussions sur l'API pin depuis le début et je ne me souviens pas avoir jamais vu le double problème négatif soulevé avant ce fil.

@tikue J'ai Unpin a été soulevée plusieurs fois comme un problème et a toujours été résolue en faveur de Unpin . Comme je l'ai déjà dit, si quelqu'un de l'équipe libs veut enregistrer une objection (tardive), je vais bien y répondre, mais ils ont tous approuvé la proposition de stabilisation ci-dessus qui n'inclut clairement pas le nom de Unpin comme une question non résolue: nous avons discuté des alternatives, et ce FCP était le processus pour décider que nous étions prêts à nous stabiliser sur les décisions qui avaient été prises, y compris le nom Unpin .

@cramertj Pourriez-vous s'il vous plaît fournir un lien vers où cette discussion a eu lieu? Je ne doute pas de vous, je voudrais juste voir les arguments en faveur de Unpin , car je ne crois pas qu'ils aient été donnés ici. Comme mentionné, les références que j'ai reçues jusqu'à présent ne fournissent aucune résolution sur la dénomination de Unpin .

@cramertj +1 à la demande de

Je pense qu'il existe même une règle officielle selon laquelle les décisions RFC ne peuvent être prises que sur la base d'arguments connus du public?

Ouais, c'est vrai - et pour mémoire, je ne suis pas dans l'équipe libs et donc je n'ai pas été présent pour les discussions de l'équipe libs uniquement. En parcourant le fil RFC et le problème de suivi Pin , le nom de Unpin été mentionné à plusieurs reprises. Je ne vois personne dire spécifiquement les mots "double négatif", mais je me souviens certainement que " !Unpin est un double négatif" a été évoqué auparavant, en plus de la règle générale de l'API de nommer les traits pour ce que vous pouvez faites avec eux, plutôt que ce que vous ne pouvez pas (comme je l'ai souligné ci-dessus, je pense que Unpin suit en fait ces deux règles, bien que réalisant que cela nécessite d'entendre "Unpin" comme un verbe plutôt que de l'entendre comme un adjectif "not-pin", ce qui n'est pas intuitif pour les gens).

@wmanley Cela ne fonctionne pas, par exemple impl<T> Drop for Vec<T> casserait parce que nous n'avons pas Vec<T>: Unpin . En outre, des propositions dans ce sens ont déjà été faites, même dans ce fil . Veuillez lire les discussions avant de répondre. Je sais que cela demande beaucoup, mais c'est le seul moyen d'éviter que les mêmes problèmes ne soient expliqués encore et encore.

Je pense qu'il existe même une règle officielle selon laquelle les décisions RFC ne peuvent être prises que sur la base d'arguments connus du public?

C'est ce qu'on appelle officieusement la règle «pas de nouvelle justification» .

Vous ne savez pas où publier cela, mais quelqu'un pourrait-il jeter un coup d'œil à https://github.com/rust-lang/rust/issues/56256 ?

Lié à # 56256, impl<T> From<Box<T>> for Pin<Box<T>> n'est pas listé dans l'OP comme étant stabilisé, mais il deviendra implicitement stable une fois que Pin fera. Y a-t-il d'autres implémentations de traits qui ne sont pas triviales et qui devraient être examinées pour la stabilisation? (l'analyse des documents tous les autres semblent être des implémentations de délégation triviales pour le pointeur enveloppé vers moi).

Nous avons parlé de ce problème dans l'équipe libs aujourd'hui. Les dernières semaines de discussion ont montré qu'il y a encore quelques points à régler en premier, notamment en ce qui concerne la dénomination de Unpin .

Par conséquent, nous n'irons pas de l'avant pour stabiliser cela pour le moment (malgré le FCP).

Il serait très apprécié que quelqu'un puisse rassembler les idées dans ce fil et préparer une proposition autonome pour améliorer la situation de dénomination.

@Kimundi

Il serait très apprécié que quelqu'un puisse rassembler les idées dans ce fil et préparer une proposition autonome pour améliorer la situation de dénomination.

Cela signifie-t-il que l'équipe libs n'est pas disposée à stabiliser les API actuelles telles quelles? Personnellement, je ne pense pas que quiconque ait trouvé un meilleur nom que l'ensemble de noms actuel tel que mis en œuvre, et je ne suis donc pas en mesure de faire une telle proposition, mais je me soucie beaucoup de voir ces API stabilisées, donc si quelqu'un de l'équipe libs a un nom qu'il préfère alors: shipit:

Bikeshedded ceci avec un groupe de collègues et @anp a suggéré DePin , ce que j'aime un peu en fait, car cela supprime la connotation "pas d'épingle" de Unpin et souligne qu'il parle d'un type qui peut être de- Pin 'd.

@Kimundi pouvez-vous ou quelqu'un de l'équipe libs s'il vous plaît enregistrer une "préoccupation fcp" explicite pour retirer cela du FCP et exposer plus clairement ce que signifie "quelques points à régler"?

@rfcbot concerne le naming-of-Unpin

Je ne suis pas sûr que cela fonctionne vraiment une fois que FCP est entré, mais je voudrais placer un problème de blocage formel sur le nom du trait Unpin . Le trait Unpin semble être assez crucial pour l'API, et le "double négatif" (comme discuté ci-dessus) me jette un tour à chaque fois que je le lis.

Il y a eu tout un tas de commentaires sur différents noms, mais malheureusement, je n'en suis pas encore très enthousiasmé. Mon "favori" est toujours dans le sens de MoveFromPin ou Relocate (mon dieu, il y a tellement de commentaires ici que je ne sais pas comment revoir cela).

Je suis personnellement d'accord avec le nom de Pin lui-même ainsi que de Pinned pour un ZST qui n'implémente pas le trait Unpin .

Je suis entièrement d'accord avec @alexcrichton pour Unpin est le gros point de discorde ici. Je ne pense pas qu'il y ait de problèmes techniques pour autant que je puisse voir la fonctionnalité proposée elle-même (bien qu'il y ait beaucoup de commentaires, donc j'aurais pu manquer quelque chose).

Je pense toujours que Pinned est un nom étrange pour le ZST, parce que quelque chose qui contient un Pinned n'est pas vraiment épinglé .. Ce n'est tout simplement pas Unpin . PhantomPinned (comme il a été renommé en # 55992) a le même problème en ce sens qu'il fait référence à Pin , lorsque le ZST est _ vraiment_ environ Unpin .

Je pense aussi toujours que la documentation a besoin de plus de travail compte tenu de la subtilité de cette fonctionnalité, mais cela ne compte probablement pas comme un bloqueur.

De plus, @Kimundi , je suis heureux de voir que l'équipe libs est prête à donner un peu plus de temps pour régler le

D'accord avec @jonhoo à propos de Pinned toujours bizarre, et PhantomPinned n'étant pas mieux (il n'est en aucun cas épinglé, même pas de manière fantôme). Je pense que si nous trouvons un bon nom pour Unpin , alors Pinned se prêtera naturellement à être renommé Not{NewNameForUnpin} .

Je ne pense vraiment pas que nous ayons besoin de passer plus de temps à discuter de PhantomPinned - ce type n'apparaîtra presque jamais aux utilisateurs finaux. PhantomNotUnpin / PhantomNotDePin / PhantomNotMoveFromPin / etc. ne va pas le rendre plus ou moins courant ou rendre l'API plus ou moins évidente pour les utilisateurs déjà suffisamment à l'aise avec l'API pour avoir proposé une utilisation légitime pour PhantomPinned .

Juste une petite idée: trait Move et ZST Anchor .

Chaque type est Move able, sauf s'il contient un Anchor qui le fait coller à un Pin .
J'aime la façon dont Anchor: !Move fait intuitivement beaucoup de sens.

Je ne suggère pas que nous passions du temps spécifiquement sur PhantomPinned , mais je pense que ce serait peu d'effort de garder l'esprit ouvert, car il est possible que tout ce qui est atterri pour Unpin fonctionnera aussi bien pour PhantomPinned .

Nous avons couvert Move et expliqué pourquoi il est inapproprié à plusieurs reprises. Tous les types sont mobiles jusqu'à ce qu'ils soient épinglés. De même, Anchor a déjà été suggéré, mais il n'est pas du tout clair d'après le nom qu'il est utilisé pour se désengager de Unpin ing / Move ing.

@stjepang Je pense que Move a été abandonné il y a quelque temps parce que quelque chose étant !Unpin ne l'empêche pas réellement d'être déplacé. C'est seulement si un type est sous Pin , et ce n'est pas Unpin , que vous êtes "contractuellement obligé" de ne pas le déplacer.

Dans le commentaire original, @withoutboats a déclaré:

Le wrapper Pin modifie le pointeur pour "épingler" la mémoire à laquelle il se réfère en place

Je pense qu'il est étrange que les types implémentent Unpin parce que les valeurs ne sont pas "épinglées" - la mémoire l'est. Cependant, il est logique de parler de valeurs qui sont «sans danger». Et si nous renommions Unpin en MoveSafe ?

Considérez le trait UnwindSafe . C'est juste un trait de marqueur «indicatif» et sa mise en œuvre est sûre. Si vous laissez une !UnwindSafe traverser une limite de catch_unwind (avec AssertUnwindSafe ), vous la «casserez» en invalidant ses invariants.

De même, si vous avez une !Unpin / !MoveSafe , elle peut toujours être déplacée (bien sûr), mais vous la «casserez» en invalidant ses auto-références. Le concept semble similaire.

Le trait Unpin signifie simplement MoveSafe . Il me semble qu'il ne s'agit pas de valeurs qui peuvent être déplacées de la mémoire derrière Pin . Il s'agit plutôt de valeurs qui ne «se cassent» pas lorsque vous les déplacez.

MoveSafe a le même problème que Move - toutes les valeurs de n'importe quel type peuvent être déplacées en toute sécurité. Il ne devient impossible de déplacer une valeur qu'après avoir été épinglée.

MoveSafe a le même problème que Move - toutes les valeurs de n'importe quel type peuvent être déplacées en toute sécurité.

C'est vrai, mais c'est une signification différente de «sûr», tout comme dans UnwindSafe . Quoi qu'il en soit, je serais bien avec Relocate ou quoi que ce soit de ce genre.

Pour résumer, je pense que le nom du trait ne devrait pas être DePin , Unpin , ou quoi que ce soit avec "pin" dans son nom. Pour moi, c'est la principale source de confusion. Le trait n'est pas vraiment une "trappe de sortie" des chaînes de Pin - le trait indique que la valeur ne sera pas invalidée lorsqu'elle est déplacée.

Je vois juste Pin et Unpin comme des choses totalement séparées. :)

Je ressens exactement le contraire;). Le trait n'a de sens que par rapport à Pin, qui est le seul type que nous ayons qui exprime de manière significative des contraintes sur la mobilité de la valeur sous-jacente. Sans Pin, Unpin n'a aucun sens.

J'aime penser à épingler comme mettre quelque chose sur un tableau d'affichage. Si vous supprimez l'épingle d'un objet épinglé au tableau, vous pouvez déplacer l'objet. La suppression de la broche entraîne un détachement.
J'aime le nom Unpin .

Je peux aussi voir comment! Unpin est une double négation et peut prêter à confusion. Cependant, je me demande à quelle fréquence vous devez écrire !Unpin .

Un autre nom que je pourrais trouver pour Unpin est Detach . Revenons à la métaphore du tableau d'affichage, vous ne voudriez pas _Unpin_, mais _Detach_ le Pin de son objet.

Je pense que j'aime vraiment DePin ! Jusqu'à présent, c'est mon préféré - c'est concis, c'est clairement un verbe et non un adjectif, et !DePin semble également assez clair ("ne peut pas être dé-épinglé").

Je pense qu'il est étrange que les types implémentent Unpin parce que les valeurs ne sont pas "épinglées" - la mémoire l'est.

La valeur est épinglée en mémoire. Mais la valeur est également d'une importance cruciale. L'épinglage de la mémoire, pour moi, consiste simplement à s'assurer qu'il reste déréférencable, mais il n'est pas violé par mem::swap . Épingler une valeur en mémoire consiste à ne pas déplacer cette valeur épinglée ailleurs, ce qui est exactement ce Pin quoi sert

Je peux aussi voir comment! Unpin est une double négation et peut prêter à confusion. Cependant, je me demande à quelle fréquence vous devez écrire!

Je vous entends quand vous dites que vous ne trouvez pas le nom déroutant. Moi, et beaucoup d'autres dans ce fil, nous nous sommes retrouvés confus par la dénomination.

Je n'ai pas le code sous la main, mais la première fois que j'ai essayé d'utiliser des futures, je voulais qu'une fonction renvoie un impl Future<Output=T> . Je ne me souviens pas de ce qui s'est passé, mais j'ai immédiatement eu une erreur de compilateur épineuse se plaignant de T et Unpin. La question à laquelle j'avais besoin d'une réponse était "Est-il prudent de contraindre T à ne pas être épinglé". Et cela m'a conduit à regarder dans l'abîme pendant environ 2 heures déchirantes.

"Oh, ok alors si c'est ce que signifie Pin. Donc Unpin signifie ... Box? Pourquoi est-ce un trait?"

"Attends, impl !Unpin ? Pourquoi n'est-ce pas impl Pin ?"

"Bien, Pin et Unpin ... ne sont pas opposés. Ce sont des choses totalement différentes. Attendez, alors qu'est-ce que cela signifie encore? Pourquoi s'appelle-t-il ainsi?"

"Que diable signifie !Unpin ? Pas ... l'autre, pas une épingle? Hrrrgh"

Pourtant, la seule façon dont cela a du sens dans ma tête est de remplacer «détacher» par «relocalisable». «Le type ne peut pas être déplacé en mémoire» est parfaitement logique. Et pourtant, même en sachant ce que fait Pin , «le type n'est pas détaché» me laisse perplexe.

Renommer Unpin en Relocatable (ou Relocate ) obtient mon vote 🗳. Mais je trouverais à peu près toutes les autres suggestions meilleures que Unpin.

Le trait n'a de sens que par rapport à Pin, qui est le seul type que nous ayons qui exprime de manière significative des contraintes sur la mobilité de la valeur sous-jacente. Sans Pin, Unpin n'a aucun sens.

Pour mettre un point là-dessus - les garanties autour de la broche sont entièrement autour de certains comportements , pas de types . Par exemple, une fonction de générateur qui donne () pourrait implémenter de manière triviale FnOnce en reprenant à plusieurs reprises. Bien que ce type puisse ne pas implémenter Unpin - parce que ses états de rendement pourraient être auto-référentiels, son interface FnOnce (qui se déplace elle-même) est complètement sûre car elle n'a pas encore été épinglée lorsque vous FnOnce it, ce qui est nécessaire pour l'amener dans un état autoréférentiel. Unpin concerne spécifiquement les types de comportements qui sont déclarés sûrs une fois que le type a été épinglé (à savoir, le déplacement), et non une propriété intrinsèque du type.

Ironiquement, je suis juste venu ici pour commenter que, bien que la dénomination Unpin ait été définitivement débattue dans le passé, le débat à ce sujet dont je me souviens avoir été témoin a été lorsque nous avons choisi de remplacer Move par Unpin , ce qui, j'allais dire, est une amélioration sans ambiguïté . Et souvent, on ne se rend compte que plus tard, alors que la conception actuelle est une nette amélioration par rapport à ce qui était antérieur, il reste encore de la place pour une amélioration

Unpin concerne spécifiquement les types de comportements qui sont déclarés sûrs une fois que le type a été épinglé (à savoir, le déplacement), pas une propriété intrinsèque du type.

Le comportement lorsqu'il est épinglé ce billet de blog .

@RalfJung, nous nous parlons. Il y a une confusion Je vois beaucoup que les types autoréférentiels "ne peuvent pas être déplacés" - ce n'est pas exact, ils ont certains états dans lesquels ils peuvent entrer pendant lesquels ils ne peuvent pas être déplacés, mais tant qu'ils sont dans d'autres états, c'est parfaitement sûr pour les déplacer (et nos API s'appuient sur la possibilité de les déplacer, par exemple pour leur appliquer des combinateurs ou les déplacer dans un Pin<Box<>> ). J'essaie de préciser que ce n'est pas le cas que ces types «ne peuvent pas être déplacés».

Ah, oui, alors je suis d'accord. Un type !DePin n'est pas toujours épinglé , et s'il ne l'est pas, il peut être déplacé comme n'importe quel autre type.

@cramertj @withoutboats Une chose que je n'ai pas encore pu vérifier, êtes-vous contre le fait de renommer Unpin ? Il ne semble pas que vous ayez tous l'impression que vous convenez qu'il faut le renommer, mais je ne sais pas si vous êtes contre.

Personnellement, je ne pense pas que le principal problème ici réside dans le nom Unpin et que "si nous pouvions simplement le renommer, tout serait intuitif". Bien que le changement de nom puisse aider un peu (et Relocate / DePin semble bien ici ...), je pense que la principale complexité vient du concept d'épinglage lui-même; ce n'est certainement pas l'un des concepts les plus faciles à comprendre ou à expliquer; pas même presque.

Par conséquent, je pense que la documentation du module core::pin doit être renforcée de manière significative et que beaucoup plus d'exemples doivent être inclus. De bons exemples montreraient à la fois des cas d'utilisation canoniques ainsi que des utilisations des fonctions non sécurisées et des implémentations qui ne sont pas fiables.

@alexcrichton Je ne suis pas contre le changement de nom, non. Je pense que Unpin est déjà bien, mais je serais d'accord avec DePin , c'est pourquoi je l'ai suggéré. RemoveFromPin est le plus manifestement "correct", mais verbeux au point de sel syntaxique, donc je pense que je m'opposerais spécifiquement à ce nom. Cependant, je suis opposé à l'idée de reporter indéfiniment la stabilisation jusqu'à ce que nous trouvions un nom que tout le monde conviendra d'être le meilleur - je pense que l'API présente des complexités inhérentes qui ne seront pas considérablement améliorées ou pires à cause du nom du Unpin trait. J'aimerais vraiment pousser vers la stabilisation bientôt afin d'éviter plus de désabonnement au code, aux documents et aux discussions autour de futures_api (et ainsi nous pouvons commencer à mettre en place les éléments pour stabiliser futures_api lui-même). Peut-être devrions-nous planifier une réunion VC dédiée à la définition du nom afin que tous ceux qui ont une opinion puissent présenter leur solution et que nous puissions avoir une opportunité plus large pour régler cela?

Mon vote est std::pin::Reloc (déménager)

J'ai deux situations très hypothétiques (enfin, vraiment juste une, mais deux exécutions différentes) dont je voudrais bien avoir déclaré explicitement si cela est autorisé ou non.

Unpin indique qu'il est trivial de passer de tous les états de T (où T: Unpin ) dans l'état de type épinglé (j'espère que je me souviens correctement de ce terme de l'un des articles de blog de Pin peut distribuer &mut T .

Supposons donc que je veuille créer un type Woof pour lequel il est également toujours possible de passer de tous les états de Woof dans l'état de type épinglé au type non épinglé - état, mais ce n'est pas anodin de le faire et ne peut donc pas implémenter Unpin , serait-il permis pour Woof d'avoir une fonction comme fn unpin(self: Pin<Box<Woof>>) -> Box<Woof> ?

Idem pour un type Meow pour lequel il n'est que parfois possible de passer de épinglé à non épinglé et une fonction comme fn unpin(self: Pin<Box<Meow>>) -> Result<Box<Meow>, Pin<Box<Meow>>> .

Mes 2 cts pour le vélo:

Si je comprends bien, ce que Unpin signifie réellement est " Pin n'a aucun effet sur moi".

Et quelque chose comme BypassPin ou IgnorePin ?

Supposons donc que je veuille créer un type Woof pour lequel il est également toujours possible de passer de tous les états de Woof dans l'état de type épinglé à l'état de type non épinglé, mais ce n'est pas trivial de le faire et ne peut donc pas implémenter Unpin, serait-il permis à Woof d'avoir une fonction telle que fn unpin (self: Pin>) -> Boîte?

Oui, cela devrait être possible. Par exemple, si Woof est un élément d'une liste chaînée intrusive, il pourrait fournir une fonction pour supprimer l'élément de la liste et supprimer le Pin en même temps (en faisant valoir que pour non -enqueued Woof s, les deux types sont équivalents, donc nous pouvons le dé-pin).

Relocate a le même problème que RePin pour moi, cela pourrait impliquer une opération permettant de déplacer une valeur d'un emplacement épinglé à un autre, sans détacher complètement la valeur. C'est une connotation moins forte que celle de RePin , mais cela semble encore un peu déroutant.

cela pourrait impliquer une opération permettant de déplacer une valeur d'un emplacement épinglé à un autre, sans détacher complètement la valeur.

Bien que ce ne soit pas exactement de quoi il s'agit, ce n'est pas tout à fait faux non plus: pour la plupart des cas d'utilisation, déplacer une valeur d'un emplacement épinglé à un autre est tout aussi catastrophique que de l'utiliser librement. En fait, étant donné que n'importe qui peut créer un Pin<Box<T>> , je ne vois même pas pourquoi vous faites une distinction fondamentale ici.

Pour la plupart des cas d'utilisation, déplacer une valeur d'un emplacement épinglé à un autre est tout aussi catastrophique que de l'utiliser librement.

Oui, c'est pourquoi avoir un trait ajoutant cette opération à un type pourrait être utile (cela ne peut pas être un trait marqueur comme Unpin car il peut avoir besoin de faire des choses comme mettre à jour les références internes). Je ne propose pas d'ajouter quelque chose comme ça maintenant, juste que c'est quelque chose que je pourrais voir être fourni dans le futur (soit en std ou en tiers) qui pourrait finir par un chevauchement confus de noms.

Je ne connais pas de cas d'utilisation forts pour cela maintenant, mais j'ai pensé à utiliser quelque chose comme ça avec des collections épinglées.

Ok alors voici quelques réflexions. Au départ, je me demandais si nous pourrions avoir un nom comme PinInert ou PinNoGuarantees car c'est aussi ce que la description est, mais en pensant que ce que je veux vraiment, c'est un trait décrivant une action par opposition à un propriété, ou du moins, il est beaucoup plus facile de penser dans ma tête.

Un problème avec Unpin (je ne sais pas pourquoi) est que je n'arrive pas à comprendre que le sens voulu est "l'action de retirer d'une épingle, de désépingler, est sûre à faire ". Le trait tel quel exprime l'action "l'acte de désépingler" mais je n'arrive pas à comprendre cela quand je le lis. Cela fait bizarre d'avoir Pin<T: Unpin> parce que si c'est "détacher", pourquoi est-il dans une épingle?

Je me demande si un nom comme CanUnpin pourrait fonctionner? Une grande partie de cela ne concerne pas une garantie ferme d'une manière ou d'une autre (la mise en œuvre de Unpin ne signifie pas que vous la supprimerez d'une épingle, cela signifie simplement que vous pouvez la supprimer d'une épingle). Comment ça sonne? Est-ce que CanUnpin suffisamment lisible pour les autres? Assez court?

(avoir le préfixe Can me transmet également le verbe beaucoup plus facilement, et cela dit que vous pouvez le supprimer d'une épingle mais vous ne le faites pas toujours).


En tant qu'autre tangente non liée, une chose que j'ai oublié de mentionner plus tôt (désolé!) Est que je ne pense pas que toutes les méthodes ci-dessus devraient nécessairement être des méthodes inhérentes. Nous avons déjà eu des tas de bogues autour des méthodes d'inférence et de conflit, et l'ajout de noms très courants comme as_ref et as_mut sur les types qui implémentent également Deref semble être un problème en attente de se produire.

Ces opérations sont-elles assez fréquentes pour justifier l'emplacement à risque pour les placer? (intrinsèquement) Ou sont-ils utilisés assez rarement pour que l'itinéraire sûr des fonctions associées puisse être stomagé?

Depuis que j'ai apparemment un skin dans ce jeu maintenant: CanUnpin me semble très proche dans l'esprit de Unpinnable ou de quelque chose de similaire, et mon impression a toujours été que la communauté fronce les sourcils sur ces types de modificateurs dans noms de trait, puisque la plupart des traits décrivent l'action que le type peut entreprendre. L'impl est lui-même un "can" ou "-able" implicite dans ce sens. Quelle que soit la décision prise (et le nom n'a pas d'importance dans la mesure indiquée ici IM-not-so-HO - très peu d'utilisateurs devront probablement taper ceci), j'encourage tout le monde à ressentir une certaine urgence pour résoudre les questions ici. Je suis enthousiasmé par cette stabilisation et je sais que beaucoup de gens le sont!

@alexcrichton

Un problème avec Unpin (je ne sais pas pourquoi) est que je n'arrive pas à comprendre que le sens voulu est "l'action de retirer une épingle, de détacher, est sûre à faire ".

Et si vous pensez que T: Unpin comme " T est immunisé contre Pin "? Ensuite, si vous avez Pin<T: Unpin> cela signifie simplement que nous avons une valeur dans un Pin qui est immunisé contre l'épinglage, donc l'épinglage est ici effectivement inutile.

Ou en d'autres termes: Unpin neutralise Pin . Pour être honnête, après tant de discussions, j'ai en quelque sorte intériorisé cela et maintenant c'est logique. 😆

Une grande partie de cela ne concerne pas une garantie ferme d'une manière ou d'une autre (la mise en œuvre de Unpin ne signifie pas que vous la supprimerez d'une épingle, cela signifie simplement que vous _pouvez_ la supprimer d'une épingle).

Un contrepoint à cela est le trait Send . Cela ne signifie pas que vous enverrez la valeur, cela signifie simplement que vous pouvez l' envoyer.

@anp Je suis d'accord que CanUnpin n'est pas un grand nom, j'essaye de faire de mon mieux pour trouver un meilleur nom! Il semble que peu de gens pensent encore que cela doit être renommé, car toutes les suggestions semblent être rejetées pour les mêmes raisons que je pense que Unpin doit être renommé en premier lieu.

Aussi, pour rappel, toute stabilisation chevauche les trains comme tous les autres changements, et avec une sortie la semaine prochaine, cela ne fera

@stjepang Moi aussi, je Unpin maintenant après avoir tellement réfléchi! Tous les noms alternatifs à ce stade semblent assez terne, donc j'arrive au point où je me contenterais d'une meilleure documentation. Dans l'idéal, j'aimerais trouver quelqu'un qui ne sait pas grand-chose sur les apis Pin pour lire ensuite ladite documentation, pour vérifier qu'elle est suffisante pour apprendre.

J'aimerais également aller de l'avant et bloquer formellement la stabilisation sur une documentation améliorée, car cela semble particulièrement critique pour ce problème.

@rfcbot concerne l'amélioration-de-la-documentation

Concrètement, ce que j'estime avoir besoin d'une meilleure documentation est:

  • Plus de prose sur chaque méthode pour expliquer pourquoi elle est sûre et / ou pourquoi elle est dangereuse. Par exemple, le contrat de l'implémentation P de DerefMut "se comportant raisonnablement" n'est pas documenté sur Pin::new_unchecked .
  • Chaque fonction unsafe devrait avoir un exemple de code expliquant pourquoi elle n'est pas sûre, ou au moins une explication claire d'une séquence d'étapes qui peuvent mal tourner.
  • Je pense que la documentation du module peut aller plus en détail au-delà de ce qu'est un Pin et ce que cela signifie. Je pense qu'ils bénéficieraient d'informations telles que "comment ce n'est pas des types d'immeubles généraux mais une forme de cela" ou "comment Pin utilisé dans la pratique, par exemple avec des contrats à terme".
  • J'aimerais voir un exemple avec des génériques et Unpin dans la documentation ou dans les exemples de code. Par exemple, il semble que de nombreux combinateurs dans les futurs doivent gérer cela, et de même certains combinateurs l' exigent spécifiquement. Quelques exemples de cas d'utilisation de la façon dont on travaille avec des génériques et de leur fonctionnement peuvent aider à comprendre un peu Unpin .

Je suis également curieux de savoir si d'autres ont des éléments concrets à ajouter à la documentation!

Ensuite, j'aimerais aussi bloquer formellement la question self avec as_ref , mais je pense que cela sera rapide à résoudre. (encore une fois désolé de ne pas avoir pensé à en parler plus tôt)

@rfcbot concerne les auto-méthodes

Cela propose as_ref , as_mut , get_ref , get_mut , into_ref et set pour être toutes des méthodes sur le Pin type. La proposition mentionne que nous ne faisons pas cela pour les pointeurs intelligents en raison de conflits de méthodes, mais cite que l'expérience montre que l'API Pin mise en œuvre aujourd'hui n'est pas cohérente et qu'elle finit souvent par gêner.

Ces noms de méthodes, cependant, sont des noms très courts et doux et sont assez courants dans tout l'écosystème. L'équipe libs a rencontré une série interminable de bogues dans le passé où nous ajoutons des implémentations de traits et autres, et cela provoque des conflits avec les méthodes de traits existantes dans diverses caisses. Ces méthodes semblent particulièrement risquées en raison de leurs noms. De plus, il n'est pas clair si nous pourrions un jour ajouter plus de méthodes à Pin à l'avenir, car même si les noms finissent maintenant par travailler, tout nom futur ajouté présente un risque élevé de collision.

Je voudrais simplement être sûr que nous repensons cette divergence par rapport à la convention, je suis personnellement particulièrement inquiet des conséquences et je pense que ce serait formidable si nous pouvions trouver un terrain d'entente qui éviterait ces risques.

Et pendant que j'y suis, une dernière chose que j'ai remarquée, et encore une fois je pense que ce sera assez rapide à résoudre

@rfcbot concerne box-pinnned-vs-box-pin

Le nom Box::pin été considéré pour Box::pinned ? Avec l'existence de Pinned et / ou PhantomPinned il semble que ce serait bien si le nom pouvait correspondre au type de retour!

@alexcrichton Quelque chose en particulier vous a-t-il convaincu contre MoveFromPin en option ? Je vois que vous y étiez favorable plus tôt (et plusieurs personnes semblaient l'apprécier également). Pour mémoire, les objections directes dont je pourrais me souvenir, ou trouver rapidement par Ctrl + F-ing, sont que "les noms qui sont des phrases entières ... seraient très unidiomatiques" (@withoutboats) et que c'est "beaucoup trop verbeux" ( @cramertj).

Je dois admettre que les motivations dans la veine de «je me suis habitué à… maintenant après y avoir tellement réfléchi» me mettent plutôt mal à l'aise. Les humains peuvent s'habituer à et rationaliser post-hoc essentiellement n'importe quoi. Quel que soit le nom, ce serait une grande surprise si nous ne nous y évidemment des noms sous-optimaux comme existential type comme temporaires dans d'autres cas, pour éviter qu'ils ne deviennent permanents.)

Privilégier accidentellement les perspectives d'utilisateurs expérimentés (ce que nous sommes tous!) Par rapport à d'autres qui aborderont les choses avec des cerveaux plus frais est le genre d'erreur que je pense que nous devons être toujours vigilants contre les erreurs, aussi difficiles soient-elles .

Bien que ce ne soit certainement pas le style de la bibliothèque std, j'ai aussi l'impression que CanUnpin rend en quelque sorte plus clair comment cette pièce particulière s'articule avec les autres.

Il est impossible d'inventer un nom qui décrit la signification de Unpin à un débutant sans lire la documentation. Send et Sync sont également difficiles à comprendre pour un débutant, mais ils sont beaux et concis, ainsi que Unpin . Si vous ne savez pas ce que signifient ces noms, vous devez lire leurs documents.

@valff En effet, c'est correct, mais il y a pas mal de gens qui ont lu la documentation et comprennent comment l'épinglage fonctionne, mais le nom Unpin leur cause toujours des difficultés mentales importantes, alors que les autres noms en causent moins.

@glaebhoerl, désolé de préciser que je suis personnellement plus fan de MoveFromPin ou CanUnpin que le Unpin actuel. J'essaie juste d'aider à arriver à une conclusion!

J'ai essayé de comprendre la proposition actuelle, peut-être que ce type de rapport d'étape peut également apporter un éclairage supplémentaire sur la dénomination.

  • L'extension des garanties de Pin à une adresse stable jusqu'à ce que Drop impose des exigences supplémentaires au créateur d'un Pin . J'aurais trouvé utile si ces conditions préalables nécessaires pour invoquer unsafe fn new_unchecked(pointer: P) -> Pin<P> étaient mentionnées directement. Comprendre comment un Pin peut être créé aide énormément à comprendre leur concept, imo.
  • Le résumé mentionné dans le problème de suivi, précédé de we agreed that we are ready to stabilize them with no major API changes. , contient beaucoup d'anciennes API. En même temps, il contient également beaucoup d'informations sur Drop et la projection de broches qui sont pertinentes. Il contient également une formulation explicite de la garantie supplémentaire mentionnée au premier point qui ne se trouve pas dans ce rapport. Ce mélange d'informations obsolètes et récentes était un peu déroutant, pensez à déplacer toutes les informations dans ce numéro ou à indiquer des paragraphes obsolètes.
  • Étant donné que la garantie supplémentaire est appelée slight extension , je m'attendais à ce qu'il y ait une sorte de moyen de la retirer. Puisque l'épinglage porte une fois un état de type sur ce pointeur jusqu'à ce qu'il soit abandonné, un moyen de revenir de cet état de type à un état «normal». En fait, c'est ce que je pensais que Unpin faire au début, mais je pense que Unpin est en fait un peu plus fort. Il dit que l'état épinglé peut être librement transformé en état non épinglé, à volonté. C'est bien que l'interface actuelle soit minimale à cet égard, mais Unpin pourrait être confondue avec une opération sur le type qui supprime un invariant interne qui nécessite l'état de la broche (comme une version logicielle de Drop ) .

Clarification: Personnellement, je préfère MoveFromPin aux autres noms, mais c'est sans doute artificiel et maladroit.

Le principal élément concret et exploitable qui me vient à l'esprit, en plus des améliorations de la documentation déjà demandées par @alexcrichton , expliquerait le raisonnement derrière la règle de champ pour la projection d'épingle dans map_unchecked et map_unchecked_mut . Quelque chose du genre:

Une épingle promet de ne pas déplacer les données référées jusqu'à ce qu'elles soient supprimées. Puisque les champs d'une structure sont supprimés après leur structure contenant, leur état de broche s'étend au-delà de l'implémentation Drop des structures elles-mêmes. La projection vers un champ promet de ne pas en bouger dans le destructeur de structures.

Qu'en est-il du nom ElidePin pour le trait de marqueur dérivé automatiquement?

Il capturerait que le type peut toujours être traité comme s'il était ou se comporte comme s'il n'était pas épinglé. Et !ElidePin semble également être assez clair, bien que cela puisse être subjectif.

Le marqueur standard n'a pas non plus de nom parfait pour comprendre l'épinglage. Pinned semble évoquer l'idée que la structure contenant elle-même est épinglée mais que l'épinglage s'applique aux pointeurs et seulement après une action explicite. Ce n'est pas aussi critique mais pas insignifiant non plus, d'autant plus qu'il pourrait s'agir du type rencontré en premier dans une implémentation d'un type autoréférentiel.

Mon opposition générale à MoveFromUnpin / RemoveFromUnpin est simplement qu'ils sont beaucoup trop verbeux et régresseraient l'ergonomie des implémentations manuelles de Future . CanUnpin / DePin deux me semblent bien - j'aimerais qu'il soit plus clair que Unpin suit le modèle de Read / Write /etc. mais il semble que ce ne soit pas intuitif pour les gens, alors je vais +1 tout ce qui rend cela plus clair sans ressembler à du sel syntaxique.

Je suis d'accord que NotWhateverUnpinBecomes est probablement le meilleur nom pour Pinned . Cela dit, Adhere signifie à la fois tenir compte et respecter. : légèrement_smiling_face:

CanUnpin / DePin me semblent tous les deux bien - j'aimerais qu'il soit plus clair que Unpin suit le modèle de lecture / écriture / etc.

Je pense que l'une des choses qui rend Unpin difficile, contrairement à Read c'est que c'est un trait marqueur. Read est facile à comprendre car il existe une méthode Read::read - tout ce que vous devez savoir se trouve juste là dans le trait . Si x est Read Je comprends que je peux appeler x.read() - de même write pour Write , etc. Il est plus difficile d'expliquer que X implémenter Unpin signifie que Pin<Ptr<X>> implémente DerefMut - ce qui signifie que vous pouvez le traiter comme s'il s'agissait simplement de X .

Read est facile à comprendre car il existe une méthode Read :: read

Si seulement nous pouvions ajouter des méthodes toujours applicables dans les définitions auto trait + GAT, nous pourrions avoir Unpin::unpin - fn unpin<P: DerefFamily>(self: Pin<P::Deref<Self>>) -> P::Deref<Self> ). ..... après réflexion, je ne pense pas que cela rendra quiconque moins confus;)

(sur une note plus sérieuse, je soutiendrais Pin::unpin pour passer de Pin<P<T>> à P<T> )

Je soutiendrais Pin :: unpin pour passer de Pin

> à P

Cela confond deux termes dans ma tête, tout comme le nom actuel lui-même. unpin ressemble beaucoup à l'inversion des garanties de l'état de type, pour une comparaison qui serait comme s'il y avait une méthode fn unborrow(&'_ T) ou fn unmove(??) . Puisque pin fournit des garanties jusqu'à ce qu'une mémoire représentant un type soit Drop::drop ed, nous n'inversons pas vraiment l'état, le type garantit simplement que toutes les autres représentations maintiennent des garanties équivalentes et nous pouvons donc ignorer cela . C'est aussi la principale différence que je vois entre les traits marqueurs et quelque chose comme io::Read . L'un active les opérations pour le compilateur ou le langage, tandis que l'autre active les opérations pour le programmeur.

En outre, il est important de noter que cela ne peut actuellement pas être représenté avec précision en utilisant simplement un typage correct. Appeler l'opération unpin donne l'impression qu'il y avait une fonction inverse pin . Cette fonction indique également à tort qu'une telle opération de désépinglage est en quelque sorte liée au travail de calcul, par exemple into_pointer() serait meilleur à cet égard en suivant également une convention de dénomination plus établie.

Enfin, je pense qu'il existe un potentiel pour les types ayant spécifiquement une fonction unpin avec un travail de calcul. Un type avec des invariants internes très spéciaux pourrait être capable de `` réparer '' son état interne de telle manière qu'il puisse offrir une interface fn unpin(self: Pin<&'a mut T>) -> &'a mut , où il renonce volontairement à toutes les garanties de Pin pendant une certaine durée de vie 'a . Dans ce cas, les deux points ci-dessus ne s'appliquent plus. Une telle fonction pourrait être envisagée comme si elle avait un effet équivalent à la suppression et à la reconstruction dans le même emplacement mémoire (supprimant ainsi réellement l'état du type). Et cela peut impliquer des calculs, par exemple en déplaçant certaines auto-références dans des allocations dynamiques.

Il serait malheureux qu'un nom déroutant rende également plus difficile pour les concepteurs et les implémenteurs de bibliothèques de choisir des noms non confus, en confondant ces deux idées.

Mon opposition générale à MoveFromUnpin / RemoveFromUnpin est simplement qu'ils sont beaucoup trop verbeux et feraient régresser l'ergonomie des implémentations manuelles de Future.

Je ne pense pas que ce soit vrai, en particulier pour MoveFromPin , ce qui me semble raisonnable de taper - et avec toute sorte d'auto-complétion intelligente ou stupide, le problème est pratiquement inexistant de toute façon.

Une partie importante de l'ergonomie devrait également être la lisibilité et la compréhensibilité du code. Rust a déjà reçu quelques critiques dans le passé pour avoir abrégé les choses de manière agressive ( fn , mut , etc.), ce qui rend certains codes plus difficiles à lire. Pour des choses qui, pour un concept encore plus compliqué à appréhender et qui remplit un objectif de niche pour la plupart des utilisateurs, l'utilisation d'un nom plus verbeux et descriptif devrait être tout à fait acceptable.

@rfcbot résout la dénomination de la suppression

Ok, je mijote depuis un moment maintenant et j'ai également parlé avec @withoutboats en personne à ce sujet. J'ai appris que je suis maintenant personnellement à l'aise au moins avec le nom Unpin comme trait marqueur. En conséquence, je vais supprimer une objection de blocage (bien que si d'autres membres de l'équipe libs se sentent différemment, veuillez nous le faire savoir!)

La principale raison pour laquelle je suis arrivé à Unpin est essentiellement ce que @cramertj a déjà dit ci - Unpin vit (ou du moins à mon avis).

Bien que je pense toujours qu'il y a de meilleurs noms pour "Je viens de voir le nom de ce trait et je veux savoir ce qu'il fait", je ne pense pas que ce soit le bon problème à résoudre ici. Il est clair pour moi à ce stade que nous ne pouvons pas, pour le moment du moins, trouver un nom différent pour ce trait qui est également idiomatique, mais plutôt nous basculons sur des noms de trait qui résolvent un problème différent. Comprendre Unpin est comme Send et Sync , la documentation doit être lue pour comprendre pleinement ce qui se passe.


Comme autre point de clarification, j'ai énuméré quelques «objections de blocage», mais ce sont plus des éléments TODO que des objections de blocage en soi. Je n'ai tout simplement pas un excellent moyen de naviguer autrement sur un fil aussi long! Dans cette veine, je pense que c'est bien pour un PR de stabilisation d'être affiché à tout moment maintenant avec FCP expiré depuis une semaine ou deux maintenant. Les derniers points sur soi / épinglé / épinglé peuvent y être brièvement discutés (si même nécessaire, ils pourraient être laissés comme proposition ci-dessus).

La documentation, je pense surtout, n'est pas une condition préalable à la stabilisation dans ce cas. Nous avons un cycle complet (6 semaines) pour ajouter des documents avant que ces types ne deviennent stables et nous avons encore plus de temps pour consolider la documentation ici avant que l'histoire complète d'async / await ne soit stable. C'est beaucoup de temps pour améliorer ce qui existe maintenant, et ce qui existe maintenant est déjà très utile!

Que signifie même «idiomatique» ici? Qu'est-ce qu'unidiomatique à propos de Reloc[ate] , Escape , Evade , Pluck ou Roam ? Ce sont tous des verbes, et aucun d'entre eux ne peut être confondu avec le problème du double négatif.

@alexcrichton Y a-t-il une raison pour laquelle Unpin est considéré comme plus idiomatique que Depin ou DePin ?

Je pense que Depin est une alternative très solide, et cela ne me cause pas les mêmes difficultés mentales que Unpin .

Une raison simple pourrait être que depin n'est pas un mot et que le désépinglage l'est. Personnellement, je n'ai aucun problème à comprendre Unpin . Je pense que cela correspond à la dénomination des autres traits marqueurs.

@jimmycuadra Il y a beaucoup de noms dans Rust qui ne sont pas de «vrais» mots, y compris dans la stdlib.

Je serais surpris si cela était considéré comme une raison importante pour choisir un nom plutôt qu'un autre.

@Pauan C'est une raison importante. En tant que locuteur natif anglais, Depin me semble comme si nous avions oublié que le mot pour détacher existe et avons essayé d'en inventer un. Cela me semble tout à fait erroné sur le plan grammatical: le mot anglais pour «depinning» est «unpinning».

Une bonne analogie serait si nous avions des API qui disaient "delock" au lieu de "unlock".

@jaredr par "idiomatique" Je veux dire suivre les conventions établies de la bibliothèque standard déjà comme les traits Read et Write (mentionnés précédemment). Nous avons une convention de noms non verbeux, de verbes, courts si possible et appropriés à la situation.

Les noms comme vous l'avez suggéré sont tous possibles, mais Unpin (ou du moins je pense) est le plus approprié pour cette action particulière (garantie "vous pouvez détacher ce type"). Les autres, bien que les synonymes de Unpin , sont en grande partie seulement renommés loin du mot "unpin" et parfois ne transmettent pas le même lien avec Pin que Unpin Est-ce que.

@Pauan Je suis d'accord avec @withoutboats que Depin ne sonne pas aussi bien que Unpin .

@aturon a noté dans https://github.com/rust-lang/rfcs/pull/2592#issuecomment -438873636 que:

L'utilisation de Pin n'est pas plus un détail d'implémentation que le choix de &self contre &mut self ; à long terme, il est probable que Pin sera lui-même un mode de référence séparé avec prise en charge du langage. Dans tous ces cas, ce qui est véhiculé dans la signature, ce sont les permissions accordées à l'appelé, avec Pin interdisant de sortir de la référence.

Apparemment, cela fait référence à un type natif &pin ou quelque chose ... mais idk comment cela est carré avec Pin<&mut T> et autres. Dans mes conversations avec @withoutboats et en particulier @cramertj, ils n'étaient pas du tout sûrs de l'idée d'un mode de référence séparé avec prise en charge du langage et de la façon dont nous pourrions y arriver à partir de Pin<T> .

Avant de stabiliser pin , il serait prudent de réconcilier ces vues pour s'assurer que nous sommes sur la même page. cet élément essentiel de l'infrastructure; alors j'aimerais surtout qu'Aaron développe cela et que les bateaux et Taylor interviennent ensuite.

Les autres, bien que des synonymes lâches de Unpin, ne renvoient en grande partie que le mot "unpin" et parfois ne transmettent pas le même lien avec Pin que Unpin.

@alexcrichton c'est en fait une bonne chose, je pense? Comme Send pour déplacer / copier (=) l'opération, Sync pour emprunter (&) l'opération. Peut-être qu'une telle connexion cause-t-elle plus de confusion?

@ crlf0710 peut-être! Je ne suis pas sûr que je serais d'accord avec une telle connexion par moi-même. Send et Sync sont plus à propos de ce qu'ils activent ("envoyer" des types à d'autres threads et "synchroniser" l'accès à travers les threads), et en les nommant, nous n'avons pas essayé de évitez de les nommer à proximité d'une opération ou d'une autre

@alexcrichton exactement! alors peut-être que ce trait devrait également concerner ce qu'il permet. ("en mouvement (Un verbe ici) sur un Pin ). Je ne suis pas de langue maternelle anglaise, mais je pense toujours que "débloquer" un pin est un peu ... bizarre?

@ crlf0710 Mais ce que le trait permet ce n'est pas <moving> partir d'un Pin, c'est <moving out of a Pin> . Le problème avec le mouvement et les synonymes de mouvement est qu'ils impliquent que le trait contrôle la capacité du type à se déplacer du tout, ce qu'il ne fait pas. La connexion à Pin est essentielle pour comprendre ce que fait réellement le trait.

Donc, le nom le plus idiomatique de ce trait serait un verbe qui signifie «sortir d'une épingle», pour lequel le mot «Détacher» est de loin le plus évident pour moi. Voici la définition du Wiktionnaire de "désépingler":

  1. A détacher en retirant une épingle.
  2. (transitif, informatique, interface utilisateur graphique) Pour détacher (une icône, une application, etc.) de l'endroit où il était précédemment épinglé.

@withoutboats merci d'avoir expliqué! En fait, je pense que je peux accepter ce nom Unpin ou n'importe quel nom que l'équipe de rust décide finalement, je ne veux pas du tout bloquer la stabilisation des types de broches, et je comprends parfaitement les préoccupations concernant le "déplacement", etc.

On a juste l'impression qu'il y a une très petite "répétition" quelque part. et je dois me persuader: cela ne veut pas dire «impénétrable», mais le contraire. Je suppose qu'après un certain temps, je m'y habituerai si c'est la décision finale.

En même temps, permettez-moi de faire ma dernière suggestion: en fait, je pense que le verbe Detach mentionné ci-dessus est également assez agréable, bien qu'un peu trop général. Comme je l'ai dit, je ne suis pas de langue maternelle, donc je ne peux pas parler pour les autres. Veuillez le voir comme une petite idée.

Je pensais à des projections épinglées disjointes sûres et j'ai eu l'idée qui pourrait le permettre. Il s'agit d' une macro pour simuler la correspondance avec des valeurs lmutables épinglées. Avec cette macro, nous pouvons écrire

pin_proj! { let MyStruct { field1, field2 } = a_pinned_mutable_reference; }

pour décomposer un Pin<&mut MyStruct> en références mutables épinglées aux champs.

Pour rendre cela sûr et utilisable, nous aurons besoin de deux autres choses:

  • Le type Kite pour marquer les champs "toujours- Unpin "
  • Rendre Unpin dangereux

Ce dernier est nécessaire pour la sécurité de la projection. Sans cela, nous pouvons définir " Kite avec des projections épinglées" en toute sécurité, ce qui est en fait faux.

Dans cet esprit, je propose de rendre Unpin dangereux avant la stabilisation pour laisser une pièce pour que d'autres opérations utiles soient en sécurité.

@qnighy Il est possible pour un type de violer les garanties d'épinglage dans son implémentation Drop , ce qui rend Drop équivalent à Unpin . Rendre Unpin unsafe ne rend aucun code supplémentaire sûr, car Drop est également sûr, et cela ne peut pas être changé. Nous en avons beaucoup parlé sur le problème du suivi, et cela a également été mentionné sur ce fil.

Fondamentalement, les projections de broches ne peuvent pas être sécurisées sans la capacité d'affirmer certaines limites négatives qui ne peuvent pas être exprimées dans Rust aujourd'hui.

@ crlf0710

En même temps, permettez-moi de faire ma dernière suggestion: en fait, je pense que le verbe Detach mentionné ci-dessus est également assez agréable, bien qu'un peu trop général. Comme je l'ai dit, je ne suis pas de langue maternelle, donc je ne peux pas parler pour les autres. Veuillez le voir comme une petite idée.

Detach semble beaucoup mieux que des noms comme Move et Relocate, mais cela semble entrer en conflit avec d'autres utilisations possibles de la métaphore de détachement (semblable à la façon dont Escape pourrait entrer en conflit avec «l'analyse d'échappement», etc.). Il n'y a que tellement de métaphores que nous avons sur la façon dont les données peuvent être liées à d'autres données en informatique, ce qui me fait penser à un autre nouvel avantage de Unpin: en s'attachant étroitement à la métaphore "pin", il n'occupe pas de place pour le futur langage métaphorique nous pouvons avoir besoin d'utiliser à des fins différentes, comme le pourraient des noms comme Detach ou Escape ou Relocate.

@withoutboats Je n'ai aucune préférence particulière pour aucun nom ou beaucoup d'investissement dans ce vélo ... mais je suis curieux; à quel autre but serait Detach (si vous spéculez ...)?

@Centril Je n'ai pas de cas d'utilisation clair en tête, mais je pourrais imaginer un cas d'utilisation lié à la déstructuration par exemple.

@withoutboats Ouais, c'est logique; à votre santé!

@withoutboats Je ne sais pas si l'entrée du Wiktionnaire est la meilleure motivation pour justifier le nom de Unpin pour activer move from a pin . Les métaphores de la représentation physique souffrent du fait que se déplacer dans Rust n'emporte pas un objet dans le monde. Pour être plus précis, «remonter» un pointeur épinglé ne me donne pas le contrôle sur la mémoire référencée, son allocation et sa validité en tant que représentation objet de T sont toujours requises et garanties par la sémantique du pointeur. Par conséquent, le désépinglage ne donne pas une représentation entièrement contrôlée de T , comme le ferait le déballage. Et, une entrée de dictionnaire définissant unpinning en termes de moving fait en fait partie de la confusion plus que la justification d'un tel nom.

Dans l'API proposée, aucune des méthodes n'a un tel effet (et ce n'est pas non plus dans le champ de pin). Je ne vois pas de moyen sûr, par exemple, de passer Pin<Box<T>> à Box<T> (et par extension à T ) et je ne suis pas non plus sûr que Unpin devrait ont de telles possibilités. Je ne sais pas exactement où serait toute la différence. Mais pour autant que je sache, Pin s'applique à certains emplacements de mémoire tandis que Unpin garantit que rien dans la représentation de T repose sur des garanties de broches. Cependant, serait-ce la même chose que de pouvoir retirer et oublier complètement la mémoire? Je crois que non. Je vais penser à un exemple plus concret.

Edit: Plus concrètement, même si T vaut Unpin , puis-je compter sur une instance de T::drop(&mut) appelée sur la mémoire qui a été épinglée, avant que cette mémoire ne soit libérée? Autant que je sache d'après le formalisme, la réponse devrait être oui, mais le nom Unpin me communique le contraire.

Edit 2: Rc permet d'observer Pin<&T> mais pour T: Unpin il n'a toujours pas drop appelé sur l'emplacement mémoire d'origine. Gardez une référence vivante en dehors de la broche, puis après la chute de la broche, vous pouvez sortir avec Rc::try_unwrap . https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca Cela répond efficacement à la question à travers les mécanismes existants mais fonctionne-t-il comme prévu?

Peut-être IgnorePin ? Si T vaut IgnorePin vous pouvez traiter Pin<Box<T>> comme &mut T ignorant essentiellement la présence de Pin (AIUI ignorant également le Box ). Ignorer est un verbe, je suppose que IgnorePin ne l'est pas mais je ne suis pas sûr de ce que c'est. IgnorePin est descriptif de ce qu'il permet, il ne décrit pas les contraintes placées sur le type, mais Unpin .

@wmanley J'avais une idée très similaire, ElidePin , dans un commentaire ci-dessus bien qu'à l'époque je n'avais pas pu exprimer concrètement pourquoi celle-ci me semblait plus précise. Mais je suis d'accord que «un verbe» est le guide de style pour les traits de marqueur Rust. Même s'il permet également une négation plus naturelle sous la forme de !ElidePin / !IgnorePin , ce n'est pas optimal.

@withoutboats Question de suivi: Puisque la broche semble spécifiée en termes de mémoire sous-jacente, comment la broche interagit-elle avec ZST? Étant donné que Pinned est un ZST, même un ZST peut être Unpin ou non. Je dirais intuitivement que sa représentation mémoire n'est jamais formellement invalidée, donc T::drop(&mut self) n'a jamais besoin d'être appelé, assez similaire à la façon dont on pourrait construire une broche à partir de &mut 'static _ mémoire par exemple à partir d'un Box divulgué

Est-il sûr de créer un Pin<P<T>> avec un T: !Unpin puis de détacher immédiatement la valeur, s'il est garanti que rien n'a observé l'épinglage? S'agit-il uniquement d'un comportement indéfini si la valeur épinglée est déplacée après qu'une méthode de type sondage a été appelée?

@HeroicKatora Malheureusement, il est aujourd'hui possible d'implémenter Drop pour un type qui pourrait être !Unpin raison de génériques, par exemple:

struct S<T>(T); // `!Unpin` if `T: !Unpin`
impl<T> Drop for S<T> { ... }

@cramertj Merci, je viens juste de

@cramertj Nous avons donc par défaut T: ?Unpin sur les limites génériques? Y a-t-il une autre raison de ne pas utiliser par défaut T: Unpin pour les types existants comme Sized ? Cela donnerait quelques exemples où cela serait extrêmement strict, mais cette limite automatique supplémentaire peut-elle entraîner des régressions?

@HeroicKatora Cela nécessiterait de saupoudrer des limites de ?Unpin sur chaque type de la bibliothèque standard et dans un tas de code qui ne se soucie pas du tout de Unpin .

Il est également sûr d'ajouter un champ PhantomPinned, comme autre moyen de créer un type Drop +! Unpin.

Y a-t-il une autre raison de ne pas utiliser par défaut T: Unpin pour les types existants?

Unpin est juste une caractéristique automatique comme Envoyer et Sync, cette proposition n'implique aucune nouvelle fonctionnalité de langage. Il a donc la même sémantique que les traits automatiques sont déjà définis, c'est-à-dire qu'ils ne sont pas appliqués aux génériques par défaut (contrairement à Sized, qui est un trait intégré au compilateur, pas un trait automatique).

S'agit-il uniquement d'un comportement indéfini si la valeur épinglée est déplacée après qu'une méthode de type sondage a été appelée?

Nous devons être clairs ici que le comportement indéfini dans ce contexte n'est pas vraiment le type traditionnel d'UB. Au contraire, c'est ce code, en observant un type dans un Pin, peut faire des hypothèses sur la sémantique de propriété de cette valeur (à savoir, que s'il n'implémente pas Unpin, il ne sera pas déplacé à nouveau ou invalidé jusqu'à ce que son destructeur ait exécuté). Ce n'est pas UB dans le sens où le compilateur suppose que cela n'arrivera jamais (le compilateur ne sait pas ce qu'est un Pin).

Alors oui, dans le sens où vous pouvez vérifier qu'aucun code ne repose sur la garantie que vous avez fournie. Mais aussi ce que vous avez fait est certainement inutile, car aucun code n'a pu observer que le type était épinglé.

@cramertj Je vois, ce serait un peu malheureux. Je suis un peu mal à l'aise avec l'interaction Pin avec Drop dans la spécification mais pas dans la langue. Tout à fait en conflit entre l'exactitude de l'interface, la convivialité et sa sortie dans un proche avenir. Puis-je obtenir des précisions sur la modification 2 ci-dessus:

Rc permet d'observer Pin <& T> mais pour T: Unpin n'a toujours pas appelé drop sur l'emplacement mémoire d'origine. Gardez une référence vivante en dehors de la broche, puis après la chute de la broche, vous pouvez sortir avec Rc :: try_unwrap. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca

@HeroicKatora le problème avec votre code est que Mem<'a>: Unpin , mais la ligne de code non sécurisé que vous utilisez repose sur des hypothèses qui ne s'appliquent qu'aux types qui n'implémentent pas Unpin . J'ai modifié votre résumé pour inclure la preuve que Mem<'a>: Unpin : https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=201d1ae382f590be8c5cac13af279aff

Vous comptez sur la borne Unpin lorsque vous appelez Pin::new , qui ne peut être appelée que sur des pointeurs dont la cible implémente Unpin.

La faille que vous pensiez avoir trouvée a été prise en compte, c'est pourquoi il n'y a aucun moyen de passer d'un Rc<T: ?Unpin> à un Pin<Rc<T>> . Vous devez construire le Rc dans la broche avec Rc::pinned .

@withoutboats Je voulais juste que cela soit confirmé, que T: Unpin opte en effet également drop appel

fn into_inner(Pin<P>) -> P where P: Deref, P::Target: Unpin;

car il ne protège aucune partie de l'interface, et décompresser un Pin<Box<T>> en Box<T> est potentiellement utile (pour T: Unpin bien sûr).

@HeroicKatora vous avez raison de dire que cette fonction serait sans danger. Cela ne me dérange pas de l'ajouter mais j'aimerais éviter d'étendre l'API que nous stabilisons en ce moment , car ce fil contient déjà des centaines de commentaires. Nous pouvons toujours l'ajouter plus tard au besoin, comme nous le faisons avec toutes les API std.

Je dirais simplement que la dénomination et la fonctionnalité de Unpin beaucoup plus de sens avec cette méthode inverse. Et sachant que les garanties Pin sont en effet limitées à T: !Unpin . Cela serait aussi directement démontré par l'existence d'une telle méthode, au lieu de construire des trappes d'échappement pour montrer la possibilité: smile:. Et sa documentation serait un endroit parfait pour expliquer (ou relier une fois de plus) les restrictions. (On pourrait même envisager de le nommer unpin au lieu de into_inner ).

Edit: Cela ne ferait surtout que généraliser ce qui existe déjà.

impl<P> Pin<P> where P: Deref, P::Target: Unpin

  • fn unpin(self) -> P

a l'instanciation pour P=&'a mut T , ce qui équivaut à la proposition

impl<'a, T: ?Sized> Pin<&'a mut T> where T: Unpin

  • fn get_mut(self) -> &'a mut T

Maintenant que la stabilisation est passée, le point du PFCP semble sans objet, donc donc:

@rfcbot annuler

Existe-t-il un suivi pour s'assurer que les commentaires développés à partir de https://github.com/rust-lang/rust/issues/55766#issuecomment-437374982 sont transformés en documents? Il semble que nous soyons déjà trop tard pour la sortie, car la version bêta s'est ramifiée ... même si cela a été explicitement appelé ici avant la stabilisation: /

Y a-t-il une raison pour laquelle cela est toujours ouvert? @Centril vous avez fermé et rouvert ceci, était-ce délibéré?

@RalfJung J'ai essayé d'annuler le FCP mais il n'a pas écouté; @alexcrichton pouvez-vous annuler le FCP?

C'est stable, donc fermant.

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