Rust: Problema de rastreamento para características TryFrom/TryInto

Criado em 5 mai. 2016  ·  240Comentários  ·  Fonte: rust-lang/rust

Problema de rastreamento para https://github.com/rust-lang/rfcs/pull/1542


Façam:

B-unstable C-tracking-issue T-libs

Comentários muito úteis

Eu gostaria que ! se tornasse um tipo de vocabulário suficiente para que Result<_, !> fosse intuitivamente lido como "resultado infalível", ou "resultado que (literalmente) nunca Err". Usar um alias (não importa um tipo separado!) me parece um pouco redundante e posso vê-lo causando pelo menos para mim uma pausa momentânea ao ler assinaturas de tipo - "espere, como isso foi diferente de ! novamente ?" YMMV, é claro.

Todos 240 comentários

Existe uma maneira de imprimir genericamente um erro com o valor original se a conversão falhar sem exigir Clone para que um método relacionado que entre em pânico em caso de falha possa ter boas mensagens de erro?

Uma discussão sobre se isso deve ir no prelúdio está pendente para quando isso ficar estável .

Desculpe se isso for abordado em outro lugar, mas o que gostaríamos de ver antes de marcar isso como estável? Tenho certeza de que reimplementei essa funcionalidade algumas vezes em vários projetos, então um traço reutilizável comum me faria 😄

Podemos trazê-lo para discussão para o próximo ciclo.

🔔 Este problema agora está entrando em um período de comentários finais de um ciclo longo para estabilização 🔔

Como ponto de estabilização, a equipe de libs também gostaria de adicionar essas características ao prelúdio como parte de sua estabilização. Isso exigirá que uma corrida de cratera seja 100% limpa _no mínimo_, mas estamos relativamente confiantes de que o estado atual de resolução torna compatível com versões anteriores para adicionar características ao prelúdio.

Eu tenho algumas perguntas sobre o propósito dessas características.

  1. Para quais tipos em std eles serão implementados?
  2. Quais tipos devem obter implementações como impl TryFrom<T> for T ?
  3. Quais tipos devem obter implementações como impl TryFrom<U> for T se já tiverem impl From<U> for T ?

cc @sfackler , você poderia expandir o conjunto atual de imps e alguns dos fundamentos também?

Acho que em geral a intuição deve ser exatamente a mesma para From / Into , exceto quando a conversão pode falhar. Assim como adicionamos gradualmente implementações de From e Into aos tipos de biblioteca padrão, espero que façamos o mesmo para TryFrom e TryInto . A diretriz mais importante aqui é que a conversão deve ser a "canonicamente óbvia" - se houver mais de uma maneira razoável de converter um tipo para outro, TryFrom ou From pode não ser o correto coisas para usar.

_Em geral_, não acho que se deva esperar que todos os tipos devam impl TryFrom<T> for T ou levantem manualmente um impl From<U> for T . Em particular, muitas vezes não fica claro qual tipo de erro escolher. No entanto, esses tipos de implementações são aqueles que podem e devem ser definidos para fazer uma API específica funcionar. Por exemplo, temos esses dois tipos de implementações para várias combinações de tipos inteiros primitivos pelas razões descritas na RFC. Como outro exemplo, uma característica ad-hoc que TryFrom / TryInto substituirá é postgres::IntoConnectParams , que tem uma implementação reflexiva para converter um ConnectParams em si mesmo.

Em geral, não acho que se deva esperar que todos os tipos impliquem TryFrom<T> for T ou levantem manualmente um impl From<U> for T .

Quando uma conversão TryFrom é infalível, o tipo de erro deve ser enum Void {} , certo? (Ou um enum semelhante com algum outro nome.) O que, a propósito, me parece um bom motivo para ter um tipo Void de propósito geral em std .

@SimonSapin que quebraria uma API de estilo IntoConnectParams , bem como o caso de uso de conversão de inteiros descrito no RFC, pois os tipos de erro das conversões infalíveis e falíveis não corresponderiam.

@sfackler Não se você usar From simples para os tipos de erro (por exemplo try! )! impl<T> From<!> for T pode e deve existir para esse propósito.

Assim como adicionamos gradualmente implementações de From e Into aos tipos de biblioteca padrão, espero que façamos o mesmo para TryFrom e TryInto .

Isso não parece ter acontecido ainda, então não sei se é porque ninguém chegou a isso ou não há tipos aplicáveis ​​em std . Se houver tipos aplicáveis ​​em std seria bom ver algumas implementações para eles. Por exemplo, alguma das seguintes implementações é boa?

impl TryFrom<u32> for char
impl TryFrom<char> for u8
impl TryFrom<Vec<u8>> for String
impl TryFrom<&[u8]> for &str

_Em geral_, não acho que se deva esperar que todos os tipos devam impl TryFrom<T> for T ou levantem manualmente um impl From<U> for T .

O problema é que se você quiser usar TryInto<T> e T não estiver na sua caixa, então você tem que esperar que impl TryFrom<T> for T tenha sido implementado. Essa é uma limitação infeliz, que não existe para From / Into .

@SimonSapin que quebraria uma API de estilo IntoConnectParams , bem como o caso de uso de conversão de inteiros descrito no RFC, pois os tipos de erro das conversões infalíveis e falíveis não corresponderiam.

Isso significa que o tipo de erro foi corrigido de alguma forma com base no tipo para o qual você está convertendo?

Por que TryFrom::Err e TryInto::Err não são limitados por std::error::Error ?

não limitado por std::error::Error?

Isso evitaria que Err fosse () , que é um tipo de erro viável para conversões infalíveis.

Se () for o tipo de erro, a função poderá retornar Err(()) . Isso não torna a função infalível. Ele simplesmente não fornece um erro útil quando falha. Ao escrever código que usa conversões que podem ou não ser falíveis, acho que a melhor maneira é vincular Into e escrever uma especialização que use TryInto .

Depois que a RFC 1216 for implementada , ! será o tipo de erro apropriado para conversões infalíveis. Eu _acho_ que isso significaria que um Error vinculado a TryFrom::Err / TryInto::Err estaria OK.

Eu certamente gostaria que a implementação From<T> rendesse uma implementação de TryFrom<T, Err = !> (e idem para Into , é claro).

Eu _acho_ que isso significaria que um limite Error em TryFrom::Err / TryInto::Err estaria OK.

Sim, definitivamente teremos impl Error for ! { ... } exatamente para esses tipos de casos.

Estou um pouco preocupado com as diferenças de tipo no caso de uso de conversões de inteiros, mas parece que provavelmente deveríamos tentar estabilizar isso até que ! -as-a-type chegue para ter a chance de experimentar.

No meu POV, todos os tipos associados que representam erros devem ser delimitados por Error . Infelizmente, já deixamos um tipo sem ele: FromStr::Err (Os únicos outros tipos públicos são TryInto e TryFrom , marcados com ack 'type Err;' e ack 'type Error;' ). Não vamos fazer isso novamente.

Discutido recentemente, a equipe de libs decidiu apostar na estabilização dessas características até que o tipo ! seja resolvido.

Acho que agora isso não está mais bloqueado, certo?

A remoção da indicação como @sfackler vai investigar os tipos ! e essas características.

Isso terá uma mudança para se estabilizar no futuro próximo?

Por que TryFrom::Err e TryInto::Err não são limitados por std::error::Error?

std::err::Error não existe em #![no_std] builds. Além disso, em muitos casos, não há benefício em implementar std::err::Error para um tipo de erro, mesmo quando ele está disponível. Assim, eu preferiria não ter tal limite.

Eu experimentei implementar isso em minha própria biblioteca. Gostaria de reiterar a preocupação expressa por @SimonSapin em https://github.com/rust-lang/rfcs/pull/1542#issuecomment -206804137: try!(x.try_into()); é confuso porque a palavra "tentar" é usado de duas maneiras diferentes na mesma declaração.

Eu entendo que muitas pessoas pensam que essas coisas devem ser escritas x.try_into()?; , no entanto eu sou uma de um número substancial de pessoas (com base em todos os debates) que preferem não usar a sintaxe ? por causa... de todas as razões mencionadas em todos os debates.

Pessoalmente, acho que ainda devemos tentar encontrar algum padrão que não exija o prefixo try_ nos nomes.

Eu não me sinto particularmente forte sobre a nomeação, mas pessoalmente não consigo pensar em nada que seja melhor.

Já se tornou semi-padrão usar try_ para tipos de funções que retornam um Result .

no entanto, sou uma de um número substancial de pessoas (com base em todos os debates) que preferem não usar a sintaxe ? por causa de... todas as razões mencionadas em todos os debates.

Esse debate acabou agora, não é? Quero dizer, eu simpatizo com esse lado do debate: não sei por que as pessoas disseram que acham try!() tão chato, ? é menos visível, incentiva o tratamento de erros preguiçoso e parece que um desperdício de sintaxe para algo para o qual já tínhamos uma macro perfeitamente boa (a menos que seja estendida para se tornar algo muito mais geral no futuro).

Mas isso é passado agora. ? está estável e não vai desaparecer. Então, todos nós podemos mudar para ele para que todos usemos a mesma coisa e possamos parar de nos preocupar com conflitos de nomes com try! .

Gostaria de reiterar a preocupação expressa por @SimonSapin em rust-lang/rfcs#1542 (comentário)

Já que sou citado pelo nome, deixe-me dizer que realmente não tenho mais essa preocupação. Na época em que fiz este comentário o operador ? era uma proposta cujo futuro era incerto, mas agora veio para ficar.

Além disso, acho que estabilizar mais cedo do que mais tarde é mais importante do que outra rodada de nomes. Já faz meses desde que o RFC foi aceito e isso foi implementado #[unstable] .

Já se tornou semi-padrão usar try_ para tipos de funções que retornam um Result.

Esta é a coisa que eu acho mais estranho sobre esse recurso. A maioria das funções que escrevo retorna um Result mas nunca nomeei nenhuma dessas funções com um prefixo try_ exceto ao tentar experimentar essa característica.

Além disso, não encontrei nenhuma vantagem prática em escrever isso:

impl TryInto<X> for Y {
    type Err = MyErrorType;

   fn try_into(self) -> Result<X, Self::Err> { ... }
}

Em vez disso, sempre posso escrever isso, muito menos sobrecarga sintática:

    fn into_x(self) -> Result<X, MyErrorType> { ... }

Eu nunca tive que escrever código genérico que fosse parametrizado por TryInto ou TryFrom apesar de ter muitas conversões, então a última forma é suficiente para todos os meus usos em tipos que defino. Eu acho que ter parâmetros TryInto<...> ou TryFrom<...> parece uma forma questionável.

Além disso, descobri que nomear o tipo de erro associado Err em vez de Error era propenso a erros, pois sempre digitei Error . Percebi que esse erro foi cometido mesmo durante a elaboração da própria RFC: https://github.com/rust-lang/rfcs/pull/1542#r60139383. Além disso, o código que usa Result já usa o nome Err extensivamente, pois é um construtor Result .

Havia uma proposta alternativa que focava especificamente em tipos inteiros e usava terminologia como “ampliar” e “restringir”, por exemplo x = try!(x.narrow()); que eu também implementei. Achei que a proposta era suficiente para meus usos da funcionalidade proposta aqui no meu uso real, pois acabei fazendo essas conversões apenas em tipos inteiros internos. Também é mais ergonômico e claro (IMO) para os casos de uso para os quais é suficiente.

Além disso, não encontrei nenhuma vantagem prática em escrever isso ...

Eu meio que concordo. Essa característica é para onde uma coisa pode ser usada para criar outra coisa, mas às vezes esse processo pode falhar - mas isso soa como quase todas as funções. Quero dizer, devemos ter esses impls?:

