Rust: [Estabilización] Pin API

Creado en 7 nov. 2018  ·  213Comentarios  ·  Fuente: rust-lang/rust

@rfcbot fcp fusionar
Nombre de la función: pin
Objetivo de estabilización: 1.32.0
Problema de seguimiento: # 49150
RFC relacionados: rust-lang / rfcs # 2349

Esta es una propuesta para estabilizar la función de biblioteca pin , haciendo que la "fijación"
API para manipular la memoria fija que se puede usar en estable.

(Intenté redactar esta propuesta como un "informe de estabilización" completo).

Característica estabilizada o API

[std|core]::pin::Pin

Esto estabiliza el tipo Pin en el submódulo pin de std / core . Pin es
un envoltorio fundamental y transparente alrededor de un tipo genérico P , que está destinado
para ser un tipo de puntero (por ejemplo, Pin<&mut T> y Pin<Box<T>> son ambos
constructos previstos válidos). El contenedor Pin modifica el puntero a "pin"
la memoria a la que se refiere en su lugar, evitando que el usuario mueva objetos
de ese recuerdo.

La forma habitual de utilizar el tipo Pin es construir una variante anclada de algunos
tipo de puntero de propiedad ( Box , Rc , etc.). La biblioteca estándar que posee punteros todos
proporcione un constructor pinned que devuelva esto. Entonces, para manipular el
valor en el interior, todos estos indicadores proporcionan una forma de degradar hacia Pin<&T>
y Pin<&mut T> . Los punteros fijados se pueden eliminar, devolviéndote &T , pero no
deref de forma mutante segura: esto solo es posible usando el get_mut inseguro
función.

Como resultado, cualquier persona que mute datos a través de un pin deberá mantener la
invariante que nunca se mueven fuera de esos datos. Esto permite que otro código
asumir con seguridad que los datos nunca se mueven, lo que permite que contengan (por
ejemplo) autorreferencias.

El tipo Pin tendrá estas API estabilizadas:

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

Implicaciones de rasgos

La mayoría de los rasgos implícitos en Pin son bastante rutinarios, estos dos son importantes para
su funcionamiento:

  • 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 es un rasgo automático seguro que opta por las garantías de fijar. Si el
objetivo de un puntero anclado implementa Unpin , es seguro cambiar
desreferencia a él. Unpin tipos
ser movido de un Pin .

Esto hace que sea tan ergonómico tratar con una referencia fijada a algo que
no contiene autorreferencias como sería tratar con un
referencia. Las garantías de Pin solo importan para tipos de casos especiales como
Estructuras autorreferenciales: esos tipos no implementan Unpin , por lo que tienen
las restricciones del tipo Pin .

Implementaciones notables de Unpin en 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>

Estos codifican la noción de que la fijación no es transitiva a través de punteros. Ese
es decir, un Pin<&T> solo fija el bloque de memoria real representado por T en un
sitio. En ocasiones, los usuarios se han sentido confundidos por esto y esperaban que un tipo
como Pin<&mut Box<T>> fija los datos de T en su lugar, pero solo fija los
memoria la referencia anclada en realidad se refiere: en este caso, los Box
representación, que es un puntero en el montón.

std::marker::Pinned

El tipo Pinned es un ZST que no implementa Unpin ; te permite
suprimir la implementación automática de Unpin en estable, donde !Unpin impls
todavía no sería estable.

Constructores de punteros inteligentes

Los constructores se agregan a los punteros inteligentes estándar para crear referencias ancladas:

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

Notas sobre fijación y seguridad

Durante los últimos 9 meses, las API de fijación han pasado por varias iteraciones como
Hemos investigado su poder expresivo y también la solidez de su
Garantías. Ahora diría con confianza que las API de fijación se estabilizaron aquí
son sólidos y lo suficientemente cercanos a los máximos locales en ergonomía y
expresividad es decir, listo para la estabilización.

Uno de los problemas más complicados de fijar es determinar cuándo es seguro realizar
una proyección de pin : es decir, pasar de un Pin<P<Target = Foo>> a un
Pin<P<Target = Bar>> , donde Bar es un campo de Foo . Afortunadamente tenemos
haber podido codificar un conjunto de reglas que pueden ayudar a los usuarios a determinar si tal
la proyección es segura:

  1. Solo es seguro anclar el proyecto si (Foo: Unpin) implies (Bar: Unpin) : that
    es decir, si nunca es el caso de que Foo (el tipo que lo contiene) sea Unpin mientras
    Bar (el tipo proyectado) no es Unpin .
  2. Solo es seguro si Bar nunca se mueve durante la destrucción de Foo ,
    lo que significa que Foo no tiene destructor, o el destructor se
    verificado para asegurarse de que nunca se mueva fuera del campo al que se proyecta.
  3. Solo es seguro si Foo (el tipo que lo contiene) no es repr(packed) ,
    porque esto hace que los campos se muevan para realinearlos.

Además, las API estándar no proporcionan una forma segura de anclar objetos a la pila.
Esto se debe a que no hay forma de implementar eso de forma segura utilizando una función API.
Sin embargo, los usuarios pueden anclar cosas a la pila de forma insegura garantizando que
Nunca vuelva a mover el objeto después de crear la referencia anclada.

La caja pin-utils en crates.io contiene macros para ayudar con ambas pilas
Pinning y proyección de pin. La macro de fijación de pila fija objetos de forma segura al
apilar utilizando un truco que implica el sombreado, mientras que existe una macro para la proyección
lo cual no es seguro, pero evita tener que escribir el texto estándar de la proyección en
que posiblemente podría introducir otro código inseguro incorrecto.

Cambios de implementación antes de la estabilización

  • [] Exportar Unpin del preludio, eliminar pin::Unpin reexportar

Como regla general, no reexportamos cosas de varios lugares en std a menos que
uno es un supermódulo de la definición real (por ejemplo, acortamiento
std::collections::hash_map::HashMap a std::collections::HashMap ). Para esto
motivo, la reexportación de std::marker::Unpin de std::pin::Unpin está fuera de
sitio.

Al mismo tiempo, se incluyen otras características importantes de los marcadores, como Enviar y Sincronizar.
en el preludio. Entonces, en lugar de reexportar Unpin del módulo pin , por
poniendo en el preludio hacemos innecesario importar std::marker::Unpin ,
por la misma razón que se puso en pin .

  • [] Cambiar funciones asociadas a métodos

Actualmente, muchas de las funciones asociadas de Pin no usan sintaxis de método.
En teoría, esto es para evitar entrar en conflicto con métodos internos despectivos. Sin embargo,
esta regla no se ha aplicado de forma coherente, y en nuestra experiencia se ha
solo hizo las cosas más incómodas. Los punteros fijados solo implementan inmutables
deref, no mutable deref o deref por valor, lo que limita la capacidad de deref
de todas formas. Además, muchos de estos nombres son bastante únicos (por ejemplo, map_unchecked )
y es poco probable que entre en conflicto.

En su lugar, preferimos dar a los métodos Pin su debida precedencia; usuarios que
necesidad de acceder a un método interior siempre puede usar UFCS, tal como lo sería
necesario para acceder a los métodos Pin si no usamos la sintaxis del método.

  • [] Cambiar el nombre de get_mut_unchecked a get_unchecked_mut

El orden actual es inconsistente con otros usos en la biblioteca estándar.

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

Esta implícita no está justificada por nuestra justificación estándar para las impls de desanclaje: no hay dirección de puntero entre Pin<P> y P . Su utilidad está cubierta por las implicaciones para los punteros.

Esta implicación de futuros deberá cambiar para agregar un límite de P: Unpin .

  • [] Marque Pin como repr(transparent)

El alfiler debe ser un envoltorio transparente alrededor del puntero dentro de él, con la misma representación.

Funciones conectadas e hitos más importantes

