Rust: Rastreamento de problema para especialização (RFC 1210)

Criado em 23 fev. 2016  ·  236Comentários  ·  Fonte: rust-lang/rust

Este é um problema de rastreamento para especialização (rust-lang / rfcs # 1210).

Principais etapas de implementação:

  • [x] Land https://github.com/rust-lang/rust/pull/30652 =)
  • [] Restrições em torno do envio vitalício (atualmente um buraco de solidez )
  • [] default impl (https://github.com/rust-lang/rust/issues/37653)
  • [] Integração com consts associados
  • [] Limites nem sempre aplicados corretamente (https://github.com/rust-lang/rust/issues/33017)
  • [] Devemos permitir impls vazios se os pais não têm default membros? https://github.com/rust-lang/rust/issues/48444
  • [] implementar "sempre aplicável" impls https://github.com/rust-lang/rust/issues/48538
  • [] descrever e testar as condições precisas do ciclo em torno da criação do gráfico de especialização (veja, por exemplo, este comentário , que observou que temos uma lógica muito cuidadosa aqui hoje)

Perguntas não resolvidas da RFC:

  • O tipo associado deve ser especializável?
  • Quando a projeção deve revelar default type ? Nunca durante o typeck? Ou quando monomórfico?
  • Os itens de característica padrão devem ser considerados default (isto é, especializáveis)?
  • Devemos ter default impl (onde todos os itens são default ) ou partial impl (onde default é opt-in); consulte https://github.com/rust-lang/rust/issues/37653#issuecomment -616116577 para alguns exemplos relevantes de onde default impl é limitante.
  • Como devemos lidar com a despachabilidade vitalícia?

Observe que o recurso specialization conforme implementado atualmente é incorreto , o que significa que pode causar comportamento indefinido sem o código unsafe . min_specialization evita a maioria das armadilhas .

A-specialization A-traits B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-specialization T-lang

Comentários muito úteis

Tenho usado #[min_specialization] em uma biblioteca experimental que venho desenvolvendo, então pensei em compartilhar minhas experiências. O objetivo é usar a especialização em sua forma mais simples: ter alguns casos estreitos com implementações mais rápidas do que o caso geral. Em particular, para ter algoritmos criptográficos no caso geral executados em tempo constante, mas se todas as entradas estiverem marcadas Public ter uma versão especializada que execute em tempo variável mais rápido (porque se eles forem públicos, não preocupam-se com o vazamento de informações sobre eles por meio do tempo de execução). Além disso, alguns algoritmos são mais rápidos dependendo se o ponto da curva elíptica está normalizado ou não. Para fazer isso funcionar, começamos com

#![feature(rustc_attrs, min_specialization)]

Então, se você precisa fazer um atributo _specialization predicate_ conforme explicado na especialização máxima mínima, você marca a declaração do traço com #[rustc_specialization_trait] .

Toda a minha especialização é feita neste arquivo e aqui está um exemplo de uma característica de predicado de especialização.

O recurso funciona e faz exatamente o que eu preciso. Obviamente, isso está usando um marcador interno rustc e, portanto, está sujeito a quebrar sem aviso.

O único feedback negativo é que não acho que a palavra-chave default faça sentido. Essencialmente, o que default significa agora é: "este impl é especializável, portanto, interprete os impls que cobrem um subconjunto deste como uma especialização dele ao invés de um impl conflitante". O problema é que isso leva a um código de aparência muito estranha:

https://github.com/LLFourn/secp256kfun/blob/6766b60c02c99ca24f816801fe876fed79643c3a/secp256kfun/src/op.rs#L196 -L206

Aqui, o segundo impl está se especializando no primeiro, mas também é default . O significado de default parece ter se perdido. Se você olhar para o resto dos impls, é muito difícil descobrir quais impls são especializados em quais. Além disso, quando eu fazia um implante errado que se sobrepunha a um existente, muitas vezes era difícil descobrir onde eu errei.

Parece-me que isso seria mais simples se tudo fosse especializável e quando você especializa algo, você declara exatamente em qual implemento está se especializando. Transformando o exemplo no RFC no que eu tinha em mente:

impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
{
    // no need for default
    fn extend(&mut self, iterable: T) {
        ...
    }
}

// We declare explicitly which impl we are specializing repeating all type bounds etc
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
    // And then we declare explicitly how we are making this impl narrower with ‘when’.
    // i.e. This impl is like the first except replace all occurances of ‘T’ with ‘&'a [A]’
    when<'a> T = &'a [A]
{
    fn extend(&mut self, iterable: &'a [A]) {
        ...
    }
}

Todos 236 comentários

Algumas perguntas abertas adicionais:

  • Devemos revisitar as regras órfãs à luz da especialização? Existem maneiras de tornar as coisas mais flexíveis agora?
  • Devemos estender a "regra da cadeia" na RFC para algo mais expressivo, como a chamada "regra da rede"?
  • Relacionado a ambos os itens acima, como o raciocínio negativo se encaixa na história? Podemos recuperar o raciocínio negativo de que precisamos por um uso inteligente o suficiente de regras de especialização / órfãs, ou devemos torná-lo mais de primeira classe?

Não tenho certeza se a especialização altera as regras órfãs:

  • As regras órfãs de "vinculação" devem permanecer as mesmas, caso contrário, você não teria uma vinculação segura.
  • Não acho que as regras órfãs de "compatibilidade futura" devam mudar. Adicionar um impl não especializável abaixo de você ainda seria uma mudança significativa.

Pior do que isso, as regras órfãs de "compatibilidade futura" mantêm a especialização cruzada sob forte controle. Sem eles, o default-impls deixando seus métodos abertos se torna muito pior.

Nunca gostei de raciocínio negativo explícito. Eu acho que a especialização de raciocínio negativo total fornece um bom compromisso.

Este implemento deve ser permitido com a especialização implementada? Ou eu estou esquecendo de alguma coisa?
http://is.gd/3Ul0pe

O mesmo com este, seria esperado que compilasse: http://is.gd/RyFIEl

Parece que há algumas peculiaridades em determinar a sobreposição quando tipos associados estão envolvidos. Isso compila: http://is.gd/JBPzIX , enquanto este código efetivamente idêntico não: http://is.gd/0ksLPX

Aqui está um trecho de código que eu esperava compilar com especialização:

http://is.gd/3BNbfK

#![feature(specialization)]

use std::str::FromStr;

struct Error;

trait Simple<'a> {
    fn do_something(s: &'a str) -> Result<Self, Error>;
}

impl<'a> Simple<'a> for &'a str {
     fn do_something(s: &'a str) -> Result<Self, Error> {
        Ok(s)
    }
}

impl<'a, T: FromStr> Simple<'a> for T {
    fn do_something(s: &'a str) -> Result<Self, Error> {
        T::from_str(s).map_err(|_| Error)
    }
}

fn main() {
    // Do nothing. Just type check.
}

A compilação falha com o compilador citando conflitos de implementação. Observe que &str não implementa FromStr , então não deve haver um conflito.

@sgrif

Tive tempo para olhar os primeiros dois exemplos. Aqui estão minhas notas.

Exemplo 1

Primeiro caso, você tem:

  • FromSqlRow<ST, DB> for T where T: FromSql<ST, DB>
  • FromSqlRow<(ST, SU), DB> for (T, U) where T: FromSqlRow<ST, DB>, U: FromSqlRow<SU, DB>,

O problema é que esses impls se sobrepõem, mas nenhum é mais específico do que o outro:

  • Você pode potencialmente ter um T: FromSql<ST, DB> onde T não é um par (então ele corresponde ao primeiro impl, mas não ao segundo).
  • Você pode potencialmente ter um (T, U) onde:

    • T: FromSqlRow<ST, DB> ,

    • U: FromSqlRow<SU, DB> , mas _não_

    • (T, U): FromSql<(ST, SU), DB>

    • (então o segundo impl corresponde, mas não o primeiro)

  • Os dois impls se sobrepõem porque você pode ter (T, U) tal que:

    • T: FromSqlRow<ST, DB>

    • U: FromSqlRow<SU, DB>

    • (T, U): FromSql<(ST, SU), DB>

Este é o tipo de situação que os impls de rede permitiriam - você teria que escrever um terceiro impl para o caso sobreposto e dizer o que ele deveria fazer. Alternativamente, impls de traço negativo podem lhe dar uma maneira de descartar a sobreposição ou então ajustar quais combinações são possíveis.

Exemplo 2

Você tem:

  • Queryable<ST, DB> for T where T: FromSqlRow<ST, DB>
  • Queryable<Nullable<ST>, DB> for Option<T> where T: Queryable<ST, DB>

Eles se sobrepõem porque você pode ter Option<T> onde:

  • T: Queryable<ST, DB>
  • Option<T>: FromSqlRow<Nullable<ST>, DB>

Mas nenhum impl é mais específico:

  • Você pode ter um T tal que T: FromSqlRow<ST, DB> mas T não seja um Option<U> (corresponde ao primeiro impl, mas não ao segundo)
  • Você pode ter um Option<T> tal que T: Queryable<ST, DB> mas não Option<T>: FromSqlRow<Nullable<ST>, DB>

@SergioBenitez

A compilação falha com o compilador citando conflitos de implementação. Observe que &str não implementa FromStr , então não deve haver um conflito.

O problema é que o compilador está assumindo conservadoramente que &str pode vir a implementar FromStr no futuro. Isso pode parecer bobo para este exemplo, mas em geral, adicionamos novos impls o tempo todo, e queremos proteger o código downstream de quebras quando adicionamos esses impls.

Esta é uma escolha conservadora e é algo que podemos querer relaxar com o tempo. Você pode obter o histórico aqui:

Obrigado por esclarecer esses dois casos. Faz todo o sentido agora

Na terça-feira, 22 de março de 2016, 18:34, Aaron Turon [email protected] escreveu:

@SergioBenitez https://github.com/SergioBenitez

A compilação falha com o compilador citando conflitos de implementação. Nota
que & str não implementa FromStr, então não deve haver um conflito.

O problema é que o compilador está assumindo conservadoramente que & str
pode vir a implementar FromStr no futuro. Isso pode parecer bobo para
este exemplo, mas, em geral, adicionamos novos impls o tempo todo e queremos
proteja o código downstream de quebrar quando adicionarmos esses impls.

Esta é uma escolha conservadora e é algo que podemos querer relaxar
hora extra. Você pode obter o histórico aqui:

-
http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/rust-lang/rust/issues/31844#issuecomment -200093757

@aturon

O problema é que o compilador está assumindo conservadoramente que & str pode vir a implementar FromStr no futuro. Isso pode parecer bobo para este exemplo, mas em geral, adicionamos novos impls o tempo todo, e queremos proteger o código downstream de quebras quando adicionamos esses impls.

Não é exatamente isso que a especialização está tentando abordar? Com a especialização, eu esperaria que mesmo se uma implementação de FromStr para &str fosse adicionada no futuro, a implementação direta do traço Simple para &str teria precedência.

@SergioBenitez você precisa colocar default fn no impl. Seu
exemplo não é especializável.

Em Ter, 22 de março de 2016, 18:54 Sergio Benitez [email protected]
escrevi:

@aturon https://github.com/aturon

O problema é que o compilador está assumindo conservadoramente que & str
pode vir a implementar FromStr no futuro. Isso pode parecer bobo para isso
exemplo, mas em geral, adicionamos novos impls o tempo todo, e queremos
proteja o código downstream de quebrar quando adicionarmos esses impls.

Não é exatamente isso que a especialização está tentando abordar? Com
especialização, eu esperaria que mesmo se uma implementação de FromStr
for & str foram adicionados no futuro, a implementação direta para o
o traço para & str teria precedência.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/rust-lang/rust/issues/31844#issuecomment -200097995

Eu acho que os itens de característica "padrão" sendo considerados automaticamente default soa confuso. Você pode querer tanto a parametricidade para um traço como em Haskell, etc., juntamente com a facilitação de impl s. Além disso, você não pode facilmente grep para eles como você pode para default . Não é difícil digitar a palavra-chave default e fornecer uma implementação padrão, mas elas não podem ser separadas como estão. Além disso, se alguém quiser esclarecer a linguagem, esses itens de características "padrão" podem ser renomeados para itens de "características propostas" na documentação.

Nota de # 32999 (comentário) : se

@Stebalien

Por que não funciona? O truque limita a especialização a um traço particular. Você não pode se especializar no traço privado se não puder acessá-lo.

@ arielb1 Ah. Bom ponto. No meu caso, o traço não é privado.

Não acho que o raciocínio "externos não podem se especializar porque órfãos de compatibilidade com versões futuras + regra de coerência" seja particularmente interessante ou útil. Especialmente quando não nos comprometemos com nossas regras de coerência específicas.

Existe uma maneira de acessar um default impl substituído? Nesse caso, isso pode ajudar na construção de testes. Consulte Design By Contract e libhoare .

Permitir a projeção de tipos associados padrão durante a verificação de tipo permitirá impor a desigualdade de tipo em tempo de compilação: https://gist.github.com/7c081574958d22f89d434a97b626b1e4

#![feature(specialization)]

pub trait NotSame {}

pub struct True;
pub struct False;

pub trait Sameness {
    type Same;
}

mod internal {
    pub trait PrivSameness {
        type Same;
    }
}

use internal::PrivSameness;

impl<A, B> Sameness for (A, B) {
    type Same = <Self as PrivSameness>::Same;
}

impl<A, B> PrivSameness for (A, B) {
    default type Same = False;
}
impl<A> PrivSameness for (A, A) {
    type Same = True;
}

impl<A, B> NotSame for (A, B) where (A, B): Sameness<Same=False> {}

fn not_same<A, B>() where (A, B): NotSame {}

fn main() {
    // would compile
    not_same::<i32, f32>();

    // would not compile
    // not_same::<i32, i32>();
}

editado pelo comentário de @burdges

Apenas fyi @rphmeier , provavelmente deve-se evitar o is.gd porque ele não resolve para os usuários do Tor devido ao uso do CloudFlare. O GitHub funciona bem com URLs completos. E play.rust-lang.org funciona bem no Tor.

@burdges FWIW play.rust-lang.org em si usa is.gd para seu botão "Encurtar".

Provavelmente, ele pode ser alterado: https://github.com/rust-lang/rust-playpen/blob/9777ef59b/static/web.js#L333

use assim (https://is.gd/Ux6FNs):

#![feature(specialization)]
pub trait Foo {}
pub trait Bar: Foo {}
pub trait Baz: Foo {}

pub trait Trait {
    type Item;
}

struct Staff<T> { }

impl<T: Foo> Trait for Staff<T> {
    default type Item = i32;
}

impl<T: Foo + Bar> Trait for Staff<T> {
    type Item = i64;
}

impl<T: Foo + Baz> Trait for Staff<T> {
    type Item = f64;
}

fn main() {
    let _ = Staff { };
}

Erro:

error: conflicting implementations of trait `Trait` for type `Staff<_>`: [--explain E0119]
  --> <anon>:20:1
20 |> impl<T: Foo + Baz> Trait for Staff<T> {
   |> ^
note: conflicting implementation is here:
  --> <anon>:16:1
16 |> impl<T: Foo + Bar> Trait for Staff<T> {
   |> ^

error: aborting due to previous error

O feture specialization suporta isso e existe algum outro tipo de implementação atualmente?

@zitsen

Esses impls não são permitidos pelo projeto de especialização atual, porque nem T: Foo + Bar nem T: Foo + Baz são mais especializados que o outro. Ou seja, se você tiver algum T: Foo + Bar + Baz , não está claro qual impl deve "ganhar".

Temos algumas ideias sobre um sistema mais expressivo que permitiria a você _também_ dar um impl para T: Foo + Bar + Baz e, assim, eliminar a ambigüidade, mas isso ainda não foi totalmente proposto.

Se o traço negativo limita trait Baz: !Bar cada terreno, isso também pode ser usado com especialização para provar que os conjuntos de tipos que implementam Bar e aqueles que implementam Baz são distintos e individualmente especializáveis.

Parece que a resposta de @rphmeier é exatamente o que eu quero, impls para T: Foo + Bar + Baz também ajudaria.

Apenas ignore isso, eu ainda tenho algo a ver com meu caso, e sempre empolgante para o desembarque de specialization e outros recursos.

Obrigado @aturon @rphmeier .

Tenho brincado com especialização recentemente e me deparei com este caso estranho:

#![feature(specialization)]

trait Marker {
    type Mark;
}

trait Foo { fn foo(&self); }

struct Fizz;

impl Marker for Fizz {
    type Mark = ();
}

impl Foo for Fizz {
    fn foo(&self) { println!("Fizz!"); }
}

impl<T> Foo for T
    where T: Marker, T::Mark: Foo
{
    default fn foo(&self) { println!("Has Foo marker!"); }
}

struct Buzz;

impl Marker for Buzz {
    type Mark = Fizz;
}

fn main() {
    Fizz.foo();
    Buzz.foo();
}

Saída do compilador:

error: conflicting implementations of trait `Foo` for type `Fizz`: [--explain E0119]
  --> <anon>:19:1
19 |> impl<T> Foo for T
   |> ^
note: conflicting implementation is here:
  --> <anon>:15:1
15 |> impl Foo for Fizz {
   |> ^

cercadinho

Eu acredito que _deve_ compilar acima, e há duas variações interessantes que realmente funcionam como planejado:

1) Removendo o limite de where T::Mark: Fizz :

impl<T> Foo for T
    where T: Marker //, T::Mark: Fizz
{
    // ...
}

cercadinho

2) Adicionar um "alias de limite de característica":

trait FooMarker { }
impl<T> FooMarker for T where T: Marker, T::Mark: Foo { }

impl<T> Foo for T where T: FooMarker {
    // ...
}

cercadinho

(O que _não_ funciona se Marker for definido em uma caixa separada (!), Veja este repositório de exemplo )

Também acredito que este problema pode estar relacionado a # 20400 de alguma forma

EDITAR : Abri um problema sobre isso: # 36587

Estou encontrando um problema com a especialização. Não tenho certeza se é um problema de implementação ou um problema na forma como a especialização é especificada.

use std::vec::IntoIter as VecIntoIter;

pub trait ClonableIterator: Iterator {
    type ClonableIter;

    fn clonable(self) -> Self::ClonableIter;
}

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
        self.collect::<Vec<_>>().into_iter()
    }
}

impl<T> ClonableIterator for T where T: Iterator + Clone {
    type ClonableIter = T;

    #[inline]
    fn clonable(self) -> T {
        self
    }
}

( cercadinho )
(a propósito, seria bom se esse código eventualmente fosse parar no stdlib um dia)

Este código falha com:

error: method `clonable` has an incompatible type for trait:
 expected associated type,
    found struct `std::vec::IntoIter` [--explain E0053]
  --> <anon>:14:5
   |>
14 |>     default fn clonable(self) -> VecIntoIter<T::Item> {
   |>     ^

Alterar o valor de retorno para Self::ClonableIter dá o seguinte erro:

error: mismatched types [--explain E0308]
  --> <anon>:15:9
   |>
15 |>         self.collect::<Vec<_>>().into_iter()
   |>         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::vec::IntoIter`
note: expected type `<T as ClonableIterator>::ClonableIter`
note:    found type `std::vec::IntoIter<<T as std::iter::Iterator>::Item>`

Aparentemente, você não pode se referir ao tipo concreto de um tipo associado predefinido, o que considero bastante limitante.

@tomaka deve funcionar, o texto RFC tem o seguinte:

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

(https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#the-default-keyword)

O que parece bastante semelhante ao seu caso para ser relevante.

@aatch esse exemplo não parece compilar com a definição intuitiva para o trait de exemplo: https://play.rust-lang.org/?gist=97ff3c2f7f3e50bd3aef000dbfa2ca4e&version=nightly&backtrace=0

o código de especialização não permite isso explicitamente - consulte # 33481, que inicialmente pensei ser um erro, mas acabou sendo um problema de diagnóstico. Meus PRs para melhorar o diagnóstico aqui passaram despercebidos, e eu não os mantinha com o mestre mais recente há algum tempo.

@rphmeier o texto RFC sugere que deveria ser permitido, porém, esse exemplo é copiado dele.

Eu joguei com alguns códigos que poderiam se beneficiar da especialização. Eu acho fortemente que devemos ir para a regra da rede em vez do encadeamento - parece natural e era a única maneira de obter a flexibilidade de que precisava (afaict).

Se optássemos por default em impl , bem como em itens individuais, poderíamos impor que, se qualquer item for substituído, todos devem ser? Isso nos permitiria raciocinar com base no tipo preciso de um tipo assoc padrão (por exemplo) nos outros itens, o que parece um aumento útil na expressividade.

O seguinte deve ser permitido? Quero especializar um tipo para que ArrayVec seja Copy quando seu tipo de elemento for Copiar e que, de outra forma, tenha um destruidor. Estou tentando fazer isso usando um campo interno que é substituído por especialização.

Eu esperava que isso compilasse, ou seja, que deduzisse a capacidade de cópia dos campos de ArrayVec<A> dos tipos de campo que são selecionados pelo limite A: Copy + Array (fragmento compilável no playground) .

impl<A: Copy + Array> Copy for ArrayVec<A>
    //where <A as Repr>::Data: Copy
{ }

A cláusula where comentada não é desejada porque expõe um tipo privado Repr na interface pública. (Ele também ICEs de qualquer maneira).

Edit: Eu tinha esquecido que já relatei o problema nº 33162 sobre isso, sinto muito.

Acompanhe meu comentário, meu caso de uso real:

// Ideal version

trait Scannable {}

impl<T: FromStr> Scannable for T {}
impl<T: FromStr> Scannable for Result<T, ()> {}

// But this doesn't follow from the specialisation rules because Result: !FromStr
// Lattice rule would allow filling in that gap or negative reasoning would allow specifying it.

// Second attempt

trait FromResult {
    type Ok;
    fn from(r: Result<Self::Ok, ()>) -> Self;
}

impl<T> Scannable for T {
    default type Ok = T;
    default fn from(r: Result<T, ()>) -> Self {...} // error can't assume Ok == T, could do this if we had `default impl`
}

impl<T> Scannable for Result<T, ()> {
    type Ok = T;
    default fn from(r: Result<T, ()>) -> Self { r }
}

fn scan_from_str<T: FromResult>(x: &str) -> T
    where <T as FromResult>::Ok: FromStr  // Doesn't hold for T: FromStr because of the default on T::Ok
{ ... }

// Can also add the FromStr bound to FromResult::Ok, but doesn't help

// Third attempt
trait FromResult<Ok> {
    fn from(r: Result<Ok, ()>) -> Self;
}

impl<T> FromResult<T> for T {
    default fn from(r: Result<Self, ()>) -> Self { ... }
}

impl<T> FromResult<T> for Result<T, ()> {
    fn from(r: Result<T, ())>) -> Self { r }
}


fn scan_from_str<U: FromStr, T: FromResult<U>>(x: &str) -> T { ... }

// Error because we can't infer that U == String
let mut x: Result<String, ()> = scan_from_str("dsfsf");

@tomaka @Aatch

O problema é que você não tem permissão para confiar no valor de outros itens padrão. Então, quando você tem este impl:

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
    //                           ^^^^^^^^^^^^^^^^^^^^
        self.collect::<Vec<_>>().into_iter()
    }
}

No local onde destaquei, clonable está contando com Self::ClonableIter , mas porque CloneableIter é declarado como padrão, você não pode fazer isso. A preocupação é que alguém possa se especializar e substituir CloneableIter mas _não_ clonable .

Havíamos conversado sobre algumas respostas possíveis aqui. Uma delas era permitir que você usasse default para agrupar itens onde, se você substituir um, você deve substituir todos:

impl<T> ClonableIterator for T where T: Iterator {
    default {
        type ClonableIter = VecIntoIter<T::Item>;
        fn clonable(self) -> VecIntoIter<T::Item> { ... }
    }
}

Isso é ok, mas um pouco "indutor de desvio para a direita". O default também se parece com um escopo de nomenclatura, o que não é. Pode haver alguma variante mais simples que apenas permite alternar entre "substituir qualquer" (como hoje) e "substituir tudo" (o que você precisa).

Também esperávamos poder sobreviver alavancando impl Trait . A ideia seria que isso surgisse com mais frequência, como é o caso aqui, quando se deseja customizar o tipo de retorno dos métodos. Então, talvez se você pudesse reescrever o traço para usar impl Trait :

pub trait ClonableIterator: Iterator {
    fn clonable(self) -> impl Iterator;
}

Isso seria efetivamente uma espécie de atalho quando implementado para um grupo padrão contendo o tipo e o fn. (Não tenho certeza se haveria uma maneira de fazer isso puramente no implante.)

PS, desculpem a demora em responder suas mensagens, que vejo data de _Julho_.

Embora impl Trait ajude, não há nenhum RFC que tenha sido aceito ou implementado que permita seu uso com corpos de traços em qualquer forma, então olhar para ele por este RFC parece um pouco estranho.

Estou interessado em implementar o recurso default impl (onde todos os itens são default ).
Você aceitaria uma contribuição sobre isso?

@giannicic Definitivamente! Eu ficaria feliz em ajudar a orientar o trabalho também.

Existe atualmente uma conclusão sobre se os tipos associados devem ser especializáveis?

A seguir, uma simplificação do meu caso de uso, demonstrando a necessidade de tipos associados especializáveis.
Eu tenho uma estrutura de dados genérica, digamos Foo , que coordena uma coleção de objetos de características de contêiner ( &trait::Property ). O traço trait::Property é implementado por Property<T> (apoiado por Vec<T> ) e PropertyBits (apoiado por BitVec , um vetor de bits).
Em métodos genéricos em Foo , gostaria de ser capaz de determinar a estrutura de dados subjacente correta para T por meio de tipos associados, mas isso requer especialização para ter um implemento cobertor para casos não especiais como segue.

trait ContainerFor {
    type P: trait::Property;
}

impl<T> ContainerFor for T {
    default type P = Property<T>; // default to the `Vec`-based version
}

impl ContainerFor for bool {
    type P = PropertyBits; // specialize to optimize for space
}

impl Foo {
    fn add<T>(&mut self, name: &str) {
        self.add_trait_obj(name, Box::new(<T as ContainerFor>::P::new())));
    }
    fn get<T>(&mut self, name: &str) -> Option<&<T as ContainerFor>::P> {
        self.get_trait_obj(name).and_then(|prop| prop.downcast::<_>());
    }
}

Obrigado @aturon !
Basicamente, estou fazendo o trabalho adicionando um novo atributo "defaultness" à ast::ItemKind::Impl struct (e, em seguida, uso o novo atributo junto com o atributo "defaultness" do item impl), mas também há um recurso rápido e fácil
possibilidade que consiste em definir como padrão todos os itens impl de default impl durante a análise.
Para mim esta não é uma solução "completa" já que perdemos a informação de que o "default" está relacionado ao impl e não a cada item do impl,
além disso, se houver um plano para introduzir partial impl a primeira solução já forneceria um atributo que pode ser usado para armazenar default , bem como partial . Mas só para ter certeza e
sem perder tempo, no que você pensa?

@giannicic @aturon, posso propor que default impl ?

A regra da rede permitiria, dado:

trait Foo {}

trait A {}
trait B {}
trait C {}
// ...

adicione implementações de Foo para o subconjunto de tipos que implementam alguma combinação de A , B , C , ...:

impl Foo for T where T: A { ... }
impl Foo for T where T: B { ... }
impl Foo for T where T: A + B { ... }
impl Foo for T where T: B + C { ... }
// ...

e permita-me "proibir" algumas combinações, por exemplo, que A + C nunca deveria acontecer:

impl Foo for T where T: A + C = delete;

?

Contexto: Eu comecei a querer isso ao implementar uma característica ApproxEqual(Shape, Shape) para diferentes tipos de formas (pontos, cubos, polígonos, ...) onde todas essas características são. Tive de contornar isso refatorando-o em características diferentes, por exemplo, ApproxEqualPoint(Point, Point) , para evitar implementações conflitantes.

@gnzlbg

e permita-me "proibir" algumas combinações, por exemplo, que A + C nunca deve acontecer:

Não, isso não é algo que a regra da rede permitiria. Isso seria mais o domínio do "raciocínio negativo" de alguma forma ou tipo.

Contexto: Eu comecei a querer isso ao implementar um traço ApproxEqual (Forma, Forma) para diferentes tipos de formas (pontos, cubos, polígonos, ...) onde todos esses traços são traços. Tive que contornar isso refatorando-o em características diferentes, por exemplo, ApproxEqualPoint (Point, Point), para evitar implementações conflitantes.

Portanto, @withoutboats tem promovido a ideia de "grupos de exclusão", onde você pode declarar que um determinado conjunto de características são mutuamente exclusivas (ou seja, você pode implementar no máximo uma delas). Eu vejo isso como uma espécie de enum (ou seja, os traços são todos declarados juntos). Gosto dessa ideia, principalmente porque (eu acho!) Ajuda a evitar alguns dos aspectos mais perniciosos do raciocínio negativo. Mas sinto que é preciso pensar mais sobre isso - e também um bom artigo que tente resumir todos os "dados" que circulam sobre como pensar sobre o raciocínio negativo. Talvez agora que (quase sempre) terminei minha série de HKT e especialização, eu possa pensar sobre isso ...

@nikomatsakis :

Portanto, @withoutboats tem promovido a ideia de "grupos de exclusão", onde você pode declarar que um determinado conjunto de características são mutuamente exclusivas (ou seja, você pode implementar no máximo uma delas). Eu vejo isso como uma espécie de enum (ou seja, os traços são todos declarados juntos). Gosto dessa ideia, principalmente porque (eu acho!) Ajuda a evitar alguns dos aspectos mais perniciosos do raciocínio negativo. Mas sinto que é preciso pensar mais sobre isso - e também um bom artigo que tente resumir todos os "dados" que circulam sobre como pensar sobre o raciocínio negativo. Talvez agora que (quase sempre) terminei minha série de HKT e especialização, eu possa pensar sobre isso ...

Pensei em grupos de exclusão enquanto escrevia isso (você mencionou nos fóruns outro dia), mas não acho que eles possam funcionar, pois neste exemplo em particular nem todas as implementações de traits são exclusivas. O exemplo mais trivial são os traços Point e Float : um Float _pode_ ser um ponto 1D, então ApproxEqualPoint(Point, Point) e ApproxEqualFloat(Float, Float) não podem ser exclusivo. Existem outros exemplos como Square e Polygon ou Box | Cube e AABB (caixa delimitadora alinhada ao eixo) onde a "hierarquia de características" realmente precisa de restrições mais complexas.

Não, isso não é algo que a regra da rede permitiria. Isso seria mais o domínio do "raciocínio negativo" de alguma forma ou tipo.

Eu seria pelo menos capaz de implementar o caso particular e colocar unimplemented!() nele. Isso seria suficiente, mas obviamente eu gostaria mais se o compilador pegasse estaticamente aqueles casos em que eu chamo uma função com unimplemented!() nela (e neste ponto, estamos novamente no terreno do raciocínio negativo) .

A especialização em rede chorar :

A ideia de "grupos de exclusão" é, na verdade, apenas limites superestritos negativos. Uma coisa que não exploramos muito profundamente é a noção de especialização de polaridade reversa - permitindo que você escreva um implemento especializado que é de polaridade reversa para seu implemento menos especializado. Por exemplo, neste caso, você apenas escreveria:

impl<T> !Foo for T where T: A + C { }

Não tenho certeza de quais são as implicações de permitir isso. Acho que se conecta aos problemas já destacados de Niko sobre como a especialização está misturando a reutilização de código com o polimorfismo agora.

Com toda essa discussão de raciocínio negativo e impls negativos, me sinto compelido a trazer à tona a velha ideia de Haskell de "cadeias de instância" novamente ( papel , papel , rastreador de problemas GHC , Rust pré-RFC ), como uma fonte potencial de inspiração, senão outro.

Essencialmente, a ideia é que em qualquer lugar que você possa escrever um trait impl, você também pode escrever qualquer número de "cláusulas else if" especificando um impl que deve ser aplicado no caso dos anteriores não fez, com uma "cláusula else" final opcional especificando um impl negativo (isto é, se nenhuma das cláusulas para Trait aplicar, então !Trait se aplica).

@withoutboats

A ideia de "grupos de exclusão" é, na verdade, apenas limites superestritos negativos.

Acho que isso seria o suficiente para meus casos de uso.

Acho que se conecta aos problemas já destacados de Niko sobre como a especialização está misturando a reutilização de código com o polimorfismo agora.

Não sei se isso pode ser desemaranhado. Eu quero ter:

  • polimorfismo: um único traço que abstrai diferentes implementações de uma operação para muitos tipos diferentes,
  • reutilização de código: em vez de implementar a operação para cada tipo, quero implementá-los para grupos de tipos que implementam algumas características,
  • desempenho: ser capaz de substituir uma implementação já existente para um tipo específico ou um subconjunto de tipos que tem um conjunto mais específico de restrições que as implementações já existentes,
  • produtividade: ser capaz de escrever e testar meu programa incrementalmente, em vez de ter que adicionar muitos impl s para compilar.

Cobrir todos os casos é difícil, mas se o compilador me obrigar a cobrir todos os casos:

trait Foo {}
trait A {}
trait B {}

impl<T> Foo for T where T: A { ... }
impl<T> Foo for T where T: B { ... }
// impl<T> Foo for T where T: A + B { ... }  //< compiler: need to add this impl!

e também me dá impls negativos:

impl<T> !Foo for T where T: A + B { }
impl<T> !Foo for T where T: _ { } // _ => all cases not explicitly covered yet

Eu seria capaz de adicionar impls incrementalmente conforme preciso deles e também obter bons erros do compilador quando tento usar uma característica com um tipo para o qual não há impl.

Não tenho certeza de quais são as implicações de permitir isso.

Niko mencionou que existem problemas com o raciocínio negativo. FWIW, a única coisa para a qual o raciocínio negativo é usado no exemplo acima é afirmar que o usuário sabe que um impl para um caso específico é necessário, mas decidiu explicitamente não fornecer uma implementação para ele.

Acabei de digitar # 33017 e ainda não o vejo vinculado aqui. Ele está marcado como um orifício de segurança, então seria bom rastreá-lo aqui.

Para https://github.com/dtolnay/quote/issues/7 , preciso de algo semelhante a este exemplo da RFC, que ainda não funciona. cc @tomaka @Aatch @rphmeier que comentou sobre isso anteriormente.

trait Example {
    type Output;
    fn generate(self) -> Self::Output;
}

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

Eu tropecei na seguinte solução alternativa que dá uma maneira de expressar a mesma coisa.

#![feature(specialization)]

use std::fmt::{self, Debug};

///////////////////////////////////////////////////////////////////////////////

trait Example: Output {
    fn generate(self) -> Self::Output;
}

/// In its own trait for reasons, presumably.
trait Output {
    type Output: Debug + Valid<Self>;
}

fn main() {
    // true
    println!("{:?}", Example::generate(true));

    // box("s")
    println!("{:?}", Example::generate("s"));
}

///////////////////////////////////////////////////////////////////////////////

/// Instead of `Box<T>` just so the "{:?}" in main() clearly shows the type.
struct MyBox<T: ?Sized>(Box<T>);

impl<T: ?Sized> Debug for MyBox<T>
    where T: Debug
{
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "box({:?})", self.0)
    }
}

///////////////////////////////////////////////////////////////////////////////

/// Return type of the impl containing `default fn`.
type DefaultOutput<T> = MyBox<T>;

impl Output for bool {
    type Output = bool;
}

impl<T> Example for T where T: Pass {
    default fn generate(self) -> Self::Output {
        T::pass({
            // This is the impl you wish you could write
            MyBox(Box::new(self))
        })
    }
}

impl Example for bool {
    fn generate(self) -> Self::Output {
        self
    }
}

///////////////////////////////////////////////////////////////////////////////
// Magic? Soundness exploit? Who knows?

impl<T: ?Sized> Output for T where T: Debug {
    default type Output = DefaultOutput<T>;
}

trait Valid<T: ?Sized> {
    fn valid(DefaultOutput<T>) -> Self;
}

impl<T: ?Sized> Valid<T> for DefaultOutput<T> {
    fn valid(ret: DefaultOutput<T>) -> Self {
        ret
    }
}

impl<T> Valid<T> for T {
    fn valid(_: DefaultOutput<T>) -> Self {
        unreachable!()
    }
}

trait Pass: Debug {
    fn pass(DefaultOutput<Self>) -> <Self as Output>::Output;
}

impl<T: ?Sized> Pass for T where T: Debug, <T as Output>::Output: Valid<T> {
    fn pass(ret: DefaultOutput<T>) -> <T as Output>::Output {
        <T as Output>::Output::valid(ret)
    }
}

Ainda estou trabalhando em https://github.com/dtolnay/quote/issues/7 e precisava de um padrão de diamante. Aqui está minha solução. cc @zitsen que perguntou sobre isso antes e @aturon e @rphmeier que responderam.

#![feature(specialization)]

/// Can't have these impls directly:
///
///  - impl<T> Trait for T
///  - impl<T> Trait for T where T: Clone
///  - impl<T> Trait for T where T: Default
///  - impl<T> Trait for T where T: Clone + Default
trait Trait {
    fn print(&self);
}

fn main() {
    struct A;
    A.print(); // "neither"

    #[derive(Clone)]
    struct B;
    B.print(); // "clone"

    #[derive(Default)]
    struct C;
    C.print(); // "default"

    #[derive(Clone, Default)]
    struct D;
    D.print(); // "clone + default"
}

trait IfClone: Clone { fn if_clone(&self); }
trait IfNotClone { fn if_not_clone(&self); }

impl<T> Trait for T {
    default fn print(&self) {
        self.if_not_clone();
    }
}

impl<T> Trait for T where T: Clone {
    fn print(&self) {
        self.if_clone();
    }
}

impl<T> IfClone for T where T: Clone {
    default fn if_clone(&self) {
        self.clone();
        println!("clone");
    }
}

impl<T> IfClone for T where T: Clone + Default {
    fn if_clone(&self) {
        self.clone();
        Self::default();
        println!("clone + default");
    }
}

impl<T> IfNotClone for T {
    default fn if_not_clone(&self) {
        println!("neither");
    }
}

impl<T> IfNotClone for T where T: Default {
    fn if_not_clone(&self) {
        Self::default();
        println!("default");
    }
}

Acertou um bug (ou pelo menos um comportamento inesperado da minha perspectiva) com especialização e inferência de tipo: # 38167

Espera-se que esses dois impls sejam válidos com especialização, certo? Parece que não está conseguindo pegá-lo.

impl<T, ST, DB> ToSql<Nullable<ST>, DB> for T where
    T: ToSql<ST, DB>,
    DB: Backend + HasSqlType<ST>,
    ST: NotNull,
{
    ...
}

impl<T, ST, DB> ToSql<Nullable<ST>, DB> for Option<T> where
    T: ToSql<ST, DB>,
    DB: Backend + HasSqlType<ST>,
    ST: NotNull,
{
    ...
}

Eu preenchi https://github.com/rust-lang/rust/issues/38516 por algum comportamento inesperado que encontrei enquanto trabalhava na construção de especialização em Serde. Semelhante a https://github.com/rust-lang/rust/issues/38167 , este é um caso em que o programa compila sem o impl especializado e quando ele é adicionado há um erro de tipo. cc @bluss que estava preocupado com esta situação anteriormente.

E se permitíssemos a especialização sem a palavra-chave default em uma única caixa, semelhante a como permitimos o raciocínio negativo em uma única caixa?

Minha principal justificativa é esta: "o padrão de iteradores e vetores". Às vezes, os usuários desejam implementar algo para todos os iteradores e vetores:

impl<I> Foo for I where I: Iterator<Item = u32> { ... }
impl Foo for Vec<u32> { ... }

(Isso é relevante para outras situações além de iteradores e vetores, é claro, este é apenas um exemplo.)

Hoje isso não compila e há tsuris e ranger de dentes. A especialização resolve este problema:

default impl<I> Foo for I where I: Iterator<Item = u32> { ... }
impl Foo for Vec<u32> { ... }

Mas, ao resolver esse problema, você adicionou um contrato público à sua caixa: é possível substituir o implemento iterativo de Foo . Talvez não queiramos forçá-lo a fazer isso - portanto, especialização local sem default .


A pergunta que suponho é: qual é exatamente o papel de default . Exigir default era, eu acho, originalmente um gesto em direção à explicitação e código autodocumentado. Assim como o código Rust é imutável por padrão, privado por padrão, seguro por padrão, ele também deve ser final por padrão. No entanto, como "não finalização" é uma propriedade global, não posso especializar um item, a menos que permita que você especialize um item.

Exigir default era, eu acho, originalmente um gesto em direção à explicitação e código autodocumentado. No entanto [..] não posso especializar um item, a menos que eu deixe você especializar um item.

Isso é realmente tão ruim? Se você quer se especializar em implemento, talvez outras pessoas também queiram.

Eu me preocupo porque só de pensar neste RFC já estou me dando flashbacks de PTSD de trabalhar em bases de código C ++ que usam quantidades obscenas de sobrecarga e herança e não tenho ideia de que wtf está acontecendo em qualquer linha de código que tenha uma chamada de método nela. Eu realmente aprecio o esforço que @aturon fez para tornar a especialização explícita e

Isso é realmente tão ruim? Se você quer se especializar em implemento, talvez outras pessoas também queiram.

Se outras pessoas apenas "talvez" queiram se especializar também, e se houver bons casos em que não gostaríamos, não deveríamos tornar impossível especificar isso. (um pouco semelhante ao encapsulamento: você deseja acessar alguns dados e talvez outras pessoas também queiram - então você marca explicitamente _estes dados_ como públicos, em vez de padronizar todos os dados como públicos.)

Eu me preocupo porque só de pensar neste RFC já estou me dando flashbacks de PTSD ...

Mas como não permitir essa especificação evitaria que essas coisas acontecessem?

se houver bons casos em que não queremos, não devemos tornar impossível especificar isso.

Não é necessariamente uma boa ideia dar aos usuários um poder sempre que eles tiverem um bom caso de uso para ele. Não, se também permitir que os usuários escrevam códigos confusos.

Mas como não permitir essa especificação evitaria que essas coisas acontecessem?

Digamos que você veja foo.bar() e queira ver o que bar() faz. Agora, se você encontrar o método implementado em um tipo de correspondência e não estiver marcado default você sabe que é a definição de método que está procurando. Com a proposta de @withoutboats , isso não será mais verdade - em vez disso, você nunca saberá com certeza se está realmente olhando para o código que está sendo executado.

em vez disso, você nunca saberá com certeza se está realmente olhando para o código que está sendo executado.

Isso é um grande exagero do efeito de permitir a especialização de impls não padrão para tipos locais. Se você está olhando um implemento de concreto, sabe que está olhando para o implante correto. E você tem acesso a toda a fonte desta caixa; você pode determinar se este impl é especializado ou não significativamente antes de "nunca".

Enquanto isso, mesmo com default , o problema permanece quando um impl não foi finalizado. Se o impl correto é na verdade um default impl, você está na mesma situação em que não tem certeza se este é o impl correto. E, claro, se a especialização for empregada, esse será normalmente o caso (por exemplo, este é o caso hoje para quase todos os impl de ToString ).

Na verdade, acho que esse é um problema bastante sério, mas não estou convencido de que default resolva. O que precisamos são melhores ferramentas de navegação de código. Atualmente rustdoc faz uma abordagem de 'melhor esforço' quando se trata de impls de característica - ele não liga para sua fonte e nem mesmo lista impls que são fornecidos por impls de cobertor.

Não estou dizendo que essa mudança seja um desastre de forma alguma, mas acho que vale a pena uma consideração mais sutil.

Não é necessariamente uma boa ideia dar aos usuários um poder sempre que eles tiverem um bom caso de uso para ele. Não, se também permitir que os usuários escrevam códigos confusos.

Exatamente, eu concordo totalmente. Acho que estou falando de um "usuário" diferente aqui, que é o usuário das caixas que você escreve. Você não quer que eles se especializem livremente nas características de sua caixa (possivelmente afetando o comportamento de sua caixa de uma forma hackeada). Por outro lado, estaríamos dando mais poder ao "usuário" de que você está falando, ou seja, o autor da caixa, mas mesmo sem a proposta de @withoutboats ', você teria que usar "default" e ter o mesmo problema .

Eu acho que default ajuda no sentido de que se você quiser simplificar a leitura de um código, você pode pedir que ninguém use default ou estabeleça regras de documentação rigorosas para usá-lo. Nesse ponto, você só precisa se preocupar com os default s de std , que provavelmente as pessoas entenderiam melhor.

Lembro-me da ideia de que as regras de documentação poderiam ser impostas aos usos da especialização contribuíram para obter a aprovação da RFC de especialização.

@semoutboats estou correto ao ler sua motivação para afrouxar default porque você quer uma forma restrita de default que significa "substituível, mas apenas nesta caixa" (ou seja, pub(crate) mas por default )? No entanto, para manter as coisas simples, você está propondo alterar a semântica de omitir default , em vez de adicionar graduações de default -ness?

Corrigir. Fazer algo como default(crate) parece um exagero.

A priori, imagino que se pudesse simular isso através do que a caixa exporta, não? Existem situações em que você não poderia simplesmente introduzir um traço de ajudante privado com os métodos default e chamá-lo de seus próprios impl s finais? Você deseja que o usuário use seus default s, mas não forneça nenhum deles?

Corrigir. Fazer algo como inadimplência (crate) parece um exagero.

Discordo. Eu realmente quero uma forma restrita de inadimplência. Eu tenho pretendido propor isso. Minha motivação é que às vezes impls de interseção etc irão forçá-lo a adicionar o padrão, mas isso não significa que você deseja permitir que caixas arbitrárias mudem seu comportamento. Desculpe, tenho uma reunião, posso tentar elaborar com um exemplo um pouco.

@nikomatsakis Tenho a mesma motivação, o que estou propondo é que apenas removamos o requisito padrão de nos especializarmos na mesma caixa, em vez de adicionar mais alavancas. :-)

Se por acaso esse padrão não exportado for o uso mais comum, um recurso #[default_export] seria mais fácil de lembrar por analogia com #[macro_export] . Uma opção intermediária pode permitir esse recurso de exportação para pub use ou pub mod linhas.

Usar a palavra-chave pub seria melhor, já que Macros 2.0 suportará macros como itens normais e usará pub vez de #[macro_use] . Usar pub para indicar visibilidade em todo o quadro seria uma grande vitória por sua consistência.

@sem barcos de qualquer maneira, acho que às vezes você vai querer se especializar localmente, mas não necessariamente abrir as portas para todos

Usar a palavra-chave pub seria melhor

Ter pub default fn significa "exportar publicamente a inadimplência do fn" em vez de afetar a visibilidade da própria função seria muito confuso para os novatos.

@jimmycuadra é isso que você quis dizer com a palavra-chave pub ? Concordo com @sgrif que parece mais confuso e, se permitirmos que você defina o escopo da inadimplência explicitamente, a mesma sintaxe que decidimos para a visibilidade do escopo parece ser o caminho correto.

Provavelmente não pub default fn exatamente, porque isso é ambíguo, como vocês dois mencionaram. Eu estava apenas dizendo que vale a pena ter pub universalmente significando "expor algo de outra forma privado para o exterior." Provavelmente existe alguma formulação de sintaxe envolvendo pub que seria visualmente diferente para não ser confundida com tornar a própria função pública.

Embora seja um pouco de sintaxe, eu não me oporia a default(foo) funcionando como pub(foo) - a simetria entre os dois supera marginalmente a complexidade da sintaxe para mim.

Aviso de bicicleta: consideramos chamá-lo de overridable vez de default ? É mais literalmente descritivo e overridable(foo) me lê melhor do que default(foo) - o último sugere "este é o padrão dentro do escopo de foo , mas outra coisa pode ser o padrão em outro lugar ", enquanto o anterior diz" isso é substituível no escopo de foo ", o que está correto.

Acho que as duas primeiras perguntas são realmente: Exportar ou não exportar default ness é significativamente mais comum? Exportar default ness não deveria ser o comportamento padrão?

Sim, caso: Você poderia maximizar a semelhança com as exportações em outros lugares, algo como pub mod mymodule default; e pub use mymodule::MyTrait default; , ou talvez com overridable . Se necessário, você pode exportar default ness apenas para alguns métodos com pub use MyModule::MyTrait::{methoda,methodb} default;

Nenhum caso: você precisa expressar privacidade, não publicidade, o que difere consideravelmente de qualquer outra coisa em Rust, de qualquer forma, agora default(crate) se torna a maneira normal de controlar essas exportações.

Além disso, se exportar e não exportar default ness são comparativamente comuns, então vocês provavelmente podem escolher arbitrariamente estar no caso sim ou não, então, novamente, apenas escolher pub use MyModule::MyTrait::{methoda,methodb} default; funciona bem.

Todas essas notações parecem compatíveis de qualquer maneira. Outra opção pode ser algum impl que fechou os default s, mas que parece complexo e estranho.

@burdges Você tem os rótulos "sim case" e "não case" ao contrário, ou estou entendendo mal o que você está dizendo?

Sim, oops! Fixo!

Temos impl<T> Borrow<T> for T where T: ?Sized para que um limite de Borrow<T> possa tratar os valores possuídos como se fossem emprestados.

Suponho que poderíamos usar a especialização para otimizar chamadas externas para clone de um Borrow<T> , sim?

pub trait CloneOrTake<T> {
    fn clone_or_take(self) -> T;
}

impl<B,T> CloneOrTake<T> for B where B: Borrow<T>, T: Clone {
    #[inline]
    default fn clone_or_take(b: B) -> T { b.clone() }
}
impl<T> CloneOrTake<T> for T {
    #[inline]
    fn clone_or_take(b: T) -> T { b };
}

Eu acho que isso pode tornar Borrow<T> utilizável em mais situações. Eu deixei cair T: ?Sized porque alguém presumivelmente precisa de Sized ao retornar T .

Outra abordagem pode ser

pub trait ToOwnedFinal : ToOwned {
    fn to_owned_final(self) -> Self::Owned;
}

impl<B> ToOwnedFinal for B where B: ToOwned {
    #[inline]
    default fn to_owned_final(b: B) -> Self::Owned { b.to_owned() }
}
impl<T> ToOwnedFinal for T {
    #[inline]
    fn to_owned_final(b: T) -> T { b };
}

Fizemos algumas descobertas possivelmente perturbadoras hoje, você pode ler os logs do IRC aqui: https://botbot.me/mozilla/rust-lang/

Não estou 100% confiante sobre todas as conclusões a que chegamos, especialmente porque os comentários de Niko depois do fato parecem edificantes. Por um tempo, pareceu um pouco apocalíptico para mim.

Uma coisa que tenho bastante certeza é que exigir o default não pode ser compatível com a garantia de que adicionar novos default impls é sempre compatível com versões anteriores. Aqui está a demonstração:

caixa parent v 1.0.0

trait A { }
trait B { }
trait C {
    fn foo(&self);
}

impl<T> C for T where T: B {
    // No default, not specializable!
    fn foo(&self) { panic!() }
}

caixa client (depende de parent )

extern crate parent;

struct Local;

impl parent::A for Local { }
impl parent::C for Local {
    fn foo(&self) { }
}

Implementos locais A e C mas não B . Se local implementado B , seu impl de C entraria em conflito com o implemento de cobertor não especializável de C for T where T: B .

caixa parent v 1.1.0

// Same code as before, but add:
default impl<T> B for T where T: A { }

Este impl foi adicionado e é um impl completamente especializável, então dissemos que é uma alteração ininterrupta. No entanto , ele cria uma implicação transitiva - já tínhamos "todos B impl C (não especializáveis)", ao adicionar "todos A impl B (especializáveis)," adicionamos implicitamente a instrução "todos A impl C (não especializáveis) " Agora a caixa infantil não pode ser atualizada.


Pode ser que a ideia de garantir que adicionar impls especializáveis ​​não seja uma mudança significativa esteja totalmente fora da janela, porque Aaron mostrou (como você pode ver nos logs do link acima) que você pode escrever impls que oferecem garantias equivalentes em relação à inadimplência . No entanto, os comentários posteriores de Niko sugerem que tais impls podem ser proibidos (ou pelo menos proibidos) pelas regras órfãs.

Portanto, é incerto para mim se a garantia 'impls não quebra' pode ser recuperada, mas é certo que não é compatível com o controle explícito sobre a finalidade do implemento.

Existe algum plano para permitir isso?

struct Foo;

trait Bar {
    fn bar<T: Read>(stream: &T);
}

impl Bar for Foo {
    fn bar<T: Read>(stream: &T) {
        let stream = BufReader::new(stream);

        // Work with stream
    }

    fn bar<T: BufRead>(stream: &T) {
        // Work with stream
    }
}

Então, essencialmente, uma especialização para uma função de modelo que tem um parâmetro de tipo com um limite em A onde a versão especializada tem um limite em B (que requer A ).

@torkleyy não atualmente, mas você pode fazer isso secretamente criando um traço que é implementado para T: Read e T: BufRead e contendo as partes do seu código que você deseja se especializar nos impls desse traço. Ele nem precisa estar visível na API pública.

Com relação ao problema de compatibilidade com versões anteriores, acho que graças às regras órfãs, podemos nos safar com estas regras:

_Um impl é compatível com versões anteriores para adicionar, a menos que : _

  • _O traço que está sendo implantado é um traço automático._
  • _O receptor é um parâmetro de tipo e todas as características no impl existiam anteriormente._

Ou seja, acho que em todos os exemplos problemáticos o impl adicionado é um implante de cobertura. Queríamos dizer que os implantes de cobertor totalmente padrão também estão bem, mas acho que só temos que dizer que adicionar os implantes de cobertor existentes pode ser uma mudança radical.

A questão é que garantia queremos oferecer diante disso - por exemplo, acho que seria uma propriedade muito boa se pelo menos um implante de cobertor só pudesse ser uma alteração significativa com base no código em sua caixa, para que você possa revisar sua caixa e saiba com certeza se você precisa ou não incrementar a versão principal.

@withoutboats

Com relação ao problema de compatibilidade com versões anteriores, acho que graças às regras órfãs, podemos nos safar com estas regras:

_Um impl é compatível com versões anteriores para adicionar, a menos que : _

  • _O traço que está sendo implantado é um traço automático._
  • _O receptor é um parâmetro de tipo e todas as características no impl existiam anteriormente._

Ou seja, acho que em todos os exemplos problemáticos o impl adicionado é um implante de cobertura. Queríamos dizer que os implantes de cobertor totalmente padrão também estão bem, mas acho que só temos que dizer que adicionar os implantes de cobertor existentes pode ser uma mudança radical.

Uma semana e muitas discussões depois, infelizmente não foi esse o caso .

Os resultados que obtivemos são: cry_cat_face :, mas acho que o que escrevi lá é igual à sua conclusão. Adicionar cobertores implantes é uma mudança radical, não importa o quê. Mas apenas impls de cobertor (e impls de autotratamento); até onde eu sei, não encontramos um caso em que um impl não-cobertor pudesse quebrar o código downstream (e isso seria muito ruim).

Eu pensei em um ponto que poderíamos relaxar as regras órfãs para que você pudesse implementar características para tipos como Vec<MyType> , mas se fizéssemos isso, a situação seria exatamente da mesma maneira aqui:

//crate A

trait Foo { }

// new impl
// impl<T> Foo for Vec<T> { }
// crate B
extern crate A;

use A::Foo;

trait Bar {
    type Assoc;
}

// Sadly, this impl is not an orphan
impl<T> Bar for Vec<T> where Vec<T>: Foo {
    type Assoc = ();
}
// crate C

struct Baz;

// Therefore, this impl must remain an orphan
impl Bar for Vec<Baz> {
    type Assoc = bool;
}

@semoutboats Ah, entendi sua lista de dois ou em vez de e , o que parece ser o que você quis dizer?

@aturon Sim, eu quis dizer 'ou' - esses são os dois casos em que é uma alteração significativa. Qualquer implicação de traço automático, não importa o quão concreto, é uma mudança significativa devido à maneira como permitimos que o raciocínio negativo sobre eles se propague: https://is.gd/k4Xtlp

Ou seja, a menos que contenha novos nomes. AFAIK um implante que contém um novo nome nunca está quebrando.

@semoutboats Eu me pergunto se podemos / devemos restringir as pessoas que confiam na lógica negativa em torno das auto-características. Ou seja, se dissermos que adicionar novos impls de traços de automóveis é uma alteração legal de quebra, poderíamos então alertar sobre impls que poderiam ser quebrados por um engradado upstream adicionando Send . Isso funcionaria melhor se tivéssemos:

  1. especialização estável, pode-se superar os avisos adicionando default em locais estratégicos (na maior parte do tempo);
  2. alguma forma de impls negativos explícitos, de modo que tipos como Rc possam declarar sua intenção de nunca ser Send - mas temos esses para traços de automóveis, então podemos levá-los em consideração.

Não sei, acho que depende de haver ou não uma motivação forte. Parece especialmente improvável que você perceba que um tipo pode ter um unsafe impl Send/Sync depois que você já o lançou; Acho que na maioria das vezes isso seria seguro, você teria escrito um tipo com a presciência de que seria seguro (porque esse é o ponto do tipo).

Eu adiciono unsafe impl Send/Sync após o fato o tempo todo. Às vezes porque eu o torno thread-safe, às vezes porque percebo que a API C com a qual estou fazendo interface pode ser compartilhada entre threads e às vezes é apenas porque algo deveria ser Send / Sync isn não é no que estou pensando quando apresento um tipo.

Eu também os adiciono após o fato ao vincular APIs C - muitas vezes porque alguém pede explicitamente esses limites, então eu examino e verifico o que a biblioteca subjacente garante.

Uma coisa que não adoro em como a especialização de características associadas funciona agora é que esse padrão não funciona:

trait Buffer: Read {
    type Buffered: BufRead;
    fn buffer(self) -> impl BufRead;
}

impl<T: Read> Buffer for T {
    default type Buffered = BufReader<T>;
    default fn buffer(self) -> BufReader<T> {
        BufReader::new(self)
    }
}

impl<T: BufRead> Buffer for T {
    type Buffered = Self;
    fn buffer(self) -> T {
        self
    }
}

Isso ocorre porque o sistema atual exige que este impl seja válido:

impl Buffer for SomeRead {
    type Buffered = SomeBufRead;
    // no overriding of fn buffer, it no longer returns Self::Buffered
}

impl Trait em traits liberaria muito desejo por esse tipo de padrão, mas eu me pergunto se não há uma solução melhor onde o impl genérico é válido, mas essa especialização não funciona porque introduz um erro de tipo ?

@withoutboats Sim, esta é uma das principais questões não resolvidas sobre o design (que eu esqueci de trazer à tona em discussões recentes). Há uma boa discussão sobre isso no tópico RFC original, mas tentarei escrever um resumo das opções / compensações em breve.

@aturon A solução atual é a mais conservadora (compatível com o que queremos fazer) ou é uma decisão que devemos tomar antes de estabilizar?

Eu pessoalmente acho que a única solução real para este problema que @withoutboats levantou é permitir que os itens sejam "agrupados" quando você especifica a tag default . É uma espécie de solução melhor-é-melhor, mas eu sinto que a variante pior-é-melhor (substituindo qualquer meio substituindo todos) é um pouco pior. (Mas, na verdade, @withoutboats a maneira como você escreveu este código é confusa. Acho que em vez de usar impl BufRead como o tipo de retorno de Buffer , você quis dizer Self::BufReader , certo?)

Nesse caso, o seguinte seria permitido:

trait Buffer: Read {
    type Buffered: BufRead;
    fn buffer(self) -> impl BufRead;
}

impl<T: Read> Buffer for T {
    default {
        type Buffered = BufReader<T>;
        fn buffer(self) -> BufReader<T> {
            BufReader::new(self)
        }
    }
}

impl<T: BufRead> Buffer for T {
    type Buffered = Self;
    fn buffer(self) -> T {
        self
    }
}

Mas talvez possamos inferir esses agrupamentos? Não pensei muito nisso, mas parece que o fato de que os padrões dos itens estão "emaranhados" é visível na definição do traço.

Mas na verdade @withoutboats a maneira como você escreveu este código é confusa. Acho que em vez de usar o BufRead impl como o tipo de retorno do Buffer, você quis dizer Self :: BufReader, certo?

Sim, eu modifiquei a solução para uma baseada em uma característica impl e depois mudei de volta, mas perdi o tipo de retorno na característica.

Talvez algo como o sistema de tipos desta linguagem também seja interessante, já que parece ser semelhante ao Rusts, mas com algumas características, que podem resolver os problemas atuais.
( A <: B em Rust seria verdadeiro quando A é uma estrutura e implementa o traço B , ou quando A é um traço, e implementações genéricas para objetos de esse traço existe, eu acho)

Parece que há um problema com o traço Display para especialização.
Por exemplo, este exemplo não compila:

use std::fmt::Display;

pub trait Print {
    fn print(&self);
}

impl<T: Display> Print for T {
    default fn print(&self) {
        println!("Value: {}", self);
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

com o seguinte erro:

error[E0119]: conflicting implementations of trait `Print` for type `()`:
  --> src/main.rs:41:1
   |
35 |   impl<T: Display> Print for T {
   |  _- starting here...
36 | |     default fn print(&self) {
37 | |         println!("Value: {}", self);
38 | |     }
39 | | }
   | |_- ...ending here: first implementation here
40 | 
41 |   impl Print for () {
   |  _^ starting here...
42 | |     fn print(&self) {
43 | |         println!("No value");
44 | |     }
45 | | }
   | |_^ ...ending here: conflicting implementation for `()`

enquanto este compila:

pub trait Print {
    fn print(&self);
}

impl<T: Default> Print for T {
    default fn print(&self) {
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

Obrigado por corrigir esse problema.

@antoyo tem certeza que isso é porque Display é especial, ou poderia ser porque Display não foi implementado para tuplas enquanto Default é?

@shepmaster
Não sei se é sobre Display , mas o seguinte funciona com um traço Custom não implementado para tuplas:

pub trait Custom { }

impl<'a> Custom for &'a str { }

pub trait Print {
    fn print(&self);
}

impl<T: Custom> Print for T {
    default fn print(&self) {
    }
}

impl Print for () {
    fn print(&self) {
        println!("No value");
    }
}

fn main() {
    "Hello, world!".print();
    ().print();
}

A propósito, aqui está o que quero alcançar com a especialização:

pub trait Emit<C, R> {
    fn emit(callback: C, value: Self) -> R;
}

impl<C: Fn(Self) -> R, R, T> Emit<C, R> for T {
    default fn emit(callback: C, value: Self) -> R {
        callback(value)
    }
}

impl<C> Emit<C, C> for () {
    fn emit(callback: C, _value: Self) -> C {
        callback
    }
}

Quero chamar uma função por padrão ou retornar um valor se o parâmetro for unit.
Recebo o mesmo erro sobre implementações conflitantes.
É possível (ou será possível) fazer isso com especialização?
Se não, quais são as alternativas?

Edit: Acho que descobri por que ele não compila:
T em for T é mais geral do que () em for () então o primeiro impl não pode ser a especialização.
E C é mais geral do que C: Fn(Self) -> R então o segundo impl não pode ser a especialização.
Por favor me diga se estou errado.
Mas ainda não entendo por que não funciona com o primeiro exemplo com Display .

Atualmente, este é o comportamento correto.

No exemplo Custom , esses impls não se sobrepõem devido ao raciocínio negativo local especial. Como a característica é desta caixa, podemos inferir que () , que não tem um impl de Custom , não se sobrepõe a T: Custom . Nenhuma especialização necessária.

No entanto, não realizamos esse raciocínio negativo para características que não são de sua caixa. A biblioteca padrão poderia adicionar Display for () na próxima versão, e não queremos que seja uma alteração importante. Queremos que as bibliotecas tenham a liberdade de fazer esse tipo de mudança. Portanto, embora () não implemente Display, não podemos usar essa informação na verificação de sobreposição.

Mas também, como () não implica Display, não é mais específico do que T: Display . É por isso que a especialização não funciona, enquanto no caso padrão, (): Default , portanto, esse impl é mais específico do que T: Default .

Impls como este estão no 'limbo', onde não podemos presumir que se sobrepõe ou não. Estamos tentando descobrir uma maneira baseada em princípios de fazer isso funcionar, mas não é a primeira implementação da especialização, é uma extensão compatível com versões anteriores para esse recurso que virá mais tarde.

Eu preenchi o número 40582 para rastrear o problema de solidez relacionado à vida inteira.

Tive um problema ao tentar usar a especialização, não acho que seja exatamente o mesmo que @antoyo tinha, arquivei como um problema separado # 41140, posso trazer o código de exemplo desse aqui se necessário

@ afonso360 Não, um problema separado está bom.

Como um ponto geral: neste ponto, o trabalho adicional em especialização está bloqueado no trabalho em Chalk , o que deve nos permitir lidar com questões de solidez e também é provável que esclarecer os ICEs que estão sendo atingidos hoje.

Alguém pode esclarecer se isso é um bug, ou algo que é proibido propositalmente? https://is.gd/pBvefi

@sgrif Acredito que o problema aqui é apenas que a projeção de tipos padrão associados não é permitida. O diagnóstico poderia ser melhor, porém: https://github.com/rust-lang/rust/issues/33481

Você poderia explicar por que se espera que ela não seja permitida? Sabemos que nenhum impl mais específico poderia ser adicionado, uma vez que violaria as regras órfãs.

Este comentário indica que é necessário em alguns casos para exigir solidez (embora eu não saiba por quê) e em outros para forçar os consumidores da interface a tratá-la como um tipo abstrato: https://github.com/rust- lang / rust / blob / e5e664f / src / librustc / traits / project.rs # L41

Alguém já conseguiu acessar https://github.com/rust-lang/rust/issues/31844#issuecomment -266221638? Esses impls devem ser válidos com especialização, tanto quanto posso dizer. Acredito que haja um bug que os está impedindo.

@sgrif Acredito que o problema com seu código pode ser semelhante ao problema em https://github.com/rust-lang/rust/issues/31844#issuecomment -284235369 que @withoutboats explicado em https://github.com / rust-lang / rust / issues / 31844 # issuecomment -284268302. Dito isso, com base no comentário de @withoutboats , parece que o raciocínio local atual deve permitir que seu exemplo seja compilado, mas talvez eu esteja enganado quanto ao que deve funcionar.

Como um aparte, tentei implementar o seguinte, sem sucesso:

trait Optional<T> {
    fn into_option(self) -> Option<T>;
}

impl<R, T: Into<R>> Optional<R> for T {
    default fn into_option(self) -> Option<R> {
        Some(self.into())
    }
}

impl<R> Optional<R> for Option<R> {
    fn into_option(self) -> Option<R> {
        self
    }
}

Eu esperava intuitivamente que Option<R> fosse mais específico do que <R, T: Into<R>> T , mas é claro, nada impede um impl<R> Into<R> for Option<R> no futuro.

Não sei por que isso não é permitido, no entanto. Mesmo se impl<R> Into<R> for Option<R> fosse adicionado no futuro, eu ainda esperaria que Rust escolhesse a implementação diferente de default , pelo que posso ver, permitir que este código não tenha implicação compatibilidade.

Ao todo, acho a especialização muito frustrante de se trabalhar. Quase tudo que espero que funcione, não funciona. Os únicos casos em que tive sucesso com a especialização são aqueles que são muito simples, como ter dois impl s que incluem T where T: A e T where T: A + B . Tenho dificuldade em fazer outras coisas funcionarem, e as mensagens de erro não indicam por que as tentativas de especialização não funcionam. Claro, ainda há um caminho pela frente, então não espero mensagens de erro muito úteis. Mas parece que há alguns casos em que eu realmente espero que algo funcione (como acima), mas simplesmente não funciona, e atualmente é muito difícil para mim determinar se é porque eu não entendi o que é permitido (e mais importante, por quê), se algo está errado ou se algo ainda não foi implementado. Uma boa visão geral do que está acontecendo com esse recurso na forma em que se encontra seria muito útil.

Não tenho certeza se isso está no lugar certo, mas encontramos um problema no fórum de usuários que gostaria de mencionar aqui.

O código a seguir (que é adaptado da RFC aqui ) não é compilado à noite:

#![feature(specialization)]

trait Example {
    type Output;
    fn generate(self) -> Self::Output;
}

default impl<T> Example for T {
    type Output = Box<T>;
    fn generate(self) -> Self::Output { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> Self::Output { self }
}

Isso não parece realmente uma falha, mas mais um problema de usabilidade - se um hipotético impl especializado apenas no tipo associado no exemplo acima, o defaulti impl de generate wouldn 't typecheck.

Link para o tópico aqui

@ burns47, há uma solução alternativa confusa, mas útil aqui: https://github.com/rust-lang/rust/issues/31844#issuecomment -263175793.

@dtolnay Não é totalmente satisfatório - e se nos especializarmos em características que não possuímos (e não podemos modificar)? Não deveríamos precisar reescrever / refatorar definições de características para fazer este IMO.

Alguém pode comentar se o código na edição a seguir foi rejeitado intencionalmente? https://github.com/rust-lang/rust/issues/45542

A especialização permitiria adicionar algo como o seguinte ao libcore?

impl<T: Ord> Eq for T {}

impl<T: Ord> PartialEq for T {
    default fn eq(&self, other: &Self) -> bool {
        self.cmp(other) == Ordering::Equal
    }
}

impl<T: Ord> PartialOrd for T {
    default fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

Desta forma, você poderia implementar Ord para seu tipo personalizado e ter Eq , PartialEq e PartialOrd implementados automaticamente.

Observe que implementar Ord e simultaneamente derivar PartialEq ou PartialOrd é perigoso e pode levar a erros muito sutis! Com esses impls padrão, você ficaria menos tentado a derivar essas características, portanto, o problema seria um pouco mitigado.


Como alternativa, modificamos a derivação para tirar proveito da especialização. Por exemplo, escrever #[derive(PartialOrd)] acima de struct Foo(String) poderia gerar o seguinte código:

impl PartialOrd for Foo {
    default fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl PartialOrd for Foo where Foo: Ord {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

Desta forma, o impl padrão é usado se Ord não for implementado. Mas se for, então PartialOrd depende de Ord . Infelizmente, isso não compila: error[E0119]: conflicting implementations of trait `std::cmp::PartialOrd` for type `Foo`

@stjepang Eu certamente espero que cobertores como esse possam ser adicionados - impl<T:Copy> Clone for T também.

eu acho que

impl<T: Ord> PartialEq for T

deveria estar

impl<T, U> PartialEq<U> for T where T : PartialOrd<U>

porque PartialOrd requer PartialEq e também pode fornecê-lo.

No momento, não se pode realmente usar tipos associados para restringir uma especialização, porque eles não podem ser deixados sem especificação e porque disparam recursão desnecessária . Veja https://github.com/dhardy/rand/issues/18#issuecomment -358147645

Eventualmente, adoraria ver o que estou chamando de grupos de especialização com a sintaxe proposta por @nikomatsakis aqui https://github.com/rust-lang/rust/issues/31844#issuecomment -249355377 e independentemente por mim. Gostaria de escrever um RFC sobre essa proposta mais tarde, quando estivermos mais próximos de estabilizar a especialização.

Caso ninguém tenha visto, esta postagem do blog cobre uma proposta para tornar a especialização válida em face do despacho vitalício.

Como o fechamento de cópia já foi estabilizado no Beta, os desenvolvedores têm mais motivação para estabilizar na especialização agora. A razão é que Fn e FnOnce + Clone representam dois conjuntos sobrepostos de fechamentos e, em muitos casos, precisamos implementar características para ambos.

Basta descobrir que o texto da rfc 2132 parece implicar que existem apenas 5 tipos de fechos:

  • FnOnce (a move closure com todas as variáveis ​​capturadas sendo nem Copy nem Clone )
  • FnOnce + Clone (um fechamento de move com todas as variáveis ​​capturadas sendo Clone )
  • FnOnce + Copy + Clone (um fechamento de move com todas as variáveis ​​capturadas sendo Copy e assim Clone )
  • FnMut + FnOnce (um fechamento não move com variáveis ​​capturadas mutadas)
  • Fn + FnMut + FnOnce + Copy + Clone (um fechamento não move sem variáveis ​​capturadas mutantes)

Portanto, se a especificação não estiver disponível em um futuro próximo, talvez devêssemos atualizar nossa definição de Fn traços para que Fn não se sobreponha a FnOnce + Clone ?

Eu entendo que alguém já implementou tipos específicos que são Fn sem Copy/Clone , mas isso deve ser descontinuado? Acho que sempre há uma maneira melhor de fazer a mesma coisa.

O seguinte deveria ser permitido pela especialização (observe a ausência de default ) ou é um bug?

#![feature(specialization)]
mod ab {
    pub trait A {
        fn foo_a(&self) { println!("a"); }
    }

    pub trait B {
        fn foo_b(&self) { println!("b"); }
    }

    impl<T: A> B for T {
        fn foo_b(&self) { println!("ab"); }
    }

    impl<T: B> A for T {
        fn foo_a(&self) { println!("ba"); }
    }
}

use ab::B;

struct Foo;

impl B for Foo {}

fn main() {
    Foo.foo_b();
}

sem especialização, falha em construir com:

error[E0119]: conflicting implementations of trait `ab::B` for type `Foo`:
  --> src/main.rs:24:1
   |
11 |     impl<T: A> B for T {
   |     ------------------ first implementation here
...
24 | impl B for Foo {}
   | ^^^^^^^^^^^^^^ conflicting implementation for `Foo`

@glandium o que diabos está acontecendo aí? Bom exemplo, aqui o link do playground: https://play.rust-lang.org/?gist=fc7cf5145222c432e2 relevantde1b0a425cd & mode=debug

é isso? não há impl vazio em meu exemplo.

@glandium

 impl B for Foo {}

@MoSal, mas esse impl "não está vazio", pois B adiciona um método com uma implementação padrão.

@gnzlbg Está vazio por definição. Nada entre os colchetes.


#![feature(specialization)]

use std::borrow::Borrow;

#[derive(Debug)]
struct Bla {
    bla: Vec<Option<i32>>
}

// Why is this a conflict ?
impl From<i32> for Bla {
    fn from(i: i32) -> Self {
        Bla { bla: vec![Some(i)] }
    }
}

impl<B: Borrow<[i32]>> From<B> for Bla {
    default fn from(b: B) -> Self {
        Bla { bla: b.borrow().iter().map(|&i| Some(i)).collect() }
    }
}

fn main() {
    let b : Bla = [1, 2, 3].into();
    println!("{:?}", b);
}

error[E0119]: conflicting implementations of trait `std::convert::From<i32>` for type `Bla`:
  --> src/main.rs:17:1
   |
11 | impl From<i32> for Bla {
   | ---------------------- first implementation here
...
17 | impl<B: Borrow<[i32]>> From<B> for Bla {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Bla`
   |
   = note: upstream crates may add new impl of trait `std::borrow::Borrow<[i32]>` for type `i32` in future versions

A especialização não evitaria possíveis conflitos futuros?

Meu Deus, este é um recurso de movimento lento! Nenhum progresso em mais de dois anos, ao que parece (certamente de acordo com a postagem original). A equipe lang abandonou isso?

@alexreg consulte http://aturon.github.io/2018/04/05/sound-specialization/ para o desenvolvimento mais recente.

@alexreg Acontece que a solidez é _hard_. Acredito que haja algum trabalho sobre a ideia dos "impls sempre aplicáveis" acontecendo atualmente, então há progresso. Consulte https://github.com/rust-lang/rust/pull/49624. Além disso, acredito que o grupo de trabalho do giz está trabalhando na implementação da ideia de "impls sempre aplicáveis", mas não sei até onde isso foi.

Depois de um pouco de discussão, parece que é possível implementar efetivamente impls de interseção já por meio de um hack usando specialization e overlapping_marker_traits .

https://play.rust-lang.org/?gist=cb7244f41c040db41fc447d491031263&version=nightly&mode=debug

Tentei escrever uma função especializada recursiva para implementar um equivalente a este código C ++:


Código C ++

#include <cassert>
#include <vector>

template<typename T>
size_t count(T elem)
{
    return 1;
}

template<typename T>
size_t count(std::vector<T> vec)
{
    size_t n = 0;
    for (auto elem : vec)
    {
        n += count(elem);
    }
    return n;
}

int main()
{
    auto v1 = std::vector{1, 2, 3};
    assert(count(v1) == 3);

    auto v2 = std::vector{ std::vector{1, 2, 3}, std::vector{4, 5, 6} };
    assert(count(v2) == 6);

    return 0;
}


Eu tentei isso:


Código de ferrugem

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

default impl<T> Count for T {
    fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
    ];
    assert_eq!(v.count(), 6);
}


Mas estou recebendo um:

overflow evaluating the requirement `{integer}: Count`

Não acho que isso deva acontecer porque impl<T> Count for T where T::Item: Count não deve estourar.

EDIT: desculpe, acabei de ver que isso já foi mencionado

@Boiethios Seu caso de uso está funcionando se você usar como padrão o fn e não o impl:

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

impl<T> Count for T {
    default fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![vec![1, 2, 3], vec![4, 5, 6]];
    assert_eq!(v.count(), 6);
}

O orifício de integridade ainda não foi consertado?

@alexreg Acho que não. Consulte http://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/

Meu palpite é que todos estão focados na edição agora ...

Ok, obrigado ... parece que esse problema está se arrastando para sempre, mas é justo. É difícil, eu sei. E a atenção está voltada para outro lugar agora, infelizmente.

Alguém pode explicar mais concretamente a razão por trás de não permitir projeções para tipos associados padrão em casos totalmente monomórficos? Tenho um caso de uso em que gostaria que essa funcionalidade (em particular, seria semanticamente incorreto para o traço ser invocado com tipos que não fossem totalmente monomórficos) e, se não houver problema de integridade, não entendo completamente por quê não é permitido.

@pythonesque Há alguma discussão em https://github.com/rust-lang/rust/pull/42411

Ah, eu entendo se acabar que a projeção interage mal com a especialização em geral. . E é verdade que o que eu quero tem um sabor de "raciocínio negativo" (embora traços fechados não fossem realmente suficientes).

Infelizmente, não tenho certeza se há realmente alguma maneira de fazer o que quero sem esse recurso: eu gostaria de ter um tipo associado que produza "True" quando dois tipos passados ​​que implementam uma característica específica são sintaticamente iguais, e "Falso" quando não são (com o caso "Falso" acionando uma pesquisa de traço mais cara que pode decidir se eles são "semanticamente" iguais). A única alternativa real parece (para mim) ser apenas sempre fazer a pesquisa cara; o que é bom em teoria, mas pode ser muito mais caro.

(Eu poderia contornar isso se a característica fosse fechada, apenas enumerando todos os pares possíveis de construtores na posição da cabeça e fazendo com que eles produzissem True ou False; mas foi planejado para ser aberto para extensão fora do repositório, para que possa possivelmente não funcione, especialmente porque as implementações em dois repositórios de usuário diferentes não necessariamente se conheceriam).

De qualquer forma, talvez isso seja apenas uma indicação de que o que eu quero fazer não se encaixa no sistema de características e devo mudar para algum outro mecanismo, como macros: P

E é verdade que o que eu quero tem um sabor de "raciocínio negativo" (embora traços fechados não fossem realmente suficientes).

Uma alternativa ao raciocínio negativo é exigir que um tipo implemente apenas uma característica de um conjunto fechado de características, de modo que as implementações com outras características no conjunto não possam se sobrepor (por exemplo, T implementa um de { Float | Int | Bool | Ptr } )

Mesmo se houvesse uma maneira de impor isso na Rust (o que não existe, AFAIK?), Não acho que isso resolveria meu problema. Gostaria que os usuários em caixas diferentes pudessem implementar um número arbitrário de novas constantes, que deveriam ser comparadas apenas a eles próprios e diferentes de todas as outras constantes definidas, incluindo aquelas desconhecidas no momento da definição da caixa. Não vejo como qualquer conjunto fechado de características (ou mesmo conjunto de famílias de características) pode realizar esse objetivo por si mesmo: este é um problema que fundamentalmente não pode ser resolvido sem olhar diretamente para os tipos. A razão pela qual seria viável com as projeções padrão é que você poderia padronizar tudo para "não comparar igual" e, em seguida, implementar a igualdade de sua nova constante em qualquer caixa em que você definiu a constante, o que não entraria em conflito com o órfão regras porque todos os tipos na implementação do traço estavam na mesma caixa. Se eu quisesse quase qualquer regra além da igualdade, até isso não funcionaria, mas a igualdade é boa o suficiente para mim :)

Nas noites de hoje, isso funciona:

trait Foo {}
trait Bar {}

impl<T: Bar> Foo for T {}
impl Foo for () {}

mas mesmo com a especialização, e usando noturno, isso não:

#![feature(specialization)]

trait Foo<F> {}
trait Bar<F> {}

default impl<F, T: Bar<F>> Foo<F> for T {}
impl<F> Foo<F> for () {}

Isso tem uma justificativa ou é um bug?

@rmanoka Não são apenas as regras órfãs normais? No primeiro caso, nenhuma caixa downstream poderia impl Bar for () então o compilador permite isso, mas no segundo exemplo, uma caixa downstream poderia impl Bar<CustomType> for () que entraria em conflito com seu impl padrão.

@Boscop Nesse cenário, o impl padrão deve, de qualquer maneira, ser substituído pelo não padrão abaixo. Por exemplo, se eu tivesse: impl Bar<bool> for () {} adicionado antes dos outros impls, então eu esperaria que funcionasse (conforme RFC / expectativa). Não é correto?

Indo mais fundo ao longo das linhas do contra-exemplo que você mencionou, eu percebo (ou acredito) que o exemplo satisfaz o teste "sempre aplicável" e pode estar sendo trabalhado.

Este problema provavelmente depende de # 45814.

Existem planos para oferecer suporte a limites de característica no padrão que não estão presentes na especialização?

A título de exemplo isso seria muito útil, de forma que você possa facilmente compor manuseio de diferentes tipos criando uma Struct genérica com Inner arbitrário para a funcionalidade que não deve ser compartilhada.

#![feature(specialization)]
trait Handler<M> {
    fn handle(&self, m:M);
}

struct Inner;
impl Handler<f64> for Inner {
    fn handle(&self, m : f64) {
        println!("inner got an f64={}", m);
    }
}

struct Struct<T>(T);
impl<T:Handler<M>, M:std::fmt::Debug> Handler<M> for Struct<T> {
    default fn handle(&self, m : M) {
        println!("got something else: {:?}", m);
        self.0.handle(m)
    }
}
impl<T> Handler<String> for Struct<T> {
    fn handle(&self, m : String) {
        println!("got a string={}", m);
    }
}
impl<T> Handler<u32> for Struct<T> {
    fn handle(&self, m : u32) {
        println!("got a u32={}", m);
    }
}

fn main() {
    let s = Struct(Inner);
    s.handle("hello".to_string());
    s.handle(5.0 as f64);
    s.handle(5 as u32);
}

Além disso, no exemplo acima, algo estranho que experimentei - depois de remover o trait vinculado ao implante Handler padrão (e também self.0.handle (m)) o código compila sem problemas. No entanto, quando você remove a implementação para u32, parece quebrar a outra dedução de característica:

#![feature(specialization)]
trait Handler<M> {
    fn handle(&self, m:M);
}

struct Struct<T>(T);
impl<T, M:std::fmt::Debug> Handler<M> for Struct<T> {
    default fn handle(&self, m : M) {
        println!("got something else: {:?}", m);
    }
}
impl<T> Handler<String> for Struct<T> {
    fn handle(&self, m : String) {
        println!("got a string={}", m);
    }
}
// impl<T> Handler<u32> for Struct<T> {
//     fn handle(&self, m : u32) {
//         println!("got a u32={}", m);
//     }
// }
fn main() {
    let s = Struct(());
    s.handle("hello".to_string());
    s.handle(5.0 as f64);
}

Mesmo que não haja nenhum código chamando o manipulador de u32, a ausência de especialização faz com que o código não seja compilado.

Edit: este parece ser o mesmo que o segundo problema ("No entanto, quando você remove a implementação para u32, parece quebrar a outra dedução de traço") que Gladdy mencionou um post atrás.

Com rustc 1.35.0-nightly (3de010678 11/04/2019), o código a seguir fornece um erro:

#![feature(specialization)]
trait MyTrait<T> {
    fn print(&self, parameter: T);
}

struct Message;

impl<T> MyTrait<T> for Message {
    default fn print(&self, parameter: T) {}
}

impl MyTrait<u8> for Message {
    fn print(&self, parameter: u8) {}
}

fn main() {
    let message = Message;
    message.print(1_u16);
}

erro:

error[E0308]: mismatched types
  --> src/main.rs:20:19
   |
18 |     message.print(1_u16);
   |                   ^^^^^ expected u8, found u16

No entanto, o código compila e funciona quando eu omito o bloco impl MyTrait<u8> :

#![feature(specialization)]
trait MyTrait<T> {
    fn print(&self, parameter: T);
}

struct Message;

impl<T> MyTrait<T> for Message {
    default fn print(&self, parameter: T) {}
}

/*
impl MyTrait<u8> for Message {
    fn print(&self, parameter: u8) {}
}
*/

fn main() {
    let message = Message;
    message.print(1_u16);
}

Isso ocorre por design, porque a implementação está incompleta ou é um bug?

Além disso, gostaria de saber se este caso de uso para especialização (implementação de características com parâmetros de tipo sobrepostos para um único tipo de concreto em oposição à implementação da mesma característica para tipos sobrepostos) será suportado. Lendo a seção "Definindo as regras de precedência" na RFC 1210, acho que seria compatível, mas a RFC não dá esses exemplos e não sei se ainda estamos seguindo estritamente esta RFC.

Relate uma esquisitice:

trait MyTrait {}
impl<E: std::error::Error> MyTrait for E {}

struct Foo {}
impl MyTrait for Foo {}  // OK

// But this one is conflicting with error message:
//
//   "... note: upstream crates may add new impl of trait `std::error::Error` for type
//    std::boxed::Box<(dyn std::error::Error + 'static)>` in future versions"
//
// impl MyTrait for Box<dyn std::error::Error> {}

Por que Box<dyn std::error::Error> peculiar (evite usar a palavra "especial") neste caso? Mesmo que implique std::error::Error no futuro, impl MyTrait for Box<dyn std::error::Error> ainda é uma especialização válida de impl<E: std::error::Error> MyTrait for E , não?

ainda é uma especialização válida

No seu caso, o impl<E: std::error::Error> MyTrait for E não pode ser especializado, pois não possui nenhum método default .

@ bjorn3 Parece que deve funcionar, mas não funciona mesmo se você adicionar métodos fictícios

na caixa bar

pub trait Bar {}
impl<B: Bar> Bar for Box<B> {}

Na caixa foo

#![feature(specialization)]

use bar::*;

trait Trait {
    fn func(&self) {}
}

impl<E: Bar> Trait for E {
    default fn func(&self) {}
}

struct Foo;
impl Trait for Foo {}  // OK

impl Trait for Box<dyn Bar> {} // Error error[E0119]: conflicting implementations of trait

Observe que se você alterar a caixa bar para

pub trait Bar {}
impl<B: ?Sized + Bar> Bar for Box<B> {}

Em seguida, crie foo compila.

@ bjorn3 Parece que não precisamos de um método default para especializá-lo ( playground ).

@KrishnaSannasi Não consigo reproduzir o erro de "implementações conflitantes" em seu exemplo ( playground ).

Atualização: Oh, entendo. A característica Bar deve ser de uma caixa upstream para que o exemplo funcione.

@updogliu seu exemplo não mostra especialização porque Foo não implementa Error .

Estou programando tarde demais esta noite ou isso não deveria causar um estouro de pilha?

#![feature(specialization)]
use std::fmt::Debug;

trait Print {
    fn print(self);
}

default impl<T> Print for [T; 1] where T: Debug {
    fn print(self) {
        println!("{:?}", self);
    }
}

impl<T> Print for [T; 1] where T: Debug + Clone {
    fn print(self) {
        println!("{:?}", self.clone());
    }
}

fn main() {
    let x = [0u8];
    x.print();
}

Link do parque

Blocos default impl de granulação grosseira sempre fizeram coisas muito estranhas para mim, eu sugeriria tentar a sintaxe de especialização de granulação fina default fn em vez disso.

EDIT: Após a verificação cruzada do RFC, isso é esperado, pois default impl realmente _não_ significa que todos os itens no bloco impl são default ed. Acho essa semântica surpreendente, para dizer o mínimo.

Link do parque

@ HadrienG2 Na verdade, sempre usei default fn neste projeto, mas desta vez esqueci a palavra-chave default e o compilador sugeriu adicioná-la a impl . Não tinha visto o problema de recursão da pilha antes e não tinha certeza se era esperado neste estágio. Obrigado pela sugestão, default fn funciona bem.

Olhando para o RFC original, há uma seção sobre especialização de impls inerentes. Alguém deu que eu tentei?

A abordagem proposta na RFC pode não funcionar mais diretamente, pelo menos, para métodos const inerentes:

// This compiles correctly today:
#![feature(specialization)] 
use std::marker::PhantomData;
struct Foo<T>(PhantomData<T>);
impl<T> Foo<T> {
    default const fn foo() -> Self { Self(PhantomData) }
    // ^^should't default here error?
}
// ----
// Adding this fails:
impl<T: Copy> Foo<T> {
    const fn foo() -> Self { Self(PhantomData) }
}

A RFC original propõe transformar o método em um traço, implementando-o para o tipo e especializando-se o impl. Suponho que, para os métodos const fn, os impls da característica para o tipo precisariam ser const impls.

Para qualquer um que tenha conhecimento disso e esteja curioso sobre o status - houve alguns avanços conceituais significativos em 2018:
http://smallcultfollowing.com/babysteps/blog/2018/02/09/maximally-minimal-specialization-always-applicable-impls/
http://aturon.github.io/tech/2018/04/05/sound-specialization/

Mais recentemente, no mês passado @nikomatsakis escreveu (como exemplo, em outro contexto; meu negrito) que:

havia um problema chave [na especialização] que nunca foi resolvido de forma satisfatória, uma preocupação de solidez técnica em torno de vidas e características [...] Então, [aqueles dois posts vinculados acima]. Parece que essas ideias basicamente resolveram o problema , mas estivemos ocupados nesse meio tempo e não tivemos tempo para fazer o acompanhamento.

Parece esperançoso, embora ainda haja trabalho a ser feito.

(Postando isso porque encontrei este tópico algumas semanas atrás e não tinha ideia do progresso do ano passado, então, mais recentemente, deparei com essas postagens por acidente. Há comentários acima mencionando-os, mas o GitHub torna cada vez mais difícil ver qualquer um, exceto o primeiro e últimos comentários em um longo tópico: cry:. Pode ser útil se esta atualização foi incluída na descrição do problema.)

Olá a todos! Alguém poderia me dizer por que esse caso de uso não funciona? Bugs ou comportamentos esperados?

Como este exemplo . impl A for i32 está ok, mas impl A for () não pode ser compilado em 1.39.0-nightly.

#![feature(specialization)]

trait A {
    fn a();
}

default impl <T: ToString> A for T {
    fn a() {}
}

impl A for i32 {
    fn a() {}
}

impl A for () {
    fn a() {}
}

mensagem de compilação:

error[E0119]: conflicting implementations of trait `A` for type `()`:
  --> src/lib.rs:16:1
   |
8  | default impl <T: ToString> A for T {
   | ---------------------------------- first implementation here
...
16 | impl A for () {
   | ^^^^^^^^^^^^^ conflicting implementation for `()`
   |
   = note: upstream crates may add new impl of trait `std::fmt::Display` for type `()` in future versions

@Hexilee Coloque default nos métodos, não no impl.

@KrishnaSannasi exemplo 2

@zserik sim, eu sei. Acho que ainda não foi implementado ou foi abandonado. Em qualquer caso, não funciona agora.

Obviamente não funciona agora, mas acho que deveria funcionar.

Estou perguntando isso aqui, porque não notei este tópico surgir em nenhum outro lugar - há algum plano para default -ify várias funções de biblioteca padrão, da mesma forma como temos const -ified funciona quando for considerado seguro fazê-lo? O principal motivo pelo qual estou perguntando é que as implementações genéricas padrão From e Into ( impl<T, U: From<T>> Into<U> for T e impl<T> From<T> for T ) tornam difícil escrever From genérico abrangente Into implementações downstream de core , e seria bom se eu pudesse substituir essas conversões em minhas próprias caixas.

Mesmo se permitirmos a especialização para From / Into isso não ajudaria em impls genéricos por causa do problema da rede.

@KrishnaSannasi Não acredito que seja esse o caso. Por exemplo, este código deveria funcionar se From e Into fossem especializáveis, mas não funciona porque não são:

impl<M: Into<[S; 2]>, S> From<M> for GLVec2<S> {
    fn from(to_array: M) -> GLVec2<S> {
        unimplemented!()
    }
}
impl<M, S> Into<M> for GLVec2<S>
where
    [S; 2]: Into<M>,
{
    fn into(self) -> M {
        unimplemented!()
    }
}

pub struct GLVec2<S> {
    pub x: S,
    pub y: S,
}

Isso funciona se você converter From e Into em um traço personalizado que não tenha essas implementações genéricas: https://play.rust-lang.org/?version=stable&mode=debug&edition= 2018 & gist = cc126b016ff62643946aebc6bab88c98

@Osspial Bem, se você tentar simular usando um impl padrão, você verá o problema,

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=e5b9da0eeca05d063e2605135a0b5ead

Vou repetir, alterar From/Into impl para ser um impl padrão na biblioteca padrão não tornará possíveis impls genéricos para Into . (e não afeta impls genéricos de From )

Olá, há um bug sério na implementação da especialização atual. Eu o rotulo de bug porque mesmo que tenha sido uma decisão de design explícita, nos impede de usar um dos recursos de especialização mais poderosos, ou seja, a possibilidade de criação de "tipos opacos" (este não é um nome formal). Este padrão é um dos blocos de construção mais primitivos em outras linguagens que fornecem classes de tipo, como Haskell ou Scala.

Este padrão é simples - podemos definir estruturas como WithLabel ou WithID que adicionam alguns campos e métodos às estruturas subjacentes, por exemplo, se criarmos WithLabel<WithID<MyType>> , poderemos para obter id , label e todos os campos / métodos de MyType também. Infelizmente, com a implementação atual, não é possível.

Abaixo está um código de exemplo que mostra o uso desse padrão. O código comentado não compila, embora deva tornar esse padrão realmente útil:

#![feature(specialization)]

use std::ops::Deref;
use std::ops::DerefMut;

// =================
// === WithLabel ===
// =================

struct WithLabel<T>(String, T);

pub trait HasLabel {
    fn label(&self) -> &String;
}

impl<T> HasLabel for WithLabel<T> {
    fn label(&self) -> &String { 
        &self.0
    }
}

// THIS SHOULD COMPILE, BUT GETS REJECTED
// impl<T> HasLabel for T
// where T: Deref, <Self as Deref>::Target : HasLabel {
//     default fn label(&self) -> &String { 
//         self.deref().label() 
//     }
// }

impl<T> Deref for WithLabel<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// ==============
// === WithID ===
// ==============

struct WithID<T>(i32, T);

pub trait HasID {
    fn id(&self) -> &i32;
}

impl<T> HasID for WithID<T> {
    fn id(&self) -> &i32 { 
        &self.0
    }
}

// THIS SHOULD COMPILE, BUT GETS REJECTED
// impl<T> HasID for T
// where T: Deref, <Self as Deref>::Target : HasID {
//     default fn id(&self) -> &i32 { 
//         self.deref().id() 
//     }
// }

impl<T> Deref for WithID<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// =============
// === Usage ===
// =============

struct A(i32);

type X = WithLabel<WithID<A>>;

fn test<T: HasID + HasLabel> (t: T) {
    println!("{:?}", t.label());
    println!("{:?}", t.id());
}

fn main() {
    let v1 = WithLabel("label1".to_string(), WithID(0, A(1)));
    // test(v1); // THIS IS EXAMPLE USE CASE WHICH DOES NOT COMPILE
}

Para fazer a linha test(v1) funcionar, precisamos adicionar o atributo impl manualmente:

impl<T: HasID> HasID for WithLabel<T> {
    fn id(&self) -> &i32 { 
        self.deref().id()
    }
}

Claro, para torná-lo completo, também precisaríamos fazer esse traço impl:

impl<T: HasLabel> HasLabel for WithID<T> {
    fn label(&self) -> &String { 
        self.deref().label()
    }
}

E isso é MUITO RUIM . Para apenas 2 tipos, é simples. No entanto, imagine que temos 10 definições de tipos opacos diferentes, que adicionam campos diferentes, como WithID , WithLabel , WithCallback , ... você escolhe. Com o comportamento atual das especializações, precisaríamos definir ... mais de 1000 implementações de características diferentes! Se o código comentado fosse aceito, precisaríamos apenas de 10 implementações de características e a implementação de cada novo tipo exigirá apenas uma única implementação adicional .

Não tenho certeza de como seu código está relacionado à especialização. Seu argumento (seu código inicial compila, mas a linha test(v1); comentada não compila sem o manual que você apresenta) ainda se aplica se a primeira #![feature(specialization)] linha for removida.

@qnighy O código deve ser compilado após descomentar os impls HasLabel for T e HasID for T - eles estão usando especialização. Atualmente, eles são rejeitados (tente removê-los no código que forneci!). Isso faz sentido agora para você? 🙂

Vamos considerar três instâncias WithLabel<WithID<A>> , WithID<WithLabel<A>> e WithLabel<WithLabel<A>> . Então

  • o primeiro impl cobre WithLabel<WithID<A>> e WithLabel<WithLabel<A>> .
  • o segundo impl cobre WithID<WithLabel<A>> e WithLabel<WithLabel<A>> .

Portanto, o par de impls não satisfaz a seguinte cláusula do RFC :

Para garantir que a especialização seja coerente, garantiremos que para quaisquer dois impls I e J que se sobrepõem, tenhamos I < J ou J < I . Ou seja, um deve ser realmente mais específico do que o outro.

E é um problema real no seu caso também porque HasLabel impl de WithLabel<WithLabel<A>> pode ser interpretado de duas maneiras.

Como podemos cobrir este caso já foi discutido na RFC também, e a conclusão é:

As limitações que a regra de rede aborda são relativamente secundárias aos objetivos principais de especialização (conforme definido na Motivação) e, portanto, uma vez que a regra de rede pode ser adicionada posteriormente, a RFC fica com a regra de cadeia simples por enquanto.

@qnighy , obrigado por pensar nisso.

E é um problema real no seu caso também, porque o impl HasLabel de WithLabel<WithLabel<A>> pode ser interpretado de duas maneiras.

Isso é verdade se não considerarmos impl<T> HasLabel for WithLabel<T> mais especializado do que impl<T> HasLabel for T para a entrada de WithLabel<WithLabel<A>> . A parte do RFC que você colou realmente cobre isso, no entanto, acredito que essa seja uma limitação séria e gostaria de pedir uma reconsideração do suporte para este caso de uso na primeira versão desta extensão.

Nesse ínterim, eu estava jogando com negative trait impls porque eles podem resolver os pontos que você cobriu. Criei um código que não tem os problemas que você descreve (a menos que esteja faltando alguma coisa), no entanto, ainda não compila. Desta vez, não entendo de onde vêm as restrições mencionadas no erro, pois a resolução não deve ser ambígua.

O bom é que agora tudo compila agora (incluindo especializações), mas não o uso de test(v1) :

#![feature(specialization)]
#![feature(optin_builtin_traits)]

use std::ops::Deref;
use std::ops::DerefMut;

// =================
// === WithLabel ===
// =================

struct WithLabel<T>(String, T);

auto trait IsNotWithLabel {}
impl<T> !IsNotWithLabel for WithLabel<T> {}

pub trait HasLabel {
    fn label(&self) -> &String;
}

impl<T> HasLabel for WithLabel<T> {
    fn label(&self) -> &String { 
        &self.0
    }
}

impl<T> HasLabel for T
where T: Deref + IsNotWithLabel, <Self as Deref>::Target : HasLabel {
    default fn label(&self) -> &String { 
        self.deref().label() 
    }
}

impl<T> Deref for WithLabel<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// ==============
// === WithID ===
// ==============

struct WithID<T>(i32, T);

pub trait HasID {
    fn id(&self) -> &i32;
}

impl<T> HasID for WithID<T> {
    fn id(&self) -> &i32 { 
        &self.0
    }
}

auto trait IsNotWithID {}
impl<T> !IsNotWithID for WithID<T> {}

impl<T> HasID for T
where T: Deref + IsNotWithID, <Self as Deref>::Target : HasID {
    default fn id(&self) -> &i32 { 
        self.deref().id() 
    }
}

impl<T> Deref for WithID<T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        &self.1
    }
}

// =============
// === Usage ===
// =============

struct A(i32);

type X = WithLabel<WithID<A>>;

fn test<T: HasID + HasLabel> (t: T) {
    println!("{:?}", t.label());
    println!("{:?}", t.id());
}

fn main() {
    let v1 = WithLabel("label1".to_string(), WithID(0, A(1)));
    test(v1);
}

Nesse ínterim, você pode explorar RFC1268 overlapping_marker_traits para permitir a sobreposição de características não marcadoras, mas esse hack requer mais três características (uma para passar pelas características de marcador, duas para readquirir dados apagados por meio da especialização).

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b66ee0021db73efaaa5d46edfb4f3990

@qnighy Eu criei um problema separado sobre esse bug: https://github.com/rust-lang/rust/issues/66041

Ok, acabei de descobrir que auto traits nunca será a solução aqui, as (de acordo com https://doc.rust-lang.org/nightly/unstable-book/language-features/optin-builtin-traits. html) eles se propagam para todos os campos em uma estrutura:

Traços automáticos, como Enviar ou Sincronizar na biblioteca padrão, são traços de marcador que são implementados automaticamente para cada tipo, a menos que o tipo, ou um tipo que ele contenha, tenha optado explicitamente pela exclusão por meio de um impl negativo.

EDITAR
@qnighy, de alguma forma,

Em tal situação, overlapping marker traits são os únicos hack que podemos usar agora, mas acho que seria bom permitir no futuro algum tipo de solução mais fácil para expressar tipos opacos (conforme descrito em meu post anterior: https : //github.com/rust-lang/rust/issues/31844#issuecomment-549023367).

Um exemplo muito simples (simplificação do exemplo acima ) que falha:

trait Trait<T> {}
impl<T> Trait<T> for T {}
impl<T> Trait<()> for T {}

Não acredito que isso atinja o problema identificado com as regras de rede , mas talvez um solucionador excessivamente simplista pense que sim?

Sem isso, a implementação atual é inútil para meus propósitos. Se o acima fosse permitido, então eu acredito que também seria possível implementar From em tipos de invólucro (embora eu não tenha certeza sobre Into ).

Para qualquer corpo que ainda não saiba: existe este truque incrível descoberto por dtolnay que permite usar especialização (muito limitada) em ferrugem estável

Não tenho certeza se isso já foi resolvido, mas as características com implementações padrão para seus métodos precisam ser redefinidas apenas para que possam ser marcadas como default . Exemplo;

trait Trait {
    fn test(&self) { println!("default implementation"); }
}

impl<T> Trait for T {
    // violates DRY principle
    default fn test(&self) { println!("default implementation"); }
}

Proponho a seguinte sintaxe para corrigir isso (se precisar de conserto):

impl<T> Trait for T {
    // delegates to the already existing default implementation
    default fn test(&self);
}

Movido para # 68309

@jazzfool Por favor, fazem perguntas semelhantes aqui) e me

Existe uma abordagem para testar a especialização? Por exemplo, ao escrever um teste que verifica a exatidão de uma especialização, você primeiro precisa saber se a especialização que está tentando testar é realmente aplicada em vez da implementação padrão.

@ the8472 , você quer dizer testar o compilador ou testar em seu próprio código? Você certamente pode escrever testes de unidade que se comportam de maneira diferente (ou seja, chame um fn e veja se obtém a variante especializada). Talvez você esteja dizendo que as duas variantes são equivalentes, exceto que uma deve ser mais rápida e, portanto, você não tem certeza de como testar qual versão está obtendo? Nesse caso, concordo, não sei como você pode testar isso agora.

Você poderia supor que você poderia fazer alguma outra característica com o mesmo conjunto de impls, mas onde os fns se comportam de maneira diferente, apenas para se tranquilizar.

Talvez você esteja dizendo que as duas variantes são equivalentes, exceto que uma deve ser mais rápida e, portanto, você não tem certeza de como testar qual versão está obtendo? Nesse caso, concordo, não sei como você pode testar isso agora.

Você pode testar isso usando uma macro. Estou um pouco enferrujado com minha ferrugem, mas algo nesse sentido ...

[#cfg(test)]
static mut SPECIALIZATION_TRIGGERED : bool = false;

[#cfg(test)]
macro_rules! specialization_trigger {
    () =>  { SPECIALIZATION_TRIGGERED = true; };
}

[#cfg(not(test))]
macro_rules! specialization_trigger {
    () => {};
}

Então use specialization_trigger!() no impl especializado, e nos testes use assert!(SPECIALIZATION_TRIGGERED);

[#cfg(test)]
static mut SPECIALIZATION_TRIGGERED : bool = false;
...

Você vai querer usar thread_local! { static VAR: Cell<bool> = Cell::new(false); } vez de static mut porque, caso contrário, a variável poderia ser definida em um thread de caso de teste e lida por engano em outro thread. Além disso, lembre-se de redefinir a variável no início de cada teste, caso contrário, você obterá true do teste anterior.

Tenho uma pergunta sobre o texto RFC, espero que este seja um bom lugar para perguntar.

Na seção de reutilização , este exemplo é dado:

trait Add<Rhs=Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
    fn add_assign(&mut self, rhs: Rhs);
}

// the `default` qualifier here means (1) not all items are implied
// and (2) those that are can be further specialized
default impl<T: Clone, Rhs> Add<Rhs> for T {
    fn add_assign(&mut self, rhs: Rhs) {
        let tmp = self.clone() + rhs;
        *self = tmp;
    }
}

Eu me pergunto como isso deve verificar o tipo, dado que tmp tem o tipo Self::Output e nada se sabe sobre este tipo associado. O texto RFC não parece explicar isso, pelo menos não perto de onde o exemplo é dado.

Existe algum mecanismo aqui que é / deveria fazer isso funcionar?

Esse padrão poderia ser restrito where T: Add<Output = T> ? Ou isso é um laço de causalidade?

@RalfJung Concordo que parece errado.

Eu tenho uma pergunta sobre o procedimento: quão significativo é este problema e quão significativo é para as pessoas experimentarem este recurso? Pelo que entendi, a implementação atual é incorreta e incompleta e provavelmente será completamente substituída por giz ou qualquer outra coisa. Se isso for verdade, devemos apenas desimplementar esse recurso e outros (por exemplo, GATs) até que possam ser refeitos corretamente?

Por favor, não desimplemente. Quebrado, defeituoso e incompleto ainda permite a experimentação.

Se isso for verdade, devemos apenas desimplementar esse recurso e outros (por exemplo, GATs) até que possam ser refeitos corretamente?

Por favor, não, PyO3 (biblioteca de ligações Python) atualmente depende de especialização. Veja https://github.com/PyO3/pyo3/issues/210

Uma boa quantia de std depende disso também? Achei que me lembrava de ter visto muitas implementações internas especializadas para coisas relacionadas a vetores e strings. Não que isso deva impedir a desimplementação, apenas que não seria tão simples quanto remover as seções relevantes do verificador de tipo.

@Lucretiel sim, muitas otimizações úteis (especialmente em torno de iteradores) dependem da especialização, então seria uma grande regressão de desempenho implementá-la.

Por exemplo, FusedIterator e TrustedLen são inúteis sem especialização.

PyO3 (biblioteca de ligações Python) atualmente depende de especialização

Isso é assustador, por causa das partes "doentias". A biblioteca padrão tinha bugs de solidez críticos devido ao uso incorreto de especialização. Você tem certeza de que não tem os mesmos bugs? Tente usar min_specialization vez disso, espero que pelo menos seja menos incorreto.

Talvez specialization deva receber um aviso semelhante a const_generics dizendo "este recurso está incompleto, incorreto e quebrado, não use em produção ".

muitas otimizações úteis (especialmente em torno de iteradores) dependem da especialização, portanto, seria uma grande regressão de desempenho implementá-la.

Hoje em dia, eles dependem de min_specialization (veja, por exemplo, https://github.com/rust-lang/rust/pull/71321), que possui os maiores buracos de solidez obstruídos.

@nikomatsakis

Eu concordo que parece errado.

Alguma ideia de qual é o código pretendido? A princípio pensei que default impl pretendia também definir type Output = Self; , mas isso é realmente impossível no RFC proposto . Então, talvez a intenção fosse ter um limite de Output = T ?

@RalfJung Alguma chance de min_specialization ser documentado? Eu acho que é mais arriscado usar um recurso completamente não documentado em uma caixa do que um que tem bugs de integridade conhecidos (e possivelmente desconhecidos). Nenhum é bom, mas pelo menos o último não é apenas parte interna do compilador.

Não consegui encontrar nenhuma menção a min_specialization neste problema de rastreamento fora do # 71321 PR - e de acordo com o livro Instável, este é o problema de rastreamento desse recurso.

Também não sei muito sobre esse recurso, acabei de ver as correções de som do libstd. Ele foi apresentado em https://github.com/rust-lang/rust/pull/68970, que explica mais algumas coisas sobre ele.

@matthewjasper faria sentido documentar isso um pouco mais e pedir aos usuários noturnos de feature(specialization) que migrem?

Parece que deveria haver pelo menos um aviso. Parece que esse recurso está abertamente quebrado e perigoso de usar em seu estado atual.

Eu acho que specialization poderia se tornar um sinônimo para min_specialization , mas adicione outro recurso unsound_specialization se necessário para projetos existentes, como PyO3 ou qualquer outro. Isso economizaria esforço considerável de qualquer pessoa que usa apenas min_specialization , mas qualquer outra pessoa receberá a mensagem de erro e poderá procurar aqui o novo nome.

@RalfJung

Alguma ideia de qual é o código pretendido?

Bem, em algum ponto, estávamos considerando um modo em que os padrões poderiam contar uns com os outros. Então eu imagino que nesse ponto o seguinte teria funcionado:

default impl<T: Clone, Rhs> Add<Rhs> for T {
    type Output = T;

    fn add_assign(&mut self, rhs: Rhs) {
        let tmp = self.clone() + rhs;
        *self = tmp;
    }
}

A ressalva era que se você substituir qualquer membro de impl , você terá que substituir todos eles. Mais tarde, desistimos dessa ideia e, em seguida, lançamos várias iterações, como "grupos padrão" (que também funcionariam aqui) e, em última análise, não adotamos nenhuma solução porque imaginamos que poderíamos chegar a ela mais tarde, uma vez que lidássemos com o outro, er, problemas urgentes (cc # 71420).

Por favor, não, PyO3 (biblioteca de ligações Python) atualmente depende de especialização. Consulte PyO3 / pyo3 # 210

Mantenedor do PyO3 aqui - somos a favor de abandonar a especialização para que possamos entrar no Rust estável. É provável que min_specialization se estabilize antes que o resto da especialização seja concluído?

Acho que houve alguma discussão sobre a tentativa de estabilizar a min_specialization na reunião de design do lang de planejamento da edição 2021 (está no youtube; desculpe, estou no meu telefone ou tentaria encontrar um link). Eu esqueci o que eles disseram sobre isso embora

Acho que houve alguma discussão sobre a tentativa de estabilizar a min_specialization na reunião de design do lang de planejamento da edição 2021 (está no youtube; desculpe, estou no meu telefone ou tentaria encontrar um link). Eu esqueci o que eles disseram sobre isso embora

Acho que este é o link correto do YouTube: https://youtu.be/uDbs_1LXqus
(também no meu telefone)

Sim, é isso. Aqui está um link para a discussão específica: https://youtu.be/uDbs_1LXqus?t=2073

Tenho usado #[min_specialization] em uma biblioteca experimental que venho desenvolvendo, então pensei em compartilhar minhas experiências. O objetivo é usar a especialização em sua forma mais simples: ter alguns casos estreitos com implementações mais rápidas do que o caso geral. Em particular, para ter algoritmos criptográficos no caso geral executados em tempo constante, mas se todas as entradas estiverem marcadas Public ter uma versão especializada que execute em tempo variável mais rápido (porque se eles forem públicos, não preocupam-se com o vazamento de informações sobre eles por meio do tempo de execução). Além disso, alguns algoritmos são mais rápidos dependendo se o ponto da curva elíptica está normalizado ou não. Para fazer isso funcionar, começamos com

#![feature(rustc_attrs, min_specialization)]

Então, se você precisa fazer um atributo _specialization predicate_ conforme explicado na especialização máxima mínima, você marca a declaração do traço com #[rustc_specialization_trait] .

Toda a minha especialização é feita neste arquivo e aqui está um exemplo de uma característica de predicado de especialização.

O recurso funciona e faz exatamente o que eu preciso. Obviamente, isso está usando um marcador interno rustc e, portanto, está sujeito a quebrar sem aviso.

O único feedback negativo é que não acho que a palavra-chave default faça sentido. Essencialmente, o que default significa agora é: "este impl é especializável, portanto, interprete os impls que cobrem um subconjunto deste como uma especialização dele ao invés de um impl conflitante". O problema é que isso leva a um código de aparência muito estranha:

https://github.com/LLFourn/secp256kfun/blob/6766b60c02c99ca24f816801fe876fed79643c3a/secp256kfun/src/op.rs#L196 -L206

Aqui, o segundo impl está se especializando no primeiro, mas também é default . O significado de default parece ter se perdido. Se você olhar para o resto dos impls, é muito difícil descobrir quais impls são especializados em quais. Além disso, quando eu fazia um implante errado que se sobrepunha a um existente, muitas vezes era difícil descobrir onde eu errei.

Parece-me que isso seria mais simples se tudo fosse especializável e quando você especializa algo, você declara exatamente em qual implemento está se especializando. Transformando o exemplo no RFC no que eu tinha em mente:

impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
{
    // no need for default
    fn extend(&mut self, iterable: T) {
        ...
    }
}

// We declare explicitly which impl we are specializing repeating all type bounds etc
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
    // And then we declare explicitly how we are making this impl narrower with ‘when’.
    // i.e. This impl is like the first except replace all occurances of ‘T’ with ‘&'a [A]’
    when<'a> T = &'a [A]
{
    fn extend(&mut self, iterable: &'a [A]) {
        ...
    }
}

Obrigado pelo feedback.

Meu comentário aqui , especificamente o item 6, fornece um caso concreto na biblioteca padrão onde pode ser desejável ter uma especialização que seja apenas parcialmente substituível: IndexSet precisaria de um tipo Output distinto porque IndexSet poderia ser implementado sem Index , mas provavelmente não queremos permitir que os dois tipos coexistam com tipos Output diferentes. Uma vez que IndexSet poderia ter uma implementação padrão em termos de IndexMut , seria razoável permitir a especialização do método index_set sem permitir a especialização de Output .

Eu tenho dificuldade com vídeos, então não posso procurar o vídeo com link, no entanto, tenho uma pergunta sobre #[min_specialization] . No estado em que se encontra, existe um atributo rustc_unsafe_specialization_marker em características como FusedIterator que fornecem dicas de otimização, para que possam ser especializados. @matthewjasper escreveu:

Isso é incorreto, mas permitimos a curto prazo porque não pode causar o uso após liberações com código puramente seguro, da mesma forma que a especialização em métodos de características pode.

Presumo que o plano seja implementar a proposta de @aturon e adicionar uma modalidade de especialização para características como essas ( where specialize(T: FusedIterator) ). Mas, atualmente, parece que qualquer código pode se especializar nessas características . Se estiver estabilizado como está, as pessoas podem escrever especializações estáveis ​​que dependem dele, o que significa que essa deficiência seria estabilizada.

A especialização nessas características também deve ser limitada à biblioteca padrão, então? A biblioteca padrão obtém benefícios suficientes por ser capaz de se especializar neles?

Se estiver estabilizado como está, as pessoas podem escrever especializações estáveis ​​que dependem dele, o que significa que essa deficiência seria estabilizada.

É meu entendimento que min_specialization as-is não se destina à estabilização.

Eu gostaria de ter algum tipo de marcador em impls especializados. Tem havido alguns casos de código em rustc e a biblioteca padrão não fazendo o que parece porque não há como saber se a especialização está realmente acontecendo:

Uma especialização desnecessária de Copy :
https://github.com/rust-lang/rust/pull/72707/files#diff -3afa644e1d09503658d661130df65f59L1955

Uma "especialização" que não é:
https://github.com/rust-lang/rust/pull/71321/files#diff -da456bd3af6d94a9693e625ff7303113L1589

Uma implementação gerada por uma macro, a menos que um sinalizador seja passado substituindo um impl padrão:
https://github.com/rust-lang/rust/pull/73851/files?file-filters%5B%5D=#diff -ebb36dd2ac01b28a3fff54a1382527ddR124

@matthewjasper o último link não parece vincular a nenhum snippet específico.

Não tenho certeza se este é um objetivo explícito, mas AIUI o fato de que os impls especializados não são marcados oferece uma maneira de evitar alterações interrompidas nos impls de cobertor. Um novo default impl<T> Trait for T não entra em conflito com impls downstream - eles acabaram de se especializar.

Poderia ser um aviso apenas para tê-lo desmarcado?

Tem havido alguns casos de código em rustc e a biblioteca padrão não fazendo o que parece porque não há como saber se a especialização está realmente acontecendo

Minha experiência com java é semelhante (embora não exatamente análoga). Pode ser difícil descobrir qual subclasse de uma classe está realmente executando ...

Queremos algum marcador em impls especializáveis ​​também, também para maior clareza ao ler, certo?

Poderíamos colocar os marcadores em ambos os lugares, o que melhora o erro rustc ou as mensagens de aviso porque agora eles sabem se a especialização é desejada e podem apontar para o outro lugar, se houver.

Se um engradado upstream adiciona um impl, então, além de simplesmente atualizar, um engradado downstream pode empregar truques que permitem a compilação com a versão nova e antiga, não tenho certeza se isso é benéfico.

Acho que a diferença pode ser muito grande para mostrar a mudança. Ele está apontando para isso: https://github.com/rust-lang/rust/blob/fb818d4321dee29e1938c002c1ff79b0e7eaadff/src/librustc_span/def_id.rs#L124

Re: Blanket impls, eles estão quebrando as alterações de qualquer maneira:

  • Eles podem se sobrepor parcialmente a um implemento a jusante, o que não é permitido
  • A coerência pode assumir sua inexistência de maneiras mais sutis (é por isso que os impls de reserva foram adicionados internamente)
  • Impls especializados devem ser sempre aplicáveis, o que significa:

    • Nós quebramos os impls das pessoas (o que min_specialization faz).

    • Exigimos que eles anotem de alguma forma seus limites de traços como sendo sempre aplicáveis ​​quando necessário.

    • Fazemos a mudança sempre aplicável para eles implicitamente e potencialmente introduzimos bugs de tempo de execução sutis quando o impl padrão agora se aplica.

@cuviper , na verdade, acho que ainda havia casos extremos em torno da adição de novos implantes de cobertor, mesmo com especialização. Lembro que estava tentando descobrir o que seria necessário para nos permitir adicionar um impl<T: Copy> Clone for T { } imp. Em qualquer caso, escrevi esta postagem do blog sobre isso ... mas não consigo me lembrar agora qual foi minha conclusão .

Independentemente disso, poderíamos tornar um aviso de lint para não ter uma anotação #[override] .

Dito isso, se pudéssemos fazer o usuário declarar quais impls eles estão se especializando (não tenho ideia de como faríamos isso), isso simplificaria algumas coisas. No momento, o compilador precisa deduzir as relações entre os impls e isso é sempre um pouco complicado.

Um dos itens pendentes que temos de fazer no projeto de giz é tentar voltar e explicar como a especialização deve ser expressa ali.

Tem havido alguns casos de código em rustc e a biblioteca padrão não fazendo o que parece porque não há como saber se a especialização está realmente acontecendo

Minha experiência com java é semelhante (embora não exatamente análoga). Pode ser difícil descobrir qual subclasse de uma classe está realmente executando ...

Em maio, propus uma alternativa para a especialização em IRLO que não depende realmente de impls sobrepostos, mas permite que um único impl seja where match em seu parâmetro de tipo:

impl<R, T> AddAssign<R> for T {
    fn add_assign(&mut self, rhs: R) where match T {
        T: AddAssignSpec<R> => self.add_assign(rhs),
        T: Add<R> + Copy => *self = *self + rhs,
        T: Add<R> + Clone => { let tmp = self.clone() + rhs; *self = tmp; }
    }
}

As caixas a jusante podem então usar impl para implementar "especialização", porque por convenção tal impl para o traço Trait corresponderia primeiro a tipos que implementam outro traço TraitSpec e tipos a jusante seria capaz de implementar essa característica para substituir o comportamento genérico:

// Crate upstream
pub trait Foo { fn foo(); }
pub trait FooSpec { fn foo(); }

impl<T> Foo for T {
    fn foo() where T {
        T : FooSpec => T::foo(),
        _ => { println!("generic implementation") }
    }
}

fn foo<T : Foo>(t: T) {
    T::foo()
}

// crate downstream
struct A {}
struct B {}

impl upstream::FooSpec for A {
    fn foo() { println!("Specialized"); }
}

fn main() {
    upstream::foo(A); // prints "specialized"
    upstream::foo(B); // prints "generic"
}

Essa formulação dá mais controle ao upstream para escolher a ordem dos impls aplicáveis ​​e, como isso faz parte da assinatura do traço / função, isso apareceria na documentação. IMO, isso evita "implantar" para saber qual ramificação é realmente aplicável, pois a ordem de resolução é explícita.

Isso pode tornar os erros em torno de tempos de vida e igualdade de tipo mais aparentes, já que apenas o upstream poderia enfrentá-los durante a implementação da especialização (já que o downstream está implementando apenas uma "característica de especialização".

As desvantagens dessa formulação são que é uma rota muito diferente da RFC, e está sendo implementada desde 2016, e que pelo menos algumas pessoas no tópico expressaram preocupação de que não seria tão expressivo e / ou intuitivo quanto o atual recurso de especialização (acho "correspondência de tipos" bastante intuitivo, mas sou tendencioso ao propor a formulação).

A sintaxe de correspondência pode ter outro benefício (sintático): se em algum ponto no futuro fosse estendida com guardas de correspondência avaliadas por const, não seria necessário fazer ginástica de tipo para expressar limites condicionais em expressões const. Por exemplo, pode-se aplicar especializações baseadas em size_of , align_of , needs_drop ou tamanhos de array.

@dureuill obrigado pela informação! Essa é realmente uma ideia interessante. Uma preocupação que tenho é que isso não necessariamente resolve alguns dos outros casos de uso previstos para especialização, especialmente o caso de "comportamento de refinamento incremental", conforme descrito por @aturon nesta postagem do blog . Ainda assim, vale a pena manter em mente.

@dureuill A ideia é realmente interessante e pode ter muito potencial, mas nem sempre a troca alternativa é equivalente.
Acho que não é porque não é dada a oportunidade de substituir totalmente a implementação mais geral. Além disso, outro problema pode ser o fato de que não temos suporte para todos os recursos presentes na sintaxe RFC where da qual sua sugestão depende.
A sugestão é intrigante, então talvez pudesse ter seu próprio RFC como um recurso separado em vez de um concorrente da especialização, porque ambos seriam úteis e não vejo razão para que eles não possam morar juntos.

@ the8472 @nikomatsakis , @ Dark-Legion: Obrigado pelo feedback positivo! Tento responder a alguns de seus comentários no tópico IRLO , já que não quero ser muito barulhento no problema de rastreamento (sinto muito por cada um de vocês que esperava notícias sobre especialização e acabou de encontrar minhas divagações: flushed :).

Posso abrir um RFC separado se conseguir escrever algo publicável. Enquanto isso, estou muito aberto a comentários sobre o tópico vinculado do

Também sou a favor de ter algum tipo de marcador sobre impls especializados.

A edição 2021 se aproxima, o que nos permite reservar mais palavras-chave (como specialize ). Olhando para a complexidade e história deste recurso, eu não acho que ele vai se estabilizar antes do lançamento da Edição 2021 (sinta-se à vontade para provar que estou errado) o que significa - na minha opinião - brincar com (a) nova (s) palavra (s) ) é razoável.

Caso contrário, a única palavra-chave existente que parece ... bem ... adequada como marcador, pode ser super ?

Resumo reutilizando o exemplo de @LLFourn de https://github.com/rust-lang/rust/issues/31844#issuecomment -639977601:

  • super (já reservado, mas também pode ser mal interpretado como alternativa a default )
super impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • specialize
specialize impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • spec (abreviação de specialize como impl é implement ) ( preocupação válida levantada por @ssokolow em https://github.com/rust-lang / rust / issues / 31844 # issuecomment-690980762)
spec impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>
  • override (já reservado, obrigado @ the8472 https://github.com/rust-lang/rust/issues/31844#issuecomment-691042082)
override impl<A, T> Extend<A, T> for Vec<A> where T: IntoIterator<Item=A>

palavras-chave já reservadas podem ser encontradas aqui

ou spec (abreviação de specialize como impl é para implement )

"spec" já é mais familiar para as pessoas como uma abreviação para "especificação" (por exemplo, "A especificação HTML 5"), então não acho que seria uma boa abreviatura para "especializar".

override é uma palavra-chave reservada, suponho que se destine a funções, por isso pode ser utilizável para um bloco impl.

specialize Também depende da localidade - como australiano, é specialize para mim, então usar 'spec' remove a ambigüidade da localidade.

specialize Também depende da localidade - como australiano, é specialize para mim, então usar 'spec' remove a ambigüidade da localidade.

que funciona, exceto que 'spec' é uma abreviatura comum para especificação, então acho que usar 'spec' para significar especialização seria confuso. Mesmo que as palavras sejam soletradas de maneira diferente na Austrália, todos ainda podem entender qual palavra se destina se ela for escrita com um 'z' ou um 's'.

Como canadense, devo dizer que especializar / especializar não é a única palavra usada em programação que varia de acordo com a localidade.

Aqui, usamos "cor", mas isso sempre me confunde nas raras ocasiões em que uma linguagem de programação ou biblioteca usa isso em vez de "cor". Para o bem ou para o mal, o inglês americano é um pouco o padrão

Dado o quão fortemente eu espero que "spec" signifique "especificação", acho que esta é outra situação em que devemos considerar a grafia do inglês americano a menos pior opção.

Pode ser o fato, mas isso não significa que não há problema em usá-los. Eu me pego fazendo importações como "usar cor como cor", por exemplo. Eu sempre tropeço no s vs z também. Acho que, dada a atitude positiva de Rust em relação à inclusão e acessibilidade, faz sentido escolher termos de linguagem que não sejam dependentes de localidade, já que pequenas frustrações do usuário como cor / cor es / z aumentam.

Eu concordo em princípio. Só estou cético quanto à possibilidade de, neste caso, haver uma escolha de local neutro que não causa mais problemas do que resolve.

Como um falante nativo que não é o inglês, acho um tanto divertido que os falantes nativos de inglês se queixem de u extras como sendo uma barreira à inclusão. Imagine como seria se tudo não estivesse escrito de maneira um pouco estranha, mas escrito em uma linguagem totalmente diferente.

Colocado de outra forma: cada termo usado em Rust depende da localidade.

Para o bem ou para o mal, as coisas em Rust são soletradas em inglês americano. Para muitos aqui, isso significa trabalhar em sua segunda ou terceira língua; para outros, significa ter que ajustar um pouco a ortografia. Isso é o que é necessário para fazer um monte de pessoas trabalharem juntas. Acho que o benefício de tentar escolher palavras com a mesma grafia em muitas variantes do inglês é marginal em comparação com escolher um termo bom e não ambíguo - e spec é ambíguo, conforme apontado acima.

usar special como palavra-chave?

Alternativamente, faça duas palavras-chave: specialize e specialise e torne-as equivalentes ...

(Ou vocês, não americanos engraçados, podem aprender a soletrar bem de verdade: nós: 😂)

Eu não posso falar era o que a maioria das línguas fazem, mas CSS usa a grafia do inglês americano para tudo que é novo. Curiosamente, o inglês americano também parece ser usado com mais frequência na programação.

@ mark-im Infelizmente, essa é uma ladeira escorregadia que leva a argumentos de que Rust deveria ter conjuntos alternativos de palavras-chave em todas as principais línguas de onde os alunos possam vir.

Também complica desnecessariamente a linguagem ter vários sinônimos para palavras-chave, já que as pessoas e os analisadores estão acostumados apenas com a ideia de que os espaços em branco podem variar dessa forma.

(Sem mencionar que isso poderia potencialmente desencadear um impulso para sinônimos equivalentes em bibliotecas, o que exigiria um projeto de rustdoc e trabalho de implementação para evitar que fossem negativos.)

Em vez de discutir sobre em qual dialeto do inglês queremos que esse identificador esteja, talvez possamos comprometer e colocá-lo em hebraico .

@ssokolow, embora o argumento de declive escorregadio em geral não seja um argumento forte a ser usado, concordo com você neste caso. Alguém poderia argumentar que vários idiomas são adequados, mas há pelo menos dois motivos pelos quais não é:

  • Algumas palavras em diferentes idiomas parecem iguais, mas significam coisas diferentes (não é possível criar um exemplo relacionado à programação agora, mas um exemplo aleatório: a em eslovaco é and em inglês)
  • As pessoas terão uma grande dificuldade em ler código em outra linguagem, mesmo que conheçam a linguagem . Eu sei por experiência própria. (Resumindo a história: tive uma grande dificuldade em entender alguns textos com termos traduzidos diretamente do inglês para a minha "língua materna" no esquema de ensino universitário .)

Agora, retrocedendo, por que diferentes dialetos do inglês devem ser preferidos, se não outras línguas? Eu não vejo um ponto. Consistência (tudo em inglês americano) parece mais simples, mais fácil de entender e menos sujeito a erros.

Com tudo isso dito, eu ficaria muito feliz com "você quis dizer XXX?" abordagem da mensagem de erro. Palavras neutras que não têm outros problemas também funcionam.

Pelo menos ninguém precisa discutir futebol em código. ;)

Cerca de 70% dos falantes nativos de inglês vivem em países que usam a ortografia dos EUA.

Além disso..

"A grafia -ize é frequentemente incorretamente vista como um americanismo na Grã-Bretanha. Tem sido usada desde o século 15, antes de -ise em mais de um século. -Ize vem diretamente do grego -ιζειν -izein e do latim -izāre, enquanto - ise vem via French -iser. O Oxford English Dictionary (OED) recomenda -ize e lista a forma -ise como alternativa. "

"Publicações da Oxford University Press (OUP) - como A Dictionary of Modern English Usage de Henry Watson Fowler, Hart's Rules e The Oxford Guide to English Usage - também recomendam -ize. No entanto, Pocket Fowler's Modern English Usage de Robert Allan considera ambas as grafia para ser aceitável em qualquer lugar, exceto nos EUA. "

ref. https://en.wikipedia.org/wiki/American_and_British_English_spelling_differences# -ise, _- ize _ (- isation, _- ization)

Parece que o espanhol e o italiano têm um ou dois, então não tem certeza de onde o francês tira -iser, talvez do alemão?

É possível mover mais bicicletas em torno de palavras-chave e nomes específicos para um tópico interno ? Estou acompanhando este problema para atualizações de progresso no recurso, e esta discussão está começando a ficar um pouco barulhenta.

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