Rust: [Estabilização] Pin APIs

Criado em 7 nov. 2018  ·  213Comentários  ·  Fonte: rust-lang/rust

@rfcbot fcp merge
Nome do recurso: pin
Meta de estabilização: 1.32.0
Problema de rastreamento: # 49150
RFCs relacionados: rust-lang / rfcs # 2349

Esta é uma proposta para estabilizar o recurso de biblioteca pin , tornando a "fixação"
APIs para manipular memória fixada utilizável em estável.

(Tentei escrever esta proposta como um "relatório de estabilização" abrangente.)

Recurso estabilizado ou APIs

[std|core]::pin::Pin

Isso estabiliza o tipo Pin no submódulo pin de std / core . Pin é
um invólucro transparente fundamental em torno de um tipo genérico P , que se destina
para ser um tipo de ponteiro (por exemplo, Pin<&mut T> e Pin<Box<T>> são ambos
válidos, construtos pretendidos). O wrapper Pin modifica o ponteiro para "fixar"
a memória a que se refere no local, evitando que o usuário mova objetos para fora
dessa memória.

A maneira usual de usar o tipo Pin é construir uma variante fixada de alguns
tipo de ponteiro de propriedade ( Box , Rc , etc). A biblioteca std possui todos os ponteiros
fornece um construtor pinned que retorna isso. Então, para manipular o
valor dentro, todos esses indicadores fornecem uma maneira de degradar em direção a Pin<&T>
e Pin<&mut T> . Ponteiros fixados podem ser apagados, devolvendo &T , mas não podem
desreferenciável com segurança mutável: isso só é possível usando o inseguro get_mut
função.

Como resultado, qualquer pessoa que modifique dados por meio de um pino será obrigada a manter o
invariante que eles nunca saem desses dados. Isso permite que outro código para
assumir com segurança que os dados nunca são movidos, permitindo que contenham (por
exemplo) referências próprias.

O tipo Pin terá estas APIs 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

Trait impls

A maioria dos impls de trait em Pin são bastante mecânicos, esses dois são importantes para
sua operação:

  • 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

Desafixar é um traço automático seguro que exclui as garantias de fixação. Se o
alvo de um ponteiro fixado implementa Unpin , é seguro para mutably
desreferência a ele. Unpin tipos não têm qualquer garantia de que não
ser retirado de um Pin .

Isso torna ergonômico lidar com uma referência fixa a algo que
não contém autorreferências como seria para lidar com um não fixado
referência. As garantias de Pin só importam para tipos de casos especiais como
estruturas autorreferenciais: esses tipos não implementam Unpin , então eles têm
as restrições do tipo Pin .

Implementações notáveis ​​de Unpin no padrão:

  • 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>

Isso codifica a noção de que a fixação não é transitiva entre os ponteiros. que
é, um Pin<&T> apenas fixa o bloco de memória real representado por T em um
Lugar, colocar. Os usuários ocasionalmente se confundem com isso e esperam que um tipo
como Pin<&mut Box<T>> fixa os dados de T no lugar, mas apenas fixa o
memória à qual a referência fixada realmente se refere: neste caso, os Box 's
representação, que um ponteiro para o heap.

std::marker::Pinned

O tipo Pinned é um ZST que não implementa Unpin ; permite que você
suprimir a implementação automática de Unpin no stable, onde !Unpin impls
não seria estável ainda.

Construtores de ponteiro inteligente

Construtores são adicionados aos ponteiros inteligentes padrão para criar referências fixas:

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

Notas sobre fixação e segurança

Nos últimos 9 meses, as APIs de fixação passaram por várias iterações como
investigamos seu poder expressivo e também a solidez de seus
garantias. Eu agora diria com segurança que as APIs de fixação estabilizaram aqui
são sólidos e próximos o suficiente dos máximos locais em ergonomia e
expressividade; isto é, pronto para estabilização.

Uma das questões mais complicadas de fixar é determinar quando é seguro executar
uma projeção de pino : isto é, ir de um Pin<P<Target = Foo>> para um
Pin<P<Target = Bar>> , onde Bar é um campo de Foo . Felizmente, temos
foi capaz de codificar um conjunto de regras que podem ajudar os usuários a determinar se tal
a projeção é segura:

  1. Só é seguro fixar o projeto se (Foo: Unpin) implies (Bar: Unpin) : isso
    é, se nunca for o caso que Foo (o tipo que o contém) é Unpin enquanto
    Bar (o tipo projetado) não é Unpin .
  2. Só é seguro se Bar nunca for movido durante a destruição de Foo ,
    o que significa que Foo não tem destruidor ou o destruidor está cuidadosamente
    verificado para ter certeza de que ele nunca sai do campo sendo projetado.
  3. Só é seguro se Foo (o tipo que o contém) não for repr(packed) ,
    porque isso faz com que os campos sejam movidos para realinhá-los.

Além disso, as APIs std não fornecem uma maneira segura de fixar objetos na pilha.
Isso ocorre porque não há como implementar isso com segurança usando uma API de função.
No entanto, os usuários podem fixar coisas na pilha de forma insegura, garantindo que eles
nunca mova o objeto novamente após criar a referência fixada.

A caixa pin-utils em crates.io contém macros para ajudar com ambas as pilhas
fixação e projeção de pinos. A macro de fixação de pilha fixa objetos com segurança ao
empilhar usando um truque envolvendo sombreamento, enquanto uma macro para projeção existe
o que não é seguro, mas evita que você tenha que escrever o clichê de projeção em
que você possivelmente poderia introduzir outro código inseguro incorreto.

Mudanças de implementação antes da estabilização

  • [] Exportar Unpin do prelúdio, remover pin::Unpin reexportar

Como regra geral, não reexportamos coisas de vários lugares em std, a menos que
um é um supermódulo da definição real (por exemplo, encurtamento
std::collections::hash_map::HashMap a std::collections::HashMap ). Por esta
razão, a reexportação de std::marker::Unpin de std::pin::Unpin está fora de
Lugar, colocar.

Ao mesmo tempo, outras características importantes do marcador, como enviar e sincronizar, estão incluídas
no prelúdio. Então, em vez de reexportar Unpin do módulo pin , por
colocando no prelúdio, tornamos desnecessário importar std::marker::Unpin ,
pelo mesmo motivo que foi colocado em pin .

  • [] Alterar funções associadas a métodos

Atualmente, muitas das funções associadas de Pin não usam sintaxe de método.
Em teoria, isso é para evitar conflito com métodos internos desfeitáveis. Contudo,
esta regra não foi aplicada de forma consistente e, em nossa experiência,
apenas tornou as coisas mais inconvenientes. Ponteiros fixados apenas implementam imutáveis
deref, não mutável deref ou deref por valor, limitando a capacidade de deref
de qualquer forma. Além disso, muitos desses nomes são bastante únicos (por exemplo, map_unchecked )
e improvável de conflito.

Em vez disso, preferimos dar aos métodos Pin sua devida precedência; usuários que
precisa acessar um método interior sempre pode usar UFCS, assim como seria
necessário para acessar os métodos Pin se não usarmos a sintaxe do método.

  • [] Renomear get_mut_unchecked para get_unchecked_mut

A ordem atual é inconsistente com outros usos na biblioteca padrão.

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

Este impl não é justificado por nossa justificativa padrão para desafixar impls: não há direção do ponteiro entre Pin<P> e P . Sua utilidade é coberta pelos próprios impls para ponteiros.

Este implemento futuro precisará ser alterado para adicionar um limite de P: Unpin .

  • [] Marque Pin como repr(transparent)

O pino deve ser um invólucro transparente ao redor do ponteiro dentro dele, com a mesma representação.

Recursos conectados e marcos maiores