Las API de pin son importantes para manipular de forma segura secciones de memoria que pueden
estar garantizado que no se moverá. Si los objetos en esa memoria no
implementar Unpin , su dirección nunca cambiará. Esto es necesario para
creando generadores autorreferenciales y funciones asincrónicas. Como resultado,
el tipo Pin aparece en la biblioteca estándar future API y pronto
también aparecen en las API para generadores (# 55704).

Estabilizar el tipo Pin y sus API es un precursor necesario para estabilizar
las API Future , que es en sí misma un precursor necesario para estabilizar
async/await sintaxis y mover todo el ecosistema futures 0.3 async IO
sobre el óxido estable.

cc @cramertj @RalfJung

T-libs finished-final-comment-period

Comentario más útil

¿Alguien tiene ganas de escribir un capítulo de nomicon sobre futuros? Parece increíblemente necesario dado lo sutil que es esto. En particular, creo que los ejemplos, especialmente los ejemplos de implementaciones defectuosas / malas y cómo no son sólidas, serían esclarecedores.

Todos 213 comentarios

@rfcbot fcp fusionar

El miembro del equipo @withoutboats ha propuesto fusionar esto. El siguiente paso lo revisan el resto de los miembros del equipo etiquetados:

  • [x] @Kimundi
  • [] @SimonSapin
  • [x] @alexcrichton
  • [x] @dtolnay
  • [] @sfackler
  • [x] @withoutboats

Preocupaciones:

  • box-pinnned-vs-box-pin (https://github.com/rust-lang/rust/issues/55766#issuecomment-443371976)
  • mejorando-la-documentación (https://github.com/rust-lang/rust/issues/55766#issuecomment-443367737)
  • naming-of-Unpin resuelto por https://github.com/rust-lang/rust/issues/55766#issuecomment -445074980
  • métodos propios (https://github.com/rust-lang/rust/issues/55766#issuecomment-443368794)

Una vez que la mayoría de los revisores aprueben (y como máximo 2 aprobaciones estén pendientes), entrará en su período de comentarios final. Si detecta un problema importante que no se ha planteado en ningún momento de este proceso, ¡hable!

Consulte este documento para obtener información sobre los comandos que pueden darme los miembros del equipo etiquetados.

¡Gracias por el informe detallado aquí @withoutboats! También me han confundido históricamente las diversas garantías de Pin , y actualmente tengo algunas preguntas sobre las API que se estabilizan aquí con las garantías de seguridad. Para ayudar a resolver esto en mi propia cabeza, pensé que intentaría escribir estas cosas.

Al tratar de empezar a escribir esto, sigo chocando contra una pared de "¿qué es Unpin ?" Estoy un poco confundido por lo que es y las diversas garantías que lo rodean. ¿Puede volver a decir lo que significa para T y también para no implementar Unpin ? Además, si Unpin es un rasgo seguro para implementarlo ingenuamente, parece que podría usarse para socavar fácilmente las garantías inseguras de Pin<T> , pero seguramente me estoy perdiendo algo.

si tengo esto correcto, todo tipo que no sea autorreferencial (es decir, no un generador) es Unpin

No se trata solo de autorreferencialidad, hay otros casos de uso para direcciones de memoria estables que Pin también puede admitir. Sin embargo, son relativamente pocos y distantes entre sí.

Lo que entiendo que Unpin es seguro de implementar es que al implementarlo puede violar un invariante requerido de otro código inseguro que haya escrito (lo que es crucial, solo usted puede escribir este código inseguro, ningún código externo puede depender de si ha implementado Unpin ). No hay nada que pueda hacer con la API segura de Pin que cause problemas, haya implementado o no Unpin . Al optar por usar algunas de las API inseguras de Pin , está garantizando que solo implementará Unpin cuando sea seguro hacerlo. Eso está cubierto por el punto 1 de la sección anterior "Notas sobre fijación y seguridad".

Hm todavía no entiendo realmente Unpin . Al principio, estoy tratando de entender qué significa implementar o no impulsar Unpin .

En primer lugar, probablemente sea útil saber qué tipos implementan Unpin automáticamente. Se mencionó anteriormente que los tipos de punteros comunes (Arc / Rc / Box / referencias) implementan Unpin , pero creo que eso es todo. Si se trata de una característica automática, ¿significa eso que un tipo MyType implementa automáticamente Unpin si solo contiene punteros? ¿O ningún otro tipo implementa automáticamente Unpin ?

Sigo tratando de resumir o indicar lo que Unpin garantiza y demás, pero me resulta muy difícil hacerlo. ¿Alguien puede ayudar reiterando nuevamente lo que significa implementar Unpin así como lo que significa no implementar Unpin ?

Creo que entiendo las garantías de Pin<P> donde no puede mudarse de ninguno de los miembros en línea de P::Target , pero ¿es así?

@alexcrichton Gracias por las preguntas, estoy seguro de que las API de fijación pueden ser un poco confusas para las personas que no han formado parte del grupo que se centra en ellas.

En primer lugar, probablemente sea útil saber qué tipos implementan Desanclar automáticamente.

Desanclar es un rasgo automático como Enviar o Sincronizar, por lo que la mayoría de los tipos lo implementan automáticamente. Los generadores y los tipos de funciones asíncronas son !Unpin . Los tipos que podrían contener un generador o un cuerpo de función asíncrona (en otras palabras, tipos con parámetros de tipo) tampoco son automáticamente Unpin menos que sus parámetros de tipo lo sean.

Las implicaciones explícitas para los tipos de puntero son hacer que Unpin incluso si sus parámetros de tipo no lo son. Es de esperar que la razón de esto sea más clara al final de este comentario.

¿Alguien puede ayudar reiterando de nuevo lo que significa implementar Unpin, así como lo que significa no implementar Unpin?

Aquí está el tipo de idea fundamental de las API de anclaje. Primero, dado un tipo de puntero P , Pin<P> actúa como P excepto que a menos que Pin tiene que mantener:

  • Si obtiene un &mut P::Target de un Pin<P> , nunca debe mover P::Target .
  • Si puede construir un Pin<P> , debe garantizarse que nunca podrá obtener un puntero no fijado a los datos a los que apunta el puntero hasta que se ejecute el destructor.

La implicación de todo esto es que si construye un Pin<P> , el valor señalado por P nunca se moverá de nuevo, que es la garantía que necesitamos para estructuras autorreferenciales y intrusivas. colecciones. Pero puede optar por no participar en esta garantía simplemente implementando Unpin para su tipo.

Entonces, si implementa Unpin para un tipo, está diciendo que el tipo opta por no tener garantías de seguridad adicionales de Pin ; es posible desreferenciar de manera mutante los punteros que lo apuntan. Esto significa que está diciendo que el tipo no necesita ser inamovible para usarse de manera segura.

Mover un tipo de puntero como Rc<T> no mueve T porque T está detrás de un puntero. De manera similar, fijar un puntero a un Rc<T> (como en Pin<Box<Rc<T>> ) en realidad no fija T , solo fija ese puntero en particular. Es por eso que cualquier cosa que mantenga sus genéricos detrás de un puntero puede implementar Unpin incluso cuando sus genéricos no lo hagan.

Además, si Unpin es un rasgo seguro para implementarlo ingenuamente, parece que podría usarse para socavar fácilmente las garantías inseguras de Pin, pero seguramente me estoy perdiendo algo

Esta fue una de las partes más complicadas de la API de fijación, y al principio nos equivocamos.

Desanclar significa "incluso una vez que se ha puesto algo en un pin, es seguro obtener una referencia mutable". Hay otro rasgo que existe hoy que le da el mismo acceso: Drop . Entonces, lo que descubrimos fue que, dado que Drop es seguro, Unpin también debe ser seguro. ¿Esto socava todo el sistema? No exactamente.

Implementar realmente un tipo autorreferencial requeriría un código inseguro; en la práctica, los únicos tipos autorreferenciales que le interesan a alguien son los que genera el compilador: generadores y máquinas de estado de función asíncrona. Éstos dicen explícitamente que no implementan Unpin y que no tienen una implementación Drop , por lo que ya sabe, para estos tipos, una vez que tenga un Pin<&mut T> , lo harán en realidad, nunca obtienen una referencia mutable, porque son un tipo anónimo que sabemos que no implementa Desanclar o Soltar.

El problema surge una vez que tiene una estructura que contiene uno de estos tipos anónimos, como un futuro combinador. Para pasar de Pin<&mut Fuse<Fut>> a Pin<&mut Fut> , debe realizar una "proyección de pin". Aquí es donde puede tener problemas: si fija el proyecto al campo futuro de un combinador, pero luego implementa Drop para el combinador, puede salir de un campo que se supone que debe estar fijado.

Por esta razón, la proyección de pines no es segura. Para realizar una proyección de pin sin violar los invariantes de pinning, debe garantizar que nunca haga varias cosas, que enumeré en la propuesta de estabilización.

Entonces, tl; dr: Drop existe, por lo que Unpin debe ser seguro. Pero esto no arruina todo, solo significa que la proyección del pin es unsafe y cualquiera que quiera pinchar el proyecto debe mantener un conjunto de invariantes.

generadores y máquinas de estado de función asíncrona. Estos dicen explícitamente que no implementan Unpin y que no tienen una implementación de Drop, por lo que, para estos tipos, una vez que tenga un Pin <& mut T>, nunca obtendrán una referencia mutable, porque son un tipo anónimo que sabemos que no implementa Desanclar o Soltar.

¿No debería una máquina de estado asíncrona tener una implementación Drop ? Las cosas que están en la "pila" de la función asíncrona (que probablemente sea igual a los campos de la máquina de estado) deben destruirse cuando la función asíncrona se complete o se cancele. ¿O esto sucede de otra manera?

Supongo que lo que importa en este contexto es si existe un elemento impl Drop for Foo {…} , que ejecutaría código con &mut Foo que podría usar, por ejemplo, mem::replace para "exfiltrar" y mover el Foo Valor de

Esto no es lo mismo que "soltar pegamento", que se puede llamar mediante ptr::drop_in_place . Soltar pegamento para un tipo determinado Foo llamará Drop::drop si está implementado, luego llamará recursivamente al soltar pegamento para cada campo. Pero esas llamadas recursivas nunca involucran &mut Foo .

Además, aunque un generador (y, por lo tanto, una máquina de estado asíncrono) tiene pegamento de colocación personalizado, es solo para eliminar el conjunto correcto de campos en función del estado actual, promete nunca mover ninguno de los campos durante la colocación.

La terminología que utilizo (aunque no creo que haya ningún estándar): "soltar pegamento" es el recorrido recursivo de campos generado por el compilador, llamando a sus destructores; La "implementación de la gota" es una implementación del rasgo Drop , y el "destructor" es la combinación del pegamento de la gota y la implementación de la gota. El pegamento de gota nunca mueve nada, por lo que solo nos preocupan las implementaciones de gota.

¿Alguien tiene ganas de escribir un capítulo de nomicon sobre futuros? Parece increíblemente necesario dado lo sutil que es esto. En particular, creo que los ejemplos, especialmente los ejemplos de implementaciones defectuosas / malas y cómo no son sólidas, serían esclarecedores.

@Gankro Claro, puedes dejarme por eso.

¡Gracias a todos por la explicación!

Personalmente, soy muy nuevo en async Rust y las API Pin, pero he jugado un poco con él durante los últimos días (https://github.com/rust-lang-nursery/futures-rs/pull/1315 - donde Traté de explotar la fijación de colecciones intrusivas en código asincrónico).

Durante la experimentación, tuve algunas preocupaciones con estas API:

  • El concepto de fijación parece muy difícil de entender completamente, incluso si se sabe que se requieren direcciones de memoria estables. Algunos desafíos que enfrenté:

    • Mensajes de error como:

      > dentro de impl core::future::future::Future+futures_core::future::FusedFuture , el rasgo std::marker::Unpin no se implementa para std::marker::Pinned

      Este suena bastante recursivo y contradictorio (¿por qué algo que está anclado no debería estar anclado?)

    • ¿Cómo puedo acceder a campos mutables en mi método que toma un Pin como parámetro? Me tomó un poco de búsqueda, aprender sobre nuevos términos ( Pin proyección), tratar de copiar los métodos de pin-utils , hasta que encontré una solución que usa 2 métodos inseguros para hacer lo que necesitaba .

    • ¿Cómo puedo usar mi futuro en un método async y un caso select ? Resultó que necesitaba pin_mut!() para apilar futuros. Lo cual no es realmente una pila, lo que lo hace confuso.

    • ¿Por qué mi método drop() ahora vuelve a tomar &mut self , en lugar de Pin ?

    • En general, fue una sensación de usar aleatoriamente las API relacionadas con la fijación insegura con la esperanza de que las cosas se compilaran, lo que definitivamente no debería ser la situación ideal. Sospecho que otros podrían intentar hacer cosas similares.

  • El concepto se propaga mucho a lo largo del código base, lo que parece obligar a mucha gente a entender lo que son en realidad Pin y Unpin . Esto parece una fuga de detalles de implementación para algunos casos de uso más especiales.
  • Pin parece estar algo relacionado con la vida, pero también algo ortogonal. Básicamente, un Pin nos dice que podríamos volver a ver un objeto con exactamente la misma dirección hasta que se llame a drop() . Lo que le da una especie de vida virtual entre el alcance actual y 'static . Parece confuso tener 2 conceptos que están relacionados, pero aún completamente diferentes. Me preguntaba si se puede modelar con algo como 'pinned .

Mientras compilaba cosas, a menudo pensaba en C ++, donde se evitan la mayoría de estos problemas: los tipos se pueden declarar inamovibles eliminando el constructor de movimiento y el operador de asignación. Cuando un tipo no es movible, los tipos que contienen ese tipo tampoco lo son. Por lo tanto, la propiedad y los requisitos fluyen de forma nativa a través de la jerarquía de tipos y son verificados por el compilador, en lugar de reenviar la propiedad dentro de algunas llamadas (no todas, por ejemplo, no drop() ). Creo que esto es mucho más fácil de entender. Pero tal vez no sea aplicable a Rust, ya que Future s deben moverse a menudo antes de que algo comience a sondearlos. Pero por otro lado eso podría arreglarse con una nueva definición con ese rasgo, o separando movimiento y fase de votación.

En cuanto a Alex Unpin comentario: Estoy comprendiendo lentamente lo que significa Unpin , pero estoy de acuerdo en que es difícil de entender. Quizás otro nombre ayude, pero no puedo encontrar uno conciso en este momento. Algo a lo largo de ThingsInsideDontBreakIfObjectGetsMoved , DoesntRequireStableAddress , PinDoesntMatter , etc.

Lo que aún no he entendido por completo es por qué obtener &mut self de Pin<&mut self> no puede ser seguro para todos los tipos. Si Pin solo significa que la dirección del objeto en sí es estable, entonces esto debería ser cierto para la mayoría de los tipos típicos de Rust. Parece que hay otra preocupación integrada en Pin, que es que tampoco los tipos autorreferenciales dentro de él no se rompen. Para aquellos tipos, manipularlos después de tener un Pin siempre es inseguro. Pero creo que en la mayoría de los casos serán generados por el compilador, y los punteros inseguros o incluso sin procesar deberían estar bien. Para un futuro combinador manual que necesite reenviar llamadas fijadas a subcampos, obviamente sería necesario que haya llamadas inseguras para crear pines en los campos antes de sondearlos. Pero en ese caso, veo que la inseguridad está más relacionada con el lugar donde realmente importa (¿el pin sigue siendo válido?) Que con el acceso a otros campos para los que no importa en absoluto.

Otro pensamiento que tuve es si es posible obtener un sistema más simple si se aplican algunas limitaciones. Por ejemplo, si las cosas que requieren fijación solo se pueden usar dentro de métodos asincrónicos y no con futuros combinadores. Tal vez eso podría simplificar las cosas a uno de Pin o Unpin . Teniendo en cuenta que para muchos métodos de código async / await con soporte de selección / unión puede ser favorable para los combinadores, esta podría no ser la mayor pérdida. Pero realmente no he pensado lo suficiente en si esto realmente ayuda.

En el lado positivo: ¡Poder escribir código asincrónico / en espera con préstamos de "pila" es muy bueno y muy necesario! Y la capacidad de usar la fijación para otros casos de uso, como colecciones intrusivas, ayudará al rendimiento y a los objetivos como sistemas integrados o kernels. Así que tengo muchas ganas de encontrar una solución al respecto.

Información menor en el informe de estabilización:

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

Supongo que esto es en realidad un unsafe fn ?

@ Matthias247 no puede obtener & mut T de Pin<P<T>> si T no es Unpin porque entonces podría mem::swap mover T fuera del Pin, lo que anularía el propósito de fijar cosas.

Una cosa que me cuesta explicarme a mí mismo es qué hace que Future sea fundamentalmente diferente de otros rasgos, de modo que Pin debe ser parte de su API. Quiero decir, sé intuitivamente que es porque async / await requiere fijación, pero ¿dice eso algo específicamente sobre Futures que sea diferente de, digamos, iteradores?

¿Podría la encuesta tomar & mut self y solo implementar Future para Pin<P> tipos o tipos que son Unpin?

¿Cómo puedo acceder a campos mutables en mi método que toma un Pin como parámetro?

Esto me hace preguntarme si faltan algunos métodos en 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>
}

Estos podrían usarse en la macro pin_utils::unsafe_unpinned! .

Estoy tratando de averiguar el _por qué_ esta macro afirma que no es segura. Si somos !Unpin y tenemos un campo Unpin , ¿por qué no sería seguro proyectar en este campo?

La única situación en la que puedo ver que no es correcto es implementar un tipo !Unpin , llevar un puntero sin procesar a un campo Unpin propio (y confiar en que tenga una dirección estable / apuntando al misma instancia), luego adquiriendo un &mut en el mismo campo y pasándolo a una función externa. Sin embargo, esto parece caer en el mismo razonamiento de por qué implementar Unpin es seguro, al llevar un puntero sin procesar a un campo de !Unpin , está optando por no poder llamar a algunos de los seguros API.

Para hacer que la situación anterior sea más segura, podría haber un contenedor construido en Pinned para marcar que normalmente los campos Unpin de una estructura son en realidad !Unpin lugar de simplemente agregar Pinned a la estructura como un todo:

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 { ... }
}

Todo esto parece retrocompatible con la API actual, podría eliminar parte de la inseguridad de los combinadores futures-rs (por ejemplo, daría acceso seguro a campos adicionales como este ), pero no es necesario para los casos de uso actuales. Probablemente podríamos experimentar con algunas API como esta para implementar tipos !Unpin (como colecciones intrusivas) y agregarlos más adelante.

@ Nemo157 Esas funciones de mapa no son seguras porque podría mem::swap en el &mut T la función me pasa antes de devolver un &mut U . (bueno, el mutable es, el inmutable podría no ser inseguro)

EDITAR: Además, la macro pin-utils es diferente, unsafe_unpinned no tiene que ver con que el tipo de destino sea Unpin , es solo una "proyección no fijada" - una proyección a un &mut Field . Es seguro de usar siempre y cuando nunca ancle el proyecto a ese campo, incluso si el campo es !Unpin .

Una cosa que me cuesta explicarme a mí mismo es qué hace que Future sea fundamentalmente diferente de otros rasgos, de modo que Pin debe ser parte de su API. Quiero decir, sé intuitivamente que es porque async / await requiere fijación, pero ¿dice eso algo específicamente sobre Futures que sea diferente de, digamos, iteradores?

No hay diferencias teóricas, pero sí pragmáticas. Por un lado, Iterator es estable. Pero como resultado, a menos que descubramos algo muy inteligente, nunca podrá ejecutar un bucle for en un generador autorreferencial sin una doble indirección, aunque eso debería ser completamente seguro sin él ( porque el bucle for consumirá y nunca moverá el generador).

Otra diferencia pragmática importante es que los patrones de código entre Iterator y Future son bastante diferentes. No puede pasar 10 minutos sin querer pedir prestado a través de un punto de espera en un futuro, por lo que en futuros 0.1 ve estos Arc s apareciendo por todas partes para que pueda usar la misma variable en dos diferentes and_then llamadas. Pero hemos llegado bastante lejos sin ser capaz de expresar iteradores autorreferenciales en absoluto, simplemente no es tan importante de un caso de uso.

Esas funciones de mapa no son seguras porque podría mem::swap en el &mut T la función me pasa antes de devolver un &mut U

Ah, maldición, me olvidé de esa parte: fruncir el ceño:

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

¿Deberían llamarse as_pinned_mut y into_pinned_ref , para evitar confundirlos con métodos que realmente devuelven referencias?

Implementaciones notables de Unpin en std:

También tenemos impl<P> Unpin for Pin<P> {} . Para los tipos que usamos, esto no tiene ningún efecto y parece un poco más seguro.
No importa, lo tienes en tu lista. ;)

Garantía de caída

Creo que tenemos que codificar la garantía Drop antes de la estabilización, de lo contrario será demasiado tarde:

Es ilegal invalidar el almacenamiento de un objeto fijo (el objetivo de una referencia Pin ) sin llamar a drop en el objeto.

"Invalidar" aquí puede significar "desasignación", pero también "reutilización": al cambiar x de Ok(foo) a Err(bar) , el almacenamiento de foo se invalida .

Esto tiene la consecuencia, por ejemplo, de que se vuelve ilegal desasignar un Pin<Box<T>> , Pin<Rc<T>> o Pin<Arc<T>> sin llamar primero drop::<T> .

Reutilización de Deref , DerefMut

Todavía me siento un poco incómodo acerca de cómo esto reutiliza el rasgo Deref para que signifique "este es un puntero inteligente". También usamos Deref para otras cosas, por ejemplo, para "herencia". Eso podría ser un anti-patrón, pero todavía es de uso bastante común y, francamente, es útil. :RE

Sin embargo, creo que esto no es un problema de solidez.

Entendiendo Unpin

Enchufe desvergonzado: escribí dos publicaciones de blog sobre esto, que podrían ayudar o no dependiendo de cuánto disfrutes leyendo la sintaxis (semi) formal. ;) Las API han cambiado desde entonces, pero la idea básica no.

Proyecciones de pines seguras

@ Matthias247 Creo que un problema con el que se está encontrando es que la construcción de abstracciones que implican anclar actualmente casi siempre requiere un código inseguro. Usar esas abstracciones está bien, pero, por ejemplo, definir un futuro combinador en código seguro no funcionará. La razón de esto es que las "proyecciones de pines" tienen restricciones que solo podemos verificar de manera segura con cambios del compilador. Por ejemplo, preguntas

¿Por qué mi método drop () ahora vuelve a tomar & mut self, en lugar de un Pin?

Bueno, drop() es antiguo, existe desde Rust 1.0, por lo que no podemos cambiarlo. Nos encantaría que solo tomara Pin<&mut Self> , y luego Unpin tipos podrían obtener sus &mut como lo hacen ahora, pero ese es un cambio no compatible con versiones anteriores.

Para hacer que la escritura de futuros combinadores sea segura, necesitaríamos proyecciones de pines seguras, y para eso tenemos que cambiar el compilador, esto no se puede hacer en una biblioteca. Casi podríamos hacerlo en un derive proc_macro, excepto que necesitaríamos una forma de afirmar "este tipo no tiene ninguna implementación de Drop o Unpin ".

Creo que valdría la pena el esfuerzo de descubrir cómo obtener una API tan segura para proyecciones de pines. Sin embargo, eso no tiene por qué bloquear la estabilización: la API que estabilizamos aquí debería ser compatible con las proyecciones de pines seguras. ( Unpin podría tener que convertirse en un elemento de idioma para implementar la afirmación anterior, pero eso no parece tan malo).

¡Fijar no lo es!

A menudo pensaba en C ++, donde se evitan la mayoría de estos problemas: los tipos se pueden declarar inamovibles eliminando el constructor de movimiento y el operador de asignación. Cuando un tipo no es movible, los tipos que contienen ese tipo tampoco lo son.

Hubo varios intentos de definir tipos inamovibles para Rust. Se complica muy rápidamente.

¡Una cosa importante a entender es que la fijación no proporciona tipos inamovibles! Todos los tipos permanecen móviles en Rust. Si tiene un T , puede moverlo a cualquier lugar que desee. En lugar de tipos inamovibles, utilizamos la capacidad de Rust para definir nuevas API encapsuladas, ya que definimos un tipo de puntero del que no puede salir . Toda la magia está en el tipo de puntero ( Pin<&mut T> ), no en el puntero ( T ). No hay forma de que un tipo diga "No puedo moverme nunca". Sin embargo, hay una manera para un tipo que decir "Si me clavé, no me mueva de nuevo". Por lo que un MyFuture que tengo siempre será móvil, pero Pin<&mut MyFuture> es un puntero a una instancia de MyFuture que no se puede conseguir movido más.

Este es un punto sutil, y probablemente deberíamos dedicar algún tiempo a desarrollar los documentos aquí para ayudar a evitar este malentendido muy común.

Pero hemos llegado bastante lejos sin poder expresar iteradores autorreferenciales en absoluto, simplemente no es un caso de uso tan importante.

Esto se debe a que, hasta ahora, todos los iteradores se definen mediante un tipo y un bloque impl Iterator for … . Cuando el estado debe mantenerse entre dos iteraciones, no hay más remedio que guardarlo en campos del tipo de iterador y trabajar con &mut self .

Sin embargo, incluso si esta no es la motivación principal para incluir generadores en el lenguaje, sería muy bueno poder eventualmente usar la sintaxis del generador yield para definir algo que se pueda usar con for loop. Tan pronto como eso exista, pedir prestado entre yield porque es tan importante para los iteradores del generador como para los futuros del generador.

( Unpin podría tener que convertirse en un elemento de idioma para implementar la afirmación anterior, pero eso no parece tan malo).

Unpin y Pin ya tienen que ser elementos lang para soportar generadores inamovibles seguros.

Ok gracias por las explicaciones a todos! Estoy de acuerdo con @Gankro en que un capítulo al estilo nomicon sobre Pin y demás sería enormemente útil aquí. Sospecho que hay mucho historial de desarrollo sobre por qué existen varios métodos seguros y por qué los métodos inseguros son inseguros y cosas así.

En un esfuerzo por ayudarme a entender esto, quería intentar nuevamente escribir por qué cada función es segura o por qué es unsafe . Con el entendimiento de arriba, tengo todo lo siguiente, pero hay algunas preguntas aquí y allá. ¡Si otros pueden ayudarme a completar esto, sería genial! (o ayudarme a señalar dónde está mal mi forma de pensar)

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

    • Este es un método seguro para construir Pin<P> , y si bien la existencia de
      Pin<P> normalmente significa que P::Target nunca se moverá de nuevo en el
      programa, P::Target implementa Unpin que se lee "las garantías de
      Pin ya no se mantienen ". Como resultado, la seguridad aquí es porque no hay
      garantías para mantener, para que todo pueda seguir como de costumbre.
  • unsafe fn new_unchecked(P) -> Pin<P> where P: Deref

    • A diferencia de la anterior, esta función es unsafe porque P no
      implementar necesariamente Unpin , por lo que Pin<P> debe mantener la garantía de que
      P::Target nunca más se moverá después de que se cree Pin<P> .

    Una forma trivial de violar esta garantía, si esta función es segura, parece
    me gusta:

    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
    }
    

    En consecuencia, depende del usuario garantizar que Pin<P> realmente significa
    P::Target nunca se vuelve a mover después de la construcción, ¡así que es unsafe !

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

    • Dado Pin<P> tenemos la garantía de que P::Target nunca se moverá. Eso es
      parte del contrato de Pin . Como resultado, significa trivialmente que
      &P::Target , otro "puntero inteligente" a P::Target proporcionará lo mismo
      garantía, por lo que &Pin<P> se puede traducir con seguridad a Pin<&P::Target> .

    Este es un método genérico para pasar de Pin<SmartPointer<T>> a Pin<&T>

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

    • Creo que la seguridad aquí es aproximadamente la misma que la as_ref anterior.
      No estamos entregando acceso mutable, solo Pin , por lo que nada puede ser fácil
      violado todavía.

    Pregunta : ¿qué pasa con un DerefMut impl "malicioso"? Esta es una forma segura
    para llamar a un DerefMut proporcionado por el usuario que almacena &mut P::Target forma nativa,
    presumiblemente permitiéndole modificarlo también. ¿Cómo es esto seguro?

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

    • Pregunta : Dado Pin<P> (y el hecho de que no sabemos nada sobre
      Unpin ), ¿no debería esto garantizar que P::Target nunca se mueva? Si podemos
      reinicialice con otro P::Target , sin embargo, ¿no debería ser esto inseguro?
      ¿O esto está relacionado de alguna manera con los destructores y todo eso?
  • unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>

    • Esta es una función unsafe , por lo que la pregunta principal aquí es "¿por qué no
      seguro "? Un ejemplo de infracción de garantías si fuera seguro se ve así:

    ...

    Pregunta : ¿cuál es el contraejemplo aquí? Si esto fuera seguro, ¿qué
    el ejemplo que muestra violando las garantías de Pin ?

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

    • La garantía de Pin<&T> significa que T nunca se moverá. Devolución &T
      no permite la mutación de T , por lo que debería ser seguro hacerlo mientras se mantiene
      esta garantía.

    Un "tal vez te pillé" aquí es la mutabilidad interior, ¿y si T fueran
    RefCell<MyType> ? Esto, sin embargo, no viola las garantías de
    Pin<&T> porque la garantía solo se aplica a T como un todo, no al
    campo interior MyType . Mientras que la mutabilidad interior puede moverse
    internos, todavía fundamentalmente no puede mover toda la estructura detrás de un
    & referencia.

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

    • Pin<&mut T> significa que T nunca se moverá. Como resultado, significa Pin<&T>
      ofrece la misma garantía. No debería ser un gran problema con esta conversión,
      se trata principalmente de tipos de cambio.
  • unsafe fn get_unchecked_mut(Pin<&'a mut T>) -> &'a mut T

    • Pin<&mut T> significa que T nunca debe moverse, por lo que esto es trivialmente unsafe
      porque puede usar mem::replace en el resultado para mover T (de forma segura). los
      unsafe aquí "aunque te estoy dando &mut T , no puedes hacerlo nunca
      mover T ".
  • unsafe fn map_unchecked_mut<U, F: FnOnce(&mut T) -> &mut U>(Pin<&'a mut T>, f: F) -> Pin<&'a mut U>

    • Creo que el unsafe aquí es básicamente al menos el mismo que el anterior, estamos
      repartir &mut T forma segura (no puede requerir un cierre inseguro) que podría
      se puede utilizar fácilmente con mem::replace . Probablemente también haya otra inseguridad aquí
      con la proyección, pero parece razonable que al menos no sea seguro
      por eso.
  • fn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin

    • Al implementar Unpin un tipo dice " Pin<&mut T> no tiene garantías, es
      sólo un contenedor newtype de &mut T ". Como resultado, sin garantías de
      mantener, podemos devolver &mut T forma segura
  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }

    • Esto se puede implementar de forma segura con as_ref seguido de get_ref , por lo que
      la seguridad de este impl se deriva de lo anterior.
  • impl<P: DerefMut> DerefMut for Pin<P> where T::Target: Unpin { }

    • Esto se puede implementar de forma segura con as_mut seguido de get_mut , por lo que
      la seguridad de este impl se sigue de lo anterior.
  • impl<T: ?Sized> Unpin for Box<T> (y otras implementaciones relacionadas con punteros)

    • Si no se realiza ninguna otra acción, Box<T> implementaría el rasgo Unpin
      solo si T implementado Unpin . Esta implementación aquí codifica que incluso
      si T explícitamente no implementa Unpin , Box<T> implementa Unpin .

    Pregunta : ¿Cuál es un ejemplo de lo que esto es fundamentalmente empoderador?

    Por ejemplo, qué caja fuerte podría requerir unsafe si esta impl

    no existió.


@ Matthias247 no puedes obtener & mut T de Pin de forma segura

> si T no es Unpin porque entonces podría mem :: swap para mover T fuera del Pin, lo que anularía el propósito de fijar cosas.

¡Gracias! Sí, dado que swap no es un método inseguro, esto es obviamente problemático. Pero, ¿podría solucionarse agregando un Unpin vinculado a swap() ? Dado que todo el código hasta ahora debería ser Unpin o no es seguro de todos modos, esto no debería romper nada.

Supongo que una de las cosas que todavía me confunde más es que Pin<T> codifica múltiples garantías: que la dirección de la T es estable, así como algunas garantías sobre su estado interno (es algo congelado / inmutable en algunos situaciones, pero tampoco realmente).

Me pareció que mover el código / proyecciones inseguras solo a los lugares donde se necesitan más llamadas basadas en Pin (por ejemplo, en poll s en los campos) podría ser preferible a tratar con esas proyecciones inseguras en todos los casos. Sin embargo, ahora también me di cuenta de que hay otro problema con eso: el código que tiene acceso a la referencia mutable puede mover el campo libremente, y cuando drop() se llama de forma segura en este campo, podría romperse debido a su dirección almacenada en otro lugar. Para eso, se necesitaría la sobrecarga drop(Pin<T>) que se habló.

@RalfJung ¡ Gracias por las explicaciones! Estoy de acuerdo en el hecho de que ciertamente traté de hacer algo inseguro en general y, por lo tanto, debería estar bien solicitar mi comprensión adicional. Me preocupan más las personas que quieren escribir combinadores futuros más generalmente seguros, pero que ahora también podrían enfrentarse a todos esos términos. Si pueden escribir combinadores sin tener que entender Unpin y proyecciones de clavijas en absoluto, y luego solo obtienen combinadores que funcionan de manera reducida (solo en futuros de Unpin ), parece preferible. Como no lo he probado, no puedo decir si es el caso actualmente. Creo que todavía se necesita agregar al menos Unpin límites manualmente.

También entiendo el hecho de que los tipos no móviles son diferentes del tipo de pasador. Sin embargo, actualmente estoy más centrado en los casos de uso que en la diferencia. Y para el caso de uso de colecciones intrusivas, los tipos no móviles funcionan muy bien sin introducir demasiada complejidad. Para los futuros, obviamente, se requeriría algo de investigación, ya que falta el camino de un tipo móvil a uno inmóvil. Si esa forma no fuera más ergonómica que las API de Pin, tampoco habría ganancia.

Agregar T: Unpin a mem::swap significaría que no podría usarse en ciertos tipos incluso cuando no están dentro de un Pin.

Pero, ¿podría solucionarse agregando un enlace Unpin a swap ()? Dado que todo el código hasta ahora debería ser Unpin o no es seguro de todos modos, esto no debería romper nada.

Eso rompería todo el código genérico: si escribe una función en el óxido estable de hoy por unos T sin restricciones, puede llamar a swap en ella. Esto tiene que seguir funcionando.

Los tipos no móviles funcionan muy bien sin introducir demasiada complejidad.

Nadie ha demostrado una forma de agregar tipos no móviles a Rust de una manera compatible con versiones anteriores sin que la complejidad exceda significativamente lo que se propone para la estabilización aquí. Debería encontrar algunas de estas antiguas propuestas y discusiones en el repositorio RFC. Consulte https://github.com/rust-lang/rfcs/pull/1858 para ver un ejemplo.

El RFC en https://github.com/rust-lang/rfcs/pull/2349 , así como la serie de blogs de barcos que comienzan aquí, deberían ayudarlo a brindarle algunos antecedentes y una impresión de los otros diseños que se consideraron. (Observe también las fechas, ¡este diseño ha estado en proceso durante casi 10 meses!)

También mem :: swap es una pista falsa, porque no es en absoluto una función interesante. Es literalmente solo

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

@Gankro ¿no usa código inseguro? Afaik, no es posible escribir eso literalmente.

Editar: Supongo que otra forma de pensar en esto es que agregar T: Unpin a mem::swap realidad está cambiando la definición de seguridad a nivel de idioma. Rompería todos los mycrate::swap fns en la naturaleza.

Agregar T: Unpin a mem :: swap significaría que no podría usarse en ciertos tipos incluso cuando no están dentro de un Pin.

Si Unpin se deriva automáticamente (de la misma manera que Sync / Send son), entonces pensé que esto no debería ser un problema.

Eso rompería todo el código genérico: si escribe una función en el óxido estable de hoy para algún T sin restricciones, puede llamar a swap. Esto tiene que seguir funcionando.

Pero este obviamente lo es. No pensé en el hecho de que los límites de los rasgos deben propagarse explícitamente en Rust.

Nadie ha demostrado una forma de agregar tipos no móviles a Rust de una manera compatible con versiones anteriores sin que la complejidad exceda significativamente lo que se propone para la estabilización aquí. Debería encontrar algunas de estas antiguas propuestas y discusiones en el repositorio RFC. Consulte rust-lang / rfcs # 1858 para ver un ejemplo.

Gracias, leeré un poco más sobre el trabajo anterior sobre esto si encuentro algo de tiempo. Obviamente, ya se han dedicado muchos pensamientos y esfuerzos a esto, y ciertamente no quiero bloquear las cosas. Solo quería expresar mis inquietudes y preguntas al intentar trabajar con esto.

@ Matías247

Si Unpin se deriva automáticamente (de la misma manera que Sync / Send), pensé que esto no debería ser un problema.

No estoy seguro de haber sido claro. Para aclarar, es perfectamente seguro moverse alrededor de un tipo !Unpin y, por lo tanto, perfectamente seguro para mem::swap . Solo es peligroso mover un tipo T: !Unpin después de que esté fijado, es decir, dentro de un Pin<P<T>> .

Por ejemplo, en el código asincrónico / en espera, puede moverse entre los futuros devueltos por una función asíncrona tanto como desee. Solo dejas de poder moverlos una vez que los pin_mut! o los colocas en Pin<Box<..>>> o lo que sea.

Tengo algunas preguntas variadas sobre esto:

  1. Dada la importancia de Pin<T> para async y para los generadores y la posibilidad de fallas al interactuar con otras partes de Rust (por ejemplo, swap & replace ), tiene alguna ¿Se ha realizado una verificación formal (por ejemplo, por @jhjourdan o @RalfJung) de la variante de la API de fijación que se propone aquí?

  2. ¿Esta API ofrece nuevas garantías sobre la máquina abstracta / semántica operativa de Rust? Es decir, si nos olvidamos del caso de uso como mecanismo de respaldo para los generadores async & await /, ¿podríamos poner esto dentro de una caja en el ecosistema y funcionaría dadas las garantías actuales que tenemos? ¿dar?

  3. ¿Qué tipo de API o adiciones de sistemas de tipo se vuelven imposibles como consecuencia de estabilizar la API de fijación? (esta pregunta es una extensión de 2.)

  4. ¿Qué posibilidades ofrece la API que se va a estabilizar en términos de su evolución? Un lenguaje proporcionado &pin T tipo para mejorar la proyección de campo y demás (lo que no parece genial con la API propuesta).

Tengo algunas notas variadas:

  1. La documentación de la biblioteca estándar parece bastante escasa. :(

  2. Estoy de acuerdo con otros en que la construcción de clavar es bastante exigente / compleja mentalmente.

  3. El ejemplo Unmovable en la documentación parece demasiado complicado e involucra unsafe ; esto parece subóptimo. Con la inicialización gradual como se detalla en un borrador de RFC en el lenguaje (es decir, mejorando NLL) podría darnos:

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.

Esto implica cero inseguridad y es bastante fácil para el usuario lidiar con esto.

Además, las API estándar no proporcionan una forma segura de anclar objetos a la pila.
Esto se debe a que no hay forma de implementar eso de forma segura utilizando una función API.

¿Qué pasa con una API como esta?

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

No he estado siguiendo demasiado de cerca el desarrollo de las API de fijación, por lo que esto podría haber sido mencionado o explicado en otra parte y no he podido encontrarlo, disculpas si ese es el caso:

El tipo Pinned es un ZST que no implementa Unpin ; te permite
suprimir la implementación automática de Unpin en estable, donde !Unpin impls
todavía no sería estable.

¿Hay alguna explicación sobre por qué !Unpin impls no se pudieron estabilizar?

Solo es seguro si Foo (el tipo que lo contiene) no está repr (empaquetado),
porque esto hace que los campos se muevan para realinearlos.

¿Empaquetado significa que los campos se pueden mover dinámicamente? Eso da un poco de miedo. ¿Estamos seguros de que llvm nunca generará código para mover campos en otras circunstancias? Del mismo modo, ¿es posible que llvm mueva los valores fijados en la pila?

A pesar de la sutileza, esta parece una API realmente agradable. ¡Buen trabajo!

@Centril Ralf ha escrito sobre pinning en su blog .

Las API de PIN no implicaron ningún cambio de idioma y están completamente implementadas en la biblioteca estándar utilizando características de idioma preexistentes. No tiene ningún impacto en Rust el idioma y no excluye ninguna otra característica del idioma.

Pin es solo una implementación inteligente de una de las características más valiosas y clásicas de Rust: la capacidad de introducir invariantes en una API marcando parte de la API unsafe . Pin envuelve un puntero para hacer que una operación ( DerefMut ) sea insegura, requiriendo que las personas que lo hacen mantengan ciertos invariantes (que no se muevan fuera de la referencia) y permitiendo que otro código suponga que esto nunca ocurrirá. Un ejemplo similar y mucho más antiguo de este mismo truco es String , que hace que no sea seguro colocar bytes que no sean UTF8 en una cadena, lo que permite que otro código asuma que todos los datos en String son UTF8.

¿Hay alguna explicación sobre por qué! Unpin impls no se pudo estabilizar?

Las implicaciones negativas son actualmente inestables y no tienen ninguna relación con estas API.

Las implicaciones negativas son actualmente inestables.

🤦‍♂️ Eso tiene sentido, debería haber pensado en eso un poco más. Gracias.

@alexcrichton ¡ Ese es un gran análisis de esta API, deberíamos intentar preservarlo en algún lugar mejor que algún comentario que se pierda!

Algunos comentarios:

as_mut : Pregunta: ¿qué pasa con un impl DerefMut "malicioso"? Esta es una forma segura
para llamar a un DerefMut proporcionado por el usuario que almacena & mut P :: Target de forma nativa,
presumiblemente permitiéndole modificarlo también. ¿Cómo es esto seguro?

Básicamente, cuando llamas a new_unchecked , haces una promesa sobre las implementaciones Deref y DerefMut para este tipo.

set : Pin dado

(y el hecho de que no sabemos nada sobre
Unpin), ¿no debería esto garantizar que P :: Target nunca se mueva? Si podemos
reinicialice con otro P :: Target, sin embargo, ¿no debería ser inseguro?
¿O esto está relacionado de alguna manera con los destructores y todo eso?

Esto elimina el contenido antiguo del puntero y coloca contenido nuevo allí. "Fijado" no significa "nunca se dejó caer", significa "nunca se movió hasta que se soltó". Entonces, soltarlo o sobrescribirlo mientras se llama a drop está bien. Llamar a drop es crucial, esa es la garantía de caída que mencioné anteriormente.

¿Dónde ves que algo se mueve aquí?

map_unchecked : Pregunta :: ¿Cuál es el contraejemplo aquí? Si esto fuera seguro, ¿cuál es el ejemplo que muestra la violación de las garantías de Pin?

Un ejemplo sería comenzar con Pin<&&T> y usar este método para obtener Pin<&T> . La fijación no se "propaga" a través de referencias.

get_ref : Un "tal vez te pillé" aquí es la mutabilidad interior, ¿y si T fuera
RefCell?

De hecho, esto es un problema, pero como observó, no es un problema de solidez. Lo que no sería correcto es tener un método que vaya de Pin<RefCell<T>> a Pin<&[mut] T> . Básicamente, lo que sucede es que RefCell no propaga la fijación, podríamos impl<T> Unpin for RefCell<T> .

¿Se ha realizado alguna verificación formal (por ejemplo, por @jhjourdan o @RalfJung) de la variante de la API de fijación que se propone aquí?

No, no de nuestro lado. Las publicaciones del blog que mencioné anteriormente contienen algunas ideas sobre cómo comenzaría a formalizar esto, pero en realidad no lo hemos hecho. Si me da una máquina del tiempo o un estudiante de doctorado interesado, lo haremos. ;)

si nos olvidamos del caso de uso como un mecanismo de respaldo para async & await / generators, ¿podríamos poner esto dentro de una caja en el ecosistema y simplemente funcionaría dadas las garantías actuales que brindamos?

Esa es la intencion.

¿Qué tipo de API o adiciones de sistemas de tipo se vuelven imposibles como consecuencia de estabilizar la API de fijación? (esta pregunta es una extensión de 2.)

Uh, no tengo idea de cómo responder esa pregunta con confianza. El espacio de posibles adiciones es demasiado grande y de dimensiones demasiado altas, por lo que me atrevería a hacer cualquier tipo de declaración universal al respecto.

¿Qué posibilidades ofrece la API que se va a estabilizar en términos de desarrollar un lenguaje proporcionado y un tipo de pin T para mejorar la proyección de campo y demás (lo que no parece genial con la API propuesta)?

Ya no estoy seguro sobre &pin T , no coincide muy bien con el nuevo genérico Pin<T> . En términos de manejo de proyecciones, necesitaríamos algún truco para decir " Unpin y Drop no se han implementado para este tipo", entonces podríamos hacer esto de forma segura con una macro. Para una ergonomía adicional, probablemente querríamos una capacidad genérica de "proyección de campo" en el idioma, una que también cubriera el paso de &Cell<(A, B)> a &Cell<A> .

¿Hay alguna explicación sobre por qué! Unpin impls no se pudo estabilizar?

Las implicaciones negativas AFAIK tienen algunas limitaciones de larga data, y si les agregas límites genéricos, a menudo no funcionarán de la manera que crees que deberían. (Los límites genéricos a veces simplemente se ignoran, o algo así).

Tal vez Chalk solucione todo esto, tal vez solo algo, pero de cualquier manera probablemente no querríamos bloquear esto en Chalk.

¿Empaquetado significa que los campos se pueden mover dinámicamente? Eso da un poco de miedo.

Es la única forma sólida de llamar a drop en un campo empaquetado, dado que drop espera una referencia alineada.

¿Estamos seguros de que llvm nunca generará código para mover campos en otras circunstancias? Del mismo modo, ¿es posible que llvm mueva los valores fijados en la pila?

LLVM copiar bytes alrededor no debería ser un problema, ya que no puede cambiar el comportamiento del programa. Se trata de un concepto de nivel superior de "mover" datos, de una manera que es observable en Rust (por ejemplo, porque los punteros a los datos ya no apuntan a ellos). LLVM no puede simplemente mover datos a otro lugar al que podamos tener sugerencias. Del mismo modo, LLVM no puede simplemente mover valores en la pila que tienen su dirección tomada.

Ok, gracias @RalfJung , ¡tiene sentido! Algunas preguntas de seguimiento ...

Cuando dice "nunca se movió hasta que se eliminó", esto significa que Drop se puede llamar donde &mut self ha movido desde la dirección de Pin<&mut Self> ? Los destructores no pueden confiar en que los indicadores interiores sean precisos, ¿verdad?

Mi preocupación al mirar set era cómo se manejarían los pánicos, pero creo que esto no es un problema después de profundizar un poco más en el codegen.

Cuando dice "nunca se movió hasta que se eliminó", esto significa que Drop se puede llamar donde &mut self ha movido desde la dirección de Pin<&mut Self> ? Los destructores no pueden confiar en que los indicadores interiores sean precisos, ¿verdad?

@alexcrichton, según tengo entendido, significa que nunca se movió hasta después de Drop::drop devuelve. De lo contrario, algunos casos de uso (al menos colecciones intrusivas y búferes DMA asignados a la pila) se vuelven imposibles.

Los destructores pueden confiar en que algo nunca se ha movido si pueden demostrar que estaba previamente en un Pin . Por ejemplo, si una máquina de estado solo puede ingresar a un estado a través de una API que requiere que se fije, el destructor puede asumir que se fijó antes de soltarlo si está en ese estado.

El código no puede asumir que los destructores no locales no mueven miembros, pero obviamente puedes asumir que tus propios destructores no mueven las cosas porque eres tú quien las escribe.

Cuando dice "nunca se movió hasta que se suelte", esto significa que se puede llamar a Drop donde & mut self se ha movido desde la dirección de Pin <& mut Self>? Los destructores no pueden confiar en que los indicadores interiores sean precisos, ¿verdad?

Quiero decir que los datos nunca se moverán (en el sentido de que no irán a ningún otro lugar, incluido el no ser desasignado) hasta que se llame a drop . Y sí, drop puede confiar en ejecutarse en la ubicación anclada, aunque su tipo no puede expresar eso. drop debería tomar Pin<&mut self> (para todos los tipos), pero lamentablemente, es demasiado tarde para eso.

Después de llamar a drop , los datos son solo bytes sin sentido, puede hacer cualquier cosa con ellos: poner cosas nuevas allí, desasignar, lo que sea.

Esto permite, por ejemplo, una lista enlazada intrusiva donde el destructor de un elemento lo anula ajustando los punteros vecinos. Sabemos que la memoria no desaparecerá sin que se llame a ese destructor. (El elemento aún se puede filtrar , y luego permanecerá en esa lista vinculada para siempre. Pero en ese caso, seguirá siendo una memoria válida, por lo que no hay ningún problema de seguridad).

He estado leyendo todo lo que puedo encontrar sobre Pin , Unpin , y la discusión que condujo a todo, y aunque _pienso_ que ahora entiendo lo que está pasando, también hay mucho sutileza en torno a lo que significan los diferentes tipos, así como el contrato que deben seguir los implementadores y usuarios. Lamentablemente, veo relativamente poca discusión sobre eso en los documentos para std::pin o en Pin y Unpin específicamente. En particular, me encantaría ver parte del contenido de estos comentarios:

incorporado en los docs. Específicamente:

  • Ese Pin solo protege "un nivel de profundidad".
  • Ese Pin::new_unchecked impone restricciones al impl para Deref y DerefMut .
  • La interacción entre Pin y Drop .
  • Cómo !Unpin solo no puede moverse una vez colocado en un Pin .
  • La justificación de por qué Pin<Box<T>>: Unpin where T: !Unpin . Esto se relaciona con la restricción de "un nivel de profundidad" anterior, pero creo que este ejemplo concreto con una explicación adecuada sería útil para los lectores.

Los contraejemplos de ayudaron bastante. Darle al lector una idea de lo que puede salir mal con los diferentes métodos unsafe más allá de la prosa, creo que ayudaría mucho (al menos ciertamente lo hizo para mí). En general, debido a la sutileza de esta API, me gustaría ver expandidas las secciones de Seguridad de los diversos métodos inseguros, y posiblemente también referenciadas en los documentos de nivel de módulo std::pin .

Todavía no he usado mucho esta API, así que confiaré en el juicio técnico de que ahora está en buen estado. Sin embargo, creo que los nombres Pin , Pinned y Unpin son demasiado similares e inexpresivos para lo que son tipos / rasgos muy diferentes y una API relativamente compleja, lo que dificulta su comprensión (como lo demuestran algunos comentarios en este hilo).

Parecen seguir las convenciones de nomenclatura para los rasgos de los marcadores, por lo que realmente no puedo quejarme, pero me pregunto si podríamos hacer un intercambio entre la verbosidad y los nombres autoexplicativos:

  • Pinned - algo confuso porque es la misma palabra que Pin . Dado que se usa como PhantomData para aumentar una estructura con metainformación que faltaría de otra manera, ¿qué tal PinnedData , PhantomPinned , PhantomSelfRef o incluso DisableUnpin ? Algo que indique patrón de uso y / o el efecto que va a tener.
  • Unpin - Como en https://github.com/rust-lang/rust/issues/55766#issuecomment -437266922, me confunde el nombre Unpin , porque "des-pin "puede entenderse de múltiples formas ambiguas. ¿Algo como IgnorePin , PinNeutral quizás?

Sin embargo, generalmente estoy fallando y encontrando buenos nombres alternativos ...

PhantomPin y PinNeutral me parecen nombres particularmente agradables.

Solo para proporcionar un contrapunto, encontré Unpin intuitivo (una vez que lo entendí). Pinned es más difícil de entender, dado que no lo he usado en mi propio código. ¿Qué hay de cambiar Pinned a NotUnpin ?

Estoy de acuerdo en que encontrar mejores nombres puede ser un ejercicio de eliminación de bicicletas que valga la pena con el fin de hacer que sea más fácil hablar sobre el tema de las clavijas. Propongo:

  • Pin -> Pinned : Cuando te dan un Pin , eso es realmente una promesa de que lo que te han dado _ ha sido fijado_, y _permanecerá_ fijado para siempre. Sin embargo, no estoy muy convencido de esto, ya que usted _podría_ hablar también de recibir un "pin de valor". Pinned<Box<T>> me parece mejor, especialmente porque solo se fija el Box , no el material que contiene.
  • Unpin -> Repin : para otros rasgos de marcador, generalmente hablamos de lo que puede hacer con algo que tenga ese rasgo de marcador. Que es presumiblemente por qué se eligió Unpin en primer lugar. Sin embargo, creo que lo que realmente queremos que el lector se lleve aquí es que algo que es Unpin se puede fijar y luego volver a fijar en otro lugar, sin consecuencias ni consideración. También me gusta la sugerencia de @ mark-im de PinNeutral , aunque es algo más detallada.
  • Pinned -> PermanentPin : No creo que Pinned sea ​​un buen nombre, porque algo que contiene un Pinned no está realmente fijado. simplemente no es Unpin . PhantomPin tiene un problema similar en el sentido de que se refiere a Pin , cuando Pin no es realmente a lo que quieres llegar. NotUnpin tiene un doble negativo, lo que hace que sea difícil razonar. La sugerencia de @Kimundi de PhantomSelfRef acerca bastante, aunque todavía creo que es un poco "complejo", y vincula la propiedad "no se puede mover una vez fijada" con una instancia en la que ese es el caso (cuando tener una autorreferencia). Mi sugerencia también podría expresarse como PermanentlyPinned ; No sé qué forma es menos mala.

Creo que Pinned debería terminar siendo NotX donde X es lo que sea que Unpin termine siendo nombrado. El único trabajo de Pinned es hacer que el tipo adjunto no implemente Unpin . (Editar: y si estamos cambiando Unpin a otra cosa, presumiblemente el doble negativo no es un problema)

Repin no tiene sentido para mí, porque "Este tipo se puede fijar en otro lugar" es solo un efecto secundario de "Este tipo se puede mover fuera de un pin".

@tikue En cierto sentido siento lo mismo, pero al revés. Creo que Unpin debería expresarse en negativo "esto _no_ está restringido por Pin ", mientras que Pinned debe expresarse en positivo "esto _ está_ restringido por Pin ". Sobre todo para evitar el doble negativo. Pero eso es un detalle; Me gusta la idea de que exista una dualidad. Tal vez: s/Unpin/TemporaryPin , s/Pinned/PermanentPin ?

EDITAR: Sí, veo su punto de que Repin es un efecto secundario de Unpin . Quería comunicar el hecho de que Pin es "importante" para un tipo que es Unpin , que no creo que Unpin haga muy bien. De ahí la sugerencia anterior de TemporaryPin .

@jonhoo Creo que la razón principal por la que prefiero lo contrario es porque Pinned evita que se implemente un rasgo, por lo que es, en el sentido más simple para mí, el verdadero negativo.

Editar: ¿qué pasa con:

Unpin -> Escape
Pinned -> NoEscape

Interesante ... Estoy tratando de ver cómo encajaría en la documentación. Algo como:

En general, cuando se le da un Pin<P> , eso viene acompañado de una garantía de que el objetivo de P no se moverá hasta que se elimine. La excepción a esto es si el objetivo de P es Escape . Los tipos marcados como Escape prometen que seguirán siendo válidos incluso si se mueven (por ejemplo, no contienen autorreferencias internas) y, por lo tanto, se les permite "escapar" de un Pin . Escape es un rasgo automático, por lo que todos los tipos que consisten completamente en tipos que son Escape también son Escape . Los implementadores pueden optar por no participar en esto todas las noches usando impl !Escape for T {} , o incluyendo el marcador NoEscape de std::phantom .

Eso parece bastante decente, aunque la conexión con la palabra "escapar" parece un poco tenue. Por separado, escribir lo anterior también me hizo darme cuenta de que Pin<P> no _realmente_ garantiza que el objetivo de P no se moverá (precisamente debido a Unpin ). En su lugar, garantiza que _cualquiera_ no importa si el objetivo de P mueve _o_ el objetivo de P no se moverá. Sin embargo, no sé cómo usar eso para informar una mejor elección de nombres ... Pero probablemente sea algo que debería figurar en los documentos de una forma u otra.

Personalmente, a mí también me disgusta mucho Unpin como nombre, tal vez porque se ve con mayor frecuencia como impl !Unpin que dice "not-un-pin" y requiere varios ciclos cerebrales (I soy un modelo más antiguo) para concluir que significa "está bien, este se fijará para siempre una vez que se fije la primera vez", por lo que ni siquiera puedo optimizar el doble negativo.
En general, los humanos tienden a tener más dificultades para pensar en aspectos negativos en lugar de positivos (sin una fuente directa, pero consulte el trabajo de Richard Hudson si tiene dudas).
Por cierto, Repin me suena muy bien.

Pin es difícil de explicar porque no siempre hace que el valor fijado sea inamovible. Pinned es confuso porque en realidad no fija nada. Simplemente evita que se escape un Pin .

Pin<P<T>> podría explicarse como un valor fijado: los valores fijados no se pueden mover a menos que el valor sea de un tipo que pueda escapar de un pin.

Un poco de búsqueda rápida en Google parece mostrar que en la lucha libre, cuando uno está inmovilizado, romper un pasador se llama escapar .

También me gusta el término escape en lugar de Unpin pero iría por EscapePin .

¡Muchos buenos pensamientos aquí!

Una cosa general: para mí, como hablante no nativo, Pin y Unpin son principalmente verbos / acciones. Si bien Pin tiene sentido, dado que el objeto está anclado en una ubicación de memoria a la vez, no puedo ver lo mismo para Unpin . Una vez que reciba una referencia de Pin<&mut T> , T siempre estará anclado en el sentido de que su ubicación de memoria es estable, ya sea Unpin o no. No es posible realmente desanclar un objeto como acción. La diferencia es que los tipos Unpin no requieren que se respeten las garantías de fijación en interacciones posteriores. No son autorreferenciales y su dirección de memoria, por ejemplo, no se envía a otro objeto y se almacena allí.

Estoy de acuerdo con @tikue en que sería bueno tener un trabajo sorprendente por lo que realmente significa Unpin , pero es difícil de cuantificar. ¿No es simplemente que esos tipos son móviles, y tampoco es simplemente su falta de autorreferencialidad? Tal vez sea algo sobre "ningún puntero en todo el espacio de memoria se invalida cuando se mueve el objeto". Entonces algo como StableOnMoveAfterPin , o simplemente StableMove podría ser una opción, pero tampoco suena muy bien.

Repin para mí tiene las mismas complicaciones que Unpin , y es que implica que una cosa primero se desencadena, lo que, en mi opinión, no sucede.

Dado que el rasgo define principalmente lo que sucede después de que uno ve un Pin del tipo, encuentro cosas como PinNeutral , o PinInvariant no tan mal.

Con respecto a Pin vs Pinned , creo que preferiría Pinned , ya que ese es el estado del puntero en el momento en que uno lo ve.

@ Matthias247 No creo que tengas garantizado que la dirección de P::Target sea ​​estable si P: Unpin ? ¿Podría estar equivocado sobre esto?

@ Matías247

Una vez que reciba una referencia de Pin <& mut T>, T siempre estará anclado en el sentido de que su ubicación de memoria es estable, ya sea Desanclar o no.

¿Puede aclarar lo que quiere decir con esto? Dado un T: Unpin puede escribir lo siguiente:

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 realidad, una buena pregunta. Mi razonamiento fue que los futuros se encajonan, y luego se llamará a su método poll() en la misma dirección de memoria. Pero eso obviamente solo se aplica a la tarea / futuro de nivel superior, y las capas intermedias pueden mover futuros cuando son Unpin . Entonces parece que tienes razón.

Wrt el último derrame de bicicletas:

  • Unpin : ¿qué pasa con MoveFromPin ? Si todavía no me faltan algunas sutilezas, creo que esto indica directamente qué capacidad realmente habilita el rasgo: si el tipo está dentro de un Pin , aún puede moverlo.

    Fundamentalmente, dice lo mismo que Unpin , pero enmarcado como una afirmación positiva, por lo que en lugar del doble negativo !Unpin ahora tenemos !MoveFromPin . Creo que lo encuentro más fácil de interpretar, al menos ... son tipos que no se pueden sacar de un alfiler.

    (Hay espacio para la variación de la idea básica: MoveOutOfPin , MoveFromPinned , MoveWhenPinned , etc.).

  • Pinned : esto puede convertirse en NoMoveFromPin , y su efecto es hacer un tipo !MoveFromPin . Creo que parece bastante sencillo.

  • Pin sí mismo: este no está conectado con los otros dos y tampoco es tan significativo, pero creo que aquí también podría haber espacio para una ligera mejora.

    El problema es que Pin<&mut T> (por ejemplo) no significa que el &mut esté fijado, significa que el T es (una confusión que creo haber visto en al menos una evidencia de comentario reciente). Dado que la parte Pin actúa como una especie de modificador en &mut , creo que hay un caso en el que sería mejor llamarlo Pinning .

    Hay un precedente indirecto para esto: si queremos modificar la semántica de desbordamiento de un tipo entero para envolver en lugar de pánico, decimos Wrapping<i32> lugar de Wrap<i32> .

Todos estos son más largos que los originales, pero dado lo delicado que es el razonamiento en torno a ellos, podría ser una inversión valiosa para una mayor claridad.

Re: Repin , esperaría que esto sea algo como

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

que podría usarse para admitir tipos !Unpin dentro de una colección similar a Vec que ocasionalmente mueve su contenido (esta no es una propuesta para agregar tal rasgo ahora o nunca, solo mi primera impresión del nombre del rasgo).

También bikshedding los nombres:

  • Pin<P> -> Pinned<P> : el valor al que apunta el puntero P está anclado en la memoria durante su vida útil (hasta que se elimine).
  • Unpin -> Moveable : no es necesario fijar el valor y se puede mover libremente.
  • Pinned (estructura) -> Unmoveable : debe ser Pinned y no se puede mover.

No creo que Pin o Unpin deban cambiar, todas las alternativas agregan verbosidad sin claridad en mi opinión, o incluso son bastante engañosas. Además, ya tuvimos esta conversación, tomamos la decisión de usar Pin y Unpin , y ninguno de los argumentos presentados en este hilo son nuevos.

Sin embargo, Pinned se agregó desde la discusión anterior, y creo que tiene sentido hacer que PhantomPinned quede claro que es un tipo de marcador fantasma como PhantomData .

Personalmente, a mí también me disgusta mucho Desanclar como nombre, ¡tal vez porque a menudo se ve como impl! Despin, que dice "not-un-pin" y requiere varios ciclos cerebrales (soy un modelo más antiguo) para concluir que significa "bien, este se fijará para siempre una vez que se fije la primera vez", por lo que ni siquiera puedo optimizar el doble negativo.

Esto es completamente opuesto a mi experiencia, implementar manualmente !Unpin significa que está implementando una estructura autorreferencial a mano usando código inseguro, un caso de uso extremadamente especializado. En contraste, cualquier cosa que mantenga una estructura potencialmente desanclada detrás de un puntero tiene una implicación positiva de Unpin . Todas las implicaciones de Unpin en std son polaridad positiva, por ejemplo.

@withoutboats ¿ podría proporcionar un enlace a la discusión anterior sobre estos nombres donde los argumentos presentados aquí ya han sido discutidos?

Aquí hay un hilo, aunque ciertamente también se discutió sobre el hilo RFC y el problema de seguimiento https://internals.rust-lang.org/t/naming-pin-anchor-move/6864

(los tipos en este hilo llamados ancla y pin ahora se llaman Pin<Box<T>> y Pin<&'a mut T> )

¿Se supone que Unpin se lee como la abreviatura de Unpinnable? No se puede fijar, ya que en realidad no se puede mantener el valor fijado, aunque esté dentro de un Pin. (¿Es eso incluso un entendimiento correcto de mi parte?)

Revisé algunos de los documentos y los hilos de comentarios y no vi ninguna referencia específica a Unpinnable.

Se supone que Unpin no es la abreviatura de nada. Una cosa que creo que no es obvia para muchos usuarios, pero es cierto, es que la guía de estilo de la biblioteca estándar es preferir verbos como nombres de rasgos, no como adjetivos, por lo tanto Send , no Sendable . Esto no se ha aplicado con una consistencia perfecta, pero es la norma. Unpin es como en "desanclar", ya que es posible desanclar este tipo del Pin que lo ha fijado.

Nombres como Move (no "movible", recuerde) son menos claros que Unpin porque implican que tiene que ver con poder moverlo, en lugar de conectar el comportamiento al tipo de pin. Puede mover !Unpin tipos, porque puede mover cualquier Sized en Rust.

Los nombres que son frases completas, como he visto sugerido, serían muy unidiomáticos para std.

Puede que no sea corto, pero así es exactamente como lo leo. Dado que los rasgos son verbos, debe transformarlo manualmente en un adjetivo si desea usarlo para describir un tipo en lugar de una operación sobre un tipo; std::iter::Iterator es implementado por algo que es iterable, std::io::Seek es implementado por algo que es buscable, std::pin::Unpin es implementado por algo que no se puede fijar.

@withoutboats algún problema específico con algunos de los otros nombres que no tienen el problema del verbo, por ejemplo, Escape o EscapePin ? Entendido que esta discusión ha sucedido antes, pero presumiblemente hay muchos más ojos puestos en esto ahora, así que no estoy seguro de que sea una repetición completamente redundante ...

Una cosa que creo que es cierta es que es desafortunado que Pin y Unpin puedan leerse como un par (algunos tipos son "pin" y otros son "despin"), cuando Pin Se supone que sustantivo , no un verbo . El hecho de que Pin no sea un rasgo, con suerte, aclara las cosas. El argumento a favor de Pinning tiene sentido, pero aquí nos encontramos con el problema de la longitud del nombre. Especialmente porque los receptores de métodos tendrán que repetir self dos veces, terminamos con muchos caracteres: self: Pinning<&mut Self> . No estoy convencido de que Pinning<P> sea ​​un total de cuatro caracteres con una claridad superior a Pin<P> .

@tikue, la terminología de "escape" está mucho más sobrecargada que la fijación, creo, en conflicto con conceptos como el análisis de escape.

Además, ya tuvimos esta conversación, tomamos la decisión de usar Pin y Unpin, y ninguno de los argumentos presentados en este hilo son nuevos.

Esto me molesta: ¿el informe de experiencia de la comunidad no es deseado? Personalmente, no vi un problema claro con Unpin hasta algunos de los otros comentarios en este hilo, así como la yuxtaposición con Pinned doble negativo.

Rust no realiza análisis de escape, por lo que no estoy seguro de verlo como un problema real.

: bell: Esto ahora está entrando en su período de comentarios final , según la revisión anterior . :campana:

Todavía me gustaría ver mejoras en la documentación como se describe en https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891 antes de que esto llegue :)

Abrí https://github.com/rust-lang/rust/pull/55992 para agregar la documentación sugerida arriba y renombrar Pinned a PhantomPinned .

Creo que Pinned (y PhantomPinned ) fomenta la conceptualización de un valor "fijado" como uno que no puede salir de un Pin , lo que significa que muchos valores en un Pin (aquellos cuyos tipos impl Unpin ) no están "fijados".

Eso parece confuso. Me resulta más fácil conceptualizar todos los valores en un Pin como anclados mientras están en Pin , y si el anclaje es permanente o no es lo que antes se llamaba Pinned controles. Un nombre separado de Pin* evitaría la combinación de dos conceptos distintos.

PhantomNotUnpin : P

Personalmente, a mí también me disgusta mucho Desanclar como nombre, ¡tal vez porque a menudo se lo ve como impl! Despin, que dice "not-un-pin" y requiere varios ciclos cerebrales

¡Gracias! También me ha molestado Unpin durante bastante tiempo, pero no he podido precisar (je) por qué. Ahora creo que lo entiendo: es la doble negación.

Esto es completamente opuesto a mi experiencia, implementar manualmente! Desanclar significa que estás implementando una estructura autorreferencial a mano usando código inseguro, un caso de uso extremadamente especializado. Por el contrario, cualquier cosa que mantenga una estructura potencialmente desanclada detrás de un puntero tiene una implicación positiva de Desanclar. Todas las implicaciones de Unpin en std son polaridad positiva, por ejemplo.

Sin embargo, no se trata solo de implementación, también de discusión. impl !Sync es bastante raro (no solo porque es inestable), pero hablar de los tipos Sync y !Sync es bastante común. De manera similar, !Unpin surgió bastante en las discusiones de esta característica, al menos las que he tenido.

Yo también preferiría algo que exprese positivamente una propiedad ( MoveFromPin o menos). No estoy del todo convencido de la ergonomía, ya que a diferencia de Pin uno no debería tener que escribir este límite de características con tanta frecuencia.

Rust no realiza análisis de escape, por lo que no estoy seguro de verlo como un problema real.

LLVM lo hace, por lo que el análisis de escape sigue siendo bastante relevante para Rust.

La forma de resolverlo es elegir palabras que inviertan el significado de Pin / Unpin . Por ejemplo, cambie el nombre de Unpin a Relocate . Entonces !Unpin convierte en !Relocate . Eso tiene un sentido mucho más intuitivo para mí: lo leí como "Oh, los objetos de este tipo no se pueden reubicar". Otro contendiente es Movable .

No estoy seguro de cuál es la palabra opuesta que podría reemplazar a Pin , o si es necesario. Pero ciertamente podría imaginarme a los documentos diciendo algo como esto:

Un objeto fijado se puede modificar directamente a través de DerefMut si y solo si el objeto se puede reubicar en la memoria. Relocate es un rasgo automático; se agrega de forma predeterminada. Pero si habrá punteros directos a la memoria que contiene sus valores, opte por no participar en Relocate agregando impl !Relocate en su tipo.

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

Esto tiene mucho más sentido intuitivo para mí que Unpin .

Abrí # 55992 para agregar la documentación sugerida arriba

Sin embargo, esto solo agrega una parte de lo que se sugirió en https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891.

Me gusta la sugerencia MoveFromPin . Relocate también es bueno, pero tal vez no esté asociado con Pin suficiente. Se podría volver a entenderlo como un tipo inmóvil (que no lo es). RelocateFromPin nuevamente sería bueno.

Escape ing es algo que también está asociado, por ejemplo, en Swift con cierres, y si se llaman dentro o fuera de la cadena de llamadas actual. Eso suena engañoso.

No veo ningún problema con nombres más largos siempre que ayude a la claridad.

FWIW También me gustaría votar para cambiar el nombre de Unpin a algo "positivo" como Relocate o MoveFromPin (o incluso más detallado pero quizás un poco más preciso MayMoveFromPin ).

Estoy de acuerdo en que el doble negativo de !Unpin o solo Unpin ha sido confuso para mí históricamente, y algo enmarcado en positivo "esto puede moverse a pesar de estar dentro de Pin ", creo ayudaría a aliviar algo de confusión!

FWIW Inicialmente pensé lo mismo sobre Unpin , pero cuando fui a usarlo en mi opinión, tenía sentido, no es realmente una doble negación, ya que la operación que está buscando es la capacidad de Unpin (entrar y sacar algo de Pin libremente) no la capacidad de mantener las cosas en un pin Es lo mismo que MoveFromPin , pero redactado de manera diferente. Preferiría un nombre que no haga que la gente piense que "no es Pin " o algo así, pero IMO MoveFromPin y otros son demasiado prolijos. UndoPin ? ( FreePin para la multitud de Haskell?)

Sigo pensando que !Unpin lee raro - he entrenado mi voz interior para tratarlo más como "¡No desanclar esto!" en lugar del habitual "No implementa Unpin ", pero requiere cierto esfuerzo.

¿Qué pasa con !Pluck ?

@runiq

en lugar del habitual "No implementa Desanclar"

Mencioné esto en mi comentario anterior, pero creo que esta es una buena manera de decirlo: Unpin es la operación de introducirlo y sacarlo de Pin<C<_>> . Las cosas que no implementan Unpin no proporcionan esa capacidad.

@cramertj Me gusta bastante UndoPin

@cramertj Estoy de acuerdo en que no soy un gran admirador de las alternativas propuestas hasta ahora, pero personalmente preferiría la palabra MoveFromPin sobre Unpin . Es un buen punto que no es un doble negativo, pero al leerlo (como alguien que aún no ha trabajado mucho con él) me sigue haciendo tropezar que es un doble negativo. Sigo intentando leer el prefijo "Un" como negativo ...

Tengo curiosidad, pero @cramertj u otros, ¿creen que hay una buena idea de cuánto surge ergonómicamente el límite de Unpin ? ¿Es super raro? ¿Es super común? ¿Lo suficientemente común como para ser un dolor si es doloroso escribir?

Para un nombre corto y dulce, personalmente me gusta la idea Relocate , pero para un nombre más largo y con más palabras, pero está bien porque no lo escribes tanto, me gusta MoveFromPin . Personalmente, creo que independientemente de la ergonomía de escribir el nombre, ambos son mejores que Unpin

Tengo curiosidad, pero @cramertj u otros, ¿creen que hay una buena idea de cuánto surge ergonómicamente el enlace Unpin? ¿Es super raro? ¿Es super común? ¿Lo suficientemente común como para ser un dolor si es doloroso escribir?

En mi experiencia, hay dos casos en los que Unpin realmente aparece en el código de usuario:

  1. Unpin límites de Unpin surgen porque en realidad necesitas mover un futuro mientras lo sondeas (por ejemplo, cosas como algunas API seleccionadas).
  2. Más comúnmente, si su futuro es genérico solo sobre algo que no sea futuro, generalmente desea agregar una implementación incondicional de Unpin , porque no importa si esos otros tipos son Unpin ya que nunca pin proyecto a ellos.

Un ejemplo del segundo caso es si tiene algún tipo de búfer sobre el que es genérico (por ejemplo, T: AsRef<[u8]> ). No necesita fijarlo para sacarle el corte, por lo que no le importa si implementa Unpin o no, por lo que solo quiere decir que su tipo implementa incondicionalmente Unpin para que pueda implementar Future ignorando la fijación.

Unpin es bastante común de ver como un límite-- select! , StreamExt::next y otros combinadores requieren que los tipos en los que operan sean Unpin .

@withoutboats Tengo curiosidad sobre su punto 2; ¿Creemos que impl Unpin es algo que la gente tendrá que recordar implementar a menudo? Similar a cómo los autores de bibliotecas de hoy en día a menudo olvidan #[derive(Debug)] o impl std::error::Error para sus tipos personalizados, lo que dificulta el uso de esas bibliotecas

Desanclar es un rasgo automático. La única vez que tendrá un tipo que no implemente desanclar es cuando ese tipo opte explícitamente por no hacerlo. (O contiene un campo que opta por no anclar).

Entiendo que ese es el caso. Y asumí que ese sería, con mucho, el caso más común, por lo que me sorprendió que @withoutboats siquiera mencionara el segundo punto. Me sugiere que puede ser más común de lo que pensé originalmente (aunque quizás solo cuando implementes tus propios futuros), así que tengo curiosidad sobre la frecuencia de ese tipo de casos de uso :)