impl TryInto<TcpStream> for SocketAddr {
    type Err = io::Error;
    fn try_into(self) -> Result<TcpStream, io::Error> {
        TcpStream::connect(self)
    }
}

impl<T> TryInto<MutexGuard<T>> for Mutex<T> {
    type Err = TryLockError<MutexGuard<T>>;
    fn try_into(self) -> Result<Mutex<T>, Self::Err> {
        self.try_lock()
    }
}

impl TryInto<process::Output> for process::Child {
    type Err = io::Error;
    fn try_into(self) -> Result<process::Output, io::Error> {
        self.wait_with_output()
    }
}

impl TryInto<String> for Vec<u8> {
    type Err = FromUtf8Error;
    fn try_into(self) -> Result<String, FromUtf8Error> {
        String::from_utf8(self)
    }
}

Esse traço parece quase genérico demais. Em todos os exemplos acima, seria muito melhor dizer explicitamente o que você está realmente fazendo em vez de chamar try_into .

Eu acho que ter parâmetros TryInto<...> ou TryFrom<...> parece uma forma questionável.

Também concordo. Por que você não faria a conversão e lidaria com o erro antes de passar o valor para a função?

std::err::Error não existe nas compilações #![no_std]. Além disso, em muitos casos não há benefício na implementação de std::err::Error para um tipo de erro mesmo quando ele está disponível. Assim, eu preferiria não ter tal limite.

O único benefício de ser limitado por std::error::Error é que pode ser o valor de retorno de outro cause() . Eu realmente não sei por que não existe um core::error::Error , mas eu não olhei para isso.

Além disso, descobri que nomear o tipo de erro associado Err em vez de Error era propenso a erros, pois sempre digitei Error. Percebi que esse erro foi cometido mesmo durante a elaboração da própria RFC: rust-lang/rfcs#1542 (comentário). Além disso, o código que usa Result já usa o nome Err extensivamente, pois é um construtor Result.

FromStr , que é estável, também usa Err para seu tipo associado. Seja o melhor nome ou não, acho importante mantê-lo consistente em toda a biblioteca padrão.

Se TryFrom e TryInto são ou não muito gerais, eu realmente gostaria de ver conversões falíveis, pelo menos entre tipos inteiros, na biblioteca padrão. Eu tenho uma caixa para isso, mas acho que os casos de uso vão longe o suficiente para torná-lo padrão. Quando Rust era alfa ou beta, lembro de usar FromPrimitive e ToPrimitive para esse propósito, mas essas características tinham problemas ainda maiores.

Error não pode ser movido para core devido a problemas de coerência.

Também devido a problemas de coerência Box<Error> não implementa Error , outra razão pela qual não vinculamos o tipo Err .

De qualquer forma, não há necessidade de vinculá-lo na definição do traço - você sempre pode adicionar esse limite mais tarde:

where T: TryInto<Foo>, T::Err: Error

Eu não costumo comentar sobre esses tópicos, mas estou esperando por isso há um tempo e, conforme expresso por vários acima, não tenho certeza de qual é o atraso; Eu sempre quero um traço que pode falhar. Eu tenho código em todo lugar que se chama try_from ... Essa característica _perfeitamente_ encapsula essa idéia. Por favor, deixe-me usá-lo em ferrugem estável.

Eu também escrevi um monte de outras coisas, mas desde então eu deletei porque, infelizmente, a coerência do traço impede que esse traço seja tão útil quanto poderia ser para mim. Por exemplo, eu basicamente reimplementei uma versão especializada dessa mesma característica para os tipos primitivos para um analisador altamente genérico desses tipos. Não se preocupe, eu vou falar sobre isso outra hora.

Dito isto, acredito que str::parse se beneficiará muito com isso, pois também especializa uma característica FromStr como o limite - que é exatamente ( TryFrom<str> ) especializado em mão.

Então, corrija-me se estiver errado, mas acredito que estabilizar e usar isso para str::parse :

  1. remover a reimplementação do clichê e, portanto, remover uma característica menos familiar e especializada
  2. adicione TryFrom<str> na assinatura, que está corretamente no módulo de conversão e é mais óbvio o que faz
  3. permitirá que os clientes upstream implementem TryFrom<str> para seus tipos de dados e obtenham str.parse::<YourSweetDataType>() gratuitamente, juntamente com outros try_from que desejarem implementar, o que contribui para uma melhor organização lógica do código.

Por fim, abstrações à parte, reutilização de código, bla bla, acho que um dos benefícios subestimados de características como essa é a compra semântica que elas fornecem para iniciantes e veteranos. Essencialmente, eles fornecem (ou estão começando a fornecer, quanto mais estabilizamos) uma paisagem uniforme com comportamento familiar e canonizado. Default , From , Clone são ótimos exemplos disso. Eles fornecem um cenário de funções memorável que os usuários podem acessar ao realizar certas operações e cujo comportamento e semântica eles já conhecem bem (aprenda uma vez, aplique em todos os lugares). Por exemplo:

  1. Eu quero uma versão padrão; oh, deixe-me alcançar SomeType::default()
  2. Eu quero converter isso, eu me pergunto se SomeType::from(other) está implementado (você pode apenas digitá-lo e ver se ele compila, sem buscar documentação)
  3. Eu quero clonar isso, clone()
  4. Eu quero tentar obter isso disso, e como os erros são parte integrante da ferrugem, ela pode falhar na assinatura, então deixe-me try_from - oh espere: P

Todos esses métodos se tornam comuns e se tornam parte do kit de ferramentas dos usuários do Rust, que reduz a carga lógica, permitindo-nos alcançar conceitos familiares (e isso é apenas outro nome para um Trait!) cuja documentação e comportamento semântico já internalizamos.

É claro que eles nem sempre combinam, e nesse caso especializamos nossas estruturas de dados, mas características como essa reduzem a carga da API de usuários e codificadores, dando-lhes acesso a conceitos que já estudaram, em vez de ler a documentação/encontrar seu from_some_thing , etc. Sem sequer ter que ler sua documentação, usando características std, os usuários podem navegar em sua API e estruturas de dados de forma lógica, robusta e eficiente.

De certa forma, está formalizando um acordo de cavalheiros entre nós e torna mais fácil e familiar para nós realizarmos certas operações familiares.

E isso é tudo que tenho a dizer sobre isso ;)

Isso foi bloqueado anteriormente em uma investigação sobre a possibilidade de usar ! como o tipo de erro para conversões de inteiros infalíveis. Como o recurso está implementado atualmente, isso falha até mesmo nos casos mais simples: https://is.gd/Ws3K7V.

Ainda estamos pensando em mudar os nomes dos métodos ou devemos colocar esse recurso no FCP?

@sfackler Esse link do playground funciona para mim se eu alterar o tipo de retorno na linha 29 de Result<u32, ()> para Result<u32, !> : https://is.gd/A9pWbU Ele não reconhece que let Ok(x) = val; é um padrão irrefutável quando val tem o tipo Err !, mas isso não parece ser um problema de bloqueio.

@Ixrec uma motivação primária para essas características foram as conversões de e para C integer typedefs. Se eu tiver uma função

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    x.try_into()
}

Isso compilará em destinos i686, mas não em destinos x86_64.

Da mesma forma, digamos que eu queira substituir o tipo IntoConnectParams : https://docs.rs/postgres/0.13.4/postgres/params/trait.IntoConnectParams.html. Como posso fazer isso se houver um cobertor impl<T> TryFrom<T> for T { type Error = ! } ? Eu preciso da implementação reflexiva para ConnectParams , mas com um tipo de erro concreto diferente de ! .

Ele não reconhece que let Ok(x) = val; é um padrão irrefutável quando val tem o tipo Err !

Observe que há um PR aberto para isso .

Se eu tiver uma função...

Isso deve funcionar embora

fn foo(x: i64) -> Result<c_long, TryFromIntError> {
    let val = x.try_into()?;
    Ok(val)
}

Correndo o risco de ser um comentário +1 irritante, só quero mencionar que depois que as macros 1.1 chegarem ao Rust 1.15, o try_from será o último recurso que manterá o Ruma no Rust noturno. O try_from estável é aguardado com ansiedade!

Em uma nota mais substancial...

Esta é a coisa que eu acho mais estranho sobre esse recurso. A maioria das funções que escrevo retorna um Result, mas nunca nomeei nenhuma dessas funções com um prefixo try_, exceto ao tentar experimentar essa característica.

Essa é uma boa observação, mas acho que o motivo do prefixo try_ não é que seja necessário identificar o tipo de retorno como Result , mas diferenciá-lo do equivalente não falível.

Além disso, descobri que nomear o tipo de erro associado Err em vez de Error era propenso a erros, pois sempre digitei Error. Percebi que esse erro foi cometido mesmo durante a elaboração da própria RFC: rust-lang/rfcs#1542 (comentário). Além disso, o código que usa Result já usa o nome Err extensivamente, pois é um construtor Result.

Eu concordo com este. A maioria dos outros tipos de erro que encontrei nas bibliotecas são chamados de "Error" e eu gosto que até agora "Err" sempre significou Result::Err . Parece que estabilizá-lo como "Err" resultará (sem trocadilhos) em pessoas constantemente errando o nome.

@canndrew Claro que é possível fazer o trabalho, mas o ponto principal dessa motivação para esse recurso é facilitar o manuseio correto desses tipos de diferenças de plataforma - queremos evitar todo o espaço de "compilações em x86, mas não em ARM" .

Acho que escolhi Err por consistência com FromStr mas ficaria muito feliz em mudar para Error antes de estabilizar!

o último recurso mantendo Ruma no Rust noturno

Da mesma forma, o back-end alternativo do playground só precisa de acesso noturno a TryFrom ; as coisas do serde noturno eram muito instáveis ​​para o meu gosto, então mudei para a configuração do script de compilação. Com 1.15, voltarei para #[derive] . Estou aguardando ansiosamente que esse recurso se torne estável!

@sfackler Desculpe, eu não estava seguindo. No caso de conversões inteiras, parece que o que realmente precisamos é não typedef c_ulong para u32 ou u64 dependendo da plataforma, mas de alguma forma torná-lo um novo tipo. Dessa forma, um impl TryFrom<c_ulong> não pode interferir com um impl TryFrom<u{32,64}> .
Afinal, sempre teremos problemas de "compilar uma plataforma, mas não a outra" se definirmos os tipos de maneira diferente em plataformas diferentes. É uma pena ter que sacrificar o impl TryFrom<T> for U where U: From<T> totalmente lógico apenas para que possamos apoiar o que parece ser uma prática questionável.

Não estou sugerindo seriamente que bloqueiemos este RFC até obtermos um RFC de novo tipo inteiro escrito + mesclado + estabilizado. Mas devemos ter isso em mente para o futuro.

Da mesma forma, digamos que eu queira substituir o tipo IntoConnectParams:

Mas qual é o problema aqui? Por que você não usaria um tipo de erro para TryFrom<ConnectParams> e outro para TryFrom<&'a str> ?

Eu não defenderia que deciframos literalmente todo o código FFI do mundo. Tendo tentado e falhado em pegar wrappers newtype inteiros semelhantes como Wrapping , existem enormes custos ergonômicos.

Existe um impl<T> From<!> for T na biblioteca padrão? Eu não vejo isso nos documentos, mas isso pode ser apenas um bug do rustdoc. Se não estiver lá, então não há como converter TryFrom<ConnectParams> ! Error no que eu realmente preciso.

Tendo tentado e falhado em pegar wrappers newtype inteiros semelhantes como Wrapping, existem enormes custos ergonômicos.

Eu estava pensando em algo mais como poder definir seus próprios tipos inteiros a. lá. C++:

trait IntLiteral: Integer {
    const SUFFIX: &'static str;
    const fn from_bytes(is_negative: bool, bytes: &[u8]) -> Option<Self>; // or whatever
}

impl IntLiteral for c_ulong {
    const SUFFIX: &'static str = "c_ulong";
    ...
}

extern fn foo(x: c_ulong);

foo(123c_ulong); // use a c_ulong literal
foo(123); // infer the type of the integer

Isso resolveria a maioria dos problemas ergonômicos? Na verdade, não gosto de literais definidos pelo usuário C++ - ou recursos em geral que dão às pessoas o poder de alterar o idioma de maneiras confusas - mas acho que pode acabar sendo o menor dos dois males.

Existe um impl<T> From<!> for T na biblioteca padrão?

Não atualmente porque está em conflito com o From<T> for T impl. Meu entendimento é que a especialização impl deve eventualmente ser capaz de lidar com isso.

Isso soa como algo que deveríamos voltar em alguns anos.

Qual é o cronograma de estabilização da especialização e ! ?

Para especialização eu não sei. Para ! si, é principalmente uma questão de sempre que posso mesclar meus patches.

@canndrew Eu definitivamente concordo que não deve ser implementado para tudo. Os documentos dizem _tente construir Self por meio de uma conversão_, mas o que conta como uma conversão? Que tal... _mudar a mesma coisa de uma representação para outra, ou adicionar ou remover um wrapper_? Isso abrange seus Vec<u8> -> String e Mutex<T> -> MutexGuard<T> , bem como u32 -> char e &str -> i64 ; excluindo SocketAddr -> TcpStream e process::Child -> process::Output .

Eu sinto que impl From<T> for U provavelmente deveria implicar TryFrom<T, Err=!> for U . Sem isso, funções que levam TryFrom<T> s também não podem receber From<T> s. (Usar try_from() | try_into() para uma conversão concreta e infalível provavelmente seria apenas um antipadrão. Você _seria_ capaz de fazer isso, mas… seria bobagem, então não .)

me sinto impl Depara U provavelmente deve implicar TryFrompara você.

Acordado.

Você seria capaz de fazer isso, mas... seria bobagem, então não faça.

Parece um fiapo em potencial.

@BlacklightShining Acho que deve ser implementado para tipos em que, dado o tipo de saída, a "conversão" é óbvia. Assim que várias conversões forem possíveis (utf8/16/32? serialização vs transmissão? etc...), isso deve ser evitado.

NA MINHA HUMILDE OPINIÃO:

No momento, poderíamos fornecer facilmente uma implementação geral de TryFrom<&str> para tudo que implementa FromStr :

impl<'a, T: FromStr> TryFrom<&'a str> for T {
    type Err = <T as FromStr>::Err;
    fn try_from(s: &'a s) -> Result<T, Self::Err> {
        T::from_str(s)
    }
}

Eu gostaria de ver algo assim adicionado antes que try_from seja estabilizado, ou pelo menos alguma referência a ele nos documentos. Caso contrário, pode ser confuso ter implementações de TryFrom<&'a str> e FromStr que diferem.

Concordo que seria uma boa inclusão, mas pode entrar em conflito com a proposta Try -> TryFrom impl?

@sfackler potencialmente. Seria perfeitamente bom se TryFrom , TryInto e FromStr referenciassem um ao outro nos documentos, mas minha principal preocupação é que não existe um padrão existente que diga se str::parse e str::try_into retornam o mesmo valor.

Eu ficaria bem com uma solicitação suave nos documentos para que as pessoas os implementassem para ter o mesmo comportamento, embora eu possa definitivamente ver casos em que alguém possa pensar que pode ser diferente.

Por exemplo, digamos que alguém crie uma estrutura Password para um site. Eles podem supor que "password".parse() verificaria a validade da senha e depois a converteria em um hash, enquanto Password::try_from("1234abcd") poderia assumir que "1234abcd" já é um hash armazenado no banco de dados e tentaria para analisá-lo diretamente em um hash que pode ser comparado.

Isso faz sentido, considerando como a redação de parse implica que algum nível de análise é feito, enquanto try_from é apenas uma conversão de tipo. No entanto, na realidade, podemos querer esclarecer que ambas as funções pretendem realizar a mesma coisa.

Embora a equipe de linguagem tenha proposto fechar o RFC por depreciar parâmetros anônimos , todos parecem concordar que, idealmente, deveríamos parar de criar um novo código que usa parâmetros anônimos. Com isso em mente, poderíamos atualizar a assinatura de try_from / try_into para dar nomes aos parâmetros? Ou seria mais importante manter a simetria com from / into ?

Além disso, seria útil escrever um resumo das principais questões ainda não respondidas? Eu realmente espero que possamos decidir estabilizar isso para o próximo ciclo de lançamento. Como mencionei, é o único recurso noturno restante que uso muito. :}

@jimmycuadra certamente sim! Quer enviar um PR adicionando alguns nomes de parâmetros?

No que diz respeito ao estado atual das coisas, acredito que a única pergunta sem resposta é se deveria haver uma

impl<T, U> TryFrom<U> for T
    where T: From<U>
{
    type Error = !;

    fn try_from(u: U) -> Result<T, !> {
        Ok(T::from(u))
    }
}

Isso adiciona uma boa quantidade de simetria, mas torna as coisas um pouco mais irritantes de se lidar em muitos casos, já que você não tem mais um único tipo de erro para as coisas que deseja converter.

digite Erro = !;

Sugiro fazer isso em uma RFC separada que leve em consideração o resultado de tudo o que for decidido sobre todas as coisas indecisas em relação ! .

@sfackler Acho que seria importante levar em consideração as coisas que mencionei sobre FromStr também. Devemos ter um impl semelhante para implementadores FromStr , ou eles devem ser distintos, ou devemos apenas documentar que eles devem ser os mesmos, mas não precisam ser?

Eu sou da opinião de que TryFrom<str> e FromStr devem ser funcionalmente idênticos, e a documentação deve deixar claro que as implementações dos dois devem ser idênticas. A implementação de um também deve fornecer o outro, pelo menos em termos de permitir que você use str::parse . Se Rust tivesse TryFrom desde o início, FromStr nunca teria sido necessário. Por esse motivo, eu também documentaria TryFrom<str> a forma preferida para o novo código.

@jimmycuadra nesse caso, devemos modificar parse para usar TryFrom e então colocar um impl cobertor para FromStr -> TryFrom .