As APIs de pinos são importantes para manipular com segurança as seções de memória que podem
ser garantido para não ser movido. Se os objetos naquela memória não
implementar Unpin , seu endereço nunca mudará. Isso é necessário para
criando geradores autorreferenciais e funções assíncronas. Como um resultado,
o tipo Pin aparece na biblioteca padrão future APIs e logo
aparecem nas APIs para geradores também (# 55704).

Estabilizar o tipo Pin e suas APIs é um precursor necessário para estabilizar
as APIs Future , que por si só é um precursor necessário para estabilizar o
async/await sintaxe e movendo todo o futures 0.3 async IO ecossistema
em ferrugem estável.

cc @cramertj @RalfJung

T-libs finished-final-comment-period

Comentários muito úteis

Alguém está prestes a escrever um capítulo do nomicon sobre futuros? Parece incrivelmente necessário, dado o quão sutil isso é. Em particular, eu acho que exemplos, especialmente exemplos de implementações com erros / más e como eles são inadequados, seriam esclarecedores.

Todos 213 comentários

@rfcbot fcp merge

O membro da equipe @withoutboats propôs fundir isso. A próxima etapa é revisada pelo restante dos membros da equipe marcados:

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

Preocupações:

  • box-pinnned-vs-box-pin (https://github.com/rust-lang/rust/issues/55766#issuecomment-443371976)
  • melhorando a documentação (https://github.com/rust-lang/rust/issues/55766#issuecomment-443367737)
  • naming-of-Unpin resolvido por https://github.com/rust-lang/rust/issues/55766#issuecomment -445074980
  • métodos próprios (https://github.com/rust-lang/rust/issues/55766#issuecomment-443368794)

Assim que a maioria dos revisores aprovar (e no máximo 2 aprovações pendentes), isso entrará em seu período final para comentários. Se você identificar uma questão importante que não foi levantada em algum ponto deste processo, fale!

Consulte este documento para obter informações sobre quais comandos os membros da equipe marcados podem me dar.

Obrigado pela descrição detalhada aqui @withoutboats! Também fiquei meio confuso com as várias garantias de Pin e atualmente tenho algumas perguntas sobre as APIs sendo estabilizadas aqui em relação às garantias de segurança. Para ajudar a resolver isso em minha própria cabeça, pensei em tentar escrever essas coisas.

Ao tentar começar a escrever isso, porém, continuo esbarrando na parede de "o que é Unpin ?" Estou meio confuso com o que isso significa e as várias garantias em torno disso. Você pode repetir o que significa T para e também para não implementar Unpin ? Além disso, se Unpin é um traço seguro para implementá-lo ingenuamente, parece que pode ser usado para minar facilmente as garantias inseguras de Pin<T> , mas certamente estou perdendo algo

se entendi corretamente, todo tipo que não seja autorreferencial (ou seja: não é um gerador) é Desafix

Não é apenas autorreferencialidade, há alguns outros casos de uso para endereços de memória estáveis ​​que Pin também pode suportar. Eles são relativamente poucos e distantes entre si.

Como eu entendo que Unpin seja seguro para implementar é que, ao implementá-lo, você pode violar uma invariante exigida de outro código não seguro que você escreveu (crucialmente, apenas você pode escrever este código não seguro, nenhum código externo pode estar confiando se você implementou Unpin ). Não há nada que você possa fazer com a API segura de Pin que cause problemas, independentemente de você ter implementado Unpin ou não. Ao optar por usar alguma API insegura de Pin , você está garantindo que implementará Unpin quando for seguro fazê-lo. Isso é abordado no ponto 1 da seção "Notas sobre fixação e segurança" acima.

Hm, ainda não entendi realmente Unpin . A princípio, estou apenas tentando entender o que significa implementar ou não implementar Unpin .

Em primeiro lugar, provavelmente é útil saber quais tipos implementam Unpin automaticamente! Mencionou-se acima que tipos de ponteiro comuns (Arc / Rc / Box / referências) implementam Unpin , mas acho que é isso? Se esta é uma característica automática, isso significa que um tipo MyType implementa automaticamente Unpin se contiver apenas ponteiros? Ou nenhum outro tipo implementa Unpin automaticamente?

Eu sempre tento resumir ou declarar o que Unpin garante e coisas assim, mas estou achando muito difícil fazer isso. Alguém pode ajudar reiterando novamente o que significa implementar Unpin , bem como o que significa não implementar Unpin ?

Acho que entendo as garantias de Pin<P> onde você não pode sair de nenhum dos membros inline de P::Target , mas está certo?

@alexcrichton Obrigado pelas perguntas, tenho certeza que as APIs de fixação podem ser um pouco confusas para as pessoas que não fizeram parte do grupo que se concentra nelas.

Em primeiro lugar, provavelmente é útil saber quais tipos implementam o Desafixar automaticamente!

Desafixar é um traço automático como Enviar ou Sincronizar, então a maioria dos tipos o implementa automaticamente. Os geradores e os tipos de funções assíncronas são !Unpin . Tipos que podem conter um gerador ou um corpo de função assíncrona (em outras palavras, tipos com parâmetros de tipo) também não são automaticamente Unpin menos que seus parâmetros de tipo sejam.

O impls explícito para os tipos de ponteiro é fazer Unpin mesmo se seus parâmetros de tipo não o forem. A razão para isso ficará mais clara no final deste comentário.

Alguém pode ajudar reiterando novamente o que significa implementar Desafixar, bem como o que significa não implementar Desafixar?

Aqui está o tipo de ideia fundamental das APIs de fixação. Primeiro, dado um tipo de ponteiro P , Pin<P> age como P exceto que, a menos que Pin deve manter:

  • Se você não pegar &mut P::Target de um Pin<P> forma insegura, você nunca deve mover P::Target .
  • Se você pode construir um Pin<P> , deve ser garantido que você nunca será capaz de obter um ponteiro não fixado para os dados para os quais o ponteiro aponta até que o destruidor seja executado.

A implicação de tudo isso é que se você construir um Pin<P> , o valor apontado por P nunca mais se moverá, o que é a garantia de que precisamos para estruturas autorreferenciais e intrusivas coleções. Mas você pode cancelar essa garantia apenas implementando Unpin para o seu tipo.

Portanto, se você implementar Unpin para um tipo, estará dizendo que o tipo exclui qualquer garantia de segurança adicional de Pin - é possível remover a referência mutuamente dos ponteiros que apontam para ele. Isso significa que você está dizendo que o tipo não precisa ser imóvel para ser usado com segurança.

Mover um tipo de ponteiro como Rc<T> não move T porque T está atrás de um ponteiro. Da mesma forma, fixar um ponteiro em Rc<T> (como em Pin<Box<Rc<T>> ) não fixa T , apenas fixa aquele ponteiro específico. É por isso que qualquer coisa que mantenha seus genéricos atrás de um ponteiro pode implementar Unpin mesmo quando seus genéricos não o fazem.

Além disso, se Desafixar for um traço seguro para implementá-lo ingenuamente, parece que pode ser usado para facilmente minar as garantias inseguras de Pin, mas com certeza estou perdendo algo

Essa foi uma das partes mais complicadas da API de fixação, e erramos no início.

Desafixar significa "mesmo depois que algo foi colocado em um alfinete, é seguro obter uma referência mutável a ele." Existe outra característica que existe hoje que lhe dá o mesmo acesso: Drop . Então o que descobrimos foi que, uma vez que Drop é seguro, Unpin também deve ser seguro. Isso prejudica todo o sistema? Não exatamente.

Implementar realmente um tipo autorreferencial exigiria código inseguro - na prática, os únicos tipos autorreferenciais com os quais alguém se preocupa são aqueles que o compilador gera para você: geradores e máquinas de estado de função assíncrona. Eles dizem explicitamente que não implementam Unpin e não têm uma implementação Drop , então você sabe, para esses tipos, uma vez que você tenha Pin<&mut T> , eles irão nunca realmente obtém uma referência mutável, porque eles são um tipo anônimo que sabemos que não implementa Desafixar ou Eliminar.

O problema surge quando você tem uma estrutura contendo um desses tipos anônimos, como um futuro combinador. Para ir de Pin<&mut Fuse<Fut>> a Pin<&mut Fut> , você deve realizar uma "projeção de pino". É aqui que você pode ter problemas: se você fixar o projeto no campo futuro de um combinador, mas, em seguida, implementar Drop para o combinador, você pode sair de um campo que deveria estar fixado.

Por esse motivo, a projeção do pino não é segura! Para realizar uma projeção de pino sem violar os invariantes de fixação, você tem que garantir que nunca fará várias coisas, que listei na proposta de estabilização.

Então, o tl; dr: Drop existe, então Unpin deve ser seguro. Mas isso não estraga tudo, apenas significa que a projeção do pino é unsafe e qualquer um que queira fixar o projeto precisa manter um conjunto de invariantes.

geradores e máquinas de estado de função assíncrona. Eles dizem explicitamente que não implementam Unpin e não têm uma implementação Drop, então você sabe, para esses tipos, uma vez que você tenha um Pin <& mut T>, eles nunca irão realmente obter uma referência mutável, porque eles são um tipo anônimo que sabemos que não implementa Unpin ou Drop.

Uma máquina de estado assíncrona não deveria ter uma implementação Drop ? Coisas que estão na "pilha" da função assíncrona (que provavelmente é igual a campos na máquina de estado) precisam ser destruídas quando a função assíncrona for concluída ou cancelada. Ou isso acontece de outra forma?

Acho que o que importa neste contexto é se existe um impl Drop for Foo {…} item, que executaria o código com &mut Foo que poderia usar, por exemplo, mem::replace para "exfiltrar" e mover o Foo Valor de

Isso não é o mesmo que "cola", que pode ser chamada por meio de ptr::drop_in_place . A cola descartável para um determinado tipo Foo chamará Drop::drop se for implementada e, em seguida, chamará recursivamente a cola descartável para cada campo. Mas essas chamadas recursivas nunca envolvem &mut Foo .

Além disso, embora um gerador (e, portanto, uma máquina de estado assíncrona) tenha cola de queda personalizada, é apenas para descartar o conjunto correto de campos com base no estado atual, ele promete nunca mover nenhum dos campos durante a eliminação.

A terminologia que uso (embora eu não ache que haja nenhum padrão): "drop glue" é o passeio recursivo gerado pelo compilador de campos, chamando seus destruidores; "Implementação de queda" é uma implementação do traço Drop , e "destruidor" é a combinação da cola descartável e a implementação de queda. A cola gota nunca move nada, por isso nos preocupamos apenas com as implementações do Drop.

Alguém está prestes a escrever um capítulo do nomicon sobre futuros? Parece incrivelmente necessário, dado o quão sutil isso é. Em particular, eu acho que exemplos, especialmente exemplos de implementações com erros / más e como eles são inadequados, seriam esclarecedores.

@Gankro Claro, você pode me colocar para baixo por isso.

Obrigado a todos pela explicação!

Pessoalmente, sou muito novo em APIs assíncronos de Rust e Pin, mas tenho brincado um pouco com isso nos últimos dias (https://github.com/rust-lang-nursery/futures-rs/pull/1315 - onde Tentei explorar a fixação de coleções intrusivas em código assíncrono).

Durante a experimentação, tive algumas preocupações com essas APIs:

  • O conceito de pinning parece muito difícil de entender completamente, mesmo sabendo que endereços de memória estáveis ​​são necessários. Alguns desafios que enfrentei:

    • Mensagens de erro como:

      > dentro de impl core::future::future::Future+futures_core::future::FusedFuture , o traço std::marker::Unpin não foi implementado para std::marker::Pinned

      Este parece bastante recursivo e contraditório (por que algo que está fixado não deveria ser fixado?)

    • Como posso acessar campos mutáveis ​​em meu método que leva um Pin como parâmetro. Levei um pouco de pesquisa, aprendendo sobre novos termos (projeção de Pin ), tentando copiar os métodos de pin-utils , até que cheguei a uma solução que usa 2 métodos inseguros para fazer o que eu precisava .

    • Como posso realmente usar meu futuro em um método async e um caso select . Acontece que eu precisava pin_mut!() para acumular futuros. O que não é realmente uma pilha, o que o torna confuso.

    • Por que meu método drop() agora pega &mut self , em vez de Pin .

    • No geral, foi um pouco a sensação de usar aleatoriamente APIs relacionadas à fixação insegura na esperança de conseguir que as coisas compilassem, o que definitivamente não deveria ser a situação ideal. Suspeito que outros possam tentar fazer coisas semelhantes.

  • O conceito se propaga muito por toda a base de código, o que parece forçar muitas pessoas a entender o que Pin e Unpin realmente são. Isso parece um pouco um vazamento de detalhes de implementação para alguns casos de uso mais especiais.
  • Pin parece estar relacionado a tempos de vida, mas também ortogonal. Basicamente, um Pin nos diz que podemos ver um objeto novamente com exatamente o mesmo endereço até que drop() seja chamado. O que dá a ele algum tipo de vida virtual entre o escopo atual e 'static . Parece confuso ter 2 conceitos relacionados, mas ainda assim completamente diferentes. Eu me perguntei se ele pode ser modelado por algo como 'pinned .

Durante a compilação das coisas, muitas vezes pensei em C ++, em que a maioria desses problemas é evitada: Os tipos podem ser declarados inabaláveis ​​excluindo o construtor de movimento e o operador de atribuição. Quando um tipo não é móvel, os tipos que contêm esse tipo também não são. Assim, a propriedade e os requisitos fluem nativamente através da hierarquia de tipo e são verificados pelo compilador, em vez de encaminhar a propriedade dentro de algumas chamadas (não todas, por exemplo, não drop() ). Acho que isso é muito mais fácil de entender. Mas talvez não se aplique ao Rust, já que Future s deve ser movido com frequência antes de algo começar a interrogá-los? Mas, por outro lado, isso pode ser corrigido com uma nova definição com esse traço, ou separando movimento e fase de votação.

Sobre o comentário de Alex Unpin : Estou entendendo lentamente o que Unpin significa, mas concordo que é difícil de entender. Talvez outro nome ajude, mas não consigo encontrar um conciso agora. Algo ao longo de ThingsInsideDontBreakIfObjectGetsMoved , DoesntRequireStableAddress , PinDoesntMatter , etc.

O que eu ainda não entendi completamente é por que obter &mut self de Pin<&mut self> não pode ser seguro para todos os tipos. Se Pin significasse apenas que o endereço do próprio objeto é estável, isso deveria ser verdadeiro para a maioria dos tipos Rust típicos. Parece que há outra preocupação integrada ao Pin, que também os tipos autorreferenciais dentro dele não quebram. Para aqueles tipos, manipulá-los depois de ter Pin é sempre inseguro. Mas eu acho que na maioria dos casos eles serão gerados pelo compilador, e os indicadores não seguros ou mesmo brutos devem estar bem. Para combinadores futuros manualmente que precisam encaminhar chamadas fixas para subcampos, obviamente, seria necessário haver chamadas não seguras para criar pinos para os campos antes de fazer a votação. Mas, nesse caso, vejo a insegurança mais relacionada ao lugar onde realmente importa (o pin ainda é válido?) Do que ao acessar outros campos para os quais não importa de forma alguma.

Outro pensamento que tive é se é possível obter um sistema mais simples se algumas limitações forem aplicadas. Por exemplo, se as coisas que requerem fixação só podem ser usadas dentro de métodos assíncronos e não com combinadores futuros. Talvez isso pudesse simplificar as coisas para Pin ou Unpin . Considerando que para muitos códigos assíncronos / métodos de espera com suporte de seleção / junção podem ser favoráveis ​​aos combinadores, essa pode não ser a maior perda. Mas eu realmente não pensei o suficiente se isso realmente ajuda.

Do lado positivo: ser capaz de escrever código assíncrono / esperar com empréstimos de "pilha" é muito legal e muito necessário! E a capacidade de usar a fixação para outros casos de uso, como coleções intrusivas, ajudará no desempenho e também em destinos como sistemas embarcados ou kernels. Portanto, estou realmente ansioso por uma solução para isso.

Pequenos detalhes do relatório de estabilização:

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

Suponho que este seja realmente um unsafe fn ?

@ Matthias247 você não pode obter & mut T com segurança de Pin<P<T>> se T não for Desafixar, porque então você poderia mem::swap mover T para fora do Pin, o que anularia o propósito de fixar as coisas.

Uma coisa que tenho dificuldade em explicar para mim mesmo é o que torna Future fundamentalmente diferente de outras características, de forma que Pin precisa fazer parte de sua API. Quer dizer, sei intuitivamente que é porque assíncrono / espera requer fixação, mas isso diz algo especificamente sobre Futures que é diferente de, digamos, Iteradores?

A enquete poderia levar & mut self e implementar Future apenas para Pin<P> tipos ou tipos que são Desafixados?

Como posso acessar campos mutáveis ​​em meu método que leva um Pin como parâmetro.

Isso realmente me faz pensar se existem alguns métodos ausentes em 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>
}

Eles podem ser usados ​​na macro pin_utils::unsafe_unpinned! .

Estou tentando descobrir _por que_ essa macro afirma não ser segura. Se somos !Unpin e temos um campo Unpin , por que não seria seguro projetar para este campo?

A única situação em que posso ver que ele está incorreto é implementar um tipo !Unpin , levando um ponteiro bruto para um campo Unpin de si mesmo (e contando com um endereço estável / apontando para o mesma instância), em seguida, adquirindo um &mut para o mesmo campo e passando-o para uma função externa. Isso parece se enquadrar no mesmo raciocínio de por que implementar Unpin é seguro, porém, ao levar um ponteiro bruto para um campo de !Unpin você está optando por não poder chamar alguns dos seguros APIs.

Para tornar a situação anterior mais segura, poderia haver um invólucro construído em Pinned para marcar que normalmente Unpin campos de uma estrutura são na verdade !Unpin vez de apenas adicionar um Pinned para a estrutura como um 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 { ... }
}

Tudo isso parece compatível com as versões anteriores da API atual, poderia remover parte da insegurança dos combinadores futures-rs (por exemplo, daria acesso seguro a campos extras como este ), mas não é necessário para os casos de uso atuais. Provavelmente poderíamos experimentar algumas APIs como essa para implementar tipos !Unpin (como coleções intrusivas) e adicioná-los mais tarde.

@ Nemo157 Essas funções de mapa não são seguras porque eu poderia mem::swap na &mut T a função me passa antes de retornar um &mut U . (bem, o mutável é, o imutável pode não ser inseguro)

EDIT: Além disso, a macro pin-utils é diferente, unsafe_unpinned não tem a ver com o tipo de destino sendo Unpin , é apenas uma "projeção não fixada" - uma projeção para &mut Field . É seguro usar, contanto que você nunca fixe o projeto nesse campo, mesmo se o campo for !Unpin .

Uma coisa que tenho dificuldade em explicar para mim mesmo é o que torna Future fundamentalmente diferente de outras características, de forma que Pin precisa fazer parte de sua API. Quer dizer, sei intuitivamente que é porque assíncrono / espera requer fixação, mas isso diz algo especificamente sobre Futures que é diferente de, digamos, Iteradores?

Não há diferença teórica, mas há diferenças pragmáticas. Por um lado, Iterator é estável. Mas, como resultado, a menos que descubramos algo muito inteligente, você nunca será capaz de executar um loop for em um gerador autorreferencial sem uma dupla indireção, mesmo que seja completamente seguro sem ele ( porque o loop for consumirá e nunca moverá o gerador).

Outra diferença pragmática importante é que os padrões de código entre Iterator e Future são bastante diferentes. Você não pode passar 10 minutos sem querer tomar emprestado em um ponto de espera no futuro, e é por isso que no futuro 0.1 você vê esses Arc s aparecendo em todos os lugares para que possa usar a mesma variável em dois and_then chamadas. Mas chegamos muito longe sem sermos capazes de expressar iteradores autorreferenciais, simplesmente não é um caso de uso tão importante.

Essas funções de mapa não são seguras porque eu poderia mem::swap no &mut T a função me passa antes de retornar um &mut U

Ah, caramba, esqueci essa parte: carrancudo:

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

Devem ser chamados de as_pinned_mut e into_pinned_ref , para evitar confundi-los com métodos que realmente retornam referências?

Implementações notáveis ​​de Desafixar no padrão:

Também temos impl<P> Unpin for Pin<P> {} . Para os tipos que usamos, isso não tem efeito e parece um pouco mais seguro?
Não importa, você tem isso na sua lista. ;)

Garantia de queda

Acho que temos que codificar a garantia Drop antes da estabilização, ou então será tarde demais:

É ilegal invalidar o armazenamento de um objeto fixado (o destino de uma referência Pin ) sem chamar drop no objeto.

"Invalidar" aqui pode significar "desalocação", mas também "reaproveitamento": ao alterar x de Ok(foo) para Err(bar) , o armazenamento de foo é invalidado .

Isso tem como consequência, por exemplo, que se torna ilegal desalocar Pin<Box<T>> , Pin<Rc<T>> ou Pin<Arc<T>> sem primeiro chamar drop::<T> .

Reaproveitando Deref , DerefMut

Ainda me sinto um pouco desconfortável sobre como isso reaproveita o traço Deref para significar "este é um indicador inteligente". Usamos Deref para outras coisas também, por exemplo, para "herança". Isso pode ser um antipadrão, mas ainda é de uso bastante comum e, francamente, é útil. : D

Eu acho que este não é um problema de solidez.

Compreendendo Unpin

Plugue sem vergonha: escrevi dois posts sobre isso, que podem ajudar ou não, dependendo do quanto você gosta de ler sintaxe (semi-) formal. ;) As APIs mudaram desde então, mas a ideia básica não.

Projeções de pinos seguros

@ Matthias247 Acho que um problema que você está enfrentando é que a construção de abstrações que envolvem fixação atualmente quase sempre requer código inseguro. Usar essas abstrações é bom, mas, por exemplo, definir um futuro combinador em código seguro não funcionará. A razão para isso é que as "projeções de pinos" têm restrições que só poderíamos verificar com segurança com as alterações do compilador. Por exemplo, você pergunta

Por que meu método drop () agora pega & mut self, em vez de um Pin.

Bem, drop() é antigo - existe desde o Rust 1.0 - então não podemos mudá-lo. Gostaríamos muito de fazer com que fosse necessário apenas Pin<&mut Self> , e então Unpin tipos poderiam obter seus &mut como fazem agora, mas essa é uma mudança incompatível com versões anteriores.

Para tornar a escrita de combinadores futuros seguros, precisaríamos de projeções de pinos seguros e, para isso, teríamos que mudar o compilador, isso não pode ser feito em uma biblioteca. Quase poderíamos fazer isso em um derive proc_macro, exceto que precisaríamos de uma maneira de afirmar "este tipo não tem nenhuma implementação de Drop ou Unpin ".

Acho que valeria a pena descobrir como obter uma API tão segura para projeções de pinos. No entanto, isso não precisa bloquear a estabilização: a API que estabilizamos aqui deve ser compatível com as projeções de pinos seguros. ( Unpin pode ter que se tornar um item de idioma para implementar a afirmação acima, mas isso não parece tão ruim.)

Fixar não é! Mover

Muitas vezes pensei em C ++, em que a maioria desses problemas é evitada: Os tipos podem ser declarados imóveis, excluindo o construtor de movimento e o operador de atribuição. Quando um tipo não é móvel, os tipos que contêm esse tipo também não são.

Houve várias tentativas de definir tipos imóveis para Rust. Isso se complica muito rapidamente.

Uma coisa importante a entender é que a fixação não fornece tipos imóveis! Todos os tipos permanecem móveis em Rust. Se você tiver um T , poderá movê-lo para onde quiser. Em vez de tipos que não podem ser movidos, usamos a capacidade de Rust para definir novas APIs encapsuladas, pois definimos um tipo de ponteiro do qual você não pode sair . Toda a magia está no tipo de ponteiro ( Pin<&mut T> ), não na ponta ( T ). Não há como um tipo dizer "Não consigo me mexer nunca". No entanto, uma maneira para um tipo de dizer "Se eu fosse preso, não me mover novamente". Assim, um MyFuture que eu próprio sempre será móvel, mas Pin<&mut MyFuture> é um ponteiro para uma instância de MyFuture que não pode se moveu mais.

Este é um ponto sutil e provavelmente deveríamos gastar algum tempo detalhando a documentação aqui para ajudar a evitar esse mal-entendido muito comum.

Mas chegamos muito longe sem conseguir expressar iteradores autorreferenciais, mas não é um caso de uso tão importante.

Isso ocorre porque, até agora, todos os iteradores são definidos usando um tipo e um bloco impl Iterator for … . Quando o estado precisa ser mantido entre duas iterações, não há escolha a não ser armazená-lo em campos do tipo iterador e trabalhar com &mut self .

No entanto, mesmo que esta não seja a principal motivação para incluir geradores na linguagem, seria muito bom eventualmente ser capaz de usar a sintaxe do gerador yield para definir algo que pode ser usado com um for loop. Assim que isso existir, o empréstimo de yield tão importante para geradores iteradores quanto para futuros de geradores.

( Unpin pode ter que se tornar um item de idioma para implementar a afirmação acima, mas isso não parece tão ruim.)

Unpin e Pin já devem ser itens lang para suportar geradores imóveis seguros.

Ok obrigado pelas explicações a todos! Eu concordo com @Gankro que um capítulo no estilo nomicon sobre Pin seria extremamente útil aqui. Suspeito que haja muito histórico de desenvolvimento para explicar por que existem vários métodos seguros e por que métodos inseguros não são seguros e tal.

Em um esforço para me ajudar a entender isso, porém, eu queria tentar novamente escrever por que cada função é segura ou por que é unsafe . Com o entendimento de cima, eu tenho tudo a seguir, mas há algumas perguntas aqui e ali. Se outras pessoas puderem me ajudar a preencher isso, seria ótimo! (ou ajude a apontar onde meu pensamento está errado)

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

    • Este é um método seguro para construir Pin<P> , e enquanto a existência de
      Pin<P> normalmente significa que P::Target nunca será movido novamente no
      programa, P::Target implementa Unpin que é lido "as garantias de
      Pin não retém mais ". Como resultado, a segurança aqui é porque não há
      garantias para manter, para que tudo corra normalmente.
  • unsafe fn new_unchecked(P) -> Pin<P> where P: Deref

    • Ao contrário da anterior, esta função é unsafe porque P não
      necessariamente implementam Unpin , então Pin<P> deve manter a garantia de que
      P::Target nunca mais se moverá depois que Pin<P> for criado.

    Uma maneira trivial de violar esta garantia, se esta função for segura, parece
    gostar:

    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
    }
    

    Consequentemente, cabe ao usuário garantir que Pin<P> realmente significa
    P::Target nunca mais é movido após a construção, então é unsafe !

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

    • Dados Pin<P> temos a garantia de que P::Target nunca se moverá. Isso é
      parte do contrato de Pin . Como resultado, trivialmente significa que
      &P::Target , outro "ponteiro inteligente" para P::Target fornecerá o mesmo
      garantia, então &Pin<P> pode ser traduzido com segurança para Pin<&P::Target> .

    Este é um método genérico para ir de Pin<SmartPointer<T>> para Pin<&T>

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

    • Eu acredito que a segurança aqui é praticamente a mesma que a as_ref acima.
      Não estamos distribuindo acesso mutável, apenas Pin , então nada pode ser facilmente
      violado ainda.

    Pergunta : que tal um DerefMut impl "malicioso"? Esta é uma maneira segura
    para chamar um DerefMut fornecido pelo usuário que enja &mut P::Target nativamente,
    presumivelmente permitindo que ele também o modifique. Como isso é seguro?

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

    • Pergunta : Dado Pin<P> (e o fato de que não sabemos nada sobre
      Unpin ), isso não deveria garantir que P::Target nunca se mova? Se pudermos
      reinicialize-o com outro P::Target , porém, isso não deveria ser inseguro?
      Ou isso está de alguma forma relacionado a destruidores e tudo mais?
  • unsafe fn map_unchecked<U, FnOnce(&T) -> &U>(Pin<&'a T>, f: F) -> Pin<&'a U>

    • Esta é uma função unsafe , então a questão principal aqui é "por que isso não
      seguro "? Um exemplo de violação de garantias se for seguro é o seguinte:

    ...

    Pergunta :: qual é o contra-exemplo aqui? Se isso fosse seguro, o que
    o exemplo que mostra a violação das garantias de Pin ?

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

    • A garantia de Pin<&T> significa que T nunca se moverá. Devolvendo &T
      não permite a mutação de T , portanto, deve ser seguro fazer enquanto mantém
      esta garantia.

    Um "talvez peguei" aqui é a mutabilidade interior, e se T fossem
    RefCell<MyType> ? Isso, no entanto, não viola as garantias de
    Pin<&T> porque a garantia só se aplica a T como um todo, não ao
    campo interior MyType . Enquanto a mutabilidade interior pode se mover
    internos, fundamentalmente ainda não consegue mover toda a estrutura atrás de
    Referência de & .

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

    • Pin<&mut T> significa que T nunca se moverá. Como resultado, significa Pin<&T>
      fornece a mesma garantia. Não deve ser muito problema com essa conversão,
      é principalmente a troca de tipos.
  • unsafe fn get_unchecked_mut(Pin<&'a mut T>) -> &'a mut T

    • Pin<&mut T> significa que T nunca deve se mover, então isso é trivialmente unsafe
      porque você pode usar mem::replace no resultado para mover T (com segurança). o
      unsafe aqui "embora eu esteja lhe dando &mut T , você não tem permissão para 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>

    • Acho que unsafe aqui é basicamente o mesmo que acima, estamos
      distribuir &mut T com segurança (não pode exigir um fechamento inseguro) que poderia
      ser facilmente usado com mem::replace . Provavelmente há outra insegurança aqui também
      com a projeção, mas parece razoável que seja pelo menos inseguro
      por causa disso.
  • fn get_mut(Pin<&'a mut T>) -> &'a mut T where T: Unpin

    • Ao implementar Unpin a type diz " Pin<&mut T> não tem garantias, é
      apenas um novo tipo de invólucro de &mut T ". Como resultado, sem garantias para
      podemos devolver &mut T com segurança
  • impl<P: Deref> Deref for Pin<P> { type Target = P::Target }

    • Isso pode ser implementado com segurança com as_ref seguido por get_ref , então o
      segurança deste impl segue do acima.
  • impl<P: DerefMut> DerefMut for Pin<P> where T::Target: Unpin { }

    • Isso pode ser implementado com segurança com as_mut seguido por get_mut , então o
      segurança deste impl segue do acima.
  • impl<T: ?Sized> Unpin for Box<T> (e outras implementações relacionadas a ponteiros)

    • Se nenhuma outra ação fosse realizada, Box<T> implementaria o traço Unpin
      somente se T implementado Unpin . Esta implementação aqui codifica que até
      se T explicitamente não implementa Unpin , Box<T> implementa Unpin .

    Pergunta : Qual é um exemplo do que isso é fundamentalmente fortalecedor?

    Por exemplo, o que seguro poderia exigir de outra forma unsafe se este impl

    não existe.


@ Matthias247 você não pode obter & mut T com segurança do Pin

> se T não for Desafixar, porque então você pode mem :: trocar para mover T para fora do pino, o que anularia o propósito de fixar as coisas.

Obrigado! Sim, como swap não é um método inseguro, isso é obviamente problemático. Mas isso poderia ser corrigido adicionando um Unpin vinculado a swap() ? Como todo código até agora deve ser Unpin ou não é seguro de qualquer maneira, isso não deve quebrar nada.

Acho que uma das coisas que ainda me confunde mais é que Pin<T> codifica várias garantias: Que o endereço do T é estável, bem como algumas garantias sobre seu estado interno (é meio congelado / imutável em alguns situações, mas também não realmente).

Pareceu-me que mover o código / projeções inseguras apenas para os lugares onde outras chamadas baseadas em Pin são necessárias (por exemplo, em poll s em campos) pode ser preferível a lidar com essas projeções inseguras em todos os casos. No entanto, agora também percebi que há outro problema com isso: o código que tem acesso à referência mutável pode mover o campo livremente, e quando drop() é então chamado com segurança neste campo, ele pode quebrar devido a endereço sendo armazenado em outro lugar. Para que isso fosse corrigido, seria necessária a sobrecarga de drop(Pin<T>) que falamos.

@RalfJung Obrigado pelas explicações! Concordo com o fato de que certamente tentei fazer algo inseguro em geral e, portanto, não haveria problema em exigir minha compreensão extra. Estou mais preocupado com as pessoas que desejam escrever combinadores futuros mais geralmente seguros, mas agora também podem ser confrontados com todos esses termos. Se eles puderem escrever combinadores sem ter que entender Unpin e projeções de pinos em tudo, e então obter combinadores que funcionam de forma reduzida (apenas em Unpin futuros), isso parece preferível. Como não tentei, não posso dizer se é o caso atualmente. Acho que ainda é necessário adicionar pelo menos Unpin limites manualmente.

Eu também entendo o fato de que os tipos não móveis são diferentes do tipo de pino. No entanto, atualmente estou mais focado nos casos de uso do que na diferença. E para o caso de uso de coleções intrusivas, os tipos não móveis funcionam muito bem sem introduzir muita complexidade. Para os futuros, obviamente, alguma pesquisa seria necessária, uma vez que faltam os caminhos de um tipo móvel para um tipo não móvel. Se essa forma não fosse mais ergonômica do que as APIs Pin, também não haveria vitória.

Adicionar T: Unpin a mem::swap significaria que não poderia ser usado em certos tipos, mesmo quando não estão dentro de um pin.

Mas isso poderia ser corrigido adicionando um limite Unpin a swap ()? Como todo o código até agora deve ser Desafixado ou não é seguro, isso não deve quebrar nada.

Isso quebraria todo o código genérico: se você escrever uma função na ferrugem estável de hoje para algum T sem restrições, você pode chamar swap nela. Isso tem que continuar funcionando.

os tipos imóveis funcionam muito bem sem introduzir muita complexidade.

Ninguém demonstrou uma maneira de adicionar tipos não móveis ao Rust de maneira compatível com versões anteriores, sem que a complexidade exceda significativamente o que está sendo proposto para estabilização aqui. Você deve encontrar algumas dessas propostas e discussões antigas no repositório RFC. Consulte https://github.com/rust-lang/rfcs/pull/1858 para ver um exemplo.

O RFC em https://github.com/rust-lang/rfcs/pull/2349 , bem como a série de blogs do barco começando aqui, deve ajudar a dar a você algum histórico e alguma impressão dos outros designs que foram considerados. (Observe também as datas, este design está em construção há quase 10 meses!)

Além disso, mem :: swap é uma pista falsa, porque não é uma função interessante. É literalmente apenas

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

@Gankro não usa código inseguro? Afaik, não é possível escrever isso literalmente.

Edit: Acho que outra maneira de pensar sobre isso é que adicionar T: Unpin a mem::swap está, na verdade, mudando a definição de segurança no nível da linguagem. Isso quebraria todos os mycrate::swap fns existentes.

Adicionar T: Unpin ao mem :: swap significaria que ele não poderia ser usado em certos tipos, mesmo quando não estivessem dentro de um Pin.

Se Desafixar for derivado automaticamente (da mesma forma que Sync / Send ), então pensei que isso não deveria ser um problema.

Isso quebraria todo o código genérico: se você escrever uma função na ferrugem estável de hoje para algum T sem restrições, pode chamar swap nela. Isso tem que continuar funcionando.

Mas este obviamente é. Não pensei sobre o fato de que os limites do traço precisam ser propagados explicitamente no Rust.

Ninguém demonstrou uma maneira de adicionar tipos não móveis ao Rust de maneira compatível com versões anteriores, sem que a complexidade exceda significativamente o que está sendo proposto para estabilização aqui. Você deve encontrar algumas dessas propostas e discussões antigas no repositório RFC. Veja rust-lang / rfcs # 1858 para um exemplo.

Obrigado, vou ler um pouco mais sobre o trabalho anterior sobre isso se eu encontrar algum tempo. Obviamente, muitos pensamentos e esforços já foram feitos para isso, e certamente não quero bloquear as coisas. Queria apenas fornecer minhas preocupações e perguntas ao tentar trabalhar com isso.

@ Matthias247

Se Desafixar for derivado automaticamente (da mesma forma que Sincronizar / Enviar), achei que isso não deveria ser um problema.

Não tenho certeza se fui claro. Para esclarecer, é perfeitamente seguro mover um tipo !Unpin e, portanto, perfeitamente seguro mem::swap eles. Só é inseguro mover um tipo T: !Unpin depois de ser fixado, ou seja, dentro de um Pin<P<T>> .

Por exemplo, no código assíncrono / espera, você pode mover os futuros retornados de uma função assíncrona o quanto quiser. Você só deixa de ser capaz de movê-los depois de pin_mut! eles ou colocá-los em um Pin<Box<..>>> ou algo assim.

Tenho várias perguntas sobre isso:

  1. Dada a importância de Pin<T> para async e para geradores e potencial para problemas ao interagir com outras partes de Rust (por exemplo, swap & replace ), tem algum verificação formal (por exemplo, por @jhjourdan ou @RalfJung) foi feita da variante da API de fixação proposta aqui?

  2. Esta API oferece novas garantias sobre a máquina abstrata / semântica operacional do Rust? Ou seja, se esquecermos o caso de uso como mecanismo de apoio para async & await / geradores, poderíamos colocar isso dentro de uma caixa no ecossistema e funcionaria apenas dadas as garantias atuais que nós dar?

  3. Que tipo de APIs ou adições de sistema de tipo tornam-se impossíveis como consequência da estabilização da API de fixação? (esta questão é uma extensão de 2.)

  4. Quais caminhos a API a ser estabilizada oferece em termos de evolução de uma linguagem do tipo &pin T para melhorar a projeção de campo e tal (o que não parece ótimo com a API proposta).

Eu tenho algumas notas variadas:

  1. A documentação na biblioteca padrão parece bastante esparsa. :(

  2. Eu concordo com outros que a construção de fixação é mentalmente desgastante / complexa.

  3. O exemplo Unmovable na documentação parece excessivamente complicado e envolve unsafe ; isso parece abaixo do ideal. Com a inicialização gradual conforme elaborado em um rascunho de RFC na linguagem (ou seja, aprimorando o NLL), você poderia nos fornecer:

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.

Isso envolve zero inseguro e é muito fácil para o usuário lidar com isso.

Além disso, as APIs std não fornecem uma maneira segura de fixar objetos na pilha.
Isso ocorre porque não há como implementar isso com segurança usando uma API de função.

E quanto a 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)
}

Não tenho acompanhado o desenvolvimento das APIs de fixação muito de perto, então isso poderia ter sido mencionado ou explicado em outro lugar e não fui capaz de encontrá-lo, desculpe se for o caso:

O tipo Pinned é um ZST que não implementa Unpin ; permite que você
suprimir a implementação automática de Unpin no stable, onde !Unpin impls
não seria estável ainda.

Existe alguma explicação sobre por que !Unpin impls não pode ser estabilizado?

Só é seguro se Foo (o tipo de conteúdo) não for repr (embalado),
porque isso faz com que os campos sejam movidos para realinhá-los.

Empacotado significa que os campos podem ser movidos dinamicamente? Isso é um pouco assustador. Temos certeza de que o llvm nunca gerará código para mover campos em outras circunstâncias? Da mesma forma, é possível que os valores fixados na pilha sejam movidos por llvm?

Apesar da sutileza, esta parece ser uma API muito boa. Bom trabalho!

@Centril Ralf escreveu sobre em seu blog .

As APIs pin não envolveram nenhuma alteração de idioma e são totalmente implementadas na biblioteca padrão usando recursos de linguagem pré-existentes. Não tem impacto no idioma Rust e não exclui nenhum recurso de outro idioma.

Pin é realmente apenas uma implementação inteligente de um dos recursos mais valiosos e clássicos do Rust: a capacidade de introduzir invariáveis ​​a uma API marcando parte da API unsafe . Pin envolve um ponteiro para tornar uma operação ( DerefMut ) insegura, exigindo que as pessoas que a fazem mantenham certas invariáveis ​​(que não saiam da referência) e permitindo que outro código suponha que isso nunca ocorrerá. Um exemplo semelhante e muito mais antigo desse mesmo truque é String , o que torna inseguro colocar bytes não UTF8 em uma string, permitindo que outro código presuma que todos os dados em String são UTF8.

Existe alguma explicação sobre o porquê! Unpin impls não pode ser estabilizado?

Os impls negativos são atualmente instáveis, totalmente sem relação com essas APIs.

Os impls negativos estão instáveis ​​no momento.

🤦‍♂️ Isso faz sentido, deveria ter pensado um pouco mais sobre isso. Obrigado.

@alexcrichton Essa é uma ótima análise dessa API, devemos tentar preservá-la em algum lugar melhor do que algum comentário que se perderá!

Alguns comentários:

as_mut : Pergunta: e quanto a um impl DerefMut "malicioso"? Esta é uma maneira segura
para chamar um DerefMut fornecido pelo usuário que cria & mut P :: Target nativamente,
presumivelmente permitindo que ele também o modifique. Como isso é seguro?

Basicamente, quando você chama new_unchecked , você faz uma promessa sobre as implementações Deref e DerefMut para este tipo.

set : Pin

(e o fato de que não sabemos nada sobre
Desafixar), isso não deveria garantir que P :: Target nunca se mova? Se pudermos
reinicializá-lo com outro P :: Target, porém, isso não deveria ser inseguro?
Ou isso está de alguma forma relacionado a destruidores e tudo mais?

Isso remove o conteúdo antigo do ponteiro e coloca o novo conteúdo nele. "Fixado" não significa "nunca caiu", significa "nunca foi movido até cair". Portanto, descartá-lo ou sobrescrevê-lo enquanto chama drop está bem. Chamar drop é crucial, essa é a garantia de queda que mencionei acima.

Onde você vê algo se movendo aqui?

map_unchecked : Pergunta :: qual é o contra-exemplo aqui? Se fosse seguro, qual é o exemplo que mostra violação das garantias do Pin?

Um exemplo seria começar com Pin<&&T> e usar este método para obter Pin<&T> . A fixação não se propaga por meio de referências.

get_ref : um "talvez peguei" aqui é a mutabilidade interior, e se T fosse
RefCell?

Na verdade, isso é um problema, mas, como você observou, não é um problema de solidez. O que seria incorreto é ter um método que vai de Pin<RefCell<T>> a Pin<&[mut] T> . Basicamente, o que acontece é que RefCell não propaga a fixação, poderíamos impl<T> Unpin for RefCell<T> .

alguma verificação formal (por exemplo, por @jhjourdan ou @RalfJung) foi feita da variante da API de fixação que é proposta aqui?

Não, não do nosso lado. As postagens do blog que mencionei acima contêm algumas idéias sobre como eu começaria a formalizar isso, mas ainda não examinamos e concluímos. Se você me der uma máquina do tempo ou um aluno de doutorado interessado, nós o faremos. ;)

se esquecermos o caso de uso como um mecanismo de apoio para async & await / generators, poderíamos colocá-lo dentro de uma caixa no ecossistema e ele simplesmente funcionaria dadas as garantias atuais que oferecemos?

Essa é a intenção.

Que tipo de APIs ou adições de sistema de tipo tornam-se impossíveis como consequência da estabilização da API de fixação? (esta questão é uma extensão de 2.)

Uh, eu não tenho ideia de como responder a essa pergunta com confiança. O espaço de possíveis acréscimos é muito grande e também muito alto, e eu ousaria fazer qualquer tipo de declaração universal sobre isso.

Quais caminhos a API a ser estabilizada fornece em termos de evolução para uma linguagem fornecida e tipo T de pino para melhorar a projeção de campo e tal (o que não parece ótimo com a API proposta).

Não tenho mais certeza sobre &pin T , ele não combina muito bem com o novo Pin<T> genérico. Em termos de manipulação de projeções, precisaríamos de algum hack para dizer " Unpin e Drop não foram implementados para este tipo", então poderíamos fazer isso com segurança com uma macro. Para ergonomia adicional, provavelmente desejaríamos uma capacidade genérica de "projeção de campo" na linguagem, que também cobrisse a passagem de &Cell<(A, B)> a &Cell<A> .

Existe alguma explicação sobre o porquê! Unpin impls não pode ser estabilizado?

Os impls negativos AFAIK têm algumas limitações de longa data, e se você adicionar limites genéricos a eles, muitas vezes não funcionarão da maneira que você acha que deveriam. (Limites genéricos às vezes são simplesmente ignorados, ou então.)

Talvez Chalk conserte tudo isso, talvez apenas um pouco, mas de qualquer forma, provavelmente não quereríamos bloquear isso no Chalk.

Empacotado significa que os campos podem ser movidos dinamicamente? Isso é um pouco assustador.

É a única maneira válida de chamar drop em um campo compactado, visto que drop espera uma referência alinhada.

Temos certeza de que o llvm nunca gerará código para mover campos em outras circunstâncias? Da mesma forma, é possível que os valores fixados na pilha sejam movidos por llvm?

Copiar bytes do LLVM não deve ser problema, pois não pode alterar o comportamento do programa. Trata-se de um conceito de nível superior de "movimentação" de dados, de uma forma que seja observável no Rust (por exemplo, porque os ponteiros para os dados não apontam mais para eles). O LLVM não pode simplesmente mover dados para outro lugar para onde possamos ter indicadores. Da mesma forma, o LLVM não pode simplesmente mover valores na pilha cujo endereço foi obtido.

Ok, obrigado @RalfJung , faz sentido! Algumas perguntas de acompanhamento ...

Quando você diz "nunca mudou até cair", isso significa que Drop pode ser chamado de onde &mut self mudou do endereço de Pin<&mut Self> ? Os destruidores não podem contar com a precisão dos ponteiros internos, certo?

Minha preocupação ao olhar para set era como o pânico seria tratado, mas acho que isso não é um problema depois de cavar um pouco mais no codegen.

Quando você diz "nunca mudou até cair", isso significa que Drop pode ser chamado de onde &mut self mudou do endereço de Pin<&mut Self> ? Os destruidores não podem contar com a precisão dos ponteiros internos, certo?

@alexcrichton meu entendimento é que nunca mudou antes de Drop::drop retornar. Caso contrário, alguns casos de uso (pelo menos coleções intrusivas e buffers DMA alocados na pilha) tornam-se impossíveis.

Os destruidores podem contar com algo que nunca foi movido se puderem provar que o estava anteriormente em Pin . Por exemplo, se uma máquina de estado só pode entrar em um estado por meio de uma API que requer que seja fixada, o destruidor pode assumir que ela foi fixada antes de ser descartada se estiver nesse estado.

O código não pode presumir que destruidores não locais não movam membros, mas obviamente você pode presumir que seus próprios destruidores não movem as coisas porque é você quem os escreve.

Quando você diz "nunca se moveu até cair", isso significa que Drop pode ser chamado de onde & mut self se moveu do endereço de Pin <& mut Self>? Os destruidores não podem contar com a precisão dos ponteiros internos, certo?

Quero dizer que os dados nunca serão movidos (no sentido de não ir a nenhum outro lugar, incluindo não serem desalocados) até que drop seja chamado. E sim, drop pode confiar na execução no local fixado, embora seu tipo não possa expressar isso. drop deve levar Pin<&mut self> (para todos os tipos), mas, infelizmente, tarde demais para isso.

Depois que drop foi chamado, os dados são apenas bytes sem sentido, você pode fazer qualquer coisa com eles: colocar coisas novas lá, desalocar, o que for.

Isso permite, por exemplo, uma lista encadeada intrusiva onde o destruidor de um elemento cancela o registro ajustando os ponteiros vizinhos. Sabemos que a memória não irá embora sem que o destruidor seja chamado. (O elemento ainda pode ter vazado e, em seguida, permanecerá nessa lista vinculada para sempre. Mas, nesse caso, permanecerá na memória válida, portanto, não há problema de segurança.)

Estive lendo tudo que posso encontrar em Pin , Unpin e a discussão que conduziu a tudo isso, e embora eu _ pense_ agora entendo o que está acontecendo, também há muito de sutileza em torno do que os diferentes tipos significam, bem como o contrato que os implementadores e usuários devem seguir. Infelizmente, vejo relativamente pouca discussão sobre isso nos documentos de std::pin ou em Pin e Unpin especificamente. Em particular, adoraria ver parte do conteúdo desses comentários:

incorporado aos documentos. Especificamente:

  • Esse Pin protege apenas "um nível de profundidade".
  • Isso Pin::new_unchecked coloca restrições no implemento para Deref e DerefMut .
  • A interação entre Pin e Drop .
  • Como !Unpin só não pode ser movido depois de colocado em um Pin .
  • A razão de ser Pin<Box<T>>: Unpin where T: !Unpin . Isso remete à restrição de "um nível de profundidade" acima, mas acho que este exemplo concreto com uma explicação adequada seria útil para os leitores.

Também achei os contra-exemplos de @alexcrichton muito úteis. Dar ao leitor uma ideia do que pode dar errado com os diferentes métodos unsafe além da prosa, acho que ajudaria muito (pelo menos certamente ajudou). Em geral, devido à sutileza desta API, gostaria de ver as seções de Segurança dos vários métodos inseguros expandidas e possivelmente também referenciadas nos documentos em nível de módulo std::pin .

Eu ainda não usei muito essa API, então vou confiar no julgamento técnico de que ela está em bom estado agora. No entanto, acho que os nomes Pin , Pinned e Unpin são muito semelhantes e inexpressivos para o que são tipos / características muito diferentes e uma API relativamente complexa, o que torna seu entendimento mais difícil (como evidenciado por alguns comentários neste tópico).

Eles parecem seguir as convenções de nomenclatura para traços de marcador, então não posso reclamar, mas me pergunto se poderíamos fazer uma troca entre verbosidade e nomes autoexplicativos:

  • Pinned - meio confuso porque é a mesma palavra que Pin . Dado que é usado como PhantomData para aumentar uma estrutura com meta informação que estaria faltando de outra forma, que tal PinnedData , PhantomPinned , PhantomSelfRef ou mesmo DisableUnpin ? Algo que indica o padrão de uso e / ou o efeito que vai ter.
  • Unpin - Como em https://github.com/rust-lang/rust/issues/55766#issuecomment -437266922, estou confuso com o nome Unpin , porque "un-pin "pode ​​ser entendido de várias maneiras ambíguas. Algo como IgnorePin , PinNeutral talvez?

Geralmente, estou falhando e encontrando bons nomes alternativos ...

PhantomPin e PinNeutral me parecem nomes particularmente bonitos.

Apenas para fornecer um contraponto, achei Unpin intuitivo (assim que o entendi). Pinned é mais difícil de controlar, visto que não o usei em meu próprio código. Que tal mudar Pinned para NotUnpin ?

Concordo que encontrar nomes melhores pode ser um exercício de bicicleta que vale a pena, com o propósito de tornar mais fácil falar sobre a fixação. Eu proponho:

  • Pin -> Pinned : Quando você recebe um Pin , isso é realmente uma promessa de que o que você recebeu _foi fixado_ e _remain_ estará fixado para sempre. Eu não sinto muito sobre isso, porém, como você _poderia_ falar sobre receber um "alfinete de um valor". Pinned<Box<T>> apenas me parece melhor, especialmente porque apenas Box está fixado, não o que ele contém.
  • Unpin -> Repin : para outras características de marcador, normalmente falamos sobre o que você pode fazer com algo que tenha essa característica de marcador. Presumivelmente, é por isso que Unpin foi escolhido em primeiro lugar. No entanto, acho que o que realmente queremos que o leitor tire daqui é que algo que é Unpin pode ser fixado e, em seguida, afixado novamente em outro lugar, sem consequência ou consideração. Também gosto da sugestão de @mark-im de PinNeutral , embora seja um pouco mais prolixo.
  • Pinned -> PermanentPin : Não acho que Pinned seja um bom nome, porque algo que contém Pinned não está realmente fixado. simplesmente não é Unpin . PhantomPin tem um problema semelhante no sentido de que se refere a Pin , quando Pin não é realmente o que você deseja. NotUnpin tem uma negativa dupla, o que torna difícil raciocinar a respeito. A sugestão da @Kimundi de PhantomSelfRef chega bem perto, embora eu ainda ache que é um pouco "complexo", e vincula a propriedade "não pode ser movida uma vez fixada" com uma instância onde esse é o caso (quando você ter uma auto-referência). Minha sugestão também pode ser expressa como PermanentlyPinned ; Não sei qual forma é menos ruim.

Eu acho que Pinned deve acabar sendo NotX onde X é o que Unpin acaba sendo nomeado. A única tarefa de Pinned é fazer com que o tipo delimitador não implemente Unpin . (Editar: e se estivermos mudando Unpin para outra coisa, presumivelmente o duplo negativo não é um problema)

Repin não faz sentido para mim, porque "Este tipo pode ser fixado em outro lugar" é apenas um efeito colateral de "Este tipo pode ser removido de um alfinete".

@tikue Em certo sentido, sinto o mesmo, mas ao contrário. Acho que Unpin deve ser formulado de forma negativa "isto _não_ é restrito por Pin ", enquanto Pinned deve ser formulado positivamente "isto _é_ restringido por Pin ". Principalmente para evitar o duplo negativo. Mas isso é um detalhe; Eu gosto da ideia de haver uma dualidade. Talvez: s/Unpin/TemporaryPin , s/Pinned/PermanentPin ?

EDIT: Sim, entendo seu argumento sobre Repin ser um efeito colateral de Unpin . Eu queria comunicar o fato de que Pin é "sem importância" para um tipo que é Unpin , que eu não acho que Unpin faz muito bem. Daí a sugestão acima de TemporaryPin .

@jonhoo Acho que a principal razão pela qual prefiro o oposto é porque Pinned impede que um traço seja implementado, então isso é, no sentido mais claro para mim, o verdadeiro negativo.

Editar: e quanto a:

Unpin -> Escape
Pinned -> NoEscape

Interessante .. Estou tentando ver como isso caberia na documentação. Algo como:

Em geral, quando você recebe um Pin<P> , isso vem com uma garantia de que o alvo de P não se moverá até que seja descartado. A exceção é se o alvo de P for Escape . Os tipos marcados como Escape prometem que permanecerão válidos mesmo se forem movidos (por exemplo, eles não contêm referências próprias internas) e, portanto, podem "escapar" de Pin . Escape é uma auto-característica, então todos os tipos que consistem inteiramente de tipos que são Escape também são Escape . Os implementadores podem cancelar isso todas as noites usando impl !Escape for T {} ou incluindo o NoEscape marcador de std::phantom .

Isso parece bastante decente, embora a conexão com a palavra "escapar" pareça um pouco tênue. Separadamente, escrever o acima também me fez perceber que Pin<P> não _realmente_ garante que o alvo de P não será movido (precisamente por causa de Unpin ). Em vez disso, garante que _tanto_ não importa se o alvo de P se move _ou_ P não será movido. Não sei como usar isso para informar uma melhor escolha de nomes ... Mas provavelmente é algo que deveria entrar nos documentos de uma forma ou de outra.

Pessoalmente, eu também não gosto de Unpin como nome, talvez porque é mais frequentemente visto como impl !Unpin que diz "não-des-pin" e requer vários ciclos cerebrais (I sou um modelo mais antigo) para concluir que significa "ok, este aqui será fixado para sempre assim que for fixado pela primeira vez", então não posso nem otimizar o negativo duplo.
Em geral, os humanos tendem a ter mais dificuldade para pensar em negativos em vez de positivos (sem uma fonte direta, mas verifique o trabalho de Richard Hudson se tiver dúvidas).
Aliás, Repin parece muito bom para mim.

Pin é difícil de explicar porque nem sempre torna o valor fixado imóvel. Pinned é confuso porque, na verdade, não fixa nada. Isso apenas evita o escape de Pin .

Pin<P<T>> pode ser explicado como um valor fixado: valores fixados não podem se mover a menos que o valor seja um tipo que possa escapar de um alfinete.

Algumas pesquisas rápidas no Google parecem mostrar que na luta livre, quando alguém está imobilizado, o ato de se soltar é chamado de fuga .

Também gosto do termo escape em vez de Unpin mas escolheria EscapePin .

Muitos bons pensamentos aqui!

Uma coisa geral: para mim, como um falante não nativo, Pin e Unpin são principalmente verbos / ações. Embora Pin faça sentido, já que o objeto está fixado em um local da memória por vez, não consigo ver o mesmo para Unpin . Assim que eu receber uma referência de Pin<&mut T> , T sempre será fixado no sentido de que sua localização de memória é estável, seja Unpin ou não. Não é possível realmente desafixar um objeto como uma ação. A diferença é que os tipos Unpin não requerem as garantias de fixação para serem mantidos em interações futuras. Eles não são autorreferenciais e seu endereço de memória, por exemplo, não é enviado para outro objeto e armazenado lá.

Eu concordaria com @tikue que seria bom ter um trabalho notável para o que Unpin realmente significa, mas é difícil de quantificar. Não é puramente que esses tipos sejam móveis, e também não é puramente sua falta de autorreferencialidade? Talvez seja algo sobre "nenhum ponteiro em todo o espaço da memória é invalidado quando o objeto é movido". Então algo como StableOnMoveAfterPin ou apenas StableMove pode ser uma opção, mas também não parece muito bom.

Repin para mim tem as mesmas complicações que Unpin , que é que implica que uma coisa primeiro se solta - o que, no meu entendimento, não acontece.

Como o traço define principalmente o que acontece depois que alguém vê um Pin do tipo, acho coisas como PinNeutral ou PinInvariant não tão ruins.

Em relação a Pin vs Pinned , acho que preferiria Pinned , já que esse é o estado do ponteiro no momento em que o vemos.

@ Matthias247 Não creio que você tenha a garantia de que o endereço de P::Target será estável se P: Unpin ? Posso estar errado sobre isso?

@ Matthias247

Assim que recebo uma referência de Pin <& mut T>, T sempre será fixado no sentido de que sua localização de memória é estável, seja Desafixado ou não.

Você pode esclarecer o que você quer dizer com isso? Dado um T: Unpin você pode escrever o seguinte:

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 Na verdade, uma boa pergunta. Meu raciocínio era que os futuros são encaixotados, e então seu método poll() será chamado no mesmo endereço de memória. Mas isso obviamente se aplica apenas à tarefa / futuro de nível superior, e as camadas intermediárias podem movimentar os futuros quando estiverem Unpin . Então, parece que você está certo.

Escreva as últimas novidades em bicicletas:

  • Unpin : e MoveFromPin ? Se ainda não estou perdendo algumas sutilezas, acho que isso afirma diretamente qual capacidade o traço realmente permite: se o tipo estiver dentro de Pin , você ainda pode movê-lo.

    Crucialmente, meio que diz a mesma coisa que Unpin , mas enquadrado como uma afirmação positiva, então, em vez do duplo-negativo !Unpin , agora temos !MoveFromPin . Acho que acho isso mais fácil de interpretar, pelo menos ... são uh, tipos que você não pode mover com um alfinete.

    (Há alguma variação na ideia básica: MoveOutOfPin , MoveFromPinned , MoveWhenPinned e assim por diante.)

  • Pinned : isso pode então se tornar NoMoveFromPin , e seu efeito é fazer um tipo !MoveFromPin . Acho que isso parece bastante direto.

  • Pin si: este não está conectado aos outros dois e também não é tão significativo, mas acho que pode haver espaço para algumas pequenas melhorias aqui também.

    O problema é que Pin<&mut T> (por exemplo) não significa que &mut está fixado, significa que T está (uma confusão que acho que vi em pelo menos uma evidência de comentário recente). Como a parte Pin atua como uma espécie de modificador em &mut , acho que seria melhor chamá-la de Pinning .

    Há algum precedente indireto para isso: se quisermos modificar a semântica de estouro de um tipo inteiro para envolver em vez de pânico, dizemos Wrapping<i32> vez de Wrap<i32> .

Todos eles são mais longos do que os originais, mas dado o quão delicado é o raciocínio em torno deles, pode ser um investimento valioso para maior clareza.

Re: Repin , espero que seja algo como

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

que poderia ser usado para oferecer suporte a tipos !Unpin dentro de uma coleção semelhante a Vec que ocasionalmente move seu conteúdo (esta não é uma proposta para adicionar tal característica agora ou nunca, apenas minha primeira impressão do nome do traço).

Também trocando nomes de bicicletas:

  • Pin<P> -> Pinned<P> : o valor para o qual o ponteiro P aponta é fixado na memória por toda a sua vida (até ser eliminado).
  • Unpin -> Moveable : o valor não precisa ser fixado e pode ser movido livremente.
  • Pinned (struct) -> Unmoveable : o precisa ser Pinned e não pode ser movido.

Eu não acho que Pin ou Unpin devam mudar, todas as alternativas adicionam verbosidade sem clareza na minha opinião, ou são até bastante enganosas. Também já tivemos essa conversa, chegamos à decisão de usar Pin e Unpin , e nenhum dos argumentos levantados neste tópico é novo.

No entanto, Pinned foi adicionado desde a discussão anterior, e acho que faz sentido que PhantomPinned deixe claro que é um tipo de marcador fantasma como PhantomData .

Pessoalmente, eu também não gosto do nome Desafixar, talvez porque seja mais frequentemente visto como impl! Desafixar, que diz "não desamarrar" e requer vários ciclos cerebrais (sou um modelo mais antigo) para concluir que significa "ok, este aqui será fixado para sempre assim que for fixado pela primeira vez", então não posso nem otimizar o negativo duplo.

Isso é completamente o oposto da minha experiência, implementar manualmente !Unpin significa que você está implementando uma estrutura autorreferencial usando código inseguro, um caso de uso extremamente nicho. Em contraste, qualquer coisa que mantenha uma estrutura potencialmente desafixada atrás de um ponteiro tem um impl positivo de Unpin . Todos os impls de Unpin em std são de polaridade positiva, por exemplo.

@withoutboats você poderia fornecer um link para a discussão anterior sobre esses nomes, onde os argumentos levantados aqui já foram discutidos?

Aqui está um tópico, embora certamente também tenha sido discutido no tópico RFC e no problema de rastreamento https://internals.rust-lang.org/t/naming-pin-anchor-move/6864

(os tipos neste tópico chamados âncora e pino agora são chamados de Pin<Box<T>> e Pin<&'a mut T> )

O Unpin deve ser lido como uma abreviação de Unpinnable? Não pode ser definido como em você não pode realmente manter o valor fixado, mesmo que esteja dentro de um Pin. (Isso é mesmo um entendimento correto da minha parte?)

Eu olhei através de alguns dos documentos e tópicos de comentários e não vi nenhuma referência especificamente ao Impinnable.

Desafixar não deve ser abreviação de nada. Uma coisa que eu acho que não é óbvia para muitos usuários, mas é verdade, é que o guia de estilo da biblioteca padrão é preferir verbos como nomes de características, não adjetivos - portanto, Send , não Sendable . Isso não tem sido aplicado com perfeita consistência, mas é a norma. Unpin é como "desafixar", já que é possível desafixar este tipo de Pin você o fixou.

Nomes como Move (não "móvel", lembre-se) são menos claros do que Unpin porque implicam que tem a ver com ser capaz de movê-lo, em vez de conectar o comportamento ao tipo de pino. Você pode mover tipos de !Unpin , porque pode mover qualquer Sized em Rust.

Nomes que são frases inteiras, conforme sugerido, seriam muito unidiomáticos para std.

Pode não ser a abreviatura, mas é exatamente como eu li. Visto que traços são verbos, você deve transformá-lo manualmente em um adjetivo se quiser usá-lo para descrever um tipo em vez de uma operação em um tipo; std::iter::Iterator é implementado por algo que é iterável, std::io::Seek é implementado por algo que é pesquisável, std::pin::Unpin é implementado por algo que não pode ser definido.

@sem embarcações algum problema específico com alguns dos outros nomes que não têm o verbo, por exemplo, Escape ou EscapePin ? Entendi que essa discussão já aconteceu antes, mas provavelmente muito mais olhos estão voltados para isso agora, então não tenho certeza se é uma repetição completamente redundante ...

Uma coisa que eu acho que é verdade é que é lamentável que Pin e Unpin possam ser lidos como um par (alguns tipos são "fixados" e outros "liberados"), quando Pin deve ser um substantivo , não um verbo . O fato de Pin não ser uma característica, espero, esclarece as coisas. O argumento a favor de Pinning faz sentido, mas aqui nos deparamos com o problema do comprimento do nome. Especialmente porque os receptores de método terão que repetir self duas vezes, acabamos com muitos caracteres: self: Pinning<&mut Self> . Não estou convencido de que Pinning<P> são quatro caracteres inteiros que valem mais que Pin<P>

@tikue, a terminologia de "fuga" é muito mais sobrecarregada do que fixação, eu acho, conflitando com conceitos como análise de fuga.

Além disso, já tivemos essa conversa, chegamos à decisão de usar Pin e Unpin, e nenhum dos argumentos levantados neste tópico são novos.

Isso me irrita - o relato de experiência da comunidade é indesejado? Eu pessoalmente não vi um problema claro com Unpin até alguns dos outros comentários neste tópico, bem como a justaposição com Pinned negativo duplo.

A ferrugem não executa a análise de escape, então não tenho certeza se vejo isso como um problema real.

: bell: Agora está entrando em seu período de comentário final , conforme a revisão acima . :Sino:

Ainda gostaria de ver melhorias na documentação, conforme descrito em https://github.com/rust-lang/rust/issues/55766#issuecomment-438316891 antes de chegar :)

Abri https://github.com/rust-lang/rust/pull/55992 para adicionar a documentação sugerida acima e renomear Pinned para PhantomPinned .

Acho que Pinned (e PhantomPinned ) incentiva a conceitualização de um valor "fixado" como aquele que não pode sair de Pin , o que significa que muitos valores em Pin (aqueles cujos tipos impl Unpin ) não são "fixados"!

Isso parece confuso. Acho mais fácil conceituar todos os valores em Pin como fixados enquanto eles estão em Pin , e se a fixação é ou não permanente é o que a coisa anteriormente denominada Pinned controles. Um nome separado de Pin* evitaria a fusão de dois conceitos distintos.

PhantomNotUnpin : P

Pessoalmente, eu também não gosto do nome Unpin, talvez porque ele seja mais frequentemente visto como impl! Unpin, que diz "não-des-pin" e requer vários ciclos cerebrais

Obrigado! Eu também fui incomodado por Unpin por um bom tempo que o bot não foi capaz de apontar (heh) o porquê. Agora acho que entendi: é a dupla negação.

Isso é completamente o oposto da minha experiência, implementar manualmente! Desafixar significa que você está implementando uma estrutura autorreferencial manualmente usando código inseguro, um caso de uso extremamente específico. Em contraste, qualquer coisa que mantenha uma estrutura potencialmente desafixada atrás de um ponteiro tem um impl positivo de Unpin. Todos os impls de Unpin em std têm polaridade positiva, por exemplo.

Não se trata apenas de implementação, mas também de discussão. impl !Sync é bastante raro (não sozinho porque é instável), mas falar sobre os tipos Sync e !Sync é bastante comum. Da mesma forma, !Unpin surgiu muito em discussões sobre esse recurso, pelo menos as que tive.

Eu também preferiria algo que expressasse positivamente uma propriedade ( MoveFromPin ou algo assim). Não estou totalmente convencido pela ergonomia, já que, ao contrário de Pin , não se deveria ter que escrever esse limite com tanta frequência.

A ferrugem não executa a análise de escape, então não tenho certeza se vejo isso como um problema real.

O LLVM sim, portanto, a análise de escape ainda é bastante relevante para o Rust.

A maneira de resolver isso é escolher palavras que invertem o significado de Pin / Unpin . Por exemplo, renomeie Unpin para Relocate . Então !Unpin se torna !Relocate . Isso faz sentido muito mais intuitivo para mim - eu leio como "Oh, objetos deste tipo não podem ser realocados". Outro candidato é Movable .

Não tenho certeza qual é a palavra oposta que poderia substituir Pin , ou se ainda precisamos. Mas eu certamente poderia imaginar os documentos dizendo algo assim:

Um objeto fixado pode ser modificado diretamente por meio de DerefMut se e somente se o objeto puder ser realocado na memória. Relocate é uma característica automática - é adicionada por padrão. Mas se houver ponteiros diretos para a memória que contém seus valores, cancele Relocate adicionando impl !Relocate ao seu tipo.

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

Isso faz muito mais sentido intuitivo para mim do que Unpin .

Abri # 55992 para adicionar a documentação sugerida acima

No entanto, isso apenas adiciona uma parte do que foi sugerido em https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891.

Gosto da sugestão MoveFromPin . Relocate também é bom, mas talvez não esteja suficientemente associado a Pin . Pode-se novamente entendê-lo como um tipo não móvel (o que não é). RelocateFromPin seria novamente bom.

Escape ing é algo que também está associado, por exemplo, no Swift com encerramentos, e se eles são chamados dentro ou fora da cadeia de chamadas atual. Isso parece enganoso.

Não vejo problemas com nomes mais longos, desde que ajude a clareza.

FWIW, gostaria de votar também para renomear Unpin para algo "positivo" como Relocate ou MoveFromPin (ou ainda mais detalhado, mas talvez um pouco mais preciso MayMoveFromPin ).

Eu concordo que o duplo negativo de !Unpin ou apenas Unpin tem sido confuso para mim historicamente, e algo emoldurado em um positivo "isso pode se mover apesar de estar dentro de Pin " eu acho ajudaria a aliviar alguma confusão!

FWIW Inicialmente pensei a mesma coisa sobre Unpin , mas quando realmente comecei a usá-lo IMO, fez sentido - não é realmente uma negação dupla, já que a operação que você está procurando é a capacidade de Unpin (pegar algo dentro e fora de Pin livremente) não a capacidade de manter as coisas em um alfinete. É o mesmo que MoveFromPin , mas com uma redação diferente. Eu prefiro um nome que não faça as pessoas pensarem que "não é Pin " ou algo assim, mas IMO MoveFromPin e outros são muito prolixos. UndoPin ? ( FreePin para a multidão haskell?)

Ainda acho que !Unpin parece estranho - treinei minha voz interior para tratá-lo como "Não solte isso!" em vez do usual "Não implementa Unpin ", mas é preciso algum esforço.

E quanto a !Pluck ?

@runiq

em vez do usual "Não implementa Desafixar"

Mencionei isso em meu comentário anterior, mas acho que esta é realmente uma boa maneira de dizer: Unpin é a operação de pegar e tirar Pin<C<_>> . Coisas que não implementam Unpin não fornecem essa capacidade.

@cramertj Eu gosto bastante de UndoPin

@cramertj Concordo que não sou um grande fã das alternativas propostas até agora, mas pessoalmente preferiria o prolixo MoveFromPin vez de Unpin . É um bom ponto que não é um duplo negativo, mas ao lê-lo (como alguém que não trabalhou muito com isso ainda), fico me perguntando que é um duplo negativo. Eu continuo tentando ler o prefixo "Un" como negativo ...

Estou curioso, mas @cramertj ou outros, você acha que há uma boa noção de quanto o limite de Unpin ergonomicamente aumenta? É super raro? É super comum? Comum o suficiente para ser uma dor se for uma dor para digitar?

Para um nome curto e simpático, pessoalmente gosto da ideia Relocate , mas para um nome mais longo e mais prolixo, mas ok-porque-você-não-digita-tanto-gosto de MoveFromPin . Eu pessoalmente acho que, independentemente da ergonomia de digitação do nome, ambos são melhores do que Unpin

Estou curioso, mas @cramertj ou outros, você acha que há um bom controle sobre o quanto o Unpin vinculado ergonomicamente aumenta? É super raro? É super comum? Comum o suficiente para ser uma dor se for uma dor para digitar?

Na minha experiência, existem dois casos em que Unpin realmente aparece no código do usuário:

  1. Unpin limites surgem porque você realmente precisa mover um futuro enquanto você pesquisa (por exemplo, coisas como algumas APIs selecionadas).
  2. Mais comumente, se o seu futuro é genérico apenas sobre algo que não seja um futuro, você geralmente deseja adicionar uma implementação incondicional de Unpin , porque não importa se esses outros tipos são Unpin já que você nunca fixe o projeto para eles.

Um exemplo do segundo caso é se você tem algum tipo de buffer sobre o qual é genérico (por exemplo, T: AsRef<[u8]> ). Você não precisa fixá-lo para tirar a fatia dele, então você não se importa se ele implementa Unpin ou não, então você só quer dizer que seu tipo implementa incondicionalmente Unpin para que você possa implementar Future ignorando a fixação.

Unpin é muito comum ver como um limite-- select! , StreamExt::next e outros combinadores exigem que os tipos em que operam sejam Unpin .

@semoutboats Estou curioso sobre seu ponto 2 aí; achamos que impl Unpin é algo que as pessoas terão que se lembrar de implementar com frequência? Semelhante a como os autores de bibliotecas hoje geralmente esquecem de #[derive(Debug)] ou impl std::error::Error para seus tipos personalizados, o que torna mais difícil usar essas bibliotecas?

Desafixar é um traço automático. A única vez em que você terá um tipo que não implementa a liberação é quando esse tipo explicitamente opta por excluí-lo. (Ou contém um campo que opta por não liberar).

Eu entendo que seja esse o caso. E presumi que esse seria de longe o caso mais comum, e é por isso que fiquei surpreso que @withoutboats sequer mencionasse o segundo ponto. Isso me sugere que pode ser mais comum do que eu pensava originalmente (embora talvez apenas ao implementar seus próprios futuros), então estou curioso sobre a frequência desses tipos de casos de uso :)

@alexcrichton Estou curioso, mas @cramertj ou outros, você acha que há uma boa noção de quanto o limite de Desafixar ergonomicamente aumenta? É super raro? É super comum? Comum o suficiente para ser uma dor se for uma dor para digitar?

Eu escrevi um longo post sobre minha experiência de portar meu código para Futures 0.3 (que usa Pin ). Você não precisa ler, o resumo é:

Na maioria das vezes, você não precisa se preocupar com Unpin , porque Unpin é implementado automaticamente para quase todos os tipos.

Portanto, a única vez que você precisa se preocupar com Unpin é:

  1. Você tem um tipo que é genérico sobre outros tipos (por exemplo, struct Foo<A> ).

  2. E você deseja implementar uma API de fixação (por exemplo, Future / Stream / Signal ) para esse tipo.

Nesse caso, você precisa usar isto:

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

Ou isto:

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

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

Essa é geralmente a única situação em que Unpin é necessário. Como você pode ver, isso normalmente significa precisar usar Unpin ~ 2 vezes por tipo.

No caso de não combinadores ou no caso de tipos que não implementam Future / Stream / Signal , você não precisa usar Unpin em tudo.

Portanto, eu diria que Unpin surge muito raramente, e só surge realmente na situação de criar combinadores Future / Stream / Signal .

Portanto, sou fortemente a favor de um nome como MoveFromPin . Fixar é um recurso de nicho com o qual a maioria das pessoas nunca precisa lidar, portanto, não devemos otimizar demais o tamanho do nome.

Acho que os benefícios cognitivos (evitar a dupla negação) são muito mais importantes do que salvar alguns personagens em situações raras.

Especialmente porque a fixação já é bastante difícil de entender! Portanto, não vamos tornar isso desnecessariamente mais difícil.

@jonhoo , achamos que impl Unpin é algo que as pessoas terão que se lembrar de implementar com frequência? Semelhante a como os autores de bibliotecas hoje geralmente se esquecem de #[derive(Debug)] ou impl std::error::Error para seus tipos personalizados, o que torna mais difícil usar essas bibliotecas?

Eu não acho que seja possível esquecer impl Unpin , porque se o autor esquecer, ele obterá um erro do compilador, o que impede que sua caixa seja publicada em primeiro lugar. Portanto, não é como #[derive(Debug)] .

A situação de que @withoutboats está falando é apenas para pessoas que implementam Future / Stream / Signal combinadores, não afeta ninguém (em particular, não afeta usuários posteriores da biblioteca).

(Eu acho que a dupla negação é parte disso, e talvez simplesmente mais negação do que estritamente necessária também seja, mas eu sinto que não é toda a história (tentando introspectar aqui) ... "Desafixar" é um pouco , metafórico? Indireto? Faz sentido quando explicado, e uma vez que "fixar" em si já é uma metáfora, não está claro por que meu cérebro teria problemas em levar mais longe essa metáfora, mas, mesmo assim, meu cérebro encontra o significado de "desafixar" por algum motivo obscuro e difícil de agarrar com firmeza.)

Eu acho que você está certo, @cramertj , não é realmente uma negação dupla no sentido usual, mas como @alexcrichton e @glaebhoerl, eu continuo sendo enganado por isso. "Un-" como um prefixo tem um sentimento muito de negação ("inseguro", "não implementado" e assim por diante é como eu normalmente encontro esse prefixo), e é negar a fixação aqui, mesmo que apenas como um verbo.

@semoutboats Estou curioso sobre seu ponto 2 aí; achamos que o impl Unpin é algo que as pessoas terão que se lembrar de implementar com frequência? Semelhante a como os autores de bibliotecas hoje geralmente se esquecem de # [derivar (Depurar)] ou implantar std :: error :: Error para seus tipos personalizados, o que torna mais difícil usar essas bibliotecas?

Absolutamente não! Se um usuário está implementando manualmente Future que é genérico em um não futuro, ele provavelmente deseja ser capaz de alterar o estado em sua implementação futura sem escrever código inseguro - isto é, tratar Pin<&mut Self> como &mut self . Eles obterão erros indicando que MyFuture<T: AsRef<[u8]>> não implementa Unpin . A melhor solução para este problema é implementar Unpin . Mas o único impacto que isso tem é sobre o usuário tentar implementar Future , e é impossível para eles esquecerem porque seu código não será compilado.

A situação da qual @withoutboats está falando é apenas para pessoas que implementam combinadores de Futuro / Fluxo / Sinal, ela não afeta ninguém (em particular, não afeta os usuários posteriores da biblioteca).

Estou falando especificamente sobre futuros manuais genéricos não combinadores , que deveriam ter apenas impls gerais de Unpin mesmo que seus genéricos não sejam Unpin .

Fiz um fluxograma para a pergunta "O que devo fazer sobre a fixação quando estou implementando um futuro / fluxo manual?"

pinning-flowchart

Para pedalar um pouco mais, hoje no almoço eu vim com LeavePin . Ele carrega o mesmo tom que escape sem a semântica enganosa implícita.

Há alguma interação interessante entre especialista em implantes e pinos, como acontece com vidas úteis?

A substring "specializ" ocorre no rastreamento da discussão do problema um pouco, mas não é conclusiva. Pin RFC ou RFC de especialização deve mencionar explicitamente essa interação, seja como "Verificou-se que está OK" ou "mais pesquisas são necessárias para julgá-lo seguro".

@vi não apenas não há interação de integridade, como também não é possível haver interação de integridade. As APIs de pinos são estritamente definidas na biblioteca padrão e não envolvem novos recursos de linguagem, um usuário pode defini-las com a mesma facilidade em bibliotecas de terceiros. Se um recurso de linguagem é incorreto em face da existência deste código de biblioteca, é um período incorreto, porque qualquer usuário poderia escrever este código de biblioteca hoje e ele compilará bem.

Se um recurso de linguagem é incorreto em face da existência deste código de biblioteca, é um período incorreto, porque qualquer usuário poderia escrever este código de biblioteca hoje e ele compilará bem.

Não que isso deva ter alguma relação com pin ... mas não acho que seja tão simples assim.

Se um recurso de biblioteca, ao usar unsafe , estiver fazendo uso de construções de linguagem cujo comportamento ainda não foi especificado (por exemplo, &packed.field as *const _ , ou fazendo várias suposições sobre ABI), então se alterações adicionais de linguagem invalidarem as suposições dessas bibliotecas, então eu acho que são as bibliotecas que não são sólidas e não as mudanças de linguagem. Por outro lado, se as mudanças de linguagem tornam o comportamento definido incorreto, então a culpa é das mudanças de linguagem. Compilar bem não é, portanto, uma condição suficiente para a integridade de uma biblioteca em face de mudanças inseguras e de linguagem.

+1 para MoveFromPin ou similar

Se você fizer a pergunta "Quando devo cancelar a implementação de Desafixar para meu próprio tipo?", A resposta será muito mais clara se você perguntar "Quando devo cancelar a implementação de MoveFromPin para meu próprio tipo?"

O mesmo ocorre com "Devo adicionar Desafixar como um traço vinculado aqui?" vs "devo adicionar MoveFromPin como um traço vinculado aqui?"

Fixar não é! Mover

Desculpe se isso foi mencionado em algum lugar, mas eu apenas dei uma olhada rápida na vasta discussão que houve sobre Pin aqui, no problema de implementação e no problema RFC.

Será que a ferrugem nunca vai! Certamente posso ver casos de uso para tal coisa (que diabos, vim procurar por Pin porque estava procurando uma maneira de não atirar no meu próprio pé com tipos que não podem ser movidos). Se a resposta for sim, como isso interagiria com o Pin? A existência de Pin tornaria mais difícil do que já é adicionar! Mover?

O período de comentário final, com uma disposição para mesclar , conforme a revisão acima , agora está completo .

Deve haver uma preocupação não resolvida em torno da nomenclatura de Unpin.

Como @RalfJung apontou , # 55992 também adiciona apenas uma pequena quantidade da documentação extra solicitada em https://github.com/rust-lang/rust/issues/55766#issuecomment -438316891 e em outros lugares. Não sei se isso é motivo para não se fundir ainda.

Por que meu método drop () agora pega & mut self, em vez de um Pin.

Bem, drop () é antigo - existe desde o Rust 1.0 - então não podemos alterá-lo. Adoraríamos fazer com que fosse necessário Pin <& mut Self>, e então os tipos de Desafixar poderiam obter seus & mut como fazem agora, mas essa é uma alteração incompatível com versões anteriores.

Eu queria saber se seria possível implementar essa mudança de forma compatível com versões anteriores. AIUI até adicionarmos Unpin (e as pessoas podem especificar !Unpin ) todos os tipos implementam Unpin . Portanto, podemos adicionar uma característica:

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

e então implemente esta característica para todos os tipos Unpin , que até que as pessoas possam optar por não participar são todos os tipos. Algo como:

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

Então teríamos o compilador inserir chamadas para DropPinned::drop vez de Drop::drop . Essencialmente, o traço DropPinned torna-se o lang-item em vez de Drop . AFAICS isso seria compatível com versões anteriores se e somente se esse mecanismo fosse introduzido ao mesmo tempo que Unpin .

Deve haver uma preocupação não resolvida em torno da nomenclatura de Unpin.

@tikue Nenhum membro da equipe libs registrou uma preocupação com o rfcbot antes ou durante o FCP, e não acredito que nenhum dos argumentos sobre Unpin fosse novo ou novo para este tópico, então normalmente nosso processo consideraria o atual nomeação para ser finalizado. Obviamente, se alguém tiver preocupações, ele deve se manifestar, mas o objetivo de ter as caixas de seleção da equipe seguidas de FCP é garantir que todos se sintam confortáveis ​​em estabilizar a API conforme proposto.

@cramertj Estou um pouco confuso. Várias pessoas têm falado. Quando pedi referências sobre onde mais os argumentos sobre a nomenclatura de Unpin haviam sido levantados e resolvidos, fui direcionado para https://internals.rust-lang.org/t/naming-pin-anchor- move / 6864 , que até onde posso ver também tem pessoas reclamando da nomenclatura de Unpin , e nenhum contra-argumento real. O RFC original em https://github.com/rust-lang/rfcs/pull/2349 também não tem muitos motivos para explicar por que as alternativas propostas para Unpin são piores. Mesmo neste tópico, parece que os únicos contra-argumentos que realmente surgem são "é mais curto" e "é tecnicamente correto". Você é capaz de apontar para uma discussão concreta onde nomes alternativos que são mais fáceis de entender (como MoveFromPin ) são discutidos e rejeitados?

Expliquei em comentários anteriores porque acredito que novas perspectivas foram levantadas neste tópico. Tenho acompanhado as discussões da API de pinos bastante de perto desde o início e não me lembro de alguma vez ter visto a questão da dupla negativa levantada antes deste tópico.

@tikue Eu Unpin foi levantada como um problema muitas vezes e consistentemente resolvido em favor de Unpin . Como eu disse antes, se alguém da equipe de libs quiser registrar uma objeção (tardia), não há problema em responder a isso, mas todos eles aprovaram a proposta de estabilização acima, que claramente não inclui a nomenclatura de Unpin como uma questão não resolvida: discutimos as alternativas e este FCP foi o processo para decidir que estávamos preparados para nos estabilizar nas decisões que haviam sido tomadas, incluindo o nome Unpin .

@cramertj Você poderia fornecer um link para onde essa discussão aconteceu? Não estou duvidando de você, gostaria apenas de ver os argumentos a favor de Unpin , porque não acredito que eles tenham sido dados aqui. Conforme mencionado, as referências que recebi até agora não fornecem qualquer resolução sobre a nomenclatura de Unpin .

@cramertj +1 à pergunta de

Acho que existe até uma regra oficial de que as decisões da RFC só podem ser feitas com base em argumentos conhecidos publicamente.

Sim, isso é verdade - e para registro, eu não estou na equipe de libs e, portanto, não estive presente em nenhuma discussão apenas da equipe de libs. Olhando através do segmento RFC e do problema de rastreamento Pin , houve várias vezes em que o nome de Unpin foi mencionado. Não vejo ninguém dizer especificamente as palavras "duplo negativo", mas certamente me lembro de " !Unpin é um duplo negativo" mencionado antes, além da regra geral da API de nomear características para o que você puder fazer com eles, em vez do que você não pode (como indiquei acima, acho que Unpin realmente segue essas duas regras, embora seja necessário ouvir "Desafixar" como um verbo em vez de ouvi-lo como um adjetivo "not-pin", que não é intuitivo para as pessoas).

@wmanley Isso não funciona, por exemplo, impl<T> Drop for Vec<T> quebraria porque não temos Vec<T>: Unpin . Além disso, propostas nesta linha foram feitas antes, mesmo neste segmento . Por favor, leia as discussões antes de responder. Eu sei que isso é pedir muito, mas é a única maneira de evitar que os mesmos problemas sejam explicados repetidamente.

Acho que existe até uma regra oficial de que as decisões da RFC só podem ser feitas com base em argumentos conhecidos publicamente.

Isso é informalmente conhecido como a regra "sem nova lógica" .

Não tenho certeza de onde postar isso, mas alguém poderia dar uma olhada em https://github.com/rust-lang/rust/issues/56256 ?

Relacionado a # 56256, impl<T> From<Box<T>> for Pin<Box<T>> não está listado no OP como sendo estabilizado, mas ficará implicitamente estável assim que Pin fizer. Existem outras implementações de características que não são triviais e devem ser analisadas para estabilização? (escanear os documentos todos os outros parecem ser implementações de delegação triviais para o ponteiro agrupado para mim).

Falamos sobre esse problema na equipe de libs hoje. As últimas semanas de discussão mostraram que ainda há algumas coisas que devem ser tratadas primeiro, especialmente em relação à nomenclatura de Unpin .

Portanto, não vamos avançar com a estabilização por agora (apesar do FCP).

Ficaria muito grato se alguém pudesse reunir as ideias neste tópico e preparar uma proposta autônoma para melhorar a situação de nomenclatura.

@Kimundi

Ficaria muito grato se alguém pudesse reunir as ideias neste tópico e preparar uma proposta autônoma para melhorar a situação de nomenclatura.

Isso significa que a equipe de libs não deseja estabilizar as APIs atuais como estão? Pessoalmente, não acho que alguém tenha inventado um nome melhor do que o conjunto atual de nomes conforme implementado e, portanto, não posso fazer essa proposta, mas me preocupo muito em ver essas APIs estabilizadas, então, se alguém da equipe de libs tem um nome que prefere: shipit:

Bikesh colocou isso com um bando de colegas de trabalho e @anp sugeriu DePin , o que na verdade eu gosto bastante, pois remove a conotação de "não alfinete" de Unpin e enfatiza que se trata de um tipo que pode ser de- Pin 'd.

@Kimundi, você ou alguém da equipe de libs pode registrar uma "preocupação de fcp" explícita para tirar isso do FCP e definir mais claramente o que significa "algumas coisas que devem ser tratadas"?

@rfcbot concern naming-of-Unpin

Não tenho certeza se isso funciona realmente depois que o FCP é inserido, mas gostaria de colocar uma preocupação formal de bloqueio na nomenclatura do traço Unpin . O traço Unpin parece ser bastante crucial para a API, e a "dupla negativa" (como discutido acima) me confunde toda vez que eu o leio.

Houve um monte de comentários sobre vários nomes, mas infelizmente não estou muito entusiasmado com nenhum ainda. Meu "favorito" ainda está nas linhas de MoveFromPin ou Relocate (meu Deus, há tantos comentários aqui que não tenho ideia de como revisar isso).

Pessoalmente, estou ok com a nomenclatura de Pin si, bem como de Pinned para um ZST que não implementa o traço Unpin .

Eu concordo totalmente com @alexcrichton que a nomenclatura de Unpin é o grande ponto de discórdia aqui. Não creio que haja nenhuma preocupação técnica, pelo que posso ver, sobre o recurso proposto em si (embora _ haja_ muitos comentários, então poderia ter esquecido alguma coisa).

Eu ainda acho que Pinned é um nome estranho para o ZST, porque algo que contém um Pinned não está realmente fixado .. Simplesmente não é Unpin . PhantomPinned (como foi renomeado em # 55992) tem o mesmo problema por se referir a Pin , quando o ZST é _realmente_ cerca de Unpin .

Eu também ainda acho que os documentos precisam de mais trabalho devido à sutileza desse recurso, mas isso provavelmente não conta como um bloqueador.

Além disso, @Kimundi , estou feliz em ver que a equipe de libs está disposta a dar um pouco mais de tempo para resolver isso. Pode parecer muito com bicicleta desnecessária, mas eu (e outros também) acho que é muito importante melhorar a capacidade de ensino desse recurso :)

Concordou com @jonhoo sobre Pinned ainda se sentir estranho e PhantomPinned não estar melhor (não está fixado de nenhuma forma, nem mesmo de uma forma fantasma). Eu acho que se encontrarmos um bom nome para Unpin , então Pinned irá naturalmente ser renomeado para Not{NewNameForUnpin} .

Eu realmente não acho que precisamos perder mais tempo discutindo PhantomPinned - esse tipo quase nunca aparecerá para os usuários finais. PhantomNotUnpin / PhantomNotDePin / PhantomNotMoveFromPin / etc. não vão torná-lo mais ou menos comum ou tornar a API mais ou menos óbvia para usuários que já estão confortáveis ​​o suficiente com a API para ter um uso legítimo para PhantomPinned .

Apenas uma ideia rápida: traço Move e ZST Anchor .

Cada tipo é Move capaz, a menos que contenha um Anchor que o faça aderir a um Pin .
Eu gosto de como Anchor: !Move intuitivamente faz muito sentido.

Não estou sugerindo que gastemos tempo especificamente em PhantomPinned , mas acho que seria pouco esforço manter a mente aberta, porque é possível que tudo o que foi encontrado por Unpin funcione da mesma forma por PhantomPinned .

Cobrimos Move e explicamos várias vezes por que ele é impróprio. Todos os tipos são móveis até serem fixados. Da mesma forma, Anchor foi sugerido anteriormente, mas não está claro no nome que ele é usado para cancelar Unpin ing / Move ing.

@stjepang Acho que Move foi descartado há algum tempo porque algo sendo !Unpin não o impede de ser movido. Somente se um tipo estiver sob Pin , e não for Unpin , você será "contratualmente obrigado" a não movê-lo.

No comentário original, @withoutboats disse:

O wrapper Pin modifica o ponteiro para "fixar" a memória a que se refere no lugar

Eu acho estranho que os tipos implementem Unpin porque os valores não são "fixados" - a memória é. No entanto, faz sentido falar sobre valores que são "seguros para se mover". E se renomearmos Unpin para MoveSafe ?

Considere o traço UnwindSafe . É apenas um traço de marcador de "sugestão" e é seguro para implementar. Se você deixar um !UnwindSafe cruzar um limite catch_unwind (com AssertUnwindSafe ), você o "quebrará" invalidando seus invariantes.

Da mesma forma, se você tiver um !Unpin / !MoveSafe , ele ainda pode ser movido (é claro), mas você o "quebrará" invalidando suas referências próprias. O conceito parece semelhante.

O traço Unpin realmente significa apenas MoveSafe . Parece-me que não se trata de valores que podem ser removidos da memória por trás de Pin . Em vez disso, trata-se de valores que não "quebram" quando você os move.

MoveSafe tem o mesmo problema que Move - todos os valores de qualquer tipo podem ser movidos com segurança. Só se torna impossível mover um valor após ele ter sido fixado.

MoveSafe tem o mesmo problema que Move - todos os valores de qualquer tipo podem ser movidos com segurança.

Certo, mas tem um significado diferente de "seguro", assim como em UnwindSafe . De qualquer forma, ficaria bem com Relocate ou qualquer coisa desse tipo.

Para resumir, acho que o nome do traço não deve ser DePin , Unpin , ou qualquer coisa com "pin" em seu nome. Para mim, essa é a principal fonte de confusão. A característica não é realmente uma "saída de emergência" das algemas de Pin - a característica diz que o valor não será invalidado quando movido.

Eu apenas vejo Pin e Unpin como coisas totalmente separadas. :)