@alexcrichton Tengo curiosidad, pero @cramertj u otros, ¿crees que hay una buena idea de cuánto surge ergonómicamente el enlace Unpin? ¿Es super raro? ¿Es super común? ¿Lo suficientemente común como para ser un dolor si es doloroso escribir?

Había escrito una publicación larga sobre mi experiencia de portar mi código a Futures 0.3 (que usa Pin ). No es necesario que lo lea, el resumen es:

La mayoría de las veces, no necesita preocuparse por Unpin en absoluto, porque Unpin se implementa automáticamente para casi todos los tipos.

Entonces, la única vez que debe preocuparse por Unpin es:

  1. Tiene un tipo que es genérico sobre otros tipos (por ejemplo, struct Foo<A> ).

  2. Y desea implementar una API de fijación (por ejemplo, Future / Stream / Signal ) para ese tipo.

En ese caso, necesita usar esto:

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

O esto:

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

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

Esa suele ser la única situación en la que se necesitan Unpin . Como puede ver, eso generalmente significa tener que usar Unpin ~ 2 veces por tipo.

En el caso de no combinadores, o en el caso de tipos que no implementan Future / Stream / Signal , no es necesario utilizar Unpin en absoluto.

Así que diría que Unpin aparece muy raramente, y solo aparece realmente en la situación de crear Future / Stream / Signal combinadores.