Se vamos alterar str::parse para ser implementado em termos de TryFrom , devemos também alterar outras implementações de FromStr para tipos concretos de forma semelhante (ou seja, todos os implementadores nesta lista : https://doc.rust-lang.org/stable/std/str/trait.FromStr.html)? Os documentos de FromStr devem ser atualizados para sugerir o uso de TryFrom ? As implementações concretas atuais de FromStr devem ter seus documentos movidos para a versão TryFrom ?

Acho que para compatibilidade com versões anteriores não podemos alterar FromStr , certo?

Remover FromStr a favor de TryFrom<&str> é definitivamente algo a ter em mente para Rust 2.0.

Sim, assim que esse recurso se estabilizar, registraremos um problema e o marcaremos com 2.0-breakage-wishlist.

Uma coisa a considerar também é adicionar um método parse_into a String que usa TryFrom<String> . Eu me vejo implementando TryFrom para ambos frequentemente se um tipo armazena internamente um String mas ainda requer validação.

Se vamos deixar implTryFrom para T onde T: De para um futuro RFC, esse recurso está pronto para se estabilizar agora? Eu realmente não quero perder outro ciclo de lançamento, então espero que algumas pessoas da equipe Rust tenham a largura de banda para discutir e tomar uma decisão.

Eu acho que o problema é que seria difícil estabilizar esse recurso depois que ele foi estabilizado e as pessoas forneceram imps para ambos.

Eu esperaria que já ter um T : From<U> colocaria U : TryFrom<T> na categoria de mudança de " buraco óbvio da API " quando a implementação for razoável.

Isso implica que deve haver pelo menos um T : TryFrom<T> com Error = ! , mas a versão para qualquer From infalível é claramente melhor do que isso.

IMO não há realmente uma distinção clara entre se From deve fornecer uma implementação TryFrom ou se TryFrom deve fornecer uma implementação From .

Porque, por um lado, você pode considerar T::from(val) apenas T::try_from(val).unwrap() e, por outro, você pode considerar T::try_from(val) apenas Ok(T::from(val)) . Qual é melhor? Eu não sei.

você pode considerar T::from(val) apenas T::try_from(val).unwrap()

Eu discordo desse. Não se espera que as implementações From entrem em pânico. Só o contrário faz sentido.

@clarcharr Porque From não deve entrar em pânico, as opções são From em termos de TryFrom<Error=!> ou o contrário. Mas eu odiaria que o conselho usual fosse "Você deve implementar TryFrom com type Error = ! " em vez de "Você deve implementar From ".

Alguma maneira de obter algum movimento para estabilizar isso? Estamos ficando sem tempo antes de 1.18 entrar em beta. @sfackler?

@rfcbot fcp mesclar

O membro da equipe @sfackler propôs mesclar isso. A próxima etapa é a revisão pelo restante das equipes marcadas:

  • [x] @BurntSushi
  • [x] @Kimundi
  • [x] @alexcrichton
  • [x] @aturon
  • [x] @brson
  • [x] @sfackler

Nenhuma preocupação listada no momento.

Assim que esses revisores chegarem a um consenso, este entrará em seu período de comentários finais. Se você identificar um problema importante que não foi levantado em nenhum momento deste processo, por favor, fale!

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

@sfackler : apenas para verificar, somos bons em várias preocupações em torno de ! e impls gerais? Eu sei que falamos sobre isso na reunião do libs, mas seria útil obter um resumo aqui.

@aturon A última discussão sobre isso foi @sfackler se perguntando se impl From<T> for U deveria fornecer impl TryFrom<T> for U where TryFrom::Error = ! .

@briansmith sugeriu que uma decisão sobre isso fosse um RFC separado, uma vez que as questões não resolvidas sobre o tipo nunca fossem resolvidas.

O principal problema em estabilizar isso agora não é que essa mudança não pode ser feita sem quebrar a compatibilidade com versões anteriores? Ou a solução é simplesmente não avançar com essa mudança?

Eu acho que o conjunto atual de impls é insustentável. Eu poderia entender qualquer uma dessas posições:

  1. TryFrom destina-se a conversões falíveis _somente_, portanto, não possui itens como u8 -> u128 ou usize -> usize .
  2. TryFrom destina-se a _todas_ as conversões, algumas das quais são infalíveis e, portanto, têm um tipo TryFrom::Error desabitado.

Mas agora as coisas estão em um estado híbrido estranho onde o compilador irá inserir o código de verificação para uma conversão i32 -> i32 e ainda assim você não pode fazer uma conversão String -> String .

Quais são as objeções a ! como um tipo de erro? A única coisa que notei em uma rápida análise foi "mas torna as coisas um pouco mais chatas de lidar em muitos casos, já que você não tem mais um único tipo de erro para as coisas que deseja converter", mas não estou convencido de que concordo com isso, pois você deve assumir que recebeu algo personalizado com um tipo de erro personalizado no contexto genérico, não importa o quê.

O principal problema em estabilizar isso agora não é que essa mudança não pode ser feita sem quebrar a compatibilidade com versões anteriores? Ou a solução é simplesmente não avançar com essa mudança?

Eu sou da opinião de que é muito zeloso adicionar uma implementação genérica de TryFrom quando From é implementado. Embora seja semanticamente verdade que se existe uma implementação From , existe uma implementação TryFrom que não pode produzir um erro, eu não vejo essa implementação fornecida sendo praticamente útil, muito menos uma necessidade comum o suficiente para que ela seja fornecida por padrão. Se alguém realmente precisa desse comportamento para seu tipo por algum motivo, é apenas uma implementação simples de distância.

Se houver um exemplo de caso em que você usaria try_from em vez de from para uma conversão infalível, eu certamente mudaria de ideia.

Quais são as objeções! como um tipo de erro?

! está a meses de estabilização. Escolha um de estabilizar TryFrom em um futuro próximo ou ter o impl<T, U> TryFrom<U> for T where T: From<U> .

Já consideramos usar aliases de traço (rust-lang/rfcs#1733) aqui? Quando isso acontecer, podemos alias From<T> para TryFrom<T, Error=!> , tornando as duas características uma e a mesma.

@lfary Isso quebraria o usuário impl s de From , infelizmente.

@glaebhoerl Sim, você está certo 😥 A seção de motivação desse RFC menciona alias impl s, mas a proposta real não os permite.

(Mesmo que não tenha, os métodos têm nomes diferentes etc.)

Isso pode ficar abaixo de uma lista de desejos 2.0, mas, independentemente disso, não acontecerá sem quebrar nada.

Em primeiro lugar, obrigado @sfackler por uma ótima conversa sobre isso no IRC. Depois de deixar as coisas ficarem um pouco na minha cabeça, aqui é onde eu acabei.

Escolha entre estabilizar TryFrom em um futuro próximo ou ter o impl<T, U> TryFrom<U> for T where T: From<U> .

Acho que a questão central aqui é se as conversões infalíveis pertencem ao traço. Eu acho que sim, para coisas como as conversões infalíveis no RFC e para uso genérico (análogo a como existe o T:From<T> aparentemente inútil). Dado isso, o que eu mais quero é evitar um mundo onde todo implementador de tipo deve impl TryFrom<MyType> for MyType , e cada From impl também deve resultar em um TryFrom impl. (Ou obter bugs arquivados mais tarde por não fornecê-los.)

Então, poderíamos ter o impl do cobertor sem estabilizar ! ? Acho que tem um jeito, já que já temos tipos ! -like na biblioteca, como std::string::ParseError . (" Esta enumeração é um pouco estranha: na verdade nunca existirá. ")

Um esboço de como isso poderia funcionar:

  • Um novo tipo, core::convert::Infallible , implementado exatamente como std::string::ParseError . (Talvez até mude o último para um alias de tipo para o primeiro.)
  • impl<T> From<Infallible> for T para que seja compatível em ? com qualquer tipo de erro (veja o material c_foo mais tarde)
  • Use Infallible como o tipo Error no impl geral
  • Mais tarde, considere type Infallible = !; como parte da estabilização do tipo nunca

Eu me voluntario para fazer um PR fazendo isso, se for útil para concretizá-lo.

Quanto a c_foo : O código acima continuaria a permitir código como este:

fn foo(x: c_int) -> Result<i32, TryFromIntError> { Ok(x.try_into()?) }

Mas tornaria um código como este um "footgun" de portabilidade, por causa dos diferentes tipos de erro

fn foo(x: c_int) -> Result<i32, TryFromIntError> { x.try_into() }

Pessoalmente, não estou preocupado com essa diferença porque, desde que c_int seja um alias de tipo, há uma espingarda "full-auto":

fn foo(x: c_int) -> i32 { x }

E, em geral, código esperando que o tipo associado em um traço seja o mesmo para diferentes impls parece um cheiro de código para mim. Eu li TryFrom como "generalize From "; se o objetivo fosse "melhores conversões entre subconjuntos inteiros" - o que também parece útil - então "sempre o mesmo tipo de erro" é lógico, mas eu esperaria algo direcionado em std::num , provavelmente como num::cast::NumCast (ou boost::numeric_cast ).

(Além disso: com #[repr(transparent)] em FCP merge, talvez os tipos c_foo possam se tornar newtypes, ponto em que essas conversões podem ser mais consistentes. Os impls From&TryFrom podem codificar o C "char <= short < = int <= long", bem como os tamanhos mínimos exigidos por padrão para eles, como c_int:From<i16> ou c_long:TryFrom<i64> . Então a conversão acima seria i32:TryFrom<c_int> em todos plataformas, sempre com o mesmo tipo Error , e o problema desaparece.)

Em relação a "Este enum é um pouco estranho: nunca existirá de fato."

Existe uma razão pela qual o tipo de erro não pode ser apenas unitário? Por que se preocupar com a estrutura exclusiva ParseError se a conversa nunca pode dar erro?

@sunjay () é a representação do sistema de tipo de "isso pode acontecer, mas não há nada interessante para dizer quando isso acontece". Tipos desabitados (como ! e std::string::ParseError ) são o oposto, da mesma forma que o sistema de tipos diz "essa situação não pode acontecer nunca, então você não precisa lidar com isso".

@jimmycuadra

Se houver um exemplo de caso em que você usar try_from em vez de from para uma conversão infalível, eu certamente poderia mudar de ideia.

@scottmcm

Acho que a questão central aqui é se as conversões infalíveis pertencem ao traço.

Aqui está meu caso de uso: eu tenho um formato de arquivo de configuração onde os valores podem ser bool, numérico ou string, e uma macro para escrever valores de configuração literais onde as chaves podem ser variantes de enumeração ou string. Por exemplo:

let cfg = config![
    BoolOpt::SomeCfgKey => true,
    "SomeOtherCfgKey" => 77,
];

Para encurtar a história, a macro acaba se expandindo para uma lista de chamadas ($k, $v).into() . Eu gostaria de ter uma verificação na conversão de chaves de string para garantir que eles nomeiem uma opção de configuração válida, ou seja, implementando TryFrom<(String, ???)> e alterando a macro para usar ($k, $v).try_into() . Seria mais difícil fazer tudo isso se não houvesse um único nome de método para a macro usar para todas as conversões.

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

Eu realmente gosto muito da ideia de:

impl<U: TryFrom<T, Error=!>> From<T> for U {
    fn from(val: T) -> U {
        val.unwrap()
    }
}

Porque qualquer um que queira TryFrom<Error=!> pode implementá-lo, mas as pessoas ainda podem implementar From se quiserem. Talvez pudéssemos descontinuar From , mas não precisamos.

O plano de @scottmcm para usar um enum vazio parece ótimo para mim.

@Ericson2314 você escreveu :

Não se você usar From simples para os tipos de erro (por exemplo try! )! impl<T> From<!> for T pode e deve existir para esse propósito.

Como isso funcionaria na prática? Digamos que eu esteja tentando escrever uma função assim:

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError>

Exceto que isso obviamente não funciona, eu preciso especificar Error= em TryInto . Mas que tipo devo escrever lá? MyError parece óbvio, mas não posso usar MyType com o cobertor TryFrom impl.

Você está sugerindo o seguinte?

fn myfn<E: Into<MyError>, P: TryInto<MyType, Error=E>>(p: P) -> Result<(), MyError>

Isso parece bastante verboso.

Há uma questão mais geral de como isso deve funcionar se eu quiser várias conversões “infalíveis” para o mesmo tipo, mas com diferentes tipos de erro.

Talvez a definição TryFrom deva ser alterada para o seguinte:

pub trait TryFrom<T, E>: Sized {
    type Error: Into<E>;

    fn try_from(t: T) -> Result<Self, E>;
}

Você pode restringir o erro para ser conversível em MyError sem ter que dar um nome explícito, como

fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError> where MyError: From<P::Error>

ainda é um pouco detalhado, mas realmente indica as restrições para chamar a função bem ( playground )

EDIT: E tentar com uma variante como P::Error: Into<MyError> na verdade não funciona com ? pois não há implementação geral de From para Into . Alterando TryFrom como você mostrou, eu esperaria encontrar o mesmo problema.

O período final de comentários está agora completo.

@jethrogb heh você me citou de um ano atrás, então tive que pensar um pouco. @Nemo157 está absolutamente certo e isso parece razoável.

No caso específico de ! deveríamos ter um impl geral, mas lembro que ele se sobrepõe a outro. É uma sobreposição irritante, pois ambas as implementações concordam com a implementação --- comportamento indefinido / código morto.

Um comentário sobre isso de outro problema: https://github.com/rust-lang/rust/pull/41904#issuecomment -300908910

Alguém da equipe libs tem pensamentos sobre a ideia do scottmcm ? Parece uma ótima abordagem para mim e gostaria de continuar avançando com esse recurso depois de perder outro ciclo de lançamento.

A equipe libs falou sobre isso novamente algumas semanas atrás (desculpe a demora em escrever isso)! Chegamos à conclusão de que a fonte dos problemas sobre esse recurso era principalmente que o caso FFI realmente não se encaixa em nenhum outro caso de uso desses traços - é único porque você o está chamando em tipos concretos por meio de aliases que variam com base no alvo.

Assim, o plano básico de ação é adicionar impl<T, U> TryFrom<T> for U where U: From<T> e remover os imps explícitos para conversões de inteiros que são infalíveis. Para lidar com o caso de uso FFI, criaremos uma API separada.

Usar um alias de tipo para evitar o bloqueio em ! é interessante. Minha única preocupação é se ! for mais "especial" do que os tipos desabitados normais que causariam quebra quando trocamos o alias de um enum desabitado para ! .

Abri um PR para a "API separada" para tipos integrais: https://github.com/rust-lang/rust/pull/42456

Eu nunca tive que escrever código genérico que fosse parametrizado por TryInto ou TryFrom apesar de ter muitas conversões, então a última forma é suficiente para todos os meus usos em tipos que defino. Eu acho que ter parâmetros TryInto<...> ou TryFrom<...> parece uma forma questionável.

Eu pretendo usar TryFrom uma vez que é estável como parte de uma característica derivada, e seria muito estranho chamar métodos intrínsecos ad-hoc em alguns tipos como parte de uma macro derive .

Por favor, não remova isso.

Eu nunca tive que escrever código genérico que foi parametrizado por TryInto ou TryFrom

Mesmo que seja esse o caso, não acho que isso torne TryInto e TryFrom muito menos úteis. Eu uso Into e From em todo lugar em contextos não genéricos. Adicionar impl s de características de biblioteca padrão parece muito mais "normal" e "esperado" do que um monte de métodos de conversão inerentes ad-hoc.

Eu nunca tive que escrever código genérico que foi parametrizado por TryInto ou TryFrom

De um dos meus projetos:

pub fn put_str_lossy<C, S> (&self, s: S)
    where C: TryInto<ascii::Char>,
          S: IntoIterator<Item = C>
{
    for c in s.into_iter() {
        self.put_char(match c.try_into() {
            Ok(c) => c,
            Err(_) => ascii::QUESTION_MARK,
        });
    }
}

Espera-se que as implementações dessas características sigam alguma lei específica? Por exemplo, se podemos converter A em B e B em A, é necessário que a conversão seja inversível quando for bem-sucedida?:

#![feature(try_from)]

use std::convert::{TryFrom, TryInto};

fn invertible<'a, A, B, E>(a: &'a A) -> Result<(), E>
    where A: 'a + TryFrom<&'a B>,
          A: PartialEq,
          B: 'a + TryFrom<&'a A>,
          E: From<<A as TryFrom<&'a B>>::Error>,
          E: From<<B as TryFrom<&'a A>>::Error>,
{
    let b = B::try_from(a)?;
    let a2 = A::try_from(&b)?;
    assert!(a == &a2);
    Ok(())
}

edit: s/reflexivo/invertível/

@briansmith Dado como From funciona, eu diria que não é inversível dessa maneira.

use std::collections::BinaryHeap;

fn main() {
    let a = vec![1, 2];
    let b = BinaryHeap::from(a.clone());
    let c = Vec::from(b);
    assert_ne!(a, c);
}

Então, eu estou querendo saber sobre as implementações atuais desse traço. Como surgiu em #43127 (veja também #43064), não sei se é puramente porque a implementação usa i/u128, mas parece que as chamadas TryInto não são embutidas. Isso não é muito ideal e não está realmente no espírito de abstrações de custo zero. Por exemplo, usar <u32>::try_into<u64>() deve otimizar até um simples sinal de extensão na montagem final (desde que a plataforma seja de 64 bits), mas, em vez disso, está resultando em uma chamada de função.

Existe um requisito de que as implementações da característica sejam as mesmas para todos os tipos inteiros?

De acordo com #42456, provavelmente não teremos impl TryFrom diretamente nos tipos inteiros, mas como essa característica "NumCast" (para a qual #43127 deve mudar) ainda está sendo elaborada.

Independentemente de eles acabarem mudando para outra característica, essas conversões são implementadas hoje na libcore e podem ser usadas na libcore. Acho que usar u128 / i128 para todas as conversões de inteiros foi feito para simplificar o código-fonte. Provavelmente, devemos ter um código diferente, dependendo se o tipo de origem é mais amplo ou mais estreito que o destino. (Possivelmente com diferentes chamadas de macro baseadas em #[cfg(target_pointer_width = "64")] vs #[cfg(target_pointer_width = "32")] vs #[cfg(target_pointer_width = "16")] .)

Lembrete: não é necessário muito para desbloquear este! Se você estiver disposto, dê uma olhada no resumo de @sfackler e sinta-se à vontade para enviar um ping para mim ou para outros membros da equipe de libs para obter orientação.

Eu não sabia que havia buy-in da equipe de libs que poderíamos usar a ideia de @scottmcm como uma solução alternativa para o tipo de estrondo ser instável. Eu posso trabalhar em um PR para fazer as mudanças que @sfackler mencionou usando a solução alternativa.

Incrível, obrigado @jimmycuadra!

A maior parte dessa discussão parece girar em torno da implementação de TryFrom para tipos inteiros. Se TryFrom se estabiliza ou não, não deve ser apenas por causa desses tipos, na minha opinião.

Existem outras conversões interessantes que se beneficiariam dessas características, como TryFrom<&[T]> por &[T; N] . Recentemente, enviei um PR para implementar exatamente isso: https://github.com/rust-lang/rust/pull/44764.

Conversões como essas são importantes o suficiente para que TryFrom se estabilize.

Com #44174 mesclado, acredito que agora esteja desbloqueado.

Esse PR removeu a implementação automática de FromStr para qualquer tipo que implemente TryFrom<&str> porque o sistema de tipos não pode suportá-lo atualmente, mesmo com especialização. A intenção é que FromStr e parse sejam preteridos em favor de TryFrom<&str> e try_into assim que esse recurso for estabilizado. É lamentável perder a compatibilidade provisória entre os dois - se alguém tiver ideias para um paliativo, por favor, fale.

Se não houver mais alterações a serem feitas e alguém na equipe de libs acender isso para estabilização, posso fazer a estabilização PR e uma PR para depreciar FromStr / parse .

e um PR para depreciar FromStr/parse.

Os avisos de descontinuação não devem ser adicionados ao Nightly até que a substituição esteja disponível no Stable (ou até que https://github.com/rust-lang/rust/issues/30785 seja implementado), para que seja possível a qualquer momento fazer uma crate build sem avisos em todos os três canais de lançamento.

Eu perdi o outro PR já que as referências não resultam em notificações por e-mail. Percebo que há um impl From<Infallible> for TryFromIntError específico. Isso não deveria ser impl<T> From<Infallible> for T como discutido?

@jethrogb Infelizmente, isso entra em conflito com impl<T> From<T> for T então não pode ser feito (até obtermos impls de interseção? - e usar ! também não funciona).

@scottmcm ah claro.

Eu não acho que você precisa de impls de interseção? Isso não é apenas uma especialização direta?

Eu não li os outros comentários, mas TryFrom está quebrado para mim agora (funcionou bem antes).

versão ferrugem:

rustc 1.22.0-nightly (d6d711dd8 2017-10-10)
binary: rustc
commit-hash: d6d711dd8f7ad5885294b8e1f0009a23dc1f8b1f
commit-date: 2017-10-10
host: x86_64-unknown-linux-gnu
release: 1.22.0-nightly
LLVM version: 4.0

A seção de código relevante que a ferrugem reclama está aqui: https://github.com/fschutt/printpdf/blob/master/src/types/plugins/graphics/two_dimensional/image.rs#L29 -L39 e https://github .com/fschutt/printpdf/blob/master/src/types/plugins/graphics/xobject.rs#L170 -L200 - compilou bem algumas semanas atrás, e é por isso que a biblioteca ainda tem o emblema "passagem de compilação".

No entanto, na última compilação noturna, TryFrom parece quebrar:

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::two_dimensional::image::Image`:
  --> src/types/plugins/graphics/two_dimensional/image.rs:29:1
   |
29 | / impl<T: ImageDecoder> TryFrom<T> for Image {
30 | |     type Error = image::ImageError;
31 | |     fn try_from(image: T)
32 | |     -> std::result::Result<Self, Self::Error>
...  |
38 | |     }
39 | | }
   | |_^
   |
   = note: conflicting implementation in crate `core`

error[E0119]: conflicting implementations of trait `std::convert::TryFrom<_>` for type `types::plugins::graphics::xobject::ImageXObject`:
   --> src/types/plugins/graphics/xobject.rs:170:1
    |
170 | / impl<T: image::ImageDecoder> TryFrom<T> for ImageXObject {
171 | |     type Error = image::ImageError;
172 | |     fn try_from(mut image: T)
173 | |     -> std::result::Result<Self, Self::Error>
...   |
199 | |     }
200 | | }
    | |_^
    |
    = note: conflicting implementation in crate `core`

error: aborting due to 2 previous errors

error: Could not compile `printpdf`.

Então, supostamente tem uma implementação duplicada em crate core . Se alguém puder analisar isso, seria ótimo, obrigado.

@fschutt O impl conflitante é provavelmente impl<T, U> TryFrom<T> for U where U: From<T> , adicionado em https://github.com/rust-lang/rust/pull/44174. Pode existir um T como T: ImageDecoder e Image: From<T> .

Existe alguma coisa que ainda é necessária para que isso saia do portão de recursos?

Se https://github.com/rust-lang/rust/issues/35121 for estabilizado primeiro, poderíamos remover o tipo Infallible introduzido em https://github.com/rust-lang/rust/pull/ 44174 e use ! . Não sei se isso é considerado um requisito.

Acho que o principal bloqueador aqui ainda são os tipos inteiros. Ou usamos um traço Cast separado para tipos inteiros https://github.com/rust-lang/rust/pull/42456#issuecomment -326159595, ou fazemos o lint de portabilidade #41619 acontecer primeiro.

Então, eu costumava ter um enum que implementava TryFrom para AsRef<str> , mas isso quebrou alguns meses atrás. Eu pensei que era um bug introduzido no nightly que iria embora com o tempo, mas parece que não é o caso. Este não é mais um padrão suportado para TryFrom ?

impl<S: AsRef<str>> TryFrom<S> for MyEnum {
    type Error = &'static str;

    fn try_from(string: S) -> Result<Self, Self::Error> {
        // Impl here
    }
}
...

Que outras opções existem para converter de &str e String ?

@kybishop MyEnum implementa FromStr ? Essa pode ser a fonte de sua quebra.

@nvzqz não, embora tenha sido recomendado usar TryFrom através do Rust IRC, pois é uma solução mais generalizada. TryFrom inicialmente funcionou muito bem até que a mudança ocorreu no nightly alguns meses atrás.

EDIT: Você quer dizer que eu deveria mudar para implementar FromStr , ou que se _did_ impl FromStr , isso poderia causar quebra?

EDIT 2: Aqui está o impl completo, bastante curto e simples para os curiosos: https://gist.github.com/kybishop/2fa9e9d32728167bed5b1bc0b9becd97

@kybishop existe um motivo específico para você querer uma implementação para AsRef<str> em vez de &str ?

@sfackler Fiquei com a impressão de que permite a conversão de &str e String , embora eu ainda seja um novato em Rust, então talvez eu esteja entendendo mal exatamente como AsRef<str> é usado. Vou tentar sair &str e ver se AsRef estava permitindo algo que &str não permite.

@kybishop Igual a https://github.com/rust-lang/rust/issues/33417#issuecomment -335815206, e como a mensagem de erro do compilador diz, este é um conflito real com o impl<T, U> std::convert::TryFrom<U> for T where T: std::convert::From<U> impl que foi adicionado ao libcore.

Pode haver um tipo T (possivelmente em uma caixa downstream) que implementa tantoFrom<MyEnum> e AsRef<str>e tem MyEnum: From<T> . Nesse caso, ambos os impls se aplicariam, então ambos os impls não podem existir juntos.

@SimonSapin entendido. Então, quais são as opções para as pessoas que desejam converter de &str e &String ? Só tem que implementar TryFrom para ambos?

EDIT: Acho que posso comer o trabalho extra e chamar .as_ref() em String s. Eu posso então ter um único TryFrom impl para str .

Sim, dois impls (possivelmente com um baseado no outro, como você apontou) devem funcionar desde que MyEnum não implemente From<&str> ou From<String> .

@kybishop String implementa Deref<str> , então a inferência de tipo deve permitir que &String coagir em &str ao passá-lo para o TryFrom impl. Chamar as_ref nem sempre é necessário.

Se alguém estiver procurando um exemplo rápido disso: https://play.rust-lang.org/?gist=bfc3de0696cbee0ed9640a3f60b33f5b&version=nightly

Com https://github.com/rust-lang/rust/pull/47630 prestes a estabilizar ! , há apetite por um PR para substituir Infallible por ! aqui ?

Um caminho melhor a seguir seria fazer um alias. Conserva a expressividade e usa o recurso de linguagem adaptada.

type Infallible = !;

Apenas entrando. Estou com @scottmcm nisso.

No entanto, isso adiciona a sobrecarga extra de que esse recurso ( TryInto / TryFrom ) agora depende de outro recurso instável – never_type .

Além disso, Infallible tem a vantagem de fornecer mais informações/semânticas sobre o motivo pelo qual o tipo não pode ser construído. Estou opinando comigo mesmo agora.

Eu gostaria que ! se tornasse um tipo de vocabulário suficiente para que Result<_, !> fosse intuitivamente lido como "resultado infalível", ou "resultado que (literalmente) nunca Err". Usar um alias (não importa um tipo separado!) me parece um pouco redundante e posso vê-lo causando pelo menos para mim uma pausa momentânea ao ler assinaturas de tipo - "espere, como isso foi diferente de ! novamente ?" YMMV, é claro.

@jdahlstrom concordo totalmente. Nós precisaríamos introduzir isso no livro Rust ou no nomicon para que seja “verdade do terreno” e amigável.

Já se passaram dois anos desde que o RFC para esta API foi enviado.

~ @briansmith : Há um PR de estabilização em andamento.~

EDIT : Ou talvez eu esteja muito cansado...

Você vinculou o PR da estabilização do tipo ! .

Como o PR de estabilização ! acabou de ser mesclado, enviei um PR para substituir convert::Infallible por ! : #49038

https://github.com/rust-lang/rust/pull/49038 é mesclado. Eu acredito que este foi o último bloqueador para estabilização, por favor, deixe-me saber se eu perdi um problema não resolvido.

@rfcbot fcp mesclar

O rfcbot não está respondendo porque outro FCP já foi concluído antes em https://github.com/rust-lang/rust/issues/33417#issuecomment -302817297.

Uhmm, existem alguns impls que devem ser revisitados. Agora que temos impl<T, U> TryFrom<U> for T where T: From<U> , os impls restantes de TryFrom que têm type Error = ! devem ser substituídos por impls de From , removidos ou tornados falíveis ( alterando o tipo de erro para algum tipo não desabitado).

Aqueles nesse caso que eu posso encontrar envolvem usize ou isize . Suponho que os impls From correspondentes não existam porque sua falibilidade depende do tamanho do ponteiro de destino. De fato, os impls TryFrom são gerados de forma diferente para diferentes destinos: https://github.com/rust-lang/rust/blob/1.24.1/src/libcore/num/mod.rs#L3103 -L3179 ​​Este é provavelmente um risco de portabilidade.

gerado de forma diferente para diferentes destinos

Para esclarecer: diferentes corpos de métodos para diferentes target_pointer_width no mesmo impl são bons (e provavelmente necessários), APIs diferentes (tipos de erro) não são.

Estabilização PR: #49305. Depois de alguma discussão lá, este PR também remove alguns impls TryFrom que envolvem usize ou isize porque não decidimos entre duas maneiras diferentes de implementá-los. (E é claro que só podemos ter um.)

Problema de rastreamento dedicado para aqueles: https://github.com/rust-lang/rust/issues/49415

TryFrom funcionou perfeitamente em rustc 1.27.0-nightly (ac3c2288f 2018-04-18) sem nenhum recurso, mas quebrou quando compilado com rustc 1.27.0-nightly (66363b288 2018-04-28) .

Houve regressões à estabilização desse recurso nos últimos 10 dias?

@kjetilkjeka Este recurso foi recentemente desestabilizado: #50121.

(Reabertura desde que a estabilização foi revertida)

@kjetilkjeka A estabilização de TryFrom foi revertida em https://github.com/rust-lang/rust/pull/50121 juntamente com a estabilização do tipo ! , por causa do TryFrom<U, Error=!> for T where T: From<U> impl. O tipo ! não foi estabilizado devido a https://github.com/rust-lang/rust/issues/49593.

Obrigado por explicar. Isso significa que esse recurso é essencialmente bloqueado em algumas alterações na coerção do tipo de compilador? Não consigo encontrar um problema que explique quais alterações são necessárias. A magnitude das alterações é conhecida neste momento?

Existe alguma razão fundamental para que não pudéssemos ir em frente e estabilizar o traço TryFrom si, e quaisquer imps que não envolvem ! , e apenas adiar imps estabilizantes envolvendo ! até depois da possível estabilização de ! ?

https://github.com/rust-lang/rust/pull/49305#issuecomment -376293243 classifica as várias implementações de características possíveis.

@joshtriplett Pelo que entendi, impl<T, U> TryFrom<T> for U where U: From<T> { type Err = !; } em particular não é compatível com versões anteriores para adicionar se TryFrom já estiver estável.

@SimonSapin
Por que isso não é compatível com versões anteriores para adicionar?

(E nós realmente precisamos do impl do cobertor?)

Por que isso não é compatível com versões anteriores para adicionar?

Eu acho que não é compatível com versões anteriores, pois TryFrom poderia ter sido implementado manualmente entre TryFrom foi estabilizado e ! foi estabilizado. Quando a implementação geral foi adicionada, ela entraria em conflito com a implementação manual.

(E nós realmente precisamos do impl do cobertor?)

Eu acho que o impl geral realmente faz sentido ao escrever código genérico sobre TryFrom . Ao se referir a todos os tipos que têm a possibilidade de conversão em T , na maioria das vezes você também deseja incluir todos os tipos que podem ser convertidos em T com certeza. Suponho que uma alternativa poderia ser exigir que todos também implementem TryFrom para todos os tipos que implementam From e esperem pela especialização antes de fazer a implementação geral. Você ainda teria o problema de que Err fazer Result genérico. ! parece ser uma boa maneira de expressar e impor programaticamente a infalibilidade da conversão, e espero que valha a pena esperar.

Poderíamos sempre esperar até que a especialização estivesse disponível para fornecer a implementação geral e estabilizar as várias conversões numéricas nesse ínterim.

Eu vi vários pedidos para isso no contexto de pessoas que procuram ajuda sobre como fazer conversões de inteiros idiomaticamente em Rust, e hoje encontrei alguém que se perguntou especificamente como eles deveriam converter u64 para u32 com detecção de erros.

Não estou convencido de que a especialização permita magicamente adicionar o impl de cobertura após o fato. Meu entendimento das propostas atuais é que uma característica deve optar por ser especializada, o que pode não ser compatível com as características existentes.

Para sempre que isso estiver estabilizado: _please_ adicione TryFrom e TryInto ao prelúdio.

@SergioBenitez Fizemos isso no Nightly por um tempo e, infelizmente, foi uma mudança importante para caixas que definem seu próprio traço TryFrom ou TryInto (para uso enquanto o std são instáveis), o suficiente para revertermos isso.

Acho que a lição a aprender aqui para a equipe std libs é que quando há uma característica que podemos querer adicionar ao prelúdio, devemos fazê-lo assim que for implementado e não esperar pela estabilização.

Para TryFrom e TryInto no entanto, uma solução seria ter um prelúdio diferente para a edição de 2018. Isso já foi discutido informalmente antes, mas não o encontrei arquivado, então abri https://github.com/rust-lang/rust/issues/51418.

Uma solução genérica para um problema semelhante foi implementada para extensão
métodos em https://github.com/rust-lang/rust/issues/48919 , em que instável
métodos de extensão emitem avisos quando o código estável colide.

Parece que você poderia fazer algo semelhante com novos termos adicionados ao
prelúdio?

Em qui, 7 de junho de 2018, 11h47 Simon Sapin [email protected] escreveu:

@SergioBenitez https://github.com/SergioBenitez Fizemos isso em
Todas as noites por um tempo, e infelizmente foi uma mudança de última hora para as caixas
que definem sua própria característica TryFrom ou TryInto (para uso enquanto o std
são instáveis), o suficiente para que a tenhamos revertido.

Acho que a lição a aprender aqui para a equipe std libs é que quando
há uma característica que podemos querer adicionar ao prelúdio, devemos fazer
assim que for implementado e não esperar pela estabilização.

No entanto, para TryFrom e TryInto, uma solução seria ter um
prelúdio para a edição de 2018. Isso já foi discutido informalmente antes, mas
Eu não o encontrei arquivado, então abri #51418
https://github.com/rust-lang/rust/issues/51418 .


Você está recebendo isso porque está inscrito neste tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/33417#issuecomment-395525170 ,
ou silenciar o thread
https://github.com/notifications/unsubscribe-auth/AAC2lNbHvgBjWBk48-1UO311-LuUY5lPks5t6XUvgaJpZM4IXpys
.

Nesse caso, o que está colidindo não são as características em si (o nome do item no prelúdio), mas os métodos trazidos ao escopo por essa característica. Talvez ainda haja algum ajuste semelhante que possamos fazer, mas é mais sutil.

@SimonSapin qual é o plano atual para a estabilização TryFrom/TryInto? Não consegui encontrar nada concreto além de ser revertido em 29 de abril.

@nayato Está bloqueado ao estabilizar (novamente) o tipo nunca https://github.com/rust-lang/rust/issues/35121 , que está bloqueado em https://github.com/rust-lang/rust/ questões/49593.

@SimonSapin O tipo nunca é usado apenas aqui . Poderíamos estabilizar TryFrom sem essa implementação e pousar quando nunca estiver estabilizado? TryFrom é uma interface bastante importante, mesmo além da equivalência com Try .

Infelizmente não. Seria uma mudança radical adicionar este impl geral depois que a característica foi estabilizada e as bibliotecas crates.io tiveram a chance de implementar, por exemplo TryFrom<Foo> for Bar enquanto também têm From<Foo> for Bar , como os impls iria se sobrepor.

A decisão até agora foi que tornar TryFrom "compatível" dessa maneira com From foi importante o suficiente para bloquear a estabilização.

Provavelmente pensamento ingênuo e bobo:

E se o rustc obtivesse um lint de erro sempre ativo no meio tempo que acionou Error<_, !> , impedindo quaisquer impls na área do usuário, permitindo a adição de impl s posteriormente?

Ou um impl<T: From<U>, U> TryFrom<U> for T instável com qualquer tipo de erro. Os impls de traço podem ser instáveis?

@jethrogb Não

Não tenho certeza se entendi completamente por que isso está bloqueado no tipo nunca, mas o fato de estar me sugere que um mecanismo importante está faltando no Rust. Parece que deve haver uma maneira de reservar a implementação desejada para definição futura. Talvez esse mecanismo seja algo como torná-lo instável, mas idealmente seria um mecanismo utilizável por caixas não-std também. Alguém tem conhecimento de uma solução proposta para este problema?

Ele está bloqueado por causa deste impl:

impl<T, U> TryFrom<U> for T where T: From<U> {
    type Error = !;

    fn try_from(value: U) -> Result<Self, Self::Error> {
        Ok(T::from(value))
    }
}

@rust-lang/libs O que você acha de voltar para enum Infallible {} em vez do tipo nunca, para desbloquear?

Eu pessoalmente não me importo de ter enum Infalliable {} e depois mudar para type Infalliable = ! .

Discutimos isso na reunião de triagem de libs de ontem, mas não conseguimos lembrar se tal mudança seria aceitável após a estabilização ou se já havíamos rejeitado essa ideia por causa de uma possível quebra, ou qual seria essa quebra.

O que nos lembramos é que só conseguimos fazer uma dessas substituições: se duas enumerações vazias da biblioteca padrão mais estável (tipos separados) mais tarde se tornarem do mesmo tipo, uma caixa que tivesse alguns impl s para ambos quebraria à medida que os impls se sobrepõem (ou são idênticos). Por exemplo impl From<std::convert::Invallible> for MyError e impl From<std::string::ParseError> for MyError .

A propósito, temos std::string::ParseError que, até onde sei, é a única enumeração vazia na biblioteca padrão 1.30.0. Portanto, se pudermos ter certeza de que não há outro problema com esse plano, poderíamos:

  • Mover string::ParseError para convert::Infallible
  • Reexporte-o em seu antigo local com pub use ou pub type (isso faz diferença?)
  • Use-o na implementação do cobertor TryFrom
  • Mais tarde, na mesma versão em que o tipo never é estabilizado, substitua string::ParseError e convert::Infallible por type _ = ! e use ! diretamente onde eles foram usados.
  • (Opcional) Mais tarde, emita avisos de suspensão de uso para os aliases antigos

Tendo adicionado apenas e relutantemente uma característica de espaço reservado TryFrom à minha própria caixa, e reconhecidamente sem entender completamente as implicações do RFC e dos esforços de estabilização, estou surpreso que um cobertor TryFrom por From com o tipo de erro Infallible / ! é o que está segurando isso? Não são esses objetivos secundários, depois de estabelecer um std TryFrom estável e TryInto traços gerais? Quero dizer, mesmo sem a elevação de From para TryFrom (propósito que não entendo completamente), não seria menos rotatividade se cada caixa que precisa não adicionasse seu próprio TryFrom ?

O problema óbvio se TryFrom for enviado sem a implementação geral para From é que os engradados podem implementar TryFrom e From para os mesmos tipos (possivelmente exatamente porque o cobertor impl não está lá), e eles quebrariam quando o cobertor impl fosse adicionado a libstd.

Embora, pensando bem, talvez não fosse uma mudança de ruptura com a especialização?

Hmm, por favor me perdoe novamente se eu estiver perdendo o óbvio . É quase como se essas odisseias de RFC/rastreamento de 1,5 ano precisassem de uma seção de perguntas frequentes para entender a progressão lógica. E se a orientação fosse apenas implementar From apenas para conversões infalíveis e TryFrom apenas para conversões falíveis? Isso não dá um resultado final prático semelhante, ao mesmo tempo em que oferece menos barreiras para enviá-lo em caixas padrão e ajustar?

Sim, como @glandium disse, adicionar o impl cobertor depois que os traços já estiverem estáveis ​​é uma mudança radical. A estabilização ainda não está pronta e não está claro se ela permitiria esse tipo de interseção impl de qualquer maneira (em vez de apenas impls "estritamente mais específicos").

Fornecer orientação (documentos?) que digam para não escrever programas que não funcionem não é bom o suficiente para justificar alterações de quebra. A ruptura definitivamente ainda aconteceria.

O que é necessário para que o plano descrito em https://github.com/rust-lang/rust/issues/33417#issuecomment -423073898 comece a acontecer? O que pode ser feito para ajudar?

É uma tarefa um pouco ingrata, mas ajudaria se alguém pudesse passar por esse problema de rastreamento e https://github.com/rust-lang/rust/issues/35121 para verificar se há um problema com esse plano que estamos Já discutimos antes e esqueci desde então, em particular se substituir enum Infallible por type Infallible = !; depois que o enum ficou estável em uma versão anterior poderia ser uma mudança importante.

O enum Infallible precisa ser estabilizado junto com o traço? Se for mantido instável, ninguém pode nomeá-lo e, portanto, trocá-lo por ! mais tarde deve ser bom?

@seanmonstar Não, você pode se referir a ele usando <u16 as TryFrom<u8>>::Error e é considerado um nome estável. Testemunha:

// src/lib.rs
#![feature(staged_api)]
#![stable(since = "1.0.0", feature = "a")]

#[stable(since = "1.0.0", feature = "a")]
pub trait T1 {
    #[stable(since = "1.0.0", feature = "a")]
    type A;
}

#[unstable(issue = "12345", feature = "b")]
pub struct E;

#[stable(since = "1.0.0", feature = "a")]
impl T1 for u8 {
    type A = E;
}
// src/bin/b.rs
extern crate a;

trait T3 {}

impl T3 for <u8 as a::T1>::A {}
impl T3 for a::E {}

fn main() {}

O primeiro T3 impl não causa nenhum erro. Apenas o segundo T3 impl causa o erro E0658 "uso de recurso de biblioteca instável".

Isso é... Uau, só pedindo para ser mordido >_<

Eu pessoalmente uso o truque de tornar um tipo público em um módulo não exportado para retornar tipos inomináveis ​​e, embora alguém fazendo o que você disse significaria quebra se fizesse isso, meus sentimentos são "vergonha para eles" e não considero ajustando o tipo inominável como quebrando.

Pessoalmente, não me importo de garantir que cada uma dessas enumerações não-realmente nunca em libstd seja a mesma, alterando-a para um alias de tipo para ! quando isso se estabilizar. Parece-me razoável.

Sem relação com toda essa confusão de tipo, qual é a escolha de design para uma conversão falível em um tipo não Copy ? Deve-se prestar atenção para evitar o descarte da entrada fornecida, para que ela possa ser recuperada em caso de falha.

Por exemplo, com String::from_utf8 , podemos ver que o tipo de erro contém a entrada de propriedade Vec<u8> para poder devolvê-la :

// some invalid bytes, in a vector
let sparkle_heart = vec![0, 159, 146, 150];

match String::from_utf8(sparkle_heart) {
    Ok(string) => {
        // owned String binding in this scope
        let _: String = string;
    },
    Err(err) => {
        let vec: Vec<u8> = err.into_bytes(); // we got the owned vec back !
        assert_eq!(vec, vec![0, 159, 146, 150]);
    },
};

Então, se tivéssemos uma implementação de String: TryFrom<Vec<u8>> , seria esperado que <String as TryFrom<Vec<u8>>>::Error fosse FromUtf8Error certo?

Sim, dar o valor de entrada "back" no tipo de erro é uma opção válida. Pegar uma referência com impl<'a> TryFrom<&'a Foo> for Bar é outra.

Neste exemplo específico, no entanto, não tenho certeza de que um impl TryFrom seja apropriado. UTF-8 é apenas uma das possíveis decodificações de bytes em Unicode, e from_utf8 reflete isso em seu nome.

É uma tarefa um pouco ingrata, mas ajudaria se alguém pudesse passar por esse problema de rastreamento e #35121 para verificar se há um problema com esse plano que discutimos antes e esquecemos desde então, em particular se substituir enum Infallible com type Infallible = !; depois que o enum ficou estável em uma versão anterior pode ser uma mudança importante.

Não houve problemas concretos apontados com isso nesta edição ou #35121. Havia uma preocupação em torno da possibilidade de ! ser especial de alguma forma, os tipos desinibidos não são. Mas não há preocupações no PR e fica claro pelos comentários de revisão de código que o enum se tornar estável era uma possibilidade (embora isso nunca tenha acontecido). Aqui estão os links para o que eu encontrei.

Conceito original
Uma preocupação abstrata
Vá em frente da equipe lib

Seguido por:

44174 29 de setembro: adicionado o tipo Infalível

47630 14 de março: estabilizar o tipo nunca!

49038 22 de março: convertido Infalível para o tipo nunca !

49305 27 de março: TryFrom estabilizado

49518 30 de março: removido do prelúdio

50121 21 de abril: não estabilizado

Na nota de impls de cobertor para quando ! se estabilizar,

isso funcionaria?

impl<T, U> TryFrom<U> for T
where U: Into<T> {
    type Err = !;

    fn try_from(u: U) -> Result<Self, !> { Ok(u.into()) }
}

impl<T, U> TryInto<U> for T
where U: TryFrom<T> {
    type Err = U::Err;

    fn try_into(self) -> Result<U, !> { U::try_from(self) }
}

Desta forma, todas as conversões infalíveis (com From e Into ) obtêm um TryFrom correspondente e TryInto impl onde Err é ! e obtemos um TryInto impl para cada TryFrom impl como obtemos um Into impl para cada From impl.

Desta forma, se você quisesse escrever um impl, você tentaria From então Into então TryFrom então TryInto , e um desses funcionaria para o seu cenário , se precisar de conversões infalíveis, use From ou Into (com base nas regras de coerência) e, se precisar de conversões falíveis, use TryFrom ou TryInto (baseado em regras de coerência). Se você precisar de restrições, pode escolher TryInto ou Into com base em se você pode lidar com conversões falíveis. TryInto seriam todas as conversões e Into seriam todas as conversões infalíveis.

Então podemos usar $ impl TryFrom<T, Err = !> ou impl TryInto<T, Err = !> com uma dica para usar impl From<T> ou impl Into<T> .

Ah, não os vi (muitos outros imps desordenaram os documentos). Poderíamos mudar

impl<T, U> TryFrom<U> for T where    T: From<U>,

para

impl<T, U> TryFrom<U> for T where    U: Into<T>,

porque isso permitirá estritamente mais impls, e não puniria pessoas que só podem impl Into por razões de coerência, dessa forma eles também obtêm o impl automático para TryFrom , e o From impl seria aplicado de forma transitiva devido ao impl cobertor para Into .

Você gostaria de experimentá-lo, ver se ele compila e enviar uma solicitação pull?

Sim, eu gostaria de tentar.

Existe um caso em que From<U> for T não pode ser implementado, mas Into<T> for U e TryFrom<U> for T podem ser?

Sim, por exemplo

use other_crate::OtherType;

struct MyType;

// this impl will fail to compile
impl From<MyType> for OtherType {
    fn from(my_type: MyType) -> OtherType {
        // impl details that don't matter
    }
}

// this impl will not fail to compile
impl Into<OtherType> for MyType {
    fn into(self) -> OtherType {
        // impl details that don't matter
    }
}

Isto é devido a regras órfãs.
TryFrom e From são os mesmos em termos de regras órfãs, e da mesma forma TryInto e Into são os mesmos em termos de regras órfãs

@KrishnaSannasi seu exemplo irá compilar, basta olhar para este exemplo de playground

Acho que a regra de coerência mais atualizada está descrita nesta RFC e ambas as implementações devem ser permitidas.

struct MyType<T>(T);

impl<T> From<MyType<T>> for (T,) {
    fn from(my_type: MyType<T>) -> Self {
        unimplemented!()
    }
}

impl<T> Into<(T,)> for MyType<T> {
    fn into(self) -> (T,) {
        unimplemented!()
    }
}

Parque infantil
Ah sim, mas assim que você adiciona parâmetros genéricos, cai. Se você tentar compilar cada impl individualmente, o From falhará ao compilar, enquanto o Into compilará.


Outra razão pela qual eu quero essa mudança é porque atualmente, se você escreve um Into impl, você não faz e não pode obter automaticamente um TryInto impl. Eu vejo isso como um design ruim porque é inconsistente com From e TryFrom .

Estou mais perguntando se há um caso em que você gostaria que TryFrom<U> for T fosse derivado automaticamente de Into<T> for U , mas onde From<U> for T não pudesse ser implementado. Parece-me sem sentido.

Eu certamente entendo querer um impl cobertor de Into para TryInto , mas infelizmente o sistema de tipos não permite atualmente um impl geral porque entra em conflito com os outros From para TryFrom e From para Into impls cobertores (ou, pelo menos, é o meu entendimento).

Estou bastante certo de que esses casos de uso eram exatamente o que o RFC @kjetilkjeka vinculado deveria corrigir. Depois que isso for implementado, nunca deve haver um caso em que você possa implementar Into mas não From .

@scottjmaddox Eu não quero necessariamente que Into implique TryFrom tanto quanto eu quero que Into implique TryInto . Também From e Into são semanticamente equivalentes, só porque não podemos expressar isso em nosso sistema de traços não significa que

TryFrom<U> for T seja derivado automaticamente de Into<T> for U , mas where From<U> for T não pode ser implementado.

é sem sentido. Em um mundo perfeito, não teríamos a distinção e haveria apenas um traço para converter entre os tipos, mas devido a limitações no sistema, acabamos com dois. Isso não vai mudar devido a garantias de estabilidade, mas podemos tratar essas duas características como o mesmo conceito e partir daí.

@clarcharr Mesmo que seja esse o caso, ainda não há como ter Into implicar TryInto e TryFrom implicar TryInto diretamente, pois os dois cobertores impls entraria em conflito. Eu gostaria que Into implicasse TryInto , e TryFrom implicasse TryInto . A forma que proponho irá fazê-lo, ainda que indiretamente.


Fiz um comentário no meu pull request onde coloquei meus principais motivos para essa mudança aqui


Isso é menos importante que os motivos acima, mas também gosto que com este impl, todas as conversões infalíveis agora terão uma contrapartida falível, onde o tipo Err da versão falível é ! .

Só para ficar claro, eu quero esses quatro impls

  • From implica Into (para estabilidade)
  • TryFrom implica TryInto ou TryInto implica TryFrom (porque eles são semanticamente equivalentes)
  • From implica TryFrom
  • Into implica TryInto

Eu não me importo se eles são feitos direta ou indiretamente.

Ou podemos fazer TryInto um alias:

trait TryInto<T> = where T: TryFrom<Self>;

Não tenho ideia se isso realmente funcionaria, mas parece bastante simples.

Onde o método into seria definido?

@clarcharr Como o @SimonSapin disse, onde o método into seria definido. Não temos aliases de traços. Isso precisaria ser outro RFC. E precisaríamos bloquear este nesse RFC, o que é indesejável.

@KrishnaSannasi Atualmente, é impossível ter todos os quatro From => Into , From => TryFrom , TryFrom => TryInto e Into => TryInto , porque tudo que implementa From tem dois impl concorrentes para TryInto (um de From => Into => TryInto , e outro de From => TryFrom => TryInto ).

Como From => Into e TryFrom => TryInto são críticos, Into => TryInto precisa ser sacrificado. Ou pelo menos esse é o meu entendimento.

Sim, eu não achei o alias TryInto corretamente, então apenas me ignore ><

Eu concordo com o que @scottjmaddox diz.

@scottjmaddox

Apenas para esclarecer um possível mal-entendido, estou removendo From auto impls TryFrom e substituindo-o por Into auto impls TryFrom .

Atualmente, é impossível ter todos os quatro From => Into, From => TryFrom, TryFrom => TryInto e Into => TryInto

Isso é simplesmente falso, basta olhar para a implementação proposta.

Este:
From -> Into -> TryFrom -> TryInto

From implementações automáticas Into
Into implementações automáticas TryFrom
TryFrom implementações automáticas TryInto

Por extensão, devido aos auto-impls transitivos, obtemos
From implica TryFrom (porque From auto imps Into e Into auto imps TryFrom )
Into implica TryInto (porque Into auto imps TryFrom e TryFrom auto imps TryInto )
cada um com 1 nível de indireção (acho que isso é bom)

Então todas as minhas condições foram atendidas.
Poderíamos ter todos

Impl | níveis de indireção
-----------------------|-------------------
From implica Into | Sem indireção
TryFrom implica TryInto | Sem indireção
From implica TryFrom | 1 nível de indireção
Into implica TryInto | 1 nível de indireção


From -> Into -> TryInto -> TryFrom
Também funcionaria, mas gostaria de manter a consistência e a versão original (vista acima) é mais consistente do que esta versão


Nota sobre a terminologia: (quando eu os uso)

-> significa implementação automática
auto impl refere-se ao impl real que é digitado.
implica significa que se eu tiver esse impl, também obterei esse impl


Observe também que fiz um pull request e todos os testes foram aprovados, ou seja, não há um buraco na minha lógica, isso é possível. Só precisamos discutir se esse comportamento é desejado. Acho que sim, porque vai preservar a consistência (o que é muito importante).

pull request

@KrishnaSannasi Ahhh, agora eu vejo seu ponto. Sim, isso faz sentido e agora vejo como a mudança proposta resolve o problema e fornece o comportamento desejado. Obrigado pela explicação completa!

Edit : ok, mas espere... Eu ainda não entendo porque os impl atuais do cobertor não são suficientes. Presumivelmente, há um caso em que você seria capaz de implementar Into mas não From ? E ainda é possível derivar automaticamente TryFrom ?

Edit 2 : Ok, acabei de voltar e ler sua explicação sobre como a regra órfã pode impedir implementações de From . Tudo faz sentido agora. Eu apoio totalmente essa mudança. Ainda é um pouco frustrante que a regra dos órfãos tenha tantas consequências não intencionais, mas não vamos consertar isso aqui, então faz sentido fazer o melhor das coisas.

Alguém de @rust-lang/libs estaria disposto a iniciar o FCP sobre isso agora que https://github.com/rust-lang/rust/issues/49593 foi corrigido e never_type é um candidato para estabilização de novo?

Esse recurso já passou pelo FCP para estabilização. ( Proposta , conclusão .) Acho que não é necessário passar por mais 10 dias para comentários.

Depois que um PR de estabilização para o tipo never chegar, podemos fazer um (novo) PR de estabilização para TryFrom / TryInto e usar o rfcbot lá para garantir que os membros da equipe o vejam.

Seria possível alterar os tipos de enum vazios existentes como FromStringError para serem aliases para ! junto com a estabilização?

@clarcharr Sim. Por causa da coerência implícita do traço, só conseguimos fazer isso para um tipo. Felizmente, só temos std::string::ParseError . (E é exatamente por isso que o usamos em vez de adicionar um novo tipo para impl FromString for PathBuf em https://github.com/rust-lang/rust/pull/55148.)

No entanto, isso não está relacionado a TryFrom / TryInto . Este é um tópico para https://github.com/rust-lang/rust/issues/49691 / https://github.com/rust-lang/rust/issues/57012 , pois precisa acontecer no mesmo ciclo de lançamento como estabilização de ! .

@SimonSapin Seria possível mesclar minha solicitação de pull (# 56796) antes que isso se estabilize?

Claro, adicionei-o à descrição do problema para não perdermos o controle.

Obrigado!

Isso poderia ser integrado ao estável? É uma dependência de argdata para CloudABI.

@mcandre Sim. No momento, isso está aguardando https://github.com/rust-lang/rust/issues/57012 , que foi desbloqueado recentemente. Espero que TryFrom chegue ao Rust 1.33 ou 1.34.

Estou cansado de esperar por isso e tenho tempo livre (finalmente). Se houver algum tipo de código ou documentação que eu possa fazer para avançar, eu me ofereço para ajudar.

56796 foi mesclado. Então podemos verificar isso.

@icefoxen Acho que agora o melhor que você pode fazer é fornecer feedback (e alterações) sobre os documentos dessas características. No momento, acho-os um pouco carentes em comparação com os traços From e Into .

Trabalhando na documentação. Pequeno obstáculo: eu quero assert_eq!(some_value, std::num::TryFromIntError(())); . No entanto, como TryFromIntError não tem construtor nem campos públicos, não posso criar uma instância dele. Alguma dica de como proceder? Por enquanto estou apenas fazendo assert!(some_value_result.is_err()); .

Desculpe se este é o lugar errado, mas parece que há um erro nos documentos para TryFromIntError - ele lista impl Debug for TryFromIntError , quando na verdade não há código para ele. O compilador gera um erro atualmente ao tentar desempacotar um resultado de um uso TryFrom.

Existe a intenção de haver um fmt::Debug impl sobre isso?

@marco9999

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromIntError(());

Tem um atributo #[derive(Debug)] .

Hmm sim, há... algo não está funcionando corretamente?

error[E0599]: no method named `unwrap` found for type `std::result::Result<usize, <T as std::convert::TryInto<usize>>::Error>` in the current scope
  --> src\types\b8_memory_mapper.rs:67:51
   |
67 |         let address: usize = T::try_into(address).unwrap();
   |                                                   ^^^^^^
   |
   = note: the method `unwrap` exists but the following trait bounds were not satisfied:
           `<T as std::convert::TryInto<usize>>::Error : std::fmt::Debug`

@marco9999 Você provavelmente está perdendo uma restrição genérica. TryFromIntError é usado apenas por alguns tipos, mas seu T pode ser qualquer coisa:

fn foo<T: TryInto<u8>>(x: T) -> u8
where
    <T as TryInto<u8>>::Error: Debug,
{
    x.try_into().unwrap()
}

De qualquer forma, isso está um pouco fora do tópico, desculpe a todos. O IRC pode ser um lugar melhor para fazer essas perguntas.

Eu quero assert_eq!(some_value, std::num::TryFromIntError(()));

@icefoxen Não há valor útil associado a TryFromIntError , então essa afirmação não parece ter muito valor. Se você tem um Result<_, TryFromIntError> e é um Err , não há outro valor que possa ser.

assert!(some_value_result.is_err());

Isso me parece razoável.

Obrigado @glaebhoerl.

Devido a um bug de bloqueio sendo corrigido (https://github.com/rust-lang/rust/issues/49593) eu esperava que o tipo never pudesse ser estabilizado Soon® https://github.com/rust-lang/ rust/issues/57012 e desbloqueie isso. No entanto, surgiu um novo problema (https://github.com/rust-lang/rust/issues/57012#issuecomment-460740678) e também não temos consenso sobre outro (https://github.com /rust-lang/rust/issues/57012#issuecomment-449098855).

Então, em uma reunião de bibliotecas na semana passada, trouxe novamente a ideia, acredito que proposta pela primeira vez por @scottmcm em https://github.com/rust-lang/rust/issues/33417#issuecomment -299124605, para estabilizar TryFrom e TryInto sem o tipo never e, em vez disso, ter um enum vazio que mais tarde poderia ser um alias para ! .

Na última vez que discutimos isso (https://github.com/rust-lang/rust/issues/33417#issuecomment-423069246), não conseguimos lembrar por que não tínhamos feito isso da vez anterior.

Na semana passada @dtolnay nos lembrou do problema: antes ! se tornar um tipo completo, ele já pode ser usado no lugar do tipo de retorno de uma função para indicar que ela nunca retorna. Isso inclui tipos de ponteiro de função. Portanto, supondo que https://github.com/rust-lang/rust/pull/58302 chegue neste ciclo, um código como este será válido no Rust 1.34.0:

use std::convert::Infallible;
trait MyTrait {}
impl MyTrait for fn() -> ! {}
impl MyTrait for fn() -> Infallible {}

Como fn() -> ! e fn() -> Infallible são dois tipos diferentes (ponteiro), os dois imps não se sobrepõem. Mas se substituirmos o enum vazio por um alias do tipo pub type Infallible = !; (quando ! se torna um tipo completo), os dois impls começarão a se sobrepor e o código como o acima será interrompido.

Portanto, alterar o enum para um alias seria uma mudança importante. Em princípio, não permitiríamos isso na biblioteca padrão, mas neste caso sentimos que:

  • É preciso sair do seu caminho para construir o código que é quebrado por essa mudança, então parece improvável que isso aconteça na prática
  • Usaremos Crater para obter sinal adicional quando chegar a hora
  • Se acabarmos julgando a quebra como significativa o suficiente, ter o tipo nunca e um enum vazio com o mesmo papel é uma inconsistência com a qual podemos conviver.

Com base nessa discussão, enviei https://github.com/rust-lang/rust/pull/58302 , que agora está no período de comentários finais.

58015 deve estar pronto para revisão/mesclagem agora.

@kennytm Já não é possível fazer referência a ! no stable? Por exemplo, considere o seguinte:

trait MyTrait {
    type Output;
}

impl<T> MyTrait for fn() -> T {
    type Output = T;
}

type Void = <fn() -> ! as MyTrait>::Output;

Depois de fazer isso, Void se refere ao tipo ! .

Isso parece um bug, o que significa que as garantias de estabilidade não se estendem a ele. Usar o tipo never ( ! ) como um tipo em qualquer capacidade ainda é instável, pelo menos até que #57012 seja mesclado.

Como posso ajudar com a documentação? :-)

Ah, eu pensei que https://github.com/rust-lang/rust/pull/58015 tinha pousado, mas ainda não… Vamos discutir isso aí.

O traço TryFrom poderia ter um método para verificar se o argumento pode ser convertido sem consumi-lo?

fn check(value: &T) -> bool

Uma maneira de trabalhar com a conversão impossível sem consumo pode ser retornar o valor não conversível consumido junto com o erro associado.

Ops, isso deveria ter sido fechado por https://github.com/rust-lang/rust/pull/58302. Fechando agora.


@o01eg A maneira típica de fazer conversão sem consumo é implementar, por exemplo TryFrom<&'_ Foo> em vez de TryFrom<Foo> .

Espere... isso não deve fechar até aterrissar na quinta-feira estável, certo?

Não, fechamos os problemas de rastreamento quando o PR estabilizando o recurso chega ao mestre.

Não, geralmente fechamos o problema de rastreamento quando a estabilização ou remoção chega ao ramo master . Depois disso, não há mais nada para rastrear. (A menos que um bug recém-relatado apareça, mas lidamos com isso separadamente.)

Os problemas de rastreamento são encerrados pelo PR que os estabiliza. Dependendo do ciclo de lançamento, isso pode levar até 12 semanas antes do lançamento do stable.

Entendi. Obrigado pelo esclarecimento, pessoal! :)

@gregdegruy atualize sua versão do Rust para 1.34 ou superior.

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