Eu sinto exatamente o oposto;). A característica só é significativa em relação a Pin, que é o único tipo que temos que expressa de forma significativa as restrições à mobilidade do valor subjacente. Sem o Pin, o Unpin não tem sentido.

Gosto de pensar em fixar como colocar algo em um quadro de avisos. Se você remover o pino de um objeto que está fixado no tabuleiro, poderá mover o objeto. Remover o pino é desencadear.
Gosto do nome Unpin .

Também posso ver como! Desafixar é uma negação dupla e pode causar confusão. No entanto, eu me pergunto com que freqüência você precisa escrever !Unpin .

Outro nome que eu poderia sugerir para Desafixar é Detach . De volta à metáfora do quadro de avisos, você não _Desprenderia_, mas _Detach_ o Pin de seu objeto.

Acho que realmente gosto de DePin ! Até agora, é o meu favorito - é conciso, é claramente um verbo e não um adjetivo, e !DePin parece muito claro também ("não pode ser removido").

Eu acho estranho que os tipos implementem Unpin porque os valores não são "fixados" - a memória é.

O valor é fixado na memória. Mas o valor é crucialmente importante aqui também. Fixar memória, para mim, é apenas garantir que ela permaneça desreferenciável ou algo assim, mas não seja violada por mem::swap . Fixar um valor na memória é não mover esse valor fixado para nenhum outro lugar, que é exatamente o que Pin se trata.