Así que estoy firmemente a favor de un nombre como MoveFromPin . La fijación es una característica de nicho con la que la mayoría de las personas nunca necesitan lidiar, por lo que no deberíamos optimizar demasiado la longitud del nombre.

Creo que los beneficios cognitivos (evitar la doble negación) son mucho más importantes que salvar algunos personajes en situaciones raras.

¡Especialmente porque la fijación ya es bastante difícil de entender! Así que no lo hagamos innecesariamente más difícil.

@jonhoo, ¿creemos que impl Unpin es algo que la gente tendrá que recordar implementar con frecuencia? Similar a cómo los autores de bibliotecas hoy en día a menudo olvidan #[derive(Debug)] o impl std::error::Error para sus tipos personalizados, lo que dificulta el uso de esas bibliotecas.

No creo que sea posible olvidarse de impl Unpin , porque si el autor lo olvida, obtendrá un error del compilador, lo que evitará que su caja se publique en primer lugar. Así que no es como #[derive(Debug)] en absoluto.

La situación de la que habla @withoutboats es solo para personas que implementan Future / Stream / Signal combinadores, no afecta a nadie más (en particular, no afecta usuarios intermedios de la biblioteca).

(Creo que la doble negación es parte de esto, y tal vez simplemente más negación de lo estrictamente necesario también lo es, pero siento que no es toda la historia (tratando de introspectar aquí) ... "Unpin" es un poco ¿Es metafórico? ¿Indirecto? Tiene sentido cuando se explica, y dado que "fijar" en sí mismo ya es una metáfora, no está claro por qué mi cerebro tendría problemas para llevar más allá de esa metáfora, pero, no obstante, mi cerebro encuentra el significado de "desanclar" por alguna razón oscura y difícil de sujetar firmemente).

