Problema de rastreamento para https://github.com/rust-lang/rfcs/pull/1542
Façam:
TryFrom
impl cobertor para usar Into
em vez de From
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.
std
eles serão implementados?impl TryFrom<T> for T
?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 implFrom<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.
@SimonSapin https://github.com/rust-lang/rfcs/pull/1216
@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
eInto
aos tipos de biblioteca padrão, espero que façamos o mesmo paraTryFrom
eTryInto
.
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 umimpl 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
emTryFrom::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<...>
ouTryFrom<...>
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
:
TryFrom<str>
na assinatura, que está corretamente no módulo de conversão e é mais óbvio o que fazTryFrom<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:
SomeType::default()
SomeType::from(other)
está implementado (você pode apenas digitá-lo e ver se ele compila, sem buscar documentação)clone()
try_from
- oh espere: PTodos 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 De
para U provavelmente deve implicar TryFrom para 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 impl
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)
apenasT::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:
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:
TryFrom
destina-se a conversões falíveis _somente_, portanto, não possui itens como u8 -> u128
ou usize -> usize
.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 oimpl<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:
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)Infallible
como o tipo Error
no impl geraltype Infallible = !;
como parte da estabilização do tipo nuncaEu 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 exemplotry!
)!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
ouTryFrom
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âmetrosTryInto<...>
ouTryFrom<...>
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>
eAsRef<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:
string::ParseError
para convert::Infallible
pub use
ou pub type
(isso faz diferença?)TryFrom
string::ParseError
e convert::Infallible
por type _ = !
e use !
diretamente onde eles foram usados.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
comtype 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:
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>
.
Já temos esses dois impls:
https://doc.rust-lang.org/1.31.0/std/convert/trait.TryFrom.html#impl -TryFrom%3CU%3E
https://doc.rust-lang.org/1.31.0/std/convert/trait.TryInto.html#impl -TryInto%3CU%3E
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 deInto<T> for U
, maswhere 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).
@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.
@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.
Referência cruzada: https://github.com/rust-lang/rust/pull/58302
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:
Com base nessa discussão, enviei https://github.com/rust-lang/rust/pull/58302 , que agora está no período de comentários finais.
@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.
Comentários muito úteis
Eu gostaria que
!
se tornasse um tipo de vocabulário suficiente para queResult<_, !>
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.