Também posso ver como! Desafixar é uma negação dupla e pode causar confusão. No entanto, eu me pergunto com que frequência você precisa escrever! Desafixar.

Eu ouço quando você diz que não acha o nome confuso. Eu, e muitos outros neste tópico, ficamos confusos com o nome.

Não tenho o código em mãos, mas a primeira vez que tentei usar futuros, queria que uma função retornasse impl Future<Output=T> . Não me lembro o que aconteceu, mas imediatamente recebi um erro do compilador reclamando sobre T e Unpin. A pergunta para a qual eu precisava de uma resposta era "É seguro restringir T para apenas ser Desafixado". E isso me levou a olhar para o abismo por cerca de 2 horas angustiantes.

"Oh, ok, então se é isso que Pin significa. Então, Desafixar significa ... Caixa? Por que isso é uma característica?"

"Espere, impl !Unpin ? Por que não é impl Pin ?"

"Certo, Fixar e Desafixar ... não são opostos. São coisas totalmente diferentes. Espere, então o que Desafixar significa de novo? Por que é chamado assim?"

"O que diabos !Unpin significa? Não ... a outra coisa, não pin? Hrrrgh"

Ainda assim, a única maneira de fazer sentido na minha cabeça é substituindo 'desafixar' por 'relocável'. "O tipo não é realocável na memória" faz todo o sentido. E, no entanto, mesmo sabendo o que Pin faz , "digitar não é liberar" me deixa confuso.