Supongo que tienes razón @cramertj , en realidad no es una doble negación en el sentido habitual, pero al igual que @alexcrichton y @glaebhoerl, sigo está negando la fijación aquí, aunque solo sea como verbo.

@withoutboats Tengo curiosidad sobre su punto 2; ¿Creemos que impl Unpin es algo que la gente tendrá que recordar para implementar con frecuencia? Similar a cómo los autores de bibliotecas hoy en día a menudo olvidan # [derive (Debug)] o impl std :: error :: Error para sus tipos personalizados, lo que dificulta el uso de esas bibliotecas.

¡Absolutamente no! Si un usuario está implementando manualmente un Future que es genérico sobre un no futuro, probablemente quiera poder mutar el estado en su implementación futura sin escribir código inseguro, es decir, tratar Pin<&mut Self> como &mut self . Recibirán errores que indiquen que MyFuture<T: AsRef<[u8]>> no implementa Unpin . La mejor solución a este problema es implementar Unpin . Pero el único impacto que esto tiene es en el usuario que intenta implementar Future , y es imposible que lo olvide porque su código no se compilará.

La situación de la que habla @withoutboats es solo para las personas que implementan combinadores Future / Stream / Signal, no afecta a nadie más (en particular, no afecta a los usuarios intermedios de la biblioteca).

Estoy hablando específicamente de futuros manuales genéricos no combinatorios , que solo deberían tener implicaciones generales de Unpin incluso si sus genéricos no son Unpin .

Hice un diagrama de flujo para la pregunta "¿Qué hago con la fijación cuando estoy implementando un flujo / futuro manual?"

pinning-flowchart

Para ir en bicicleta un poco más, hoy en el almuerzo se me ocurrió LeavePin . Lleva el mismo tono que escape sin la semántica engañosa implícita.

¿Existen interacciones interesantes entre la especialización implícita y los pines, como ocurre con las vidas?

La subcadena "specializ" aparece un poco en el seguimiento de la discusión del problema , pero no es concluyente. Pin RFC o RFC de especialización debe mencionar explícitamente esta interacción, ya sea como "Se verificó que está bien" o "Se necesita más investigación para juzgarlo seguro".

@vi no solo no hay una interacción posible que haya una interacción sólida. Las API pin están estrictamente definidas en la biblioteca estándar y no involucran nuevas características de lenguaje, un usuario puede definirlas con la misma facilidad en bibliotecas de terceros. Si una característica del lenguaje no es sólida frente a este código de biblioteca existente, no es un período sólido, porque cualquier usuario podría escribir este código de biblioteca hoy y compilará bien.

Si una característica del lenguaje no es sólida frente a este código de biblioteca existente, no es un período sólido, porque cualquier usuario podría escribir este código de biblioteca hoy y se compilará bien.

No es que deba tener relación con pin ... pero no creo que sea tan simple como eso.

Si una característica de la biblioteca, al usar unsafe , utiliza construcciones de lenguaje cuyo comportamiento aún no se ha especificado (por ejemplo, &packed.field as *const _ , o hacer varias suposiciones sobre ABI), entonces si los cambios de lenguaje adicionales invalidan las suposiciones de esas bibliotecas, entonces creo que son las bibliotecas las que no son sólidas y no el idioma cambia. Por otro lado, si los cambios de idioma hacen que el comportamiento definido no sea sólido, entonces es culpa de los cambios de idioma. Por lo tanto, compilar bien no es una condición suficiente para la solidez de una biblioteca frente a cambios inseguros y de idioma.

+1 a MoveFromPin o similar

Si hace la pregunta "¿Cuándo debo anular la implementación de Unpin para mi propio tipo?", La respuesta es mucho más clara si en su lugar pregunta "¿Cuándo debo anular la implementación de MoveFromPin para mi propio tipo?"

Lo mismo con "¿Debo agregar Desanclar como un rasgo enlazado aquí?" vs "¿debo agregar MoveFromPin como un rasgo enlazado aquí?"

¡Fijar no lo es!

Lo siento si esto se mencionó en alguna parte, pero solo eché un vistazo a la gran cantidad de discusión que ha habido sobre Pin aquí, en el tema de implementación y en el tema de RFC.

¡El óxido alguna vez lo habrá hecho! Ciertamente puedo ver casos de uso para tal cosa (diablos, vine buscando Pin porque estaba buscando una manera de no dispararme en el pie con tipos que no se pueden mover). Si la respuesta es sí, ¿cómo interactuaría eso con Pin? ¿La existencia de Pin lo haría más difícil de lo que ya es agregar!

El período de comentarios final, con la disposición de fusionarse , según la revisión anterior , ahora está completo .

Debería haber una preocupación no resuelta en torno al nombre de Unpin.

Como señaló @RalfJung , # 55992 también solo agrega una pequeña cantidad de la documentación adicional solicitada en https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891 y en otros lugares. Sin embargo, no sé si eso es motivo para no fusionarse todavía.

¿Por qué mi método drop () ahora vuelve a tomar & mut self, en lugar de un Pin?

Bueno, drop () es antiguo, existe desde Rust 1.0, por lo que no podemos cambiarlo. Nos encantaría hacer que solo tomara Pin <& mut Self>, y luego los tipos Unpin podrían obtener su & mut como lo hacen ahora, pero ese es un cambio no compatible con versiones anteriores.

Me preguntaba si sería posible implementar este cambio de una manera compatible con versiones anteriores. AIUI hasta que agreguemos Unpin (y la gente puede especificar !Unpin ) todos los tipos implementan Unpin . Entonces podríamos agregar un rasgo:

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

y luego implica este rasgo para todos los tipos Unpin , que hasta que la gente pueda optar por no participar es de todos los tipos. Algo como:

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

Luego tendríamos que el compilador insertara llamadas a DropPinned::drop lugar de Drop::drop . Básicamente, el rasgo DropPinned convierte en el elemento de idioma en lugar de Drop . AFAICS esto sería compatible con versiones anteriores si y solo si este mecanismo se introdujera al mismo tiempo que Unpin .

Debería haber una preocupación no resuelta en torno al nombre de Unpin.

@tikue Ningún miembro del equipo de libs presentó una inquietud con rfcbot antes o durante el FCP, y no creo que ninguno de los argumentos sobre Unpin nuevo o novedoso para este hilo, por lo que normalmente nuestro proceso consideraría el actual nombrar para ser finalizado. Obviamente, si alguien tiene inquietudes, debe hablar, pero el objetivo de tener casillas de verificación de equipo seguidas de FCP es garantizar que todos se sientan cómodos estabilizando la API según lo propuesto.

@cramertj Estoy un poco confundido. Varias personas han hablado. Cuando pedí referencias sobre dónde más se habían planteado y resuelto los argumentos sobre el nombre de Unpin , me señalaron https://internals.rust-lang.org/t/naming-pin-anchor- move / 6864 , que por lo que puedo ver también tiene gente quejándose sobre el nombre de Unpin , y sin contraargumentos reales. El RFC original en https://github.com/rust-lang/rfcs/pull/2349 tampoco tiene mucha razón de ser por qué las alternativas propuestas a Unpin son peores. Incluso en este hilo, parece que los únicos argumentos en contra que realmente surgen son "es más corto" y "es técnicamente correcto". ¿Puede señalar una discusión concreta donde los nombres alternativos que son más fáciles de entender (como MoveFromPin ) se discuten y rechazan?

He explicado en comentarios anteriores por qué creo que se han planteado nuevas perspectivas en este hilo. He estado siguiendo las discusiones de la API de PIN bastante de cerca desde el inicio y no recuerdo haber visto nunca el problema del doble negativo mencionado antes de este hilo.

@tikue He mencionado y visto el problema del doble negativo planteado varias veces, y el nombre preciso de Unpin se ha planteado como un problema muchas veces y se ha resuelto constantemente a favor de Unpin . Como dije antes, si alguien en el equipo de bibliotecas quiere registrar una objeción (tardía), entonces estoy bien respondiendo a eso, pero todos firmaron la propuesta de estabilización anterior, que claramente no incluye el nombre de Unpin como una pregunta sin resolver: hemos discutido las alternativas, y este FCP fue el proceso para decidir que estábamos preparados para estabilizar las decisiones que se habían tomado, incluido el nombre Unpin .