Renomear Unpin para Relocatable (ou Relocate ) dá o meu voto 🗳. Mas eu acho que quase todas as outras sugestões são melhores do que Desafixar.

A característica só é significativa em relação a Pin, que é o único tipo que temos que expressa de forma significativa as restrições à mobilidade do valor subjacente. Sem o Pin, o Unpin não tem sentido.

Para colocar um ponto sobre isso - as garantias em torno do pino são inteiramente em torno de certos comportamentos , não tipos . Por exemplo, uma função geradora que produz () poderia implementar trivialmente FnOnce reiniciando repetidamente. Embora este tipo possa não implementar Unpin - porque seus estados de rendimento podem ser autorreferenciais, sua interface FnOnce (que se move) é completamente segura porque ainda não foi fixada quando você FnOnce it, que é necessário para colocá-lo em um estado autorreferencial. Unpin é especificamente sobre os tipos de comportamento que são considerados seguros depois que o tipo é fixado (ou seja, movê-lo), não sobre alguma propriedade intrínseca do tipo.

Ironicamente, vim aqui apenas para comentar que, embora a nomenclatura Unpin definitivamente tenha sido debatida no passado, o debate sobre ela que me lembro de testemunhar foi quando escolhemos substituir Move por Unpin , o que, eu ia dizer, é uma melhoria inequívoca . E muitas vezes só se percebe mais tarde que, embora o design atual seja uma melhoria definitiva em relação ao que veio antes, ainda há espaço para melhorias adicionais . Qual é a direção de onde eu estava vindo.