@cramertj ¿Podría proporcionar un enlace al lugar donde ocurrió esa discusión? No dudo de ti, solo me gustaría ver los argumentos a favor de Unpin , porque no creo que se hayan dado aquí. Como se mencionó, las referencias que me han dado hasta ahora no brindan ninguna resolución sobre el nombre de Unpin .

@cramertj +1 a la pregunta de @jonhoo . Si hay discusiones que el equipo de libs tuvo entre ellos y que no están registradas en los canales oficiales, creo que la idea central de esas discusiones debería ser reiterada aquí. Creo que incluso hay una regla oficial según la cual las decisiones de RFC solo se pueden tomar en base a argumentos conocidos públicamente.

Creo que incluso hay una regla oficial según la cual las decisiones de RFC solo se pueden tomar en base a argumentos conocidos públicamente.

Sí, eso es cierto, y para que conste, no estoy en el equipo de bibliotecas y, por lo tanto, no he estado presente en ninguna discusión exclusiva del equipo de bibliotecas. Al examinar el hilo de RFC y el problema de seguimiento Pin , hubo numerosas ocasiones en las que se mencionó el nombre de Unpin . No veo a nadie decir específicamente las palabras "doble negativo", pero ciertamente recuerdo que " !Unpin es un doble negativo" se mencionó antes, además de la regla general de la API de nombrar rasgos para lo que puedas hacer con ellos, en lugar de lo que no puedes (como señalé anteriormente, creo que Unpin realidad sigue ambas reglas, aunque me doy cuenta de que eso requiere escuchar "Unpin" como un verbo en lugar de escucharlo como un adjetivo "not-pin", que no es intuitivo para la gente).

@wmanley Eso no funciona, por ejemplo, impl<T> Drop for Vec<T> se rompería porque no tenemos Vec<T>: Unpin . Además, se han hecho propuestas en esta línea antes, incluso en este hilo . Lea las discusiones antes de responder. Sé que es pedir mucho, pero es la única forma de evitar que se expliquen una y otra vez los mismos problemas.

Creo que incluso hay una regla oficial según la cual las decisiones de RFC solo se pueden tomar en base a argumentos conocidos públicamente.

Esto se conoce informalmente como la regla de "no justificación nueva" .

No estoy seguro de dónde publicar esto, pero ¿alguien podría echar un vistazo a https://github.com/rust-lang/rust/issues/56256 ?

En relación con # 56256, impl<T> From<Box<T>> for Pin<Box<T>> no aparece en el OP como estabilizado, pero implícitamente se volverá estable una vez que Pin haga. ¿Hay otras implementaciones de rasgos que no sean triviales y que se deban considerar para estabilizarlas? (Al escanear los documentos, todos los demás parecen ser implementaciones de delegación triviales para el puntero envuelto para mí).

Hoy hablamos de este problema en el equipo de bibliotecas. Las últimas semanas de discusión han demostrado que todavía hay algunas cosas que deberían abordarse primero, especialmente con respecto al nombre de Unpin .

Por lo tanto, no avanzaremos con la estabilización de esto por ahora (a pesar de FCP).

Sería muy apreciado si alguien pudiera reunir las ideas en este hilo y preparar una propuesta independiente para mejorar la situación de los nombres.

@Kimundi

Sería muy apreciado si alguien pudiera reunir las ideas en este hilo y preparar una propuesta independiente para mejorar la situación de los nombres.

¿Significa esto que el equipo de bibliotecas no está dispuesto a estabilizar las API actuales tal como están? Personalmente, no creo que a nadie se le haya ocurrido un nombre mejor que el conjunto actual de nombres implementado, por lo que no puedo hacer una propuesta de este tipo, pero me importa mucho ver estas API estabilizadas. así que si alguien del equipo de libs tiene un nombre que preferiría, entonces: shipit:

Bikeshed esto con un grupo de compañeros de trabajo y @anp sugirió DePin , que en realidad me gusta bastante, ya que elimina la connotación "no pin" de Unpin y enfatiza que está hablando de un tipo que puede ser Pin 'd.

@Kimundi, ¿ puede usted o alguien del equipo de libs registrar una "inquietud de fcp" explícita para eliminar esto de FCP y exponer más claramente lo que se entiende por "algunas cosas que deben abordarse"?

@rfcbot preocupa el naming-of-Unpin

No estoy seguro de si esto funciona realmente una vez que se ingresa FCP, pero me gustaría colocar una preocupación de bloqueo formal en el nombre del rasgo Unpin . El rasgo Unpin parece ser bastante crucial para la API, y el "doble negativo" (como se discutió anteriormente) me lanza a dar una vuelta cada vez que lo leo.

Ha habido un montón de comentarios sobre varios nombres, pero desafortunadamente todavía no estoy demasiado emocionado con ninguno. Mi "favorito" sigue estando en la línea de MoveFromPin o Relocate (Dios mío, hay tantos comentarios aquí que no tengo idea de cómo revisar esto).

Personalmente, estoy de acuerdo con el nombre de Pin sí, así como Pinned para un ZST que no implementa el rasgo Unpin .

Estoy completamente de acuerdo con @alexcrichton en que el nombre de Unpin es el gran punto de discusión aquí. No creo que haya ninguna preocupación técnica por lo que puedo ver sobre la función propuesta en sí (aunque hay muchos comentarios, por lo que podría haber pasado por alto algo).

Sigo pensando que Pinned es un nombre extraño para el ZST, porque algo que contiene un Pinned no está realmente fijado .. Simplemente no es Unpin . PhantomPinned (como se le cambió el nombre en # 55992) tiene el mismo problema en el sentido de que se refiere a Pin , cuando la ZST es _realmente_ aproximadamente Unpin .

También sigo pensando que los documentos necesitan más trabajo dada la sutileza de esta función, pero eso probablemente no cuente como un bloqueador.

Además, @Kimundi , estoy feliz de ver que el equipo de bibliotecas está dispuesto a dar un poco más de tiempo para resolver esto. Puede parecer un derrame de bicicletas innecesario, pero yo (y otros también lo parece) creo que es muy importante mejorar la capacidad de aprendizaje de esta función :)

De acuerdo con @jonhoo acerca de que Pinned todavía me siento raro, y que PhantomPinned no es mejor (no está fijado de ninguna manera, ni siquiera de manera fantasma). Creo que si encontramos un buen nombre para Unpin , entonces Pinned se prestará naturalmente a cambiar su nombre por Not{NewNameForUnpin} .

Realmente no creo que tengamos que dedicar más tiempo a discutir PhantomPinned ; este tipo casi nunca aparecerá para los usuarios finales. PhantomNotUnpin / PhantomNotDePin / PhantomNotMoveFromPin / etc.no lo harán más o menos común ni harán que la API sea más o menos obvia para los usuarios que ya se sienten lo suficientemente cómodos con la API para haber encontrado un uso legítimo de PhantomPinned .

Solo una idea rápida: rasgo Move y ZST Anchor .

Cada tipo es Move capaz, a menos que contenga un Anchor que haga que se adhiera a un Pin .
Me gusta cómo Anchor: !Move intuitivamente tiene mucho sentido.

No estoy sugiriendo que dediquemos tiempo específicamente a PhantomPinned , pero creo que sería poco esfuerzo mantener la mente abierta, porque es posible que lo que sea que se obtenga por Unpin funcione. igual de bien por PhantomPinned .

Cubrimos Move y explicamos por qué es inapropiado en numerosas ocasiones. Todos los tipos son móviles hasta que se fijan. De manera similar, se sugirió previamente Anchor pero no queda claro en absoluto por el nombre que se usa para optar por no participar en Unpin ing / Move ing.

@stjepang Creo que Move se descartó hace un tiempo porque algo que es !Unpin no impide que se mueva. Sólo si un tipo está por debajo de Pin y no es Unpin , está "obligado por contrato" a no moverlo.

En el comentario original @withoutboats dijo:

El contenedor Pin modifica el puntero para "fijar" la memoria a la que se refiere en su lugar

Creo que es extraño que los tipos implementen Unpin porque los valores no están "fijados", sino la memoria. Sin embargo, tiene sentido hablar de valores que son "seguros para moverse". ¿Qué pasa si cambiamos el nombre de Unpin a MoveSafe ?

Considere el rasgo UnwindSafe . Es solo un rasgo de marcador de "insinuación" y es seguro de implementar. Si deja que un !UnwindSafe cruce un límite catch_unwind (con AssertUnwindSafe ), lo "romperá" invalidando sus invariantes.

De manera similar, si tiene un !Unpin / !MoveSafe , aún se puede mover (por supuesto), pero lo "romperá" invalidando sus autorreferencias. El concepto parece similar.

El rasgo Unpin realmente solo significa MoveSafe . Me parece que no se trata de valores que se pueden mover de la memoria detrás de Pin . Más bien, se trata de valores que no se "romperán" cuando los mueva.

MoveSafe tiene el mismo problema que Move : todos los valores de cualquier tipo se pueden mover de forma segura. Solo se vuelve imposible mover un valor después de haberlo anclado.

MoveSafe tiene el mismo problema que Move : todos los valores de cualquier tipo se pueden mover de forma segura.

Correcto, pero es un significado diferente de "seguro", como en UnwindSafe . De todos modos, estaría bien con Relocate o algo por el estilo.

Para resumir, creo que el nombre del rasgo no debería ser DePin , Unpin , ni nada con "pin" en su nombre. Para mí, esa es la principal fuente de confusión. El rasgo no es realmente una "trampilla de escape" de los grilletes de Pin ; el rasgo dice que el valor no se invalidará cuando se mueva.

Solo veo Pin y Unpin como cosas totalmente separadas. :)

Siento exactamente lo contrario;). El rasgo solo es significativo con respecto a Pin, que es el único tipo que tenemos que expresa de manera significativa las restricciones sobre la movilidad del valor subyacente. Sin Pin, Unpin no tiene ningún sentido.

Me gusta pensar en fijar como poner algo en un tablón de anuncios. Si quita el alfiler de un objeto que está anclado al tablero, puede mover el objeto. Quitar el pasador es Desanclar.
Me gusta el nombre Unpin .

¡También puedo ver cómo! Unpin es una doble negación y puede causar confusión. Sin embargo, me pregunto con qué frecuencia debe escribir !Unpin .

Otro nombre que se me ocurre para Unpin es Detach . Volviendo a la metáfora del tablero de anuncios, no _Unpin_, sino _Detach_ el Pin de su objeto.

¡Creo que realmente me gusta DePin ! Hasta ahora es mi favorito: es conciso, claramente es un verbo y no un adjetivo, y !DePin parece bastante claro ("no se puede quitar").

Creo que es extraño que los tipos implementen Unpin porque los valores no están "fijados", sino la memoria.

El valor se fija a la memoria. Pero el valor también es de vital importancia aquí. Fijar memoria, para mí, es solo asegurarse de que permanezca desreferenciable o algo así, pero no es violado por mem::swap . Anclar un valor a la memoria consiste en no mover ese valor fijado a ningún otro lugar, que es exactamente de lo que se trata Pin .

¡También puedo ver cómo! Unpin es una doble negación y puede causar confusión. Sin embargo, me pregunto con qué frecuencia debes escribir.

Te escucho cuando dices que no encuentras el nombre confuso. Yo, y muchos otros en este hilo, nos hemos confundido con el nombre.

No tengo el código a mano, pero la primera vez que intenté usar futuros quería que una función devolviera un impl Future<Output=T> . No recuerdo lo que pasó, pero inmediatamente recibí un error de compilador retorcido quejándose de T y Unpin. La pregunta para la que necesitaba una respuesta era "¿Es seguro restringir T para que solo sea Unpin". Y eso me llevó a mirar hacia el abismo durante aproximadamente 2 horas angustiosas.

"Oh, está bien, si eso es lo que significa Pin. Entonces, Unpin significa ... ¿Caja? ¿Por qué es un rasgo?"

"Espera, impl !Unpin ? ¿Por qué no es impl Pin ?"

"Bien, Pin y Unpin ... no son opuestos. Son cosas totalmente diferentes. Espera, entonces, ¿qué significa Unpin otra vez? ¿Por qué se llama así?"

"¿Qué diablos significa !Unpin ? ¿No ... la otra cosa, no un alfiler? Hrrrgh"

Aún así, la única forma en que esto tiene sentido en mi cabeza es reemplazando 'desanclar' con 'reubicable'. "El tipo no se puede reubicar en la memoria" tiene mucho sentido. Y, sin embargo, incluso sabiendo lo que hace Pin , "escribir no desanclar" me deja confundido.

Cambiar el nombre de Unpin a Relocatable (o Relocate ) obtiene mi voto 🗳. Pero creo que cualquiera de las otras sugerencias es mejor que Unpin.

El rasgo solo es significativo con respecto a Pin, que es el único tipo que tenemos que expresa de manera significativa las restricciones sobre la movilidad del valor subyacente. Sin Pin, Unpin no tiene ningún sentido.

Para poner un punto en esto, las garantías en torno al pin se refieren completamente a ciertos comportamientos , no a tipos . Por ejemplo, una función generadora que produce () podría implementar trivialmente FnOnce reanudando repetidamente. Si bien este tipo podría no implementar Unpin , porque sus estados de rendimiento podrían ser autorreferenciales, su interfaz FnOnce (que se mueve sola) es completamente segura porque aún no se ha fijado cuando FnOnce it, que es necesario para llevarlo a un estado autorreferencial. Unpin trata específicamente sobre los tipos de comportamientos que se afirman que son seguros una vez que el tipo ha sido anclado (es decir, moverlo), no sobre alguna propiedad intrínseca del tipo.

Irónicamente, vine aquí para comentar que si bien el nombre de Unpin definitivamente se ha debatido en el pasado, el debate al respecto que recuerdo haber presenciado fue cuando decidimos reemplazar Move con Unpin , que, iba a decir, es una mejora inequívoca . Y a menudo uno solo se da cuenta más tarde de que, si bien el diseño actual es una mejora definitiva con respecto a lo que venía antes, todavía queda algo de espacio para mejorar aún más . De cuál es la dirección en la que venía esto.

Desanclar trata específicamente sobre los tipos de comportamientos que se afirma que son seguros una vez que el tipo ha sido fijado (es decir, moverlo), no sobre alguna propiedad intrínseca del tipo.

El comportamiento cuando se fija es una propiedad intrínseca del tipo, al igual que el comportamiento / invariante cuando se comparte. Entré en esto con mucho más detalle en esta publicación de blog .

@RalfJung estamos hablando entre nosotros. Hay una confusión. Veo mucho que los tipos autorreferenciales "no se pueden mover"; esto no es exacto, tienen ciertos estados en los que pueden entrar durante los cuales no se pueden mover, pero mientras se encuentran en otros estados, es perfectamente seguro. para moverlos (y nuestras API se basan en la capacidad de moverlos, por ejemplo, para aplicarles combinadores o para moverlos a Pin<Box<>> ). Estoy tratando de aclarar que no es el caso de que estos tipos "no se puedan mover".

Ah, sí, entonces estoy de acuerdo. Un tipo !DePin no siempre está anclado , y cuando no lo está, se puede mover como cualquier otro tipo.

@cramertj @withoutboats una cosa que aún no he podido resolver, ¿están en contra de cambiar el nombre de Unpin ? No parece que todos sientan que están de acuerdo en que se debe cambiar el nombre, pero no estoy seguro de si está en contra.

Personalmente, no creo que el principal problema aquí radique en el nombre Unpin y que "si pudiéramos cambiarle el nombre, todo sería intuitivo". Si bien el cambio de nombre puede ayudar un poco (y Relocate / DePin parece agradable aquí ...), creo que la principal complejidad proviene del concepto de fijar en sí; ciertamente no es uno de los conceptos más fáciles de entender o explicar; ni siquiera cerca.

Por lo tanto, creo que la documentación del módulo core::pin debe reforzarse _ significativamente_ y es necesario

@alexcrichton No estoy en contra de cambiar el nombre, no. Creo que Unpin ya está bien, pero estaría bien con DePin , por eso lo sugerí. RemoveFromPin es el más obviamente "correcto", pero detallado hasta el punto de la sal sintáctica, así que creo que me opondría a ese nombre específicamente. Sin embargo, me opongo a posponer la estabilización indefinidamente hasta que encontremos un nombre en el que todos estén de acuerdo que es el mejor; creo que la API tiene algunas complejidades inherentes que no mejorarán ni empeorarán dramáticamente debido al nombre de Unpin rasgo. Realmente me gustaría avanzar hacia la estabilización pronto para evitar más cambios en el código, los documentos y las discusiones en torno a futures_api (y así podemos comenzar a poner las piezas en su lugar para estabilizar futures_api sí mismo). ¿Quizás deberíamos programar una reunión de capital de riesgo dedicada a la determinación de nombres para que todos los que tengan una opinión puedan presentar su solución y podamos tener una oportunidad de mayor ancho de banda para resolver esto?

Mi voto es std::pin::Reloc (Reubicar)

Tengo dos situaciones muy hipotéticas (bueno, en realidad solo una, pero dos ejecuciones diferentes) de las cuales no me importaría haber declarado explícitamente si está permitido o no.

Unpin indica que es trivial hacer la transición de todos los estados de T (donde T: Unpin ) mientras se encuentra en el estado de tipo fijo (espero recordar correctamente ese término desde una de las publicaciones del blog de Pin puede repartir &mut T .

Así que supongamos que quiero crear un tipo Woof para el que siempre es posible hacer la transición de todos los estados de Woof mientras estoy en el estado de tipo anclado al tipo no anclado- estado, pero no es trivial hacerlo y por lo tanto no puede implementar Unpin , ¿estaría permitido que Woof tuviera una función como fn unpin(self: Pin<Box<Woof>>) -> Box<Woof> ?

Lo mismo para un tipo Meow para el que solo a veces es posible hacer la transición de anclado a no anclado y una función como fn unpin(self: Pin<Box<Meow>>) -> Result<Box<Meow>, Pin<Box<Meow>>> .

Mis 2 cts para el desguace de bicicletas:

Si lo entiendo correctamente, lo que Unpin realmente significa es " Pin no me afecta".

¿Qué tal algo como BypassPin o IgnorePin ?

Así que supongamos que quiero crear un tipo Woof para el que siempre es posible hacer la transición de todos los estados de Woof en el estado de tipo fijo al estado de tipo no fijo, pero no es trivial hacerlo y por lo tanto, si no se puede implementar Unpin, ¿se permitiría que Woof tuviera una función como fn unpin (self: Pin>) -> Caja?

Sí, eso debería ser posible. Por ejemplo, si Woof es un elemento de una lista vinculada intrusiva, podría proporcionar una función para eliminar el elemento de la lista y eliminar el Pin al mismo tiempo (argumentando que para no -enqueued Woof s, los dos estados de tipo son equivalentes, por lo que podemos eliminarlo).

Relocate tiene el mismo problema que RePin para mí, podría implicar una operación que permite mover un valor de una ubicación anclada a otra, sin anular el valor por completo. Es una connotación menos fuerte que la que tiene RePin , pero aún parece un poco confusa.

podría implicar una operación que permite mover un valor de una ubicación anclada a otra, sin desanclar el valor por completo.

Si bien no se trata exactamente de eso, tampoco es del todo incorrecto: para la mayoría de los casos de uso, mover un valor de una ubicación fija a otra es tan catastrófico como usarlo libremente. En realidad, dado que cualquiera puede crear un Pin<Box<T>> , ni siquiera veo por qué está haciendo una distinción fundamental aquí.

Para la mayoría de los casos de uso, mover un valor de una ubicación fija a otra es tan catastrófico como usarlo libremente.

Sí, es por eso que tener un rasgo que agregue esa operación a un tipo podría ser útil (esto no podría ser un rasgo de marcador como Unpin ya que puede necesitar hacer cosas como actualizar las referencias internas). No estoy proponiendo que agreguemos algo como esto ahora, solo que es algo que podría ver que se proporcionará en el futuro (ya sea en std o en un tercero) que podría terminar con una confusa superposición de nombres.

No conozco ningún caso de uso sólido para él ahora, pero he pensado en usar algo así con colecciones ancladas.

Ok, aquí tienes algunas ideas. Inicialmente me preguntaba si podríamos tener un nombre como PinInert o PinNoGuarantees ya que esa es también la descripción, pero pensando en eso lo que realmente quiero es un rasgo que describa una acción en lugar de una propiedad, o al menos hace que sea mucho más fácil de pensar en mi cabeza.

Un problema con Unpin (no estoy seguro de por qué) es que parece que no puedo entender que el significado pretendido es "la acción de quitar de un alfiler, desanclar, es seguro. ". El rasgo tal cual transmite acción "el acto de desanclar", pero parece que no puedo entenderlo del todo cuando lo leo. Se siente extraño tener Pin<T: Unpin> porque si está "desanclado", ¿por qué está en un Pin?

Me pregunto si un nombre como CanUnpin podría funcionar. Mucho de esto no se trata de una garantía sólida de una forma u otra (implementar Unpin no significa que lo quitará de un pin, solo significa que puede quitarlo de un pin). ¿Como suena eso? ¿Es CanUnpin suficientemente legible para otros? ¿Lo suficientemente corto?

(tener el prefijo Can transmite el verbo mucho más fácilmente también, y dice que puedes quitarlo de un pin pero no necesariamente siempre lo harás).


Como otra tangente no relacionada, una cosa que olvidé mencionar antes (¡lo siento!) Es que no creo que todos los métodos anteriores deban ser necesariamente métodos inherentes. Ya hemos tenido montones y montones de errores relacionados con la inferencia y los métodos de conflicto, y agregar nombres muy comunes como as_ref y as_mut en tipos que también implementan Deref parece un problema esperando que suceda.

¿Ocurren estas operaciones con suficiente frecuencia como para justificar la ubicación de riesgo para colocarlas? (de forma inherente) ¿O se utilizan lo suficientemente raramente como para que se pueda eliminar la ruta segura de las funciones asociadas?

Dado que aparentemente tengo skin en este juego ahora: CanUnpin parece muy cercano en espíritu a Unpinnable o algo similar, y mi impresión siempre ha sido que la comunidad desaprueba ese tipo de modificadores en nombres de rasgos, ya que la mayoría de los rasgos describen la acción que puede realizar el tipo. El implícito es un "puede" o "-able" implícito en ese sentido. Independientemente de lo que se decida (y el nombre no importa en la medida en que se indica aquí IM-not-so-HO - muy pocos usuarios probablemente tendrán que escribir esto), animo a todos a que sientan cierta urgencia por resolver las preguntas aquí. Estoy emocionado por esta estabilización, ¡y sé que muchas personas lo están!

@alexcrichton

Un problema con Unpin (no estoy seguro de por qué) es que parece que no puedo entender que el significado deseado es "la acción de quitar de un alfiler, desanclar, es seguro. ".

¿Qué pasa si piensa en T: Unpin como " T es inmune a Pin "? Entonces, si tiene Pin<T: Unpin> , solo significa que tenemos un valor dentro de Pin que es inmune a la fijación, por lo que la fijación es aquí efectivamente discutible.

O en otras palabras: Unpin neutraliza Pin . Para ser honesto, después de tanta discusión he internalizado esto y ahora tiene sentido. 😆

Mucho de esto no se trata de una garantía dura de una forma u otra (implementar Unpin no significa que lo quitará de un pin, solo significa que _ puede_ quitarlo de un pin).

Un contrapunto a esto es el rasgo Send . Esto no quiere decir que se va a enviar el valor, sólo significa que se puede enviar.

@anp Estoy de acuerdo en que CanUnpin no es un gran nombre, ¡estoy tratando de hacer todo lo posible para encontrar un nombre mejor! Sin embargo, parece que pocos todavía sienten que esto debe cambiarse de nombre, ya que todas las sugerencias parecen ser rechazadas básicamente por las mismas razones que siento que Unpin necesita un cambio de nombre en primer lugar.

También como recordatorio, cualquier estabilización se monta en los trenes como todos los demás cambios, y con un lanzamiento la próxima semana, esto definitivamente no entrará en ese lanzamiento y será un candidato para el próximo lanzamiento. Eso significa que tenemos 7 semanas, casi dos meses, para aterrizar todo esto y asegurarnos de que llegue lo antes posible. Si bien estoy de acuerdo en que la urgencia es necesaria, es sólo "no nos olvidemos de esta urgencia", no "resolvamos esto antes del lunes".

@stjepang ¡ Yo también me he acostumbrado a Unpin ahora después de pensarlo tanto! Todos los nombres alternativos en este punto parecen bastante mediocres, así que estoy llegando al punto en el que me conformaría con una mejor documentación. Idealmente, me gustaría encontrar a alguien que no sepa mucho sobre las Pin apis para leer dicha documentación después, sin embargo, para verificar que sea suficiente para aprender.

También me gustaría seguir adelante y bloquear formalmente la estabilización en la documentación mejorada, ya que se siente especialmente crítico para este problema.

@rfcbot se preocupa por mejorar-la-documentación

Concretamente, lo que creo que necesita una mejor documentación es:

  • Más prosa sobre cada método sobre por qué es seguro y / o por qué no es seguro. Por ejemplo, el contrato de la implementación de P DerefMut "comportándose razonablemente" no está documentado en Pin::new_unchecked .
  • Cada función unsafe debe tener un ejemplo de código de por qué no es segura, o al menos una explicación clara de una secuencia de pasos que pueden salir mal.
  • Siento que los documentos del módulo pueden entrar en más detalles más allá de lo que es Pin y lo que significa. Creo que se beneficiarían de información como "cómo estos no son tipos de inmuebles generales sino una forma de eso" o "cómo se usa Pin en la práctica, por ejemplo, con futuros".
  • Me gustaría ver un ejemplo con genéricos y Unpin en la documentación o en los ejemplos de código. Por ejemplo, parece que muchos combinadores en futuros tienen que manejar esto, y de manera similar, algunos combinadores lo requieren específicamente. Algunos ejemplos de casos de uso de cómo se trabaja con genéricos y cómo funcionan pueden ayudar a comprender bastante Unpin .

¡También tengo curiosidad por saber si otros tienen elementos concretos que pueden ser procesados ​​para agregar a la documentación también!

A continuación, también me gustaría bloquear formalmente la pregunta self con as_ref , pero sospecho que esto se resolverá rápidamente. (nuevamente, lo siento por no recordar mencionar esto antes)

@rfcbot se refiere a los métodos

Esto propone que as_ref , as_mut , get_ref , get_mut , into_ref y set sean todos métodos en el Pin tipo. La propuesta menciona que no hacemos esto para los punteros inteligentes debido a conflictos de métodos, pero cita que la experiencia muestra que la API Pin implementada hoy no es consistente y, a menudo, termina entorpeciendo.

Sin embargo, estos nombres de métodos son nombres muy cortos y dulces, y es bastante común encontrarlos en todo el ecosistema. El equipo de bibliotecas se ha encontrado con una serie interminable de errores en el pasado en los que agregamos implementaciones de rasgos y demás, lo que provoca choques con los métodos de rasgos existentes en varias cajas. Estos métodos parecen especialmente de alto riesgo debido a sus nombres. Además, no está claro si alguna vez podríamos agregar más métodos a Pin en el futuro, ya que incluso si los nombres ahora terminan funcionando, cualquier nombre futuro agregado tiene un alto riesgo de colisión.

Me gustaría estar seguro de que reconsideramos esta divergencia de la convención, personalmente estoy particularmente preocupado por las consecuencias y creo que sería genial si pudiéramos encontrar un término medio que evite estos peligros.

Y mientras estoy en eso, he notado una última cosa, y nuevamente creo que esto se resolverá bastante rápido

@rfcbot preocupación box-pinnned-vs-box-pin

¿Se ha considerado el nombre Box::pin para Box::pinned ? Con la existencia de Pinned y / o PhantomPinned , parece que sería genial si el nombre pudiera coincidir con el tipo de retorno.

@alexcrichton ¿ MoveFromPin como opción ? Veo que antes te mostrabas favorable (y a muchas personas también parecía gustarles). Para que conste, las objeciones directas que podría recordar, o encontrar rápidamente con Ctrl + F-ing, son que "los nombres que son frases enteras ... serían muy unidiomáticos" (@withoutboats) y que es "demasiado prolijo" ( @cramertj).

Tengo que admitir que las motivaciones en la línea de "me he acostumbrado a ... ahora después de tanto pensarlo" me ponen bastante incómodo. Los seres humanos pueden acostumbrarse y racionalizar post-hoc esencialmente cualquier cosa. No importa cuál sea el nombre, sería una gran sorpresa si no acabáramos acostumbrándonos a él. (Esta es la razón por la que obviamente elegimos nombres subóptimos como existential type como temporales en otros casos, para tratar de evitar que se vuelvan permanentes).

Privilegiar accidentalmente las perspectivas de usuarios experimentados (¡lo que somos todos!) Sobre otros que se acercarán a cosas con cerebros más frescos es el tipo de error que creo que debemos estar siempre atentos a cometer, por más difícil que sea .

Si bien ciertamente no es el estilo de la biblioteca estándar, también siento que CanUnpin alguna manera deja mucho más claro cómo encaja esa pieza en particular con las otras.

Es imposible inventar un nombre que describa el significado de Unpin para un principiante sin leer la documentación. Send y Sync también son difíciles de entender para un principiante, pero se ven bonitos y concisos, así como Unpin . Si no sabe qué significan estos nombres, debe leer sus documentos.

@valff De hecho, es correcto, sin embargo, hay bastantes personas que han leído los documentos y entienden cómo funciona la fijación, sin embargo, el nombre Unpin todavía les causa una dificultad mental significativa, mientras que los otros nombres causan menos.

@glaebhoerl oh siento aclarar que personalmente soy más fanático de MoveFromPin o CanUnpin que del actual Unpin . ¡Solo intento ayudar a llegar a una conclusión!

Intenté comprender la propuesta actual, tal vez este tipo de informe de progreso también pueda arrojar algo de luz adicional sobre la denominación.

  • Extender las garantías de Pin a una dirección estable hasta que Drop imponga requisitos adicionales al creador de un Pin . Lo habría encontrado útil si esas condiciones previas necesarias para invocar unsafe fn new_unchecked(pointer: P) -> Pin<P> se mencionaran directamente. Entender cómo se puede crear un Pin ayuda enormemente a comprender su concepto, en mi opinión.
  • El resumen mencionado en el problema de seguimiento, precedido por we agreed that we are ready to stabilize them with no major API changes. , contiene muchas api antiguas. Al mismo tiempo, también contiene mucha información sobre Drop y la proyección de pines que es relevante. También contiene una formulación explícita de la garantía adicional mencionada en el punto uno que no se encuentra en este informe. Esta combinación de información nueva y desactualizada fue un poco confusa, considere mover toda la información a este número o indicar párrafos desactualizados.
  • Dado que la garantía adicional se conoce como slight extension , esperaba que hubiera algún tipo de forma de optar por no participar. Dado que anclar una vez lleva un estado de tipo en ese puntero hasta que se suelta, hay alguna forma de volver de este estado de tipo a un estado "normal". De hecho, eso era lo que pensaba hacer Unpin al principio, pero creo que Unpin es en realidad un poco más fuerte. Dice que el estado anclado se puede convertir libremente en un estado no fijado, a voluntad. Está bien que la interfaz actual sea mínima en este sentido, pero Unpin podría confundirse con una operación en el tipo que elimina algún invariante interno que requiere el estado del pin (como una versión suave de Drop ) .

Aclaración: personalmente prefiero MoveFromPin sobre los otros nombres, pero podría decirse que es artificial y torpe.

El principal elemento procesable concreto procesable que me viene a la mente, además de las mejoras de documentación ya solicitadas por @alexcrichton , sería explicar el razonamiento detrás de la regla de campo para la proyección de pines en map_unchecked y map_unchecked_mut . Algo parecido a:

Un pin promete no mover los datos referidos hasta que se suelten. Dado que los campos de una estructura se eliminan después de su estructura contenedora, su estado pin se extiende más allá de la implementación Drop de las estructuras en sí. Proyectar a un campo promete no moverse de él dentro del destructor de estructuras.

¿Qué pasa con el nombre ElidePin para el rasgo marcador derivado automáticamente?

Capturaría que el tipo siempre se puede tratar o se comporta como si no estuviera anclado. Y !ElidePin parece ser bastante claro, aunque eso podría ser subjetivo.

El marcador estándar tampoco tiene un nombre perfecto para comprender la fijación. Pinned parece evocar la noción de que la estructura contenedora en sí está anclada, pero la fijación se aplica a los punteros y solo después de una acción explícita. Esto no es tan crítico pero tampoco insignificante, especialmente porque podría ser el tipo que se encuentra por primera vez en una implementación para un tipo autorreferencial.

Mi oposición general a MoveFromUnpin / RemoveFromUnpin es que son demasiado prolijos y harían retroceder la ergonomía de las implementaciones manuales Future . CanUnpin / DePin ambos me parecen bien; me gustaría que estuviera más claro que Unpin sigue el patrón de Read / Write / etc. pero parece que eso no es intuitivo para la gente, así que haré +1 en cualquier cosa que lo aclare sin parecer sal sintáctica.

Estoy de acuerdo que NotWhateverUnpinBecomes es probablemente el mejor nombre para Pinned . Dicho esto, Adhere significa tanto prestar atención como cumplir. : leve_sonriente_cara:

CanUnpin / DePin me parecen bien; desearía que estuviera más claro que Unpin sigue el patrón de lectura / escritura / etc.

Creo que una de las cosas que hace que Unpin difícil, a diferencia de Read es que es un rasgo marcador. Read es fácil de entender porque hay un método Read::read : todo lo que necesita saber está allí, en trait . Si x es Read , entiendo que puedo llamar x.read() - de manera similar write por Write , etc. Es más difícil explicar que X implementando Unpin significa que Pin<Ptr<X>> implementa DerefMut - lo que significa que puede tratarlo como si fuera solo X .

Leer es fácil de entender porque existe un método Read :: read

Si solo pudiéramos agregar métodos siempre aplicables en auto trait definiciones + GAT, podríamos tener Unpin::unpin - fn unpin<P: DerefFamily>(self: Pin<P::Deref<Self>>) -> P::Deref<Self> ). ..... pensándolo bien, no creo que eso vaya a confundir menos a nadie;)

(en una nota más seria, yo apoyaría Pin::unpin para pasar de Pin<P<T>> a P<T> )

Apoyaría Pin :: unpin para pasar de Pin

> a P