Desafixar é especificamente sobre os tipos de comportamentos que são considerados seguros depois que o tipo foi fixado (ou seja, movê-lo), não sobre alguma propriedade intrínseca do tipo.

O comportamento quando fixado é uma propriedade intrínseca do tipo, assim como o comportamento / invariante quando é compartilhado. Abordei isso com muito mais detalhes nesta postagem do blog .

@RalfJung , estamos falando um do outro. Há uma confusão que vejo que os tipos autorreferenciais "não podem ser movidos" - isso não é preciso, eles têm certos estados em que podem entrar durante os quais não podem ser movidos, mas enquanto estão em outros estados, é perfeitamente seguro para movê-los (e nossas APIs contam com a capacidade de movê-los, por exemplo, para aplicar combinadores a eles ou movê-los para Pin<Box<>> ). Estou tentando esclarecer que não é o caso que esses tipos "não podem ser movidos".

Ah, sim, então eu concordo. Um tipo !DePin nem sempre é fixado e, quando não está, pode ser movido como qualquer outro tipo.

@cramertj @withoutboats uma coisa que ainda não consegui perceber, vocês são contra renomear Unpin ? Não parece que vocês acham que concordam que precisa ser renomeado, mas não tenho certeza se vocês são contra.

Eu pessoalmente não acho que o principal problema aqui seja com o nome Unpin e que "se pudéssemos apenas renomeá-lo, tudo seria intuitivo". Embora renomear possa ajudar um pouco (e Relocate / DePin parece bom aqui ...), acho que a principal complexidade vem do próprio conceito de fixação; certamente não é um dos conceitos mais fáceis de entender ou explicar; nem mesmo quase.

Portanto, acho que a documentação do módulo core::pin precisa ser reforçada _significativamente_ e muitos outros exemplos precisam ser incluídos. Bons exemplos mostram casos de uso canônicos, bem como usos de funções e implementações não seguras.

@alexcrichton Não sou contra renomear, não. Acho que Unpin já está bom, mas eu concordaria com DePin , e é por isso que sugeri. RemoveFromPin é o mais obviamente "correto", mas prolixo ao ponto de sal sintático, então acho que me oporia especificamente a esse nome. No entanto, sou contra adiar a estabilização indefinidamente até encontrarmos um nome que todos concordem ser o melhor - acho que a API tem algumas complexidades inerentes que não serão dramaticamente melhor ou piores por causa do nome do Unpin traço. Eu realmente gostaria de buscar a estabilização em breve para evitar mais rotatividade no código, documentos e discussões em torno de futures_api (e assim podemos começar a colocar as peças no lugar para estabilizar futures_api em si). Talvez devêssemos agendar uma reunião de VC para definição de nomes para que todos que tenham uma opinião possam apresentar sua solução e possamos ter uma oportunidade de largura de banda maior para resolver isso.

Meu voto é std::pin::Reloc (realocar)

Tenho duas situações muito hipotéticas (bem, na verdade apenas uma, mas duas execuções diferentes) das quais não me importaria de ter declarado explicitamente se é permitido ou não.

Unpin indica que é trivial fazer a transição de todos os estados de T (onde T: Unpin ) enquanto estiver no estado de tipo fixado (espero estar me lembrando corretamente desse termo de uma das postagens do blog de Pin pode distribuir &mut T .

Então, vamos supor que eu queira criar um tipo Woof para o qual também é sempre possível fazer a transição de todos os estados de Woof , enquanto no estado do tipo fixado para o tipo não fixado estado, mas não é trivial fazê-lo e, portanto, não pode implementar Unpin , seria permitido que Woof tivesse uma função como fn unpin(self: Pin<Box<Woof>>) -> Box<Woof> ?

O mesmo para um tipo Meow para o qual só às vezes é possível fazer a transição de fixado para não fixado e uma função como fn unpin(self: Pin<Box<Meow>>) -> Result<Box<Meow>, Pin<Box<Meow>>> .

Meus 2 cts para a bicicleta:

Se bem entendi, o que Unpin realmente significa é " Pin não tem efeito sobre mim".

Que tal algo como BypassPin ou IgnorePin ?

Então, vamos supor que eu queira criar um tipo Woof para o qual também seja sempre possível fazer a transição de todos os estados do Woof, enquanto no estado do tipo fixado para o estado do tipo não fixado, mas não é trivial fazer isso e pode, portanto, não implementar Desafixar, seria permitido que o Woof tivesse uma função como fn unpin (self: Pin>) -> Caixa?

Sim, isso deveria ser possível. Por exemplo, se Woof é um elemento de uma lista encadeada intrusiva, poderia fornecer uma função para remover o elemento da lista e remover Pin ao mesmo tempo (argumentando que para não -enfileirado Woof s, os dois estados tipográficos são equivalentes, portanto, podemos removê-los).

Relocate tem o mesmo problema que RePin para mim, poderia implicar em uma operação que permite mover um valor de um local fixado para outro, não liberando o valor completamente. É uma conotação menos forte do que RePin , mas ainda parece um pouco confusa.

isso pode implicar em uma operação que permite mover um valor de um local fixo para outro, não liberando o valor completamente.

Embora não seja exatamente disso que se trata esse traço, também não é totalmente errado: para a maioria dos casos de uso, mover um valor de um local fixo para outro é tão catastrófico quanto usá-lo livremente. Na verdade, dado que qualquer pessoa pode criar um Pin<Box<T>> , nem vejo por que você está fazendo uma distinção fundamental aqui.

Para a maioria dos casos de uso, mover um valor de um local fixo para outro é tão catastrófico quanto usá-lo livremente.

Sim, é por isso que ter uma característica adicionando essa operação a um tipo pode ser útil (não pode ser uma característica de marcador como Unpin , pois pode precisar fazer coisas como atualizar referências internas). Não estou propondo adicionar algo assim agora, apenas que é algo que eu poderia ver sendo fornecido no futuro (em std ou terceiros) que poderia resultar em uma sobreposição confusa de nomes.

Não conheço nenhum caso de uso forte para ele agora, mas pensei em usar algo parecido com coleções fixadas.

Ok, então aqui está algumas reflexões. Eu estava inicialmente me perguntando se poderíamos ter um nome como PinInert ou PinNoGuarantees pois essa também é a descrição, mas pensando nisso o que eu realmente quero é um traço que descreva uma ação ao invés de um propriedade, ou pelo menos torna muito mais fácil pensar na minha cabeça.

Um problema com Unpin (não sei por quê) é que não consigo entender que o significado pretendido é "a ação de remover de um alfinete, desafixar, é segura " O traço como está transmite a ação "o ato de desafixar", mas não consigo entender isso quando leio. É estranho ter Pin<T: Unpin> porque, se estiver "liberado", por que está em um alfinete?

Será que um nome como CanUnpin poderia funcionar? Muito disso não é sobre uma garantia rígida de uma forma ou de outra (implementar Unpin não significa que você o removerá de um alfinete, apenas significa que você pode removê-lo de um alfinete). Como isso soa? CanUnpin legível o suficiente para outras pessoas? Curto o suficiente?

(ter o prefixo Can transmite o verbo para mim com muito mais facilidade também, e diz que você pode removê-lo de um alfinete, mas nem sempre o fará).


Como outra tangente não relacionada, uma coisa que esqueci de mencionar anteriormente (desculpe!) É que não acho que todos os métodos acima devam ser necessariamente métodos inerentes. Já tivemos montes e montes de bugs em torno de métodos de inferência e confronto, e adicionar nomes muito comuns como as_ref e as_mut em tipos que também implementam Deref parece um problema esperando para acontecer.

Essas operações acontecem com frequência suficiente para justificar o local de risco para colocá-las? (inerentemente) Ou raramente são usados ​​o suficiente para que a rota segura das funções associadas possa ser estornada?

Já que eu aparentemente tenho a pele neste jogo agora: CanUnpin parece muito próximo em espírito a Unpinnable ou algo semelhante, e minha impressão sempre foi que a comunidade desaprova esses tipos de modificadores em nomes de características, uma vez que a maioria das características descreve a ação que o tipo pode realizar. O impl em si é um "pode" ou "-able" implícito nesse sentido. O que quer que seja decidido (e o nome não importa na extensão declarada aqui IM-not-so-HO - muito poucos usuários provavelmente terão que digitar isso), eu encorajaria a todos a sentirem alguma urgência em resolver as questões aqui. Estou animado com essa estabilização e sei que muitas pessoas estão!

@alexcrichton

Um problema com Unpin (não sei por quê) é que não consigo entender que o significado pretendido é "a ação de remover de um alfinete, desafixar, é segura "

E se você pensar em T: Unpin como " T é imune a Pin "? Então, se você tiver Pin<T: Unpin> isso significa apenas que temos um valor dentro de Pin que é imune à fixação, portanto, a fixação é aqui efetivamente discutível.

Ou em outras palavras: Unpin neutraliza Pin . Para ser honesto, depois de tanta discussão eu meio que internalizei isso e agora faz sentido. 😆

Muito disso não é sobre uma garantia rígida de uma forma ou de outra (implementar Unpin não significa que você o removerá de um alfinete, significa apenas que você _pode_ removê-lo de um alfinete).

Um contraponto a isso é o traço Send . Isso não significa que você vai enviar o valor, significa apenas que você pode enviá-lo.

@anp Eu concordo que CanUnpin não é um bom nome, estou tentando fazer o meu melhor para descobrir um nome melhor! Parece que poucos ainda acham que isso precisa ser renomeado, já que todas as sugestões parecem ser rejeitadas basicamente pelos mesmos motivos que eu acho que Unpin precisa ser renomeado em primeiro lugar.

Também como um lembrete, qualquer estabilização percorre os trens como todas as outras mudanças, e com um lançamento na próxima semana, isso definitivamente não entrará naquele lançamento e será um candidato para o próximo lançamento. Isso significa que temos 7 semanas, quase dois meses, para pousar tudo isso e garantir que chegue o mais rápido possível. Embora eu concorde que a urgência é necessária, é apenas "não vamos esquecer essa urgência", não a urgência "vamos resolver isso antes de segunda-feira".

@stjepang Eu também me acostumei a Unpin agora depois de pensar tanto nisso! Todos os nomes alternativos neste ponto parecem bastante sem brilho, então estou chegando ao ponto em que me contentaria com uma documentação melhor. Eu gostaria de encontrar alguém que não saiba muito sobre as Pin apis para ler a referida documentação depois, para verificar se é suficiente para aprender.

Eu também gostaria de prosseguir e bloquear formalmente a estabilização na documentação aprimorada, pois parece especialmente crítico para esse problema.

@rfcbot preocupação com a melhoria da documentação

Concretamente, o que sinto que precisa de melhor documentação é:

  • Mais prosa sobre cada método sobre por que é seguro e / ou inseguro. Por exemplo, o contrato de P DerefMut implementação de P "comportando-se razoavelmente" não está documentado em Pin::new_unchecked .
  • Cada função unsafe deve ter um exemplo de código de por que não é segura, ou pelo menos uma explicação clara de uma sequência de etapas que podem dar errado.
  • Acho que a documentação do módulo pode entrar em mais detalhes além do que é um Pin e o que significa. Acho que eles se beneficiariam com informações como "como isso não é um tipo geral imóvel, mas uma forma disso" ou "como Pin usado na prática, por exemplo, com futuros".
  • Eu gostaria de ver um exemplo com genéricos e Unpin na documentação ou nos exemplos de código. Por exemplo, parece que muitos combinadores em futuros têm que lidar com isso e, da mesma forma, alguns combinadores exigem isso especificamente. Alguns exemplos de casos de uso de como trabalhar com genéricos e como eles funcionam podem ajudar a entender um pouco Unpin .

Também estou curioso para saber se outros têm itens concretos acionáveis ​​para adicionar à documentação também!

Em seguida, também gostaria de bloquear formalmente a self -pergunta com as_ref , mas suspeito que isso será resolvido rapidamente. (mais uma vez, desculpe por não lembrar de trazer isso à tona antes)

@rfcbot concerne métodos próprios

Isso propõe as_ref , as_mut , get_ref , get_mut , into_ref , e set para todos os métodos no Pin type. A proposta menciona que não fazemos isso para ponteiros inteligentes devido a conflitos de método, mas cita que a experiência mostra que a Pin API implementada de hoje não é consistente e muitas vezes acaba apenas atrapalhando.

Esses nomes de métodos, no entanto, são nomes muito curtos e agradáveis ​​e são bastante comuns de serem encontrados em todo o ecossistema. A equipe de libs encontrou uma série interminável de bugs no passado, onde adicionamos implementações de características e tal, e isso causa conflitos com os métodos de características existentes em várias caixas. Esses métodos parecem especialmente de alto risco por causa de seus nomes. Além disso, não está claro se poderíamos adicionar mais métodos a Pin no futuro, mesmo que os nomes agora acabem dando certo, qualquer nome futuro adicionado tem um alto risco de colisão.

Eu gostaria de ter certeza de que repensamos essa divergência com a convenção. Pessoalmente, estou particularmente preocupado com as consequências e acho que seria ótimo se pudéssemos encontrar um meio-termo que evitasse esses perigos.

E enquanto estou nisso, uma última coisa que notei e, novamente, acho que isso será muito rápido

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

O nome Box::pin foi considerado para Box::pinned ? Com a existência de Pinned e / ou PhantomPinned , parece que seria legal se o nome pudesse corresponder ao tipo de retorno!

@alexcrichton Alguma coisa em particular o convenceu contra MoveFromPin como opção ? Vejo que você foi favorável a isso anteriormente (e várias pessoas pareceram gostar também). Para o registro, as objeções diretas que eu poderia lembrar, ou encontrar rapidamente com Ctrl + F-ing, são que "nomes que são frases inteiras ... seriam muito unidiomáticos" (@sem barcos) e que são "muito prolixos" ( @cramertj).

Tenho que admitir que motivações do tipo "Eu me acostumei ... agora, depois de pensar tanto nisso", me deixam um tanto inquieto. Os humanos podem se acostumar e racionalizar post-hoc essencialmente qualquer coisa. Não importa qual seja o nome, seria uma grande surpresa se não acabássemos nos acostumando com ele. (Esta é a razão pela qual escolhemos obviamente nomes -subótimos como existential type como temporários em outros casos, para tentar evitar que se tornem permanentes.)

Privilegiar acidentalmente as perspectivas de usuários experientes (o que todos nós somos!) Em detrimento de outros que chegarão a coisas com cérebros mais frescos é o tipo de erro que acho que precisamos estar sempre vigilantes contra cometer, por mais difícil que seja .

Embora certamente não seja o estilo da lib std, também acho que CanUnpin alguma forma deixa mais claro como aquela peça específica se encaixa com as outras.

É impossível inventar um nome que descreva o significado de Unpin para um iniciante sem ler a documentação. Send e Sync também são difíceis de entender para um iniciante, mas têm uma aparência agradável e concisa, além de Unpin . Se você não sabe o que esses nomes significam, deve ler seus documentos.

@valff De fato, isso está correto, no entanto, existem algumas pessoas que leram os documentos e entendem como a fixação funciona, mas o nome Unpin ainda causa dificuldades mentais significativas, enquanto os outros nomes causam menos.

@glaebhoerl oh, desculpe esclarecer, eu pessoalmente sou mais fã de MoveFromPin ou CanUnpin que os atuais Unpin . Só estou tentando ajudar a chegar a uma conclusão!

Tentei entender a proposta atual, talvez este tipo de relatório de progresso possa lançar alguma luz adicional sobre a nomenclatura também.

  • Estender as garantias de Pin para um endereço estável até Drop impõe requisitos adicionais ao criador de Pin . Eu teria achado útil se essas pré-condições necessárias para invocar unsafe fn new_unchecked(pointer: P) -> Pin<P> fossem mencionadas diretamente. Entender como um Pin pode ser criado ajuda enormemente a entender seu conceito, imo.
  • O resumo mencionado no problema de rastreamento, precedido de we agreed that we are ready to stabilize them with no major API changes. , contém muitos api antigos. Ao mesmo tempo, ele também contém muitos insights sobre Drop e projeções de pinos que são relevantes. Também contém uma formulação explícita da garantia adicional referida no ponto um que não se encontra neste relatório. Essa mistura de informações desatualizadas e novas era um pouco confusa, considere mover todas as informações para este problema ou indicar parágrafos desatualizados.
  • Como a garantia adicional é chamada de slight extension , esperava que houvesse algum tipo de opção para recusá-la. Uma vez que fixar uma vez carrega um estado de tipo naquele ponteiro até que ele seja descartado, é uma forma de voltar deste estado de tipo para um estado 'normal'. Na verdade, foi isso que pensei que Unpin faria no início, mas acho que Unpin é um pouco mais forte. Ele diz que o estado fixado pode ser livremente transformado em um estado não fixado, à vontade. É bom que a interface atual seja mínima a este respeito, mas Unpin pode ser confundido com uma operação do tipo que remove algum invariante interno que requer o estado do pino (como uma versão suave de Drop ) .

Esclarecimento: eu pessoalmente prefiro MoveFromPin relação aos outros nomes, mas é indiscutivelmente artificial e desajeitado.

O principal item acionável e concreto acionável que vem à mente, além das melhorias na documentação já solicitadas por @alexcrichton , seria explicar o raciocínio por trás da regra de campo para projeção de pinos em map_unchecked e map_unchecked_mut . Algo na linha de:

Um pino promete não mover os dados referidos até que sejam descartados. Como os campos de uma estrutura são descartados após a estrutura que a contém, seu estado de pino se estende além da implementação Drop das próprias estruturas. Projetar para um campo promete não sair dele dentro do destruidor de estruturas.

E quanto ao nome ElidePin para o traço do marcador derivado automaticamente?

Isso capturaria que o tipo sempre pode ser tratado como se ou se comportasse como se estivesse solto. E !ElidePin parece ser bastante claro também, embora isso possa ser subjetivo.

O marcador padrão também não tem um nome perfeito para entender a fixação. Pinned parece evocar a noção de que o próprio struct é fixado, mas a fixação se aplica a ponteiros e somente após uma ação explícita. Isso não é tão crítico, mas também não é insignificante, especialmente porque pode ser o tipo que é encontrado pela primeira vez em uma implementação para um tipo autorreferencial.

Minha oposição geral a MoveFromUnpin / RemoveFromUnpin é apenas que eles são muito prolixos e iriam regredir a ergonomia das implementações Future manuais. CanUnpin / DePin ambos parecem bons para mim - gostaria que ficasse mais claro que Unpin segue o padrão de Read / Write / etc. mas parece que não é intuitivo para as pessoas, então vou marcar com +1 qualquer coisa que torne isso mais claro, sem parecer um sal sintático.

Concordo que NotWhateverUnpinBecomes é provavelmente o melhor nome para Pinned . Dito isso, Adhere significa obedecer e cumprir. : ligeiramente_smiling_face:

CanUnpin / DePin parecem bons para mim - gostaria que ficasse mais claro que o Unpin segue o padrão de leitura / gravação / etc

Acho que uma das coisas que torna Unpin difícil, ao contrário de Read é que é um traço marcador. Read é fácil de entender porque existe um método Read::read - tudo o que você precisa saber está lá no trait . Se x é Read entendo que posso chamar x.read() - da mesma forma write para Write , etc. É mais difícil explicar que X implementar Unpin significa que Pin<Ptr<X>> implementa DerefMut - o que significa que você pode tratá-lo como se fosse apenas X .

Ler é fácil de entender porque existe um método Read :: read

Se pudéssemos adicionar métodos sempre aplicáveis ​​em auto trait definições + GATs, poderíamos ter Unpin::unpin - fn unpin<P: DerefFamily>(self: Pin<P::Deref<Self>>) -> P::Deref<Self> ). ..... pensando bem, não acho que isso deixará ninguém menos confuso;)

(em uma nota mais séria, eu apoiaria Pin::unpin para ir de Pin<P<T>> para P<T> )

Eu apoiaria Pin :: unpin para ir de Pin

> para P