Esto confunde dos términos en mi cabeza, al igual que el nombre actual. unpin suena muy parecido a revertir las garantías del tipo estado, para una comparación que sería como si hubiera un método fn unborrow(&'_ T) o fn unmove(??) . Dado que pin proporciona garantías hasta que alguna memoria que represente un tipo sea Drop::drop ed, en realidad no invertimos el estado, el tipo simplemente garantiza que todas las demás representaciones mantienen garantías equivalentes y, por lo tanto, podemos ignorar esto. . Esta es también la principal diferencia que veo entre los rasgos de los marcadores y algo como io::Read . Uno habilita operaciones para el compilador o lenguaje, mientras que el otro habilita operaciones para el programador.

Además, es un punto importante que esto no se puede representar con precisión en la actualidad simplemente usando la escritura correcta. Llamar a la operación unpin hace que parezca que hay una función inversa pin . Al ser una función, también sugiere erróneamente que una operación de desvinculación de este tipo está vinculada de alguna manera al trabajo computacional, por ejemplo into_pointer() sería mejor en este sentido si también siguiera una convención de nomenclatura más establecida.

Por último, creo que existe la posibilidad de que los tipos tengan específicamente una función unpin con trabajo computacional. Un tipo con invariantes internos muy especiales podría 'arreglar' su estado interno de tal manera que pueda ofrecer una interfaz fn unpin(self: Pin<&'a mut T>) -> &'a mut , donde voluntariamente pierde todas las garantías de Pin durante algún tiempo 'a . En ese caso, los dos puntos anteriores ya no se aplican. Se podría imaginar una función de este tipo como si tuviera un efecto equivalente a colocar y reconstruir en la misma ubicación de memoria (eliminando así el estado de tipo). Y puede implicar cálculos, por ejemplo, moviendo algunas autorreferencias a asignaciones dinámicas.

Sería lamentable que un nombre confuso también hiciera más difícil para los diseñadores e implementadores de bibliotecas elegir nombres no confusos, al combinar estas dos ideas.

Mi oposición general a MoveFromUnpin / RemoveFromUnpin es simplemente que son demasiado prolijos y harían retroceder la ergonomía de las implementaciones futuras manuales.

No creo que eso sea cierto, especialmente para MoveFromPin , que me parece razonable escribir, y con cualquier tipo de autocompletado inteligente o tonto, el problema es casi inexistente de todos modos.

Una parte importante de la ergonomía debería ser también la legibilidad y la comprensibilidad del código. Rust ya se ganó algunas críticas en el pasado por abreviar las cosas de manera agresiva ( fn , mut , etc.), lo que hace que algunos códigos sean más difíciles de leer. Para cosas que para un concepto que es aún más complicado de comprender y que cumple un propósito específico para la mayoría de los usuarios, usar un nombre más detallado y descriptivo debería ser totalmente aceptable.

@rfcbot resolver el nombre de unpin

Ok, he estado cocinando por un tiempo y también he hablado con @withoutboats en persona sobre esto. He llegado a la conclusión de que ahora personalmente me siento cómodo al menos con el nombre Unpin como rasgo marcador. Como resultado, eliminaré una objeción de bloqueo (aunque si otros miembros del equipo de bibliotecas sienten lo contrario, háganoslo saber).

La razón principal por la que he llegado a Unpin es básicamente lo que @cramertj ya dijo anteriormente , es el nombre más idiomático para este rasgo que hemos podido encontrar. Todos los demás nombres de rasgos que he visto propuestos no cumplen con la barra idiomática que Unpin cumple (o al menos en mi opinión).

Si bien todavía creo que hay mejores nombres para "Acabo de ver el nombre de este rasgo y quiero saber qué hace", no creo que ese sea el problema correcto para resolver aquí. En este punto, para mí está claro que no podemos, al menos en este momento, encontrar un nombre diferente para este rasgo que también es idiomático, sino que estamos cambiando los nombres de los rasgos que resuelven un problema diferente. Entender Unpin es como Send y Sync , es necesario leer la documentación para comprender completamente lo que está sucediendo.


Como otro punto de aclaración, enumeré algunas "objeciones de bloqueo", pero son más elementos de TODO que las objeciones de bloqueo per se. ¡Simplemente no tengo una excelente manera de navegar por un hilo tan largo! En ese sentido, creo que está bien que se publique un PR de estabilización en cualquier momento con el FCP caducado durante una semana más o menos. Los puntos finales sobre self / pin / pinned se pueden discutir brevemente allí (si es necesario, se pueden dejar como la propuesta anterior).

Creo que la documentación especialmente no es un requisito previo para la estabilización en este caso. Tenemos un ciclo completo (6 semanas) para agregar documentos antes de que estos tipos se estabilicen y tenemos aún más tiempo para apuntalar la documentación aquí antes de que la historia completa de async / await se estabilice. Eso es mucho tiempo para mejorar lo que hay ahora, ¡y lo que hay ahora ya es bastante útil!

¿Qué significa "idiomático" aquí? ¿Qué tiene de unidiomático Reloc[ate] , Escape , Evade , Pluck o Roam ? Todos son verbos y ninguno de ellos puede confundirse con el problema del doble negativo.

@alexcrichton ¿Hay alguna razón por la que Unpin se considere más idiomático que Depin o DePin ?

Creo que Depin es una alternativa muy sólida y no me causa las mismas dificultades mentales que Unpin .

Una simple razón podría ser que depin no es una palabra y unpin sí lo es. Personalmente, no tengo ningún problema para entender Unpin . Creo que encaja con el nombre de los otros rasgos marcadores.

@jimmycuadra Hay muchos nombres en Rust que no son palabras "reales", incluso en stdlib.

Me sorprendería si eso se considerara una razón importante para elegir un nombre sobre otro.

@Pauan Es una razón importante. Como hablante nativo de inglés, Depin me suena como si hubiéramos olvidado que existe la palabra para desanclar y tratamos de inventar una. Me parece claramente incorrecto gramaticalmente: la palabra inglesa para "depinning" es "unpinning".

Una buena analogía sería si tuviéramos API que dijeran "desbloqueo" en lugar de "desbloqueo".

@jaredr por "idiomático" me refiero a seguir las convenciones establecidas de la biblioteca estándar ya como los (mencionados anteriormente) Read y Write rasgos. Tenemos una convención de nombres sin palabras, verbos, cortos cuando es posible y apropiados para la situación.

Los nombres que ha sugerido son todos posibles, pero Unpin (o eso creo al menos) es el más apropiado para esta acción en particular (garantía de "puede desanclar este tipo"). Los otros, aunque son sinónimos sueltos de Unpin , creo que en gran medida solo se renombran de la palabra "desanclar" y, a veces, no transmiten la misma conexión con Pin como Unpin hace.

@Pauan Estoy de acuerdo con @withoutboats en que Depin no suena tan bien como Unpin .

@aturon señaló en https://github.com/rust-lang/rfcs/pull/2592#issuecomment -438873636 que:

El uso de Pin no es más un detalle de implementación que la elección de &self versus &mut self ; A largo plazo, es probable que Pin sea ​​en sí mismo un modo de referencia independiente con soporte de idiomas. En todos estos casos, lo que se transmite en la firma son los permisos que se otorgan al destinatario, con Pin prohibiendo salir de la referencia.

Aparentemente, esto se refiere a un tipo nativo &pin o algo ... pero no sé cómo cuadra esto con Pin<&mut T> y tal. En mis conversaciones con @withoutboats y en particular con @cramertj, no estaban del todo seguros sobre la idea de un modo de referencia separado con soporte de idiomas y cómo podríamos llegar allí desde Pin<T> .

Antes de estabilizar pin , sería prudente conciliar estas vistas para asegurarnos de que estamos en la misma página wrt. esta pieza crítica de infraestructura; así que principalmente me gustaría que Aaron ampliara esto y que los barcos y Taylor luego intervinieran.

Los otros, aunque son sinónimos sueltos de Unpin, creo que en gran parte solo están cambiando el nombre de la palabra "unpin" y, a veces, no transmiten la misma conexión con Pin que Unpin.

@alexcrichton, creo que esto es realmente algo bueno. Como Send para mover / copiar (=) operación, Sync para pedir prestado (&) operación. ¿Quizás tal conexión está causando más confusión?

@ crlf0710 ¡puede! Aunque no estoy seguro de estar de acuerdo con tal conexión. Send y Sync son más acerca de lo que están habilitando ("enviar" tipos de ing a otros hilos y "sincronizar" el acceso hronizando a través de hilos), y al nombrarlos no intentamos Evite nombrarlos cerca de una operación u otra.

@alexcrichton exactamente! así que tal vez este rasgo también debería ser sobre lo que está habilitando. ("Moviente (Un verbo aquí) de un Pin ). No soy un hablante nativo de inglés, pero sigo pensando que "deshacerse" de un pin es un poco ... ¿raro?

@ crlf0710 Pero lo que permite el rasgo no es <moving> fuera de un Pin, es <moving out of a Pin> . El problema con el movimiento y los sinónimos de movimiento es que implican que el rasgo controla la capacidad del tipo de moverse en absoluto, lo que no hace. La conexión con Pin es vital para comprender qué hace realmente el rasgo.

Entonces, el nombre más idiomático para este rasgo sería un verbo que significa "salir de un alfiler", para lo cual la palabra "Desanclar" es la más obvia para mí por mucho. Aquí está la definición de Wiktionary de "desanclar":

  1. Para desabrochar quitando un alfiler.
  2. (transitivo, informático, interfaz gráfica de usuario) Para separar (un icono, aplicación, etc.) del lugar donde estaba previamente fijado

@withoutboats ¡ gracias por explicarme! En realidad, creo que puedo aceptar este Unpin nombre o cualquier nombre que finalmente decida el equipo de Rust, no quiero bloquear la estabilización de los tipos de Pin, y comprendo absolutamente las preocupaciones sobre "mover", etc.

Simplemente siente que hay una mínima "repetición" en alguna parte. y tengo que persuadirme a mí mismo: no significa "imposible de pinchar", sino todo lo contrario. Supongo que después de un tiempo me acostumbraré si esa es la decisión final.

Al mismo tiempo, permítame hacer mi última sugerencia: en realidad, creo que el verbo Detach mencionado anteriormente también es bastante agradable, aunque un poco demasiado general. Como dije, no soy hablante nativo, por lo que no puedo hablar por los demás. Por favor, véalo como una pequeña idea.

Estaba pensando en proyecciones seguras con clavijas disjuntas y se me ocurrió la idea que podría permitirlo. Esta es una macro para simular la coincidencia con valores l mutables fijados. Con esta macro podemos escribir

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

para descomponer un Pin<&mut MyStruct> en referencias mutables fijadas a los campos.

Para que esto sea seguro y utilizable, necesitaremos otras dos cosas:

  • El tipo Kite para marcar los campos "siempre- Unpin "
  • Hacer Unpin inseguros

Este último es necesario para la seguridad de la proyección. Sin esto, podemos definir " Kite con proyecciones fijadas" de forma segura, lo que en realidad es incorrecto.

Con esto en mente, propongo hacer que Unpin inseguro antes de la estabilización para dejar espacio para que otras operaciones útiles sean seguras.

@qnighy Es posible que un tipo viole las garantías de fijación en su implementación Drop , lo que hace que Drop equivalentemente poderoso a Unpin . Hacer que Unpin no sea seguro no hace que ningún código adicional sea seguro, porque Drop también es seguro y eso no se puede cambiar. Hemos hablado mucho de esto sobre el tema del seguimiento, y también se ha mencionado en este hilo.

Básicamente, las proyecciones de pines no se pueden hacer seguras sin la capacidad de afirmar ciertos límites negativos que no se pueden expresar en Rust hoy.

@ crlf0710

Al mismo tiempo, permítame hacer mi última sugerencia: en realidad, creo que el verbo Separar mencionado anteriormente también es bastante agradable, aunque un poco demasiado general. Como dije, no soy hablante nativo, por lo que no puedo hablar por los demás. Por favor, véalo como una pequeña idea.

Separar parece mucho mejor que nombres como Move y Relocate, pero parece entrar en conflicto con otros usos posibles de la metáfora de desconectar (similar a cómo Escape podría entrar en conflicto con el "análisis de escape", etc.). Hay pocas metáforas que tenemos sobre cómo los datos pueden relacionarse con otros datos en la informática, lo que me hace pensar en otra nueva ventaja de Unpin: al ceñirse firmemente a la metáfora del "alfiler", no ocupa espacio para el futuro lenguaje metafórico es posible que debamos usar para un propósito diferente, de la misma manera que nombres como Separar, Escape o Reubicar.

@withoutboats No tengo ninguna preferencia particular por ningún nombre o mucha inversión en este cobertizo ... pero tengo curiosidad; ¿Qué otro propósito cabría Detach (si especula ...)?

@Centril No tengo un caso de uso claro en mente, pero podría imaginar algún caso de uso relacionado con la desestructuración, por ejemplo.

@withoutboats Sí, eso tiene sentido; ¡salud!

@withoutboats No estoy seguro de si la entrada de Wiktionary es la mejor motivación para justificar el nombre de Unpin para habilitar move from a pin . Las metáforas de la representación física adolecen del hecho de que moverse en Rust no es quitar un objeto del mundo. Para ser más precisos, 'upinning' de un puntero fijo no me da control sobre la memoria referida, su asignación y validez como una representación de objeto de T todavía son requeridas y garantizadas por la semántica del puntero. Por lo tanto, desanclar no da una representación totalmente controlada de T , como lo haría el desboxing. Y una entrada de diccionario que defina unpinning en términos de moving es, de hecho, parte de la confusión más que la justificación de tal nombre.

En la API propuesta, ninguno de los métodos tiene tal efecto (y tampoco está en el alcance de pin). No veo una forma segura, por ejemplo, de hacer la transición de Pin<Box<T>> a Box<T> (y por extensión a T ) y tampoco estoy seguro de si Unpin debería tienen posibilidades tan fuertes. No estoy exactamente seguro de dónde estaría toda la diferencia. Pero por lo que entendí, Pin aplica a alguna ubicación de memoria, mientras que Unpin garantiza que nada en la representación de T s se basa en garantías de pines. Sin embargo, ¿sería eso lo mismo que poder sacar y olvidar el recuerdo por completo? Yo creo que no. Pensaré en un ejemplo más concreto.

Editar: Más concretamente, incluso si T es Unpin , ¿puedo confiar en que se llame a alguna instancia de T::drop(&mut) en la memoria que se fijó, antes de que se desasigne esa memoria? Por lo que puedo deducir del formalismo, la respuesta debería ser sí, pero el nombre Unpin me comunica lo contrario.

Edición 2: Rc permite observar Pin<&T> pero para T: Unpin aún no se ha llamado a drop en la ubicación de memoria original. Mantenga viva una referencia fuera del pin, luego, después de que se elimine el pin, puede mudarse con Rc::try_unwrap . https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca Eso responde efectivamente a la pregunta a través de los mecanismos existentes, pero ¿funciona según lo previsto?

¿Quizás IgnorePin ? Si T es IgnorePin , puede tratar Pin<Box<T>> como &mut T esencialmente ignorando la presencia de Pin (AIUI también ignora el Box ). Ignorar es un verbo, supongo que IgnorePin no lo es, pero no estoy seguro de qué es. IgnorePin es descriptivo de lo que permite, no es descriptivo de las restricciones impuestas al tipo, pero tampoco lo es Unpin .

@wmanley Tuve una idea muy similar, ElidePin , en algún comentario anterior, aunque en ese entonces no había podido expresar concretamente por qué esa se sentía más precisa. Pero estoy de acuerdo en que 'un verbo' es la guía de estilo para los rasgos de los marcadores de Rust. Aunque también permite una negación más natural en la forma de !ElidePin / !IgnorePin , no es óptima.

@withoutboats Pregunta de seguimiento: dado que el pin parece estar especificado en términos de la memoria subyacente, ¿cómo interactúa Pin con ZST? Debido a que Pinned es un ZST, incluso un ZST puede ser Unpin o no. Yo diría intuitivamente que su representación de memoria nunca se invalida formalmente, por lo que T::drop(&mut self) nunca necesita ser llamado, muy similar a cómo se podría construir un pin a partir de &mut 'static _ memoria, por ejemplo, de un Box filtrado

¿Es seguro crear un Pin<P<T>> con un T: !Unpin y luego desanclar inmediatamente el valor, si se garantiza que nada observó el anclaje? ¿Es solo un comportamiento indefinido si el valor fijado se mueve después de que se haya llamado a un método similar a una encuesta?

@HeroicKatora Desafortunadamente, hoy es posible implementar Drop para un tipo que podría ser !Unpin debido a los genéricos, por ejemplo:

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

@cramertj Gracias, me acabo de dar cuenta de eso también.

@cramertj ¿Así que el valor predeterminado es T: ?Unpin en límites genéricos? ¿Hay alguna otra razón para no establecer de forma predeterminada T: Unpin para tipos existentes como Sized ? Daría algunos casos en los que eso sería molestamente estricto, pero ¿ese enlace automático adicional puede causar regresiones?

@HeroicKatora Eso requeriría rociar ?Unpin límites en cada tipo en la biblioteca estándar y en un montón de código al que no le importa Unpin en absoluto.

También es seguro agregar un campo PhantomPinned, como otra forma de crear un tipo Drop +! Unpin.

¿Hay otra razón para no utilizar T: Desanclar para tipos existentes?

Desanclar es solo un rasgo automático como Enviar y sincronizar, esta propuesta no incluye nuevas funciones de idioma. Por lo tanto, tiene la misma semántica que los rasgos automáticos ya están definidos, que es que no se aplican a los genéricos de forma predeterminada (a diferencia de Sized, que es un rasgo incorporado al compilador, no un rasgo automático).

¿Es solo un comportamiento indefinido si el valor fijado se mueve después de que se haya llamado a un método similar a una encuesta?

Debemos tener claro aquí que el comportamiento indefinido en este contexto no es realmente el tipo tradicional de UB. Más bien, es que el código, al observar un tipo en un Pin, puede hacer suposiciones sobre la semántica de propiedad de ese valor (es decir, que si no implementa Unpin, no se moverá de nuevo ni se invalidará hasta que se haya ejecutado su destructor). No es UB en el sentido de que el compilador asume que nunca sucederá (el compilador no sabe qué es un Pin).

Así que sí, en el sentido de que puede verificar que ningún código se basa en la garantía que ha proporcionado. Pero también lo que ha hecho es ciertamente inútil, ya que ningún código pudo observar que el tipo fue anclado.

@cramertj Ya veo, eso sería un poco desafortunado. Me siento un poco incómodo con la interacción Pin con Drop en la especificación pero no en el idioma. Muy conflictivo entre la corrección de la interfaz, la usabilidad y su lanzamiento en un futuro próximo. ¿Puedo obtener una aclaración sobre la Edición 2 anterior?

Rc le permite a uno observar Pin <& T> pero para T: Unpin todavía no tiene llamada de caída en la ubicación de memoria original. Mantenga viva una referencia fuera del pin, luego, después de que el pin se haya eliminado, puede salir con Rc :: try_unwrap. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca

@HeroicKatora, el problema con su código es que Mem<'a>: Unpin , pero la línea de código inseguro que usa se basa en suposiciones que solo se aplican a los tipos que no implementan Unpin . He editado su esencia para incluir pruebas de que Mem<'a>: Unpin : https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=201d1ae382f590be8c5cac13af279aff

Confía en el enlace Unpin cuando llama a Pin::new , que solo se puede llamar en punteros cuyo objetivo implemente Unpin.

Se ha tenido en cuenta la laguna que creías haber encontrado, por lo que no hay forma de pasar de un Rc<T: ?Unpin> a un Pin<Rc<T>> . Tienes que construir el Rc en el pin con Rc::pinned .

@withoutboats Solo quería confirmarlo, que T: Unpin hecho también se excluye de la llamada drop antes de la invalidación. Eso plantea la pregunta, ¿no debería haber también

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

ya que no protege ninguna parte de la interfaz, y desenvolver un Pin<Box<T>> en Box<T> es potencialmente útil (por T: Unpin por supuesto).

@HeroicKatora tienes razón en que esa función sería segura. No me importa agregarlo, pero me gustaría evitar expandir la API que estamos estabilizando en este momento , ya que este hilo ya tiene cientos de comentarios. Siempre podemos agregarlo más tarde cuando surja la necesidad, tal como lo hacemos con todas las API estándar.

Solo diría que tanto el nombre como la funcionalidad de Unpin mucho más sentido percibido con ese método inverso. Y sabiendo que las garantías de Pin están efectivamente restringidas a T: !Unpin . Eso también se demostraría directamente por la existencia de tal método, en lugar de construir trampillas de escape para mostrar la posibilidad: sonrisa:. Y su documentación sería un lugar perfecto para explicar (o vincular una vez más) las restricciones. (Incluso se podría considerar nombrarlo unpin lugar de into_inner ).

Editar: en su mayoría solo generalizaría lo que ya está allí.

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

  • fn unpin(self) -> P

tiene la instanciación de P=&'a mut T , que es equivalente a la propuesta

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

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

Ahora que la estabilización ha pasado, el punto del PFCP parece discutible, por lo tanto:

@rfcbot cancelar

¿Existe algún seguimiento para asegurarse de que los comentarios desarrollados a partir de https://github.com/rust-lang/rust/issues/55766#issuecomment-437374982 se conviertan en documentos? Parece que ya es demasiado tarde para el lanzamiento, ya que la versión beta se bifurcó ... a pesar de que aquí se dijo explícitamente que sucediera antes de la estabilización:

¿Hay alguna razón por la que esto todavía esté abierto? @Centril cerró y volvió a abrir esto, ¿fue deliberado?

@RalfJung Traté de cancelar el FCP pero no escuchó; @alexcrichton ¿puedes cancelar el FCP?

Esto es estable, por lo que se cierra.

¿Fue útil esta página
0 / 5 - 0 calificaciones