Isso confunde dois termos na minha cabeça, assim como o próprio nome atual. unpin soa muito como inverter as garantias do estado de tipo, para uma comparação que seria como se houvesse um método fn unborrow(&'_ T) ou fn unmove(??) . Uma vez que pin fornece garantias até que alguma memória representando um tipo seja Drop::drop ed, nós realmente não invertemos o estado, o tipo meramente garante que todas as outras representações mantêm garantias equivalentes e podemos, portanto, ignorar isso . Esta é também a principal diferença que vejo entre os traços do marcador e algo como io::Read . Um habilita operações para o compilador ou linguagem, enquanto o outro habilita operações para o programador.

Além disso, é um ponto importante que isso não pode atualmente ser representado com precisão apenas usando a digitação correta. Chamar a operação unpin soa como se houvesse uma função inversa pin . Sendo uma função também sugere um pouco erroneamente que tal operação de liberação está de alguma forma ligada ao trabalho computacional, por exemplo into_pointer() seria melhor a este respeito, seguindo também uma convenção de nomenclatura mais estabelecida.

Por último, acho que há potencial para tipos com uma função unpin especificamente com trabalho computacional. Um tipo com invariantes internos muito especiais pode ser capaz de 'consertar' seu estado interno de tal forma que pode oferecer uma interface fn unpin(self: Pin<&'a mut T>) -> &'a mut , onde voluntariamente perde todas as garantias de Pin por alguma vida 'a . Nesse caso, ambos os pontos acima não se aplicam mais. Essa função pode ser imaginada como se tivesse efeito equivalente a descartar e reconstruir no mesmo local de memória (removendo, assim, o estado de tipo). E pode envolver computação, por exemplo, movendo algumas referências próprias para alocações dinâmicas.

Seria lamentável se um nome confuso também tornasse mais difícil para os designers e implementadores de bibliotecas escolherem nomes não confusos, ao combinar essas duas ideias.

Minha oposição geral a MoveFromUnpin / RemoveFromUnpin é apenas que eles são muito prolixos e iriam regredir a ergonomia das implementações futuras manuais.

Eu não acho que isso seja verdade, especialmente para MoveFromPin , o que parece razoável digitar para mim - e com qualquer tipo de preenchimento automático inteligente ou idiota, o problema é praticamente inexistente de qualquer maneira.

Uma parte importante da ergonomia também deve ser a legibilidade e a compreensão do código. Rust já recebeu algumas críticas no passado por abreviar as coisas de forma agressiva ( fn , mut , etc), o que torna alguns códigos mais difíceis de ler. Para coisas que, para um conceito ainda mais complicado de entender e que atende a um propósito de nicho para a maioria dos usuários, usar um nome mais detalhado e descritivo deve ser totalmente aceitável.

@rfcbot resolve naming-of-Unpin

Ok, estou pensando há algum tempo e também conversei com @withoutboats pessoalmente sobre isso. Descobri que agora estou pessoalmente confortável pelo menos com o nome Unpin como o traço marcador. Como resultado, vou remover uma objeção de bloqueio (embora se outros na equipe de libs pensem de forma diferente, por favor, avise-nos!)

A principal razão pela qual cheguei a Unpin é basicamente o que @cramertj já disse acima , é o nome mais idiomático para esse traço que pudemos criar. Todos os outros nomes de características que vi propostos não atendem à barreira idiomática que Unpin cumpre (ou pelo menos na minha opinião).

Embora eu ainda acredite que existam nomes melhores para "Acabei de ver o nome desse traço e quero saber o que ele faz", não acho que esse seja o problema certo para resolver aqui. Está claro para mim neste ponto que não podemos, pelo menos neste momento, descobrir um nome diferente para esse traço que também seja idiomático, mas ao invés disso, estamos trocando nomes de traço que resolvem um problema diferente. Entender Unpin é como Send e Sync , a documentação precisa ser lida para entender completamente o que está acontecendo.


Como outro ponto de esclarecimento, listei algumas "objeções de bloqueio", mas são mais itens TODO do que as objeções de bloqueio em si. Eu simplesmente não tenho uma ótima maneira de navegar em um tópico tão longo! Nesse sentido, acho que é bom para um PR de estabilização ser postado a qualquer momento agora com FCP expirado por uma semana ou mais. Os pontos finais sobre self / pin / pinned podem ser brevemente discutidos lá (se necessário, eles podem ser deixados como a proposta acima).

A documentação, creio, especialmente, não é um pré-requisito para a estabilização neste caso. Temos um ciclo completo (6 semanas) para adicionar documentos antes que esses tipos cheguem à estabilidade e temos ainda mais tempo para reforçar a documentação aqui antes que a história assíncrona / espera completa fique estável. É muito tempo para melhorar o que existe agora, e o que existe agora já é bastante útil!

O que "idiomático" significa aqui? O que há de unidiomático em Reloc[ate] , Escape , Evade , Pluck ou Roam ? São todos verbos e nenhum deles pode ser confundido com o problema de duplo negativo.

@alexcrichton Existe uma razão pela qual Unpin é considerado mais idiomático do que Depin ou DePin ?

Acho que Depin é uma alternativa muito sólida e não me causa as mesmas dificuldades mentais que Unpin .

Uma razão simples pode ser que depin não é uma palavra e desafixar é. Pessoalmente, não tenho problemas para entender Unpin . Acho que se encaixa com a nomenclatura dos outros traços marcantes.

@jimmycuadramuitos nomes em Rust que não são palavras "reais", incluindo no stdlib.

Eu ficaria surpreso se isso fosse considerado um motivo significativo para escolher um nome em vez de outro.

@Pauan É um motivo significativo. Como falante nativo de inglês, Depin me parece que esquecemos que a palavra para desafixar existe e tentamos inventar uma. Parece-me muito errado gramaticalmente: a palavra inglesa para "depinning" é "unpinning".

Uma boa analogia seria se tivéssemos APIs que dissessem "delock" em vez de "unlock".

@jaredr por "idiomático" quero dizer seguir as convenções estabelecidas da biblioteca padrão, já como os traços (mencionados anteriormente) Read e Write . Temos uma convenção de nomes sem palavras, verbos, curtos quando possível e adequados para a situação.

Todos os nomes como você sugeriu são possíveis, mas Unpin (ou assim eu acho que pelo menos) é o mais apropriado para esta ação em particular ("você pode liberar este tipo" garantia). Os outros, embora sejam sinônimos soltos de Unpin , acho que estão apenas renomeando a palavra "liberar" e às vezes não transmitem a mesma conexão com Pin como Unpin faz.

@Pauan Eu concordo com @withoutboats que Depin não soa tão bem quanto Unpin .

@aturon observou em https://github.com/rust-lang/rfcs/pull/2592#issuecomment -438873636 que:

O uso de Pin não é mais um detalhe de implementação do que a escolha de &self versus &mut self ; a longo prazo, é provável que Pin seja um modo de referência separado com suporte a idiomas. Em todos esses casos, o que está sendo transmitido na assinatura são as permissões concedidas ao receptor, com Pin proibindo a saída da referência.

Ostensivamente, isso se refere a um tipo &pin nativo ou algo assim ... mas idk como isso se enquadra com Pin<&mut T> e outros semelhantes. Em minhas conversas com @withoutboats e, em particular, @cramertj, eles não tinham certeza sobre a ideia de um modo de referência separado com suporte a idiomas e como poderíamos chegar lá com Pin<T> .

Antes de estabilizar pin , seria prudente reconciliar essas visualizações para garantir que estejamos na mesma página. esta peça crítica de infraestrutura; então, eu gostaria principalmente que Aaron expandisse isso e que os barcos e Taylor então pesassem

Os outros, embora sejam sinônimos soltos de Desafixar, acho que estão apenas renomeando a palavra "desafixar" e às vezes não transmitem a mesma conexão com Pin que Desafixar.

@alexcrichton isso é realmente uma coisa boa, eu acho? Como Send para mover / copiar (=) operação, Sync para emprestar (&) operação. Talvez essa conexão esteja realmente causando mais confusão?

@ crlf0710 pode! Não tenho certeza se concordaria com tal conexão comigo mesmo. Send e Sync são mais sobre o que eles estão habilitando ("enviar" tipos para outros tópicos e "sincronizar" o acesso rápido entre os tópicos), e ao nomeá-los, não tentamos evite nomeá-los próximos a uma operação ou outra

@alexcrichton exatamente! então, talvez essa característica deva também ser sobre o que está habilitando. ("em movimento (Um verbo aqui) de Pin ). Não sou falante nativo de inglês, mas ainda acho que "impassível" em pin é um pouco ... estranho?

@ crlf0710 Mas o que a característica permite não é <moving> fora de um Pin, é <moving out of a Pin> . O problema com movimento e sinônimos de movimento é que eles implicam que a característica controla a habilidade do tipo de se mover, o que não acontece. A conexão com o Pin é vital para entender o que a característica realmente faz.

Portanto, o nome mais idiomático para esse traço seria um verbo que significa "sair de um alfinete", para o qual a palavra "Desafixar" é a mais óbvia para mim por muitos. Esta é a definição de "liberar" do Wikcionário:

  1. Para soltar removendo um alfinete.
  2. (transitivo, computação, interface gráfica do usuário) Para desanexar (um ícone, aplicativo, etc.) do local onde foi fixado anteriormente.

@semoutboats obrigado por explicar! Na verdade, acho que posso aceitar esse Unpin nome ou qualquer nome que a equipe de ferrugem decidir finalmente, não quero bloquear a estabilização dos tipos de pinos e entendo absolutamente as preocupações sobre "mover" etc.

Parece que há uma mínima "repetição" em algum lugar. e tenho que me persuadir: isso não significa "impunível", mas sim o contrário. Acho que depois de algum tempo vou me acostumar, se essa for a decisão final.

Ao mesmo tempo, permita-me fazer minha última sugestão: na verdade, acho que o verbo Detach mencionado acima também é muito bom, embora um pouco genérico. Como eu disse, não sou falante nativo, então não posso falar pelos outros. Por favor, veja isso como uma pequena ideia.

Eu estava pensando em projeções seguras com pinos desconexos e tive a ideia de que pode permitir isso. Esta é uma macro para simular a correspondência com lvalues ​​mutáveis ​​fixos. Com esta macro podemos escrever

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

para decompor Pin<&mut MyStruct> em referências mutáveis ​​fixas aos campos.

Para tornar isso seguro e utilizável, precisaremos de duas outras coisas:

  • O tipo Kite para marcar os campos "always- Unpin "
  • Tornar Unpin inseguro

Este último é necessário para a segurança da projeção. Sem isso, podemos definir " Kite com projeções fixadas" com segurança, o que é realmente errado.

Com isso em mente, proponho tornar Unpin inseguro antes da estabilização para deixar uma sala para outras operações úteis serem seguras.

@qnighy É possível que um tipo viole as garantias de fixação em sua implementação Drop , o que torna Drop equivalente a Unpin . Tornar Unpin inseguro não torna nenhum código adicional seguro, porque Drop também é seguro e isso não pode ser alterado. Já falamos muito sobre isso no problema de rastreamento, e também foi mencionado neste tópico.

Fundamentalmente, as projeções de pinos não podem ser feitas seguras sem a capacidade de afirmar certos limites negativos que não podem ser expressos em Rust hoje.

@ crlf0710

Ao mesmo tempo, permita-me fazer minha última sugestão: na verdade, eu acho que o verbo Desanexar acima mencionado também é muito bom, embora um pouco geral. Como eu disse, não sou falante nativo, então não posso falar pelos outros. Por favor, veja isso como uma pequena ideia.

Desanexar parece muito melhor do que nomes como Mover e Relocalizar, mas parece entrar em conflito com outros usos possíveis da metáfora de desanexar (semelhante a como Escape poderia entrar em conflito com "análise de escape" etc.). Existem tantas metáforas que temos sobre como os dados podem se relacionar com outros dados na ciência da computação, o que me faz pensar em outra nova vantagem do Unpin: ao se apegar firmemente à metáfora do "alfinete", ele não ocupa espaço para linguagem metafórica futura podemos precisar usar para um propósito diferente, da maneira que nomes como Detach, Escape ou Relocate poderiam.

@semoutboats não tenho preferência particular por nenhum nome ou muito investimento neste bicicletário ... mas estou curioso; que outra finalidade Detach caberia (se você especular ...)?

@Centril Não tenho um caso de uso claro em mente, mas poderia imaginar algum caso de uso relacionado à desestruturação, por exemplo.

@withoutboats Sim, isso faz sentido; Felicidades!

@semoutboats Não tenho certeza se a entrada do Wikcionário é a melhor motivação para justificar a nomeação de Unpin para habilitar move from a pin . As metáforas da representação física sofrem com o fato de que se mover em Rust não é tirar um objeto do mundo. Para ser mais preciso, 'upinning' um ponteiro fixado não me dá controle sobre a memória referida, sua alocação e validade como uma representação de objeto de T ainda são necessários e garantidos pela semântica do ponteiro. Portanto, o desencaixotamento não fornece uma representação totalmente controlada de T , como o desencaixotamento faria. E, uma entrada de dicionário definindo unpinning em termos de moving é de fato parte da confusão mais do que a justificativa de tal nome.

Na API proposta, nenhum dos métodos tem esse efeito (e também não está no escopo de pin). Não vejo uma maneira segura, por exemplo, de fazer a transição de Pin<Box<T>> para Box<T> (e por extensão para T ) e nem tenho certeza se Unpin deveria têm possibilidades tão fortes. Não tenho certeza de onde estaria toda a diferença. Mas, pelo que entendi, Pin se aplica a algum local da memória, enquanto Unpin garante que nada na representação de T s depende de garantias de pinos. No entanto, isso seria o mesmo que ser capaz de tirar e esquecer totalmente a memória? Eu acho que não. Vou pensar em um exemplo mais concreto.

Edit: Mais concretamente, mesmo se T for Unpin , posso confiar em alguma instância de T::drop(&mut) sendo chamada na memória que foi fixada, antes que a memória seja desalocada? Pelo que eu posso dizer pelo formalismo, a resposta deveria ser sim, mas o nome Unpin me comunica o oposto.

Edição 2: Rc permite observar Pin<&T> mas para T: Unpin ainda não ter drop chamado na localização de memória original. Mantenha uma referência ativa fora do alfinete, então depois que o alfinete foi retirado você pode mover Rc::try_unwrap . https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca Isso efetivamente responde à pergunta por meio de mecanismos existentes, mas funciona como pretendido?

Talvez IgnorePin ? Se T for IgnorePin você pode tratar Pin<Box<T>> como &mut T essencialmente ignorando a presença de Pin (AIUI também ignorando Box ). Ignorar é um verbo, acho que IgnorePin não é, mas não tenho certeza do que seja. IgnorePin é descritivo do que permite, não é descritivo das restrições colocadas sobre o tipo, mas também não é Unpin .

@wmanley Eu tive uma ideia muito semelhante, ElidePin , em algum comentário acima, embora naquela época eu não tivesse sido capaz de expressar concretamente por que aquela parecia mais precisa. Mas concordo que 'um verbo' é o guia de estilo para os traços do marcador de Ferrugem. Embora também permita uma negação mais natural na forma de !ElidePin / !IgnorePin , não é o ideal.

@withoutboats Pergunta de acompanhamento: Como o pin parece especificado em termos de memória subjacente, como o Pin interage com o ZST? Como Pinned é um ZST, mesmo um ZST pode ser Unpin ou não. Eu diria intuitivamente que sua representação de memória nunca é formalmente invalidada, portanto, T::drop(&mut self) nunca precisa ser chamado, muito semelhante a como alguém poderia construir um pino de &mut 'static _ memória, por exemplo, de um Box vazado

É seguro criar Pin<P<T>> com T: !Unpin e, em seguida, liberar imediatamente o valor, se for garantido que nada observou a fixação? É apenas um comportamento indefinido se o valor fixado for movido depois de ter um método semelhante a uma votação chamado?

@HeroicKatora Infelizmente, hoje é possível implementar Drop para um tipo que poderia ser !Unpin devido a genéricos, por exemplo:

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

@cramertj Obrigado, acabei de perceber isso também.

@cramertj Então, o padrão é T: ?Unpin em limites genéricos? Existe outra razão para não padronizar para T: Unpin para tipos existentes como Sized ? Isso daria alguns exemplos em que isso seria irritantemente estrito, mas esse limite automático adicional pode causar regressões?

@HeroicKatora Isso exigiria polvilhar ?Unpin limites em cada tipo na biblioteca padrão e em um monte de código que não se preocupa com Unpin alguma.

Também é seguro adicionar um campo PhantomPinned, como outra forma de criar um tipo Soltar +! Desafixar.

Existe outra razão por trás de não padronizar para T: Unpin para tipos existentes?

Desafixar é apenas um traço automático como Enviar e Sincronizar, esta proposta não envolve novos recursos de linguagem. Portanto, ele tem a mesma semântica que as características automáticas já foram definidas para ter, ou seja, não são aplicadas a genéricos por padrão (ao contrário de Sized, que é uma característica incorporada ao compilador, não uma característica automática).

É apenas um comportamento indefinido se o valor fixado for movido depois de ter um método semelhante a uma votação chamado?

Devemos deixar claro aqui que o comportamento indefinido neste contexto não é realmente o tipo tradicional de UB. Em vez disso, é que o código, observando um tipo em um Pin, pode fazer suposições sobre a semântica de propriedade desse valor (ou seja, se ele não implementar Desafixar, ele não será movido novamente ou invalidado até que seu destruidor seja executado). Não é UB no sentido de que é assumido pelo compilador para nunca acontecer (o compilador não sabe o que é um Pin).

Portanto, sim, no sentido de que você pode verificar se nenhum código está contando com a garantia que você forneceu. Mas também o que você fez certamente não tem sentido, já que nenhum código pode observar que o tipo foi fixado.

@cramertj Eu vejo, isso seria um pouco infeliz. Estou um pouco desconfortável com a interação de Pin com Drop na especificação, mas não no idioma. Bastante conflitante entre a correção da interface, usabilidade e lançá-lo em um futuro próximo. Posso obter esclarecimentos sobre a Edição 2 acima:

Rc permite observar Pin <& T>, mas para T: Unpin ainda não tem drop chamado no local de memória original. Mantenha uma referência ativa fora do pino, então depois que o pino for solto, você pode mover para fora com Rc :: try_unwrap. https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=dec6f6c6d2c0903d87a4a9cefe50a0ca

@HeroicKatora o problema com seu código é que Mem<'a>: Unpin , mas a linha de código inseguro que você usa baseia-se em suposições que se aplicam apenas a tipos que não implementam Unpin . Editei sua essência para incluir a prova de que Mem<'a>: Unpin : https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=201d1ae382f590be8c5cac13af279aff

Você confia no limite Desafixar quando chama Pin::new , que só pode ser chamado em ponteiros cujo destino implementa Desafixar.

A brecha que você pensou ter encontrado foi considerada, e é por isso que não há como passar de Rc<T: ?Unpin> para Pin<Rc<T>> . Você tem que construir o Rc no pino com Rc::pinned .

@withoutboats Só queria que isso fosse confirmado, que T: Unpin fato também desativa a chamada drop antes da invalidação. Isso levanta a questão, não deveria haver também

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

uma vez que não protege nenhuma parte da interface, desembrulhar Pin<Box<T>> em Box<T> é potencialmente útil (para T: Unpin claro).

@HeroicKatora você está correto que essa função seria segura. Não me importo de adicioná-lo, mas gostaria de evitar expandir a API que estamos estabilizando neste momento , uma vez que este tópico já tem centenas de comentários. Sempre podemos adicioná-lo mais tarde, conforme a necessidade surgir, assim como fazemos com todas as APIs std.

Eu apenas diria que tanto a nomenclatura quanto a funcionalidade de Unpin fazem muito mais sentido percebido com esse método inverso. E sabendo que as garantias de Pin são de fato restritas a T: !Unpin . Isso também seria demonstrado diretamente pela existência de tal método, ao invés de construir escotilhas de escape para mostrar a possibilidade: sorria:. E sua documentação seria um lugar perfeito para explicar (ou vincular mais uma vez) as restrições. (Pode-se até considerar nomeá-lo unpin vez de into_inner ).

Edit: Seria apenas generalizar o que já está lá.

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

  • fn unpin(self) -> P

tem a instanciação para P=&'a mut T , que é equivalente ao proposto

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

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

Agora que a estabilização passou, o ponto do PFCP parece discutível, portanto:

@rfcbot cancel

Existe algum controle para garantir que os comentários desenvolvidos a partir de https://github.com/rust-lang/rust/issues/55766#issuecomment-437374982 sejam transformados em documentos? Parece que já é tarde demais para o lançamento, pois o beta foi ramificado ... mesmo que isso tenha sido explicitamente mencionado aqui para acontecer antes da estabilização: /

Existe algum motivo para que ainda esteja aberto? @Centril você fechou e reabriu isso, foi deliberado?

@RalfJung tentei cancelar o FCP mas não deu ouvidos; @alexcrichton você pode cancelar o FCP?

Isso é estável, então fechando.

Esta página foi útil?
0 / 5 - 0 avaliações