Rust: Rastreamento de problema para o operador `?` E blocos `try` (recursos RFC 243,` question_mark` e `try_blocks`)

Criado em 5 fev. 2016  ·  340Comentários  ·  Fonte: rust-lang/rust

Problema de rastreamento para rust-lang / rfcs # 243 e rust-lang / rfcs # 1859.

Questões de implementação:

  • [x] ? operador que é aproximadamente equivalente a try! - # 31954
  • [x] try { ... } expression - https://github.com/rust-lang/rust/issues/39849

    • [x] resolver do catch { ... } questão de sintaxe


    • [x] resolver se os blocos de captura devem "quebrar" o valor do resultado (primeiro endereçado em https://github.com/rust-lang/rust/issues/41414, agora sendo resolvido novamente em https://github.com/rust- lang / rust / issues / 70941)

    • [] Resolver problemas com inferência de tipo ( try { expr? }? atualmente requer uma anotação de tipo explícita em algum lugar).

  • [x] definir o design do traço Try (https://github.com/rust-lang/rfcs/pull/1859)

    • [x] implemente o novo Try trait (no lugar de Carrier ) e converta ? para usá-lo (https://github.com/rust-lang/rust/pull / 42275)

    • [x] adicione impls para Option e assim por diante, e uma família adequada de testes (https://github.com/rust-lang/rust/pull/42526)

    • [x] melhorar as mensagens de erro conforme descrito no RFC (https://github.com/rust-lang/rust/issues/35946)

  • [x] reserva try na nova edição
  • [x] bloquear try{}catch (ou outros idents seguintes) para deixar o espaço de design aberto para o futuro e mostrar às pessoas como fazer o que desejam com match
A-error-handling B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-try_blocks Libs-Tracked T-lang T-libs

Comentários muito úteis

@ mark-im Não acho que possamos mover razoavelmente de um para o outro após a estabilização. Tão ruim quanto eu considero que o envoltório Ok seja, o envoltório OK inconsistente que tenta adivinhar se você quer ou não seria ainda pior.

Todos 340 comentários

O RFC que acompanha discute um desugaring baseado em retorno / intervalo rotulado, estamos obtendo isso também ou haverá apenas um tratamento especial para ? e catch no compilador?

EDIT: Acho que o retorno / intervalo rotulado é uma excelente ideia separada de ? e catch , então, se a resposta for não, provavelmente abrirei um RFC separado para ele.

O retorno / interrupção rotulados é puramente para fins explicativos.

Na sexta-feira, 5 de fevereiro de 2016 às 15:56, Jonathan Reem [email protected]
escrevi:

O RFC que acompanha discute um desugaring com base no retorno / pausa rotulados,
estamos recebendo isso também ou haverá apenas um tratamento especial para? e
pegar no compilador?

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -180551605.

Outra questão não resolvida que temos que resolver antes de estabilizar é qual deveria ser o contrato que impl s de Into tem que obedecer - ou se Into é mesmo a característica certa a ser usada para o upcasting de erro aqui. (Talvez este deva ser outro item da lista de verificação?)

@reem

Acho que o retorno / intervalo rotulado é uma excelente ideia ... Provavelmente abrirei um RFC separado para isso.

Por favor faça!

Sobre o assunto Carrier traço, aqui está um exemplo essencial de tal característica que escrevi no início do processo de RFC.
https://gist.github.com/thepowersgang/f0de63db1746114266d3

Como isso é tratado durante a análise?

struct catch {
    a: u8
}

fn main() {

    let b = 10;
    catch { a: b } // struct literal or catch expression with type ascription inside?

}

@petrochenkov Bem, a definição não pode afetar a análise, mas acho que ainda temos uma regra de lookahead, baseada no segundo token após { , : neste caso, então ainda deve ser analisado como um literal de estrutura.

Além disso

let c1 = catch { a: 10 };
let c2 = catch { ..c1 }; // <--

struct catch {}
let c3 = catch {}; // <--

+ https://github.com/rust-lang/rfcs/issues/306 se (quando!) implementado.
Parece que não há conflitos além de literais de estrutura.

Dados os exemplos acima, sou a favor da solução mais simples (como sempre) - sempre trate catch { nas posições de expressão como o início de um bloco catch . Ninguém chama suas estruturas de catch qualquer maneira.

Seria mais fácil se uma palavra-chave fosse usada em vez de catch .

Esta é a lista de palavras-chave: http://doc.rust-lang.org/nightly/grammar.html#keywords

@bluss sim, eu admito que nenhum deles é ótimo ... override parece o único que está perto. Ou podemos usar do , heh. Ou uma combinação, embora eu não veja nenhuma ótima imediatamente. do catch ?

do é o único que parece estar próximo da IMO. Uma sopa de palavras-chave com do como prefixo é um pouco irregular, não é semelhante a nenhuma outra parte da linguagem? while let uma sopa de palavras-chave? Esse parece bom agora, quando você está acostumado com isso.

porta try! para usar ?

? ser transferido para usar try! ? Isso permitiria o caso de uso em que você deseja obter um caminho de retorno Result , por exemplo, durante a depuração. Com try! isso é bastante fácil, você apenas substitui a macro no início do arquivo (ou em lib / main.rs):

macro_rules! try {
    ($expr:expr) => (match $expr {
        Result::Ok(val) => val,
        Result::Err(err) => {
            panic!("Error occured: {:?}", err)
        }
    })
}

Você obterá um rastreamento de pilha de pânico a partir da primeira ocorrência de try! no caminho de retorno Result . Na verdade, se você fizer try!(Err(sth)) se descobrir um erro em vez de return Err(sth) , você obterá até mesmo o rastreamento de pilha completo.

Mas ao depurar bibliotecas estrangeiras escritas por pessoas que não implementaram esse truque, alguém confia no uso de try! algum lugar superior na cadeia. E agora, se a biblioteca usar o operador ? com comportamento codificado, obter um rastreamento de pilha será quase impossível.

Seria legal se a substituição de try! afetasse o operador ? também.

Mais tarde, quando o sistema macro obtiver mais recursos, você pode até entrar em pânico! apenas para tipos específicos.

Se esta proposta requer um RFC, por favor me avise.

Idealmente, ? poderia apenas ser estendido para fornecer suporte de rastreamento de pilha diretamente, em vez de depender da capacidade de substituir try! . Então funcionaria em qualquer lugar.

Os rastreamentos de pilha são apenas um exemplo (embora muito útil, para mim).
Se o traço Carrier for feito para funcionar, talvez isso possa abranger tais extensões?

No domingo, 7 de fevereiro de 2016 às 16h14, Russell Johnston [email protected]
escrevi:

Idealmente? poderia ser estendido para fornecer suporte de rastreamento de pilha diretamente,
em vez de confiar na capacidade de ignorar try !. Então funcionaria
em toda parte.

-
Responda a este e-mail diretamente ou visualize-o no GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -181118499.

Sem querer especular, acho que poderia funcionar, embora com alguns problemas.

Considere o caso usual em que um código retorna algum Result<V,E> . Agora precisaríamos permitir que várias implementações do traço Carrier coexistissem. Para não encontrar E0119 , é necessário fazer todas as implementações fora do escopo (possivelmente por meio de características diferentes que são por padrão não importadas), e ao usar o operador ? , o usuário deve importar o implementação.

Isso exigiria que todos, mesmo aqueles que não desejam depurar, importassem sua implementação de característica desejada ao usar ? , não haveria opção para um padrão predefinido.

Possivelmente, E0117 também pode ser um problema se você deseja fazer implementações personalizadas de Carrier para Result<V,E> , onde todos os tipos estão fora dos limites, então libstd já deve fornecer um conjunto de implementações de Carrier trait com os casos de uso mais usados ​​(implementação trivial e implementação panic! ing, talvez mais).

Ter a possibilidade de substituir por meio de uma macro forneceria uma maior flexibilidade sem a carga adicional sobre o implementador original (eles não precisam importar a implementação desejada). Mas também vejo que a ferrugem nunca teve um operador baseado em macro antes, e que implementar ? por meio de uma macro não é possível se catch { ... } deve funcionar, pelo menos não sem itens de linguagem adicionais ( return_to_catch , throw , rotulado break com o parâmetro usado no RFC 243).

Aceito qualquer configuração que permita obter Result stacktraces com um caminho de retorno Err , tendo apenas que modificar uma pequena quantidade de código, de preferência no topo do arquivo. A solução também deve funcionar independentemente de como e onde o tipo Err é implementado.

Só para falar sobre bicicletas: catch in { ... } flui muito bem.

catch! { ... } é outra escolha de backcompat.

Além disso, não que eu espere que isso mude alguma coisa, mas uma observação de que isso vai quebrar macros de múltiplos braços que estavam aceitando $i:ident ? , da mesma forma que a atribuição de tipo quebrou $i:ident : $t:ty .

Não exagere na compatibilidade com versões anteriores, apenas trate catch como uma palavra-chave quando seguida por { (possivelmente apenas na posição da expressão, mas não tenho certeza se isso muda muito em termos de compatibilidade).

Também posso imaginar alguns possíveis problemas que não envolvem literais de estrutura (por exemplo, let catch = true; if catch {} ); mas eu prefiro uma pequena alteração de interrupção em uma sintaxe mais feia.

Não tínhamos um para adicionar novas palavras-chave, de qualquer maneira? Poderíamos oferecer algum tipo de from __future__ opt-in para uma nova sintaxe; ou especifique um número de versão do idioma ferrugem na linha de comando / em Cargo.toml.
Duvido muito que, a longo prazo, possamos trabalhar apenas com as palavras-chave que já estão reservadas. Não queremos que nossas palavras-chave tenham três significados diferentes cada, dependendo do contexto.

Concordo. Este nem é o primeiro RFC em que isso surgiu (https://github.com/rust-lang/rfcs/pull/1444 é outro exemplo). Espero que não seja o último. (Também default de https://github.com/rust-lang/rfcs/pull/1210, embora não seja um RFC que eu seja a favor.) Acho que precisamos encontrar uma maneira de adicionar palavras-chave honestas, em vez de tentar descobrir como hackear a gramática ad-hoc para cada novo caso.

Todo o argumento para não reservar várias palavras-chave antes de 1.0 não era que estaríamos definitivamente introduzindo uma maneira de adicionar novas palavras-chave à linguagem de forma compatível com versões anteriores (possivelmente optando explicitamente), então não havia sentido? Parece que agora seria um bom momento.

@japaric Você está interessado em reviver seu antigo RP e assumir isso?

@aturon Minha implementação simplesmente removeu foo? da mesma maneira que try!(foo) . Também funcionou apenas em chamadas de método e função, ou seja, foo.bar()? e baz()? funcionam, mas quux? e (quux)? não. Isso seria bom para uma implementação inicial?

@japaric Qual foi a razão para restringi-lo a métodos e chamadas de função? Não seria mais fácil analisar como um operador geral de postfix?

Por que restringi-lo a métodos e chamadas de função?

maneira mais fácil (para mim) de testar a expansão ?

Não seria mais fácil analisar como um operador geral de postfix?

provavelmente

@japaric Provavelmente seria bom generalizá-lo para um operador postfix completo (como @eddyb está sugerindo), mas não há problema em pousar ? com o desugar simples e então adicionar catch mais tarde.

@aturon Tudo bem, vou dar uma olhada na versão postfix na próxima semana se ninguém chegar antes de mim :-).

rebaseou / atualizou meu PR em # 31954 :-)

E quanto ao suporte para fornecer rastreamentos de pilha? Isso está planejado?

Eu odeio ser o cara +1, mas os rastreamentos de pilha salvaram boa parte do meu tempo no passado. Talvez em compilações de depuração e, ao atingir o caminho de erro, o? operador poderia anexar o arquivo / linha a um Vec no Resultado? Talvez o Vec pudesse ser somente depurado também?

E isso pode ser exposto ou transformado em parte da descrição do erro ...

Eu continuo encontrando a situação em que quero usar try! / ? dentro de iteradores que retornam Option<Result<T, E>> . Infelizmente, isso atualmente não funciona. Eu me pergunto se a característica do portador poderia ser sobrecarregada para dar suporte a isso ou isso iria para um From mais genérico?

@hexsel Eu realmente desejo que o tipo Result<> carregue um vec de ponteiros de instrução na depuração e? acrescentaria a ele. Dessa forma, as informações DWARF podem ser usadas para construir um stacktrace legível.

@mitsuhiko Mas como você poderia criar e combinar um padrão Result ? É _apenas_ um enum atm.

Quanto ao embrulho de Option , acredito que você deseja Some(catch {...}) .

Atualmente, meu hábito agora é fazer try!(Err(bla)) vez de return Err() , para que eu possa substituir a macro try mais tarde por uma que entre em pânico, a fim de obter um backtrace. Funciona bem para mim, mas o código com o qual lido é de nível muito baixo e, principalmente, origina os erros. Eu ainda terei que evitar ? se usar um código externo que retorna Result .

@eddyb, ele precisaria de suporte de idioma para transportar valores ocultos, além dos quais você precisa manipular por outros meios. Eu queria saber se isso pode ser feito de outras maneiras, mas não consigo ver como. A única outra maneira seria uma caixa de erro padronizada que pode conter dados adicionais, mas não há nenhum requisito de inserir os erros e a maioria das pessoas não o faz.

@mitsuhiko Posso pensar em um novo método (padrão) no traço Error e TLS.
Este último é usado por desinfetantes às vezes.

@eddyb que só funciona se o Resultado puder ser identificado e requerer que seja encaixotado ou se moverá na memória conforme sobe na pilha.

@mitsuhiko O TLS? Não realmente, você só precisa ser capaz de comparar o erro por valor.

Ou mesmo apenas por tipo (com vinculação de From entradas e saídas), de quantos erros simultâneos você deseja que os rastreamentos de pilha tenham que ser propagados simultaneamente?

Eu sou contra a adição de hacks de compilador específicos de Result , pessoalmente, quando soluções mais simples funcionam.

@eddyb o erro passa para cima na pilha. O que você quer é o EIP em cada nível da pilha, não apenas onde ele se origina. Além disso, os erros são: a) atualmente não comparáveis ​​eb) só porque eles são iguais não significa que sejam o mesmo erro.

de quantos erros simultâneos você deseja que os rastreamentos de pilha tenham que ser propagados simultaneamente

Qualquer erro é detectado e relançado como erro diferente.

Eu sou contra a adição de hacks de compilador específicos para resultados, pessoalmente, quando soluções mais simples funcionam.

Não vejo como funciona uma solução mais simples, mas talvez esteja faltando alguma coisa aí.

Você pode salvar o ponteiro de instrução a cada ? e correlacioná-lo com o tipo de erro.
"Qualquer erro detectado e relançado como erro diferente." Mas como você preservaria essa informação se ela estivesse oculta em Result ?

Mas como você preservaria essa informação se ela estivesse oculta no Result?

Você não precisa armazenar essa informação no resultado. O que você precisa armazenar, porém, é um ID exclusivo para a origem da falha para que você possa correlacioná-la. E como a característica de erro é apenas uma característica e não tem armazenamento, ela pode ser armazenada no resultado. O próprio ponteiro de instrução vec não teria de ser armazenado no resultado, que poderia ir para o TLS.

Uma forma seria invocar um método failure_id(&self) no resultado e ele retornar um i64 / uuid ou algo que identifique a origem da falha.

Isso precisaria de suporte de linguagem, não importa o quê, porque o que você precisa é que, à medida que o resultado passa para cima na pilha, o compilador injeta uma instrução para registrar o quadro de pilha por onde passa. Portanto, o retorno de um resultado pareceria diferente em compilações de depuração.

"o compilador injeta uma instrução para registrar o quadro de pilha por onde ele cai" - mas ? é explícito, isso não é nada como exceções, ou você não gosta de gravar _apenas_ o ? onde passou?

De qualquer forma, se você descompactar manualmente o erro e colocá-lo de volta em um Err , como esse ID seria mantido?

"E como a característica de erro é apenas uma característica e não tem armazenamento, ela poderia ser armazenada no resultado"
Há uma variante disso: a implementação do traço Error poderia ser especial no compilador para adicionar um campo inteiro extra ao tipo, criar o tipo acionaria a geração de um ID e copiar / descartar incrementa / diminui efetivamente o refcount (e, eventualmente, apaga-o do "conjunto de erros em voo" do TLS se Result::unwrap não for usado).

Mas isso entraria em conflito com o traço Copy . Quero dizer, o mesmo aconteceria com seu plano, adicionar qualquer comportamento especial a Result que _não_ seja desencadeado por ? ou outras ações específicas do usuário pode invalidar os Copy invariantes.

EDIT : Neste ponto, você também pode incorporar um Rc<ErrorTrace> lá.
EDIT2 : O que estou dizendo, você pode limpar o rastreamento de erro associado em catch .
EDIT3 : Na verdade, no drop, veja abaixo uma explicação melhor.

"o compilador injeta uma instrução para registrar o quadro de pilha por onde passa" - mas? é explícito, isso não é nada como exceções, ou você não gosta de gravar apenas o? passou?

Isso não funciona porque há muitos quadros que você pode percorrer e que não usam ? . Sem falar que nem todo mundo vai lidar com erros com apenas ? .

De qualquer forma, se você descompactar manualmente o erro e depois colocá-lo de volta em Err, como esse ID seria mantido?

É por isso que teria que haver suporte do compilador. O compilador teria que rastrear as variáveis ​​locais que são os resultados e fazer o melhor para propagar o id do resultado em diante para reenvios. Se isso for muito mágico, pode ser restrito a um subconjunto de operações.

Isso não funciona porque há muitos quadros que você pode percorrer e que não usam ? . Sem falar que nem todo mundo vai lidar com erros com apenas ? .

Ok, eu poderia ver o retorno de Result diretamente em funções complexas com vários caminhos de retorno (alguns dos quais seriam retornos antecipados de ? ).

Se isso for muito mágico, pode ser restrito a um subconjunto de operações.

Ou tornado totalmente explícito. Você tem exemplos de re-empacotamento não ? que teriam que ser magicamente rastreados pelo compilador?

@eddyb O caso comum de tratamento manual de erros é um IoError em que você deseja tratar um subconjunto:

loop {
  match establish_connection() {
    Ok(conn) => { ... },
    Err(err) => {
      if err.kind() == io::ErrorKind::ConnectionRefused {
        continue;
      } else {
        return Err(err);
      }
    }
  }
}

@mitsuhiko Então manter o ID dentro de io::Error definitivamente funcionaria.

Então, para recapitular, um Vec<Option<Trace>> "mapa inteiro esparso" em TLS, codificado por struct ErrorId(usize) e acessado por 3 itens de idioma:

  • fn trace_new(Location) -> ErrorId , ao criar um valor de erro não const
  • fn trace_return(ErrorId, Location) , logo antes de retornar de uma função _declarada_ como -> Result<...> (ou seja, não é uma função genérica no retorno que _apensa_ a ser usada com um tipo Result lá)
  • fn trace_destroy(ErrorId) , ao descartar um valor de erro

Se a transformação for feita no MIR, Location pode ser calculado a partir de Span da instrução que aciona a construção de um valor de erro ou a gravação em Lvalue::Return , que é muito mais confiável do que um ponteiro de instrução IMO (não é uma maneira fácil de obter isso no LLVM, você teria que emitir asm para cada plataforma específica).

@eddyb

Isso não vai levar a um inchaço de tamanho binário?

@ arielb1 Você faria isso no modo de depuração apenas onde o debuginfo incha o binário de qualquer maneira - você também poderia reutilizar o debuginfo inteligentemente _shrug_.

@eddyb o que é um local nesse caso? Não tenho certeza do que é difícil ler o IP. Claro, você precisa de um JS personalizado para cada destino, mas isso não é tão difícil.

@mitsuhiko pode ser a mesma informação que usamos para verificações de estouro e outros pânicos emitidos pelo compilador. Veja core::panicking::panic .

Por que empacotar tantas informações de pilha em Error / Result enquanto a pilha é desenrolada? Por que não apenas executar o código enquanto a pilha ainda está lá? Por exemplo, e se você estiver interessado em variáveis ​​no caminho da pilha? Apenas permita que as pessoas executem o código personalizado quando um Err for invocado, por exemplo, para chamar um depurador de sua escolha. Isso é o que try! já fornece por ser uma macro (substituível). A maneira mais fácil é entrar em pânico com uma caixa Err que imprime a pilha, desde que o programa tenha sido iniciado com os parâmetros corretos.

Com try você pode fazer o que quiser em um caso Err , e você pode substituir a caixa de macro, ou muito escopo para não tocar no código crítico de desempenho, por exemplo, se o erro for difícil de reproduzir e você precisa executar muito código crítico de desempenho.

Ninguém precisaria preservar uma fração das informações em uma pilha artificial que alguém constrói porque a verdadeira será destruída. Todo o método de substituição de macro pode ser melhorado com:

  • ? sendo substituível de maneira semelhante, a maneira mais fácil seria definir ? como açúcar para try! - especialmente necessário para detectar erros não originários na borda da caixa como descrito em meu comentário acima.
  • ter um sistema de macro mais poderoso, como correspondência no tipo, a fim de permitir ainda mais flexibilidade. Sim, pode-se pensar em colocar isso no sistema de características tradicional, mas a ferrugem não permite a substituição de implementações de características, então isso vai ser um pouco complicado

@ est31 A pilha não é _automaticamente_ "desenrolada" como em um pânico, isso é um açúcar sintático para retornos antecipados.
E sim, você poderia ter o compilador inserindo chamadas de algumas funções vazias com nomes fixos nos quais você pode fazer um ponto de interrupção, isso é muito fácil (e também tem informações que permitem fazer, por exemplo, call dump(data) - onde dump e data são argumentos que os depuradores podem ver).

@eddyb Acho que funções vazias não permitiriam um caso de uso de, por exemplo, manter algumas instâncias de depuração "canário" em uma grande implantação para ver se erros aparecem nos logs, para então voltar e consertar as coisas. Eu entendo que é preferível ser proativo do que reativo, mas nem tudo é fácil de prever

@hexsel Sim, é por isso que prefiro o método baseado em TLS, onde Result::unwrap ou algum novo método ignore (ou mesmo sempre ao eliminar o erro?) despeja o rastreamento em stderr.

@eddyb Se você adicionar o ponteiro de instrução ou algo derivado do valor a uma pilha como estrutura de dados em TLS, então você basicamente reconstruirá sua pequena versão da pilha da vida real. Um retorno reduz a pilha em uma entrada. Portanto, se você fizer isso enquanto retorna, você _desventa_ parte da pilha, enquanto constrói uma versão limitada dela em algum outro lugar na RAM. Talvez "desenrolar" seja o termo errado para o comportamento resultante de retornos "legais", mas se todo o código fizer ? ou try! e no final você interceptar, o resultado final será o mesmo. É ótimo que a ferrugem não torna a propagação de erros automática, eu realmente gostei de como o java exigiu que todas as exceções fossem listadas após a palavra-chave throws , ferrugem é uma boa melhoria nisso.

@hexsel uma try! ) permitiria isso - você pode executar qualquer código que desejar e registrar em qualquer sistema de registro. Você precisaria de alguma detecção de "enganos" quando múltiplos try pegam o mesmo erro enquanto ele se propaga para cima na pilha.

@ est31 Substituir try! só funciona em seu próprio código (é literalmente um sombreamento de importação de macro), teria que ser algo diferente, como nossos "itens de idioma fraco".

@ est31 Isso não é realmente correto (sobre desenrolar), os traços e a pilha real não têm necessariamente qualquer relação, porque mover Result s ao redor não precisa subir no backtrace original, pode ir lateralmente também.
Além disso, se o binário estiver otimizado, a maior parte do backtrace desaparece e, se você não tiver informações de depuração, Location é estritamente superior. Você pode até mesmo estar executando um plugin ofuscante que substitui todas as informações de origem por hashes aleatórios que podem ser combinados pelos desenvolvedores de algum produto de código fechado.

Os depuradores são úteis (e à parte, estou adorando a saída de backtrace mais limpa de lldb ), mas eles não são uma panacéia, e já emitimos algumas informações sobre pânico para que você possa obter algumas pistas sobre o que está acontecendo.

Sobre isso - eu tive algumas idéias sobre um truque de nível de sistema de tipos em que {Option,Result}::unwrap teria um argumento de tipo extra, padronizando para um tipo dependente do local de onde a função foi chamada, de modo que os pânicos surgissem informações de localização mais úteis.

Com o progresso na parametrização de valor, isso ainda pode ser uma opção, mas definitivamente não é a única, e eu não quero descartar Result traces, em vez disso, estou tentando encontrar um modelo que seja _implementável_.

Substituir try! não é uma solução, porque está contido em sua própria caixa. Isso é inaceitável como experiência de depuração. Eu já tentei bastante tentando lidar com o try! atual. Especialmente se você tiver muitos engradados envolvidos e os erros forem transmutados várias vezes ao longo da pilha, é quase impossível descobrir onde o erro se origina e por quê. Se um bandaid como esse for a solução, teremos que revisar o tratamento de erros em geral para grandes projetos do Rust.

@eddyb então, é sua sugestão que incorporemos literalmente o nome do arquivo, nome da função, número da linha e número da coluna a esse vetor? Isso parece um enorme desperdício, especialmente porque essa informação já está contida em um formato muito mais processável no DWARF. Além disso, o DWARF nos permite usar o mesmo processo de maneira razoavelmente barata na produção, ao passo que esse tipo de informação de localização parece ser tão inútil que ninguém jamais executaria um binário de lançamento com ele.

Como seria um desperdício significativamente maior do que informações DWARF? Os nomes de arquivo seriam desduplicados e em x64 a coisa toda tem o tamanho de 3 ponteiros.

@mitsuhiko então basicamente, você concorda com a direção geral, mas não com as especificações técnicas dela?

É fácil expor informações DWARF a uma API geral do Rust?

@eddyb porque as informações DWARF não estão contidas no binário de lançamento, mas em arquivos separados. Portanto, posso ter os arquivos de depuração em servidores de símbolos e enviar um binário reduzido aos usuários.

@mitsuhiko Oh, essa é uma abordagem totalmente diferente do que eu estava assumindo. Rust atualmente não suporta esse atm, AFAIK, mas eu concordo que deveria.

Você acha que os ponteiros de instrução são realmente úteis em comparação com identificadores aleatórios gerados pelo seu sistema de compilação para fins de depuração de binários de lançamento?

Minha experiência mostra que qualquer depurador in-line tem dificuldade em recuperar muito do rastreamento da pilha, exceto para auto / recursão mútua explícita e funções muito grandes.

Sim, try! está dentro da sua caixa. Se você passar um ponteiro de função ou semelhante ao código da biblioteca, e houver um erro em sua função, a abordagem try ainda funcionará. Se uma caixa que você usa tem um erro interno ou bug, você pode precisar de seu código-fonte para já depurar, se as informações de Err retornadas não ajudarem. Os rastreamentos de pilha só são úteis se você tiver acesso ao código, portanto, as bibliotecas de código fechado (cujo código você não pode modificar) terão que passar por cima do suporte de uma forma ou de outra.

Que tal simplesmente ativar as duas abordagens e deixar o desenvolvedor decidir o que é melhor para eles? Não nego que a abordagem baseada em TLS não tenha nenhuma vantagem.

O modelo try é implementável muito facilmente, basta desugar ? para try , apenas extensões de linguagem extras são necessárias se catch entrar (você teria adicionado esse comportamento dentro do comportamento codificado de ? qualquer maneira)

@eddyb "Você acha que os ponteiros de instrução são realmente úteis comparados aos identificadores aleatórios gerados pelo seu sistema de compilação para fins de depuração de binários de lançamento?"

É assim que a depuração de binários nativos em geral funciona. Nós (Sentinela) estamos usando isso quase que inteiramente para o Suporte iOS neste momento. Obtemos um despejo de memória e resolvemos os endereços com llvm-symbolizer para os símbolos reais.

@mitsuhiko Como já incorporamos libbacktrace , poderíamos usar isso para resolver ponteiros de código para locais de origem, então não sou totalmente contra isso.

@eddyb sim. Apenas olhei para o código de pânico. Seria bom se essa fosse realmente uma API que o código Rust pudesse usar. Posso ver isso sendo útil em mais alguns lugares.

Sobre isso - eu tinha algumas idéias sobre um truque de nível de sistema de tipos em que {Option, Result} :: unfrap teria um argumento de tipo extra, padronizando para um tipo dependente do local de onde a função foi chamada, de modo que o pânico daqueles seria têm informações de localização muito mais úteis.

Falando nisso...

@glaebhoerl Hah, talvez valha a pena perseguir então? Pelo menos como um experimento instável.

@eddyb Não

Apenas um pensamento: seria útil ter um switch que faz rustc para um caso especial construindo um Err forma que chame uma função fn(TypeId, *mut ()) com a carga útil antes de retornar . Isso deve ser o suficiente para começar com coisas básicas como erros de filtragem com base na carga útil, aprisionamento em um depurador se ele encontrar um erro de interesse ou captura de rastros de certos tipos de erros.

PR 33389 adiciona suporte experimental para o traço Carrier . Como não fazia parte do RFC original, ele deve passar por um período particularmente próximo de exame e discussão antes de passarmos para o FCP (que provavelmente deve ser separado do FCP para o restante do operador ? ). Veja este tópico de

Eu me oponho a estender ? para Option s.

A redação da RFC é bastante clara sobre o fato de que o operador ? trata da propagação de erros / "exceções".
Usar Option para relatar um erro é a ferramenta errada. Retornar None faz parte do fluxo de trabalho normal de um programa bem-sucedido, enquanto retornar Err sempre indica um erro.

Se quisermos melhorar algumas áreas de tratamento de erros (por exemplo, adicionando rastreamentos de pilha), implementar ? em Option significa que teremos que excluir ? das alterações .

@tomaka podemos manter a discussão no tópico de discussão ? (Eu já resumi sua objeção ). Pessoalmente, acho que longas discussões sobre GH tornam-se bastante difíceis de manejar, e também seria bom ser capaz de separar a discussão deste ponto específico de outros pontos ou questões futuras que possam surgir.

@eddyb Aqui estão os documentos da versão lançada do recurso GHC de callstacks implícitos que mencionei anteriormente .

Sem atualizações aqui em um tempo. Alguém está trabalhando para levar isso adiante? As tarefas restantes no OP ainda são precisas? Alguma coisa aqui pode ser procurada por E-help?

Eu brinquei no último fim de semana se seria possível escrever um cliente Sentry para Rust que fizesse algum sentido. Uma vez que a maior parte do tratamento de erros agora é baseada em resultados, em vez de pânico, percebi que a utilidade disso é severamente limitada ao ponto em que decidi abandoná-lo totalmente.

Eu fui para a base de código crates.io como um exemplo para tentar integrar um sistema de relatório de erros nele. Isso me trouxe de volta a este RFC porque eu realmente acho que, a menos que possamos registrar o ponteiro da instrução de alguma forma, conforme os resultados são transmitidos e convertidos nos diferentes rastreamentos de pilha, será impossível obter um relatório de erro adequado. Já vejo que isso é uma dor enorme apenas depurar falhas lógicas complexas locais, onde hoje em dia recorro à adição de pânicos de onde eu _cho_ que o erro está vindo.

Infelizmente, no momento, não vejo como o IP pode ser gravado sem grandes mudanças em como os resultados funcionam. Alguém mais brincou com essa ideia antes?

Estávamos discutindo isso na reunião @rust-lang / lang. Algumas coisas que surgiram:

Primeiro, há um interesse definitivo em ver ? estabilizando o mais rápido possível. No entanto, acho que a maioria de nós também gostaria de ver ? operando em Option e não apenas em Result (mas não, eu acho, bool , como também foi proposto). Uma preocupação sobre a estabilização é que, se estabilizarmos sem oferecer qualquer tipo de característica que permitiria o uso de tipos diferentes de Result , então não será compatível com versões anteriores adicioná-lo posteriormente.

Por exemplo, eu mesmo escrevo código como este com alguma regularidade:

let v: Vec<_> = try!(foo.iter().map(|x| x.to_result()).collect());

onde estou contando com try! para informar a inferência de tipo que espero que collect retorne um Result<Vec<_>, _> . Se fosse usar ? , esta inferência _poderia_ falhar no futuro.

No entanto, em discussões anteriores, também decidimos que uma emenda RFC era necessária para discutir os pontos mais delicados de qualquer tipo de característica de "portadora". É claro que este RFC deve ser escrito o mais rápido possível, mas preferimos não bloquear o progresso de ? nessa discussão.

Um pensamento que tivemos foi que se pegássemos a implementação de tornássemos o traço instável e o implementássemos apenas para Result e algum tipo fictício privado, isso suprimiria a inferência enquanto ainda gerava ? utilizável com Result .

Um último ponto: acho que a maioria de nós prefere que se você usar ? em um Option , isso requer que o tipo de retorno da sua função também seja Option (não ex. Result<T,()> ). É interessante notar que isso ajudará com as limitações de inferência, uma vez que podemos finalmente inferir a partir do tipo de retorno declarado em muitos casos que tipo é necessário.

A razão para não querer a interconversão é que parece provável que leve a uma lógica frouxa, algo análogo a como C permite if x mesmo quando x tem tipo integral. Ou seja, se Option<T> denota um valor onde None faz parte do domínio desse valor (como deveria) e Result<> representa (normalmente) a falha de uma função para ter sucesso, então assumir que None significa que a função deve falhar parece suspeito (e como uma espécie de convenção arbitrária). Mas essa discussão pode esperar pelo RFC, suponho.

A razão para não querer a interconversão é que parece provável que leve a uma lógica frouxa, meio análogo a como C permite if x mesmo quando x tem tipo integral. Ou seja, se Option<T> denota um valor onde None faz parte do domínio desse valor (como deveria) e Result<> representa (normalmente) a falha de uma função para ter sucesso, então assumir que None significa que a função deve falhar parece suspeito (e como uma espécie de convenção arbitrária). Mas essa discussão pode esperar pelo RFC, suponho.

Eu concordo plenamente com isso.

Outra questão que tínhamos concordado em estabilizar o portão era pregar os contratos que impl s da característica From deveriam obedecer (ou qualquer característica que acabamos usando para Err -upcasting )

@glaebhoerl

Outra questão com a qual concordamos em estabilizar o gate era acertar os contratos que os impls da característica From deveriam obedecer (ou qualquer característica que acabamos usando para Err-upcasting).

De fato. Você pode refrescar minha memória e começar com alguns exemplos de coisas que você acha que deveriam ser proibidas? Ou talvez apenas as leis que você tem em mente? Tenho que admitir que desconfio de "leis" como essa. Por um lado, eles têm a tendência de serem ignorados na prática - as pessoas tiram vantagem do comportamento real quando ele se adequa a seus propósitos, mesmo que vá além das limitações pretendidas. Isso leva a outra questão: se tivéssemos leis, nós as usaríamos para alguma coisa? Otimizações? (Parece improvável para mim, no entanto.)

A propósito, qual é o estado de catch expressões? Eles estão implementados?

Infelizmente não :(

Na terça-feira, 26 de julho de 2016 às 06:41:44 AM -0700, Alexander Bulaev escreveu:

A propósito, qual é o estado de catch expressões? Eles estão implementados?


Você está recebendo isso porque é o autor do tópico.
Responda a este e-mail diretamente ou visualize-o no GitHub:
https://github.com/rust-lang/rust/issues/31436#issuecomment -235270663

Talvez você deva planejar sua implementação? Não há nada mais deprimente do que RFCs aceitos, mas não implementados ...

cc # 35056

Para sua informação, https://github.com/rust-lang/rfcs/pull/1450 (tipos para variantes enum) abriria algumas maneiras interessantes de implementar Carrier . Por exemplo, algo como:

trait Carrier {
    type Success: CarrierSuccess;
    type Error: CarrierError;
}

trait CarrierSuccess {
    type Value;
    fn into_value(self) -> Self::Value;
}

// (could really use some HKT...)
trait CarrierError<Equivalent: CarrierError> {
    fn convert_error(self) -> Equivalent;
}

impl<T, E> Carrier for Result<T, E>
{
    type Success = Result::Ok<T, E>;
    type Error = Result::Err<T, E>;
}

impl<T, E> CarrierSuccess for Result::Ok<T, E> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T, E1, E2> CarrierError<Result::Err<T, E2>> for Result::Err<T, E1>
    where E2: From<E1>,
{
    fn convert_error(self) -> Result::Err<T, E2> {
        Err(self.into())
    }
}

impl<T> Carrier for Option<T>
{
    type Success = Option::Some<T>;
    type Error = None;
}

impl<T> CarrierSuccess for Option::Some<T> {
    type Value = T;
    fn into_value(self) -> Self::Value {
        self.0
    }
}

impl<T> CarrierError<Option::None> for Option::None {
    fn convert_error(self) -> Option::None {
        self
    }
}

fn main() {
    let value = match might_be_err() {
        ok @ Carrier::Success => ok.into_value(),
        err @ Carrier::Error => return err.convert_error(),
    }
}

Eu só queria postar algumas idéias de https://github.com/rust-lang/rust/pull/35056#issuecomment -240129923. Esse PR apresenta um traço Carrier com tipo fictício. A intenção era proteger - em particular, queríamos estabilizar ? sem ter que estabilizar sua interação com a inferência de tipo. Esta combinação de traço mais tipo de manequim parecia ser seguramente conservadora.

A ideia era (eu acho) que escreveríamos um RFC discutindo Carrier e tentaríamos modificar o design para corresponder, estabilizando apenas quando estivéssemos satisfeitos com a forma geral (ou possivelmente removendo Carrier completamente, se não conseguirmos alcançar um design que gostamos).

Agora, falando um pouco mais especulativamente, eu _ antecipo_ que, se adotarmos uma característica Carrier , desejaríamos proibir a interconversão entre as operadoras (considerando que esta característica é basicamente uma maneira de converter de / para Result ). Portanto, intuitivamente, se você aplicar ? a um Option , não há problema se o fn retornar Option ; e se você aplicar ? a um Result<T,E> , não há problema se o fn retornar Result<U,F> onde E: Into<F> ; mas se você aplicar ? a um Option e o fn retornar Result<..> , isso não está certo.

Dito isso, esse tipo de regra é difícil de expressar no sistema de tipos de hoje. O ponto de partida mais óbvio seria algo como HKT (que é claro que não temos, mas vamos ignorar isso por enquanto). No entanto, isso não é obviamente perfeito. Se fôssemos usá-lo, seria de supor que o parâmetro Self para Carrier tem o tipo type -> type -> type , já que Result pode implementar Carrier . Isso nos permitiria expressar coisas como Self<T,E> -> Self<U,F> . No entanto, _não_ se aplicaria necessariamente a Option , que tem o tipo type -> type (tudo isso, claro, dependeria precisamente do tipo de sistema HKT que adotamos, mas não acho que ' (vou até "tipo geral lambdas"). Ainda mais extremo pode ser um tipo como bool (embora eu não queira implementar Carrier para bool, eu esperaria que algumas pessoas pudessem implementar Carrier para um novo tipo 'd bool).

O que eu havia considerado é que as regras de digitação para ? podem ser especiais: por exemplo, podemos dizer que ? só pode ser aplicado a um tipo nominal Foo<..> de _algumas tipo, e que corresponderá ao traço Carrier contra este tipo, mas exigirá que o tipo de retorno do fn delimitador também seja Foo<..> . Portanto, basicamente instanciaríamos Foo com novos parâmetros de tipo. A desvantagem dessa ideia é que, se nem o tipo ao qual ? está sendo aplicado, nem o tipo de fn delimitador for conhecido, não podemos impor essa restrição sem adicionar algum novo tipo de obrigação de característica. Também é bastante ad-hoc. :) Mas funcionaria.

Outro pensamento que tive é que deveríamos reconsiderar o traço Carrier . A ideia seria ter Expr: Carrier<Return> onde Expr é o tipo ao qual ? é aplicado e Return é o tipo de ambiente. Por exemplo, talvez seja assim:

trait Carrier<Target> {
    type Ok;
    fn is_ok(&self) -> bool; // if true, represents the "ok-like" variant
    fn unwrap_into_ok(self) -> Self::Ok; // may panic if not ok
    fn unwrap_into_error(self) -> Target; // may panic if not error
}

Então expr? desugars para:

let val = expr;
if Carrier::is_ok(&val) {
    val.unwrap_into_ok()
} else {
    return val.unwrap_into_error();
}

A principal diferença aqui é que Target não seria o tipo _error_, mas um novo tipo Result . Então, por exemplo, podemos adicionar o seguinte impl:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_ok() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { Err(F::from(self.unwrap_err())) }
}

E então podemos adicionar:

impl<T> Carrier<Option<T>> for Option<T> {
    type Ok = T;
    fn is_ok(&self) -> bool { self.is_some() }
    fn unwrap_into_ok(self) -> Self::Ok { self.unwrap() }
    fn unwrap_into_error(self) -> { debug_assert!(self.is_none()); None }
}

E, finalmente, poderíamos implementar para bool assim:

struct MyBool(bool);
impl<T> Carrier<MyBool> for MyBool {
    type Ok = ();
    fn is_ok(&self) -> bool { self.0 }
    fn unwrap_into_ok(self) -> Self::Ok { debug_assert!(self.0); () }
    fn unwrap_into_error(self) -> { debug_assert!(!self.0); self }
}

Agora esta versão é mais flexível. Por exemplo, nós _podemos_ permitir que a interconversão entre Option valores sejam convertidos em Result adicionando um impl como:

impl<T> Carrier<Result<T,()>> for Option<T> { ... }

Mas é claro que não precisamos (e não faríamos).

@Stebalien

Para sua informação, rust-lang / rfcs # 1450 (tipos para variantes enum) abriria algumas maneiras interessantes de implementar o Carrier

Enquanto estava escrevendo essa ideia que acabei de escrever, pensei em ter tipos para variantes de enum e como isso pode afetar as coisas.

Uma coisa que notei ao escrever algum código que usa ? é que é um pouco irritante não ter qualquer tipo de palavra-chave "jogar" - em particular, se você escrever Err(foo)? , o compilador não t _sabe_ que isso retornará, então você deve escrever return Err(foo) . Tudo bem, mas você não obtém into() conversões sem escrevê-las você mesmo.

Isso ocorre em casos como:

let value = if something_or_other() { foo } else { return Err(bar) };

Oh, devo acrescentar mais uma coisa. O fato de permitirmos que impls influenciem a inferência de tipo _deve_ significar que foo.iter().map().collect()? , em um contexto onde o fn retorna Result<..> , suspeito que nenhuma anotação de tipo seria necessária, pois se sabemos que o O tipo de retorno fn é Result , apenas um impl se aplicaria potencialmente (localmente, pelo menos).

Ah, e uma versão um pouco melhor do meu traço Carrier provavelmente seria:

trait Carrier<Target> {
    type Ok;
    fn into_carrier(self) -> Result<Self::Ok, Target>;
}

onde você o implementaria como:

impl<T,U,E,F> Carrier<Result<U,F>> for Result<T,E>
    where E: Into<F>
{
    type Ok = T;
    fn into_carrier(self) -> Result<T, Result<U,F>> {
        match self { Ok(v) => Ok(v), Err(e) => Err(e.into()) }
    }
}

E expr? geraria um código como:

match Carrier::into_carrier(expr) {
    Ok(v) => v,
    Err(e) => return e,
}

Uma desvantagem (ou vantagem ...) disso, é claro, é que as Into conversões são colocadas no impls, o que significa que as pessoas podem não usá-las quando fizer sentido. Mas também significa que você pode desativá-los se (para seu tipo específico) não forem desejados.

@nikomatsakis IMO, o traço deve ser IntoCarrier e IntoCarrier::into_carrier deve retornar um Carrier (um novo enum) em vez de reutilizar o resultado como este. Isso é:

enum Carrier<C, R> {
    Continue(C),
    Return(R),
}
trait IntoCarrier<Return> {
    type Continue;
    fn into_carrier(self) -> Carrier<Self::Continue, Return>;
}

@Stebalien claro, parece bom.

Nomeação para discussão (e possível FCP do operador ? sozinho) na reunião da equipe lang. Suponho que precisamos lançar algum tipo de traço temporário de Carrier nos próximos dias para FCP.

Abri rust-lang / rfcs # 1718 para discutir o traço Carrier.

Ouça, ouça! O operador ? especificamente agora está entrando no período final para comentários . Esta discussão dura aproximadamente este ciclo de lançamento, que começou em 18 de agosto. A tendência é estabilizar o operador ? .

Com relação ao traço do

@ membros rust-lang / lang, marque seu nome para sinalizar que concorda. Deixe um comentário com preocupações ou objeções. Outros, por favor, deixe comentários. Obrigado!

  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @aturon
  • [x] @eddyb
  • [] @pnkfelix (de férias)

Eu me pergunto se as bibliotecas baseadas em tokio vão acabar usando and_then muito. Esse seria um argumento para permitir que foo().?bar() seja a abreviação de foo().and_then(move |v| v.bar()) , de modo que Resultados e Futuros possam usar a mesma notação.

Só para ficar claro, este FCP é sobre o recurso question_mark , não pega , correto? O título desta edição implica o rastreamento de ambos os recursos nesta edição.

@seanmonstar este último nem foi implementado, então, sim. Presumivelmente, se o FCP resultar na aceitação, isso será alterado para rastrear catch .

Só para ficar claro, este FCP é sobre o recurso question_mark, não pega, correto? O título desta edição implica o rastreamento de ambos os recursos nesta edição.

Sim, apenas o recurso question_mark .

Acompanhamento em https://github.com/rust-lang/rfcs/issues/1718#issuecomment -241764523. Eu pensei que o comportamento atual ? poderia ser generalizado em "vincular continuação atual", mas a parte map_err(From::from) de ? torna-o um pouco mais do que apenas vincular: /. Se adicionarmos uma instância From ao resultado, então suponho que a semântica poderia ser v? => from(v.bind(current-continuation)) .

Tem havido muita discussão sobre os méritos relativos de ? neste tópico interno:

https://internals.rust-lang.org/t/the-operator-will-be-harmful-to-rust/3882/

Não tenho tempo para fazer um resumo profundo agora. Minha lembrança é que os comentários se concentram na questão de se ? é suficientemente visível para chamar a atenção para erros, mas provavelmente estou negligenciando outras facetas da discussão. Se outra pessoa tiver tempo para resumir, isso seria ótimo!

Não comentei antes e talvez seja tarde demais, mas acho o operador ? confuso também, se for usado como uma instrução de retorno oculta, como @hauleth apontou na discussão que você vinculou @ nikomatsakis.

Com try! , era óbvio que poderia haver um retorno em algum lugar, porque uma macro pode fazer isso. Com ? como um return oculto, teríamos 3 maneiras de retornar valores de uma função:

  • retorno implícito
  • retorno explícito
  • ?

Eu gosto disso, porém, como @CryZe disse:

Dessa forma, é familiar a todos, ele canaliza o erro até o final, onde você pode manipulá-lo e não há retornos implícitos. Portanto, poderia ser mais ou menos assim:

deixe a = tentar! (x? .y? .z);

Isso ajuda a tornar o código mais conciso e não oculta um retorno. E é familiarizado com outras linguagens, como coffeescript .

Como fazer ? resolver no nível de expressão em vez de no nível de função afetaria os futuros? Para todos os outros casos de uso, parece bom para mim.

Eu gosto disso, porém, como @CryZe disse:

Dessa forma, é familiar a todos, ele canaliza o erro até o final, onde você pode manipulá-lo e não há retornos implícitos. Portanto, poderia ser mais ou menos assim:

deixe a = tentar! (x? .y? .z);

Eu postulei isso. Acho que seria a solução perfeita.

Com try !, era óbvio que poderia haver um retorno em algum lugar, porque uma macro pode fazer isso.

Só é óbvio porque você está familiarizado com a forma como as macros funcionam no Rust. O que será exatamente o mesmo quando ? estiver estável, difundido e explicado em cada introdução de Rust.

@conradkleinespel

teríamos 3 maneiras de retornar valores de uma função:

  • retorno implícito

Rust não tem "retornos implícitos", tem expressões que avaliam um valor. Isso é uma diferença importante.

if foo {
    5
}

7

Se Rust tivesse "retorno implícito", esse código seria compilado. Mas isso não acontece, você precisa de return 5 .

Qual vai ser exatamente o mesmo uma vez? é estável, difundido e explicado em cada introdução de Rust.

Para obter um exemplo de como isso pode ser, https://github.com/rust-lang/book/pull/134

Com try !, era óbvio que poderia haver um retorno em algum lugar, porque uma macro pode fazer isso.
Só é óbvio porque você está familiarizado com a forma como as macros funcionam no Rust. Qual vai ser exatamente o mesmo uma vez? é estável, difundido e explicado em cada introdução de Rust.

Em qualquer idioma que conheço, "macros" significa "aqui há dragões" e que aí tudo pode acontecer. Então, eu reformularia isso para "porque você está familiarizado com o funcionamento das macros", sem a parte "in Rust".

@hauleth

deixe a = tentar! (x? .y? .z);

Eu postulei isso. Acho que seria a solução perfeita.

Eu discordo fortemente. Como você obterá um símbolo mágico que só funciona em try! e não fora.

@hauleth

deixe a = tentar! (x? .y? .z);
Eu postulei isso. Acho que seria a solução perfeita.
Eu discordo fortemente. Como você obterá um símbolo mágico que só funciona na tentativa! e não fora.

Eu não disse que ? deveria funcionar apenas em try! . O que eu estava dizendo é que ? deve funcionar como um operador de tubo que enviará os dados pelo fluxo e retornará o erro assim que ocorrer. try! não seria necessário nesse caso, mas poderia ser usado no mesmo contexto em que é usado agora.

@steveklabnik Eu penso em Rust como uma linguagem com retorno implícito. Em seu exemplo, 5 não foi retornado implicitamente, mas vamos pegar o seguinte:

fn test() -> i32 {
    5
}

Aqui, 5 é retornado implicitamente, não é? Ao contrário de return 5; você precisaria em seu exemplo. Isso cria 2 maneiras diferentes de retornar um valor. O que eu acho um pouco confuso sobre Rust. Adicionar um terceiro não ajudaria a IMO.

Não é. É o resultado de uma expressão, especificamente, o corpo da função. "retorno implícito" implica que você pode, de alguma forma, retornar implicitamente de qualquer lugar, mas isso não é verdade. Nenhuma outra linguagem baseada em expressão chama isso de "retorno implícito", pois esse seria meu exemplo de código acima.

@steveklabnik Certo, obrigado por

É tudo de bom! Eu posso ver totalmente de onde você vem, são apenas duas coisas diferentes que as pessoas usam de maneira errada com frequência. Já vi pessoas assumirem que "retorno implícito" significa que você pode simplesmente deixar ; em qualquer lugar da fonte para retornar ... isso _seria_ muito ruim: sorria:

@hauleth The? operador seria apenas açúcar sintático para and_then nesse caso. Dessa forma, você poderia usá-lo em muito mais casos e não teria que ser um retorno difícil de perder. Isso também é o que todas as outras línguas têm um? operador. Ferrugem? operador na implementação atual seria o OPOSTO exato do que todas as outras linguagens fazem. Também e então é a abordagem funcional e é encorajada de qualquer maneira, pois tem um fluxo de controle claro. Então, apenas fazendo? açúcar sintático para and_then e então mantendo o try atual! para explicitamente "desembrulhar e retornar", parece ser a situação muito mais limpa, tornando os retornos mais visíveis e o? operador mais flexível (por ser capaz de usá-lo em casos de não retorno, como correspondência de padrões).

Exatamente.

Łukasz Niemier
[email protected]

Wiadomość napisana przez Christopher Serr notificaçõ[email protected] w dniu 02.09.2016, o godz. 21:05:

@hauleth https://github.com/hauleth O? operador seria apenas açúcar sintático para and_then nesse caso. Dessa forma, você poderia usá-lo em muito mais casos e não teria que ser um retorno difícil de perder. Isso também é o que todas as outras línguas têm um? operador. Ferrugem? operador na implementação atual seria o OPOSTO exato do que todas as outras linguagens fazem. Também e então é a abordagem funcional e é encorajada de qualquer maneira, pois tem um fluxo de controle claro. Então, apenas fazendo? açúcar sintático para and_then e então mantendo o try atual! para explicitamente "desembrulhar e retornar", parece ser a situação muito mais limpa, tornando os retornos mais visíveis e o? operador mais flexível (por ser capaz de usá-lo em casos de não retorno, como correspondência de padrões).

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244461722 ou ignore o tópico https://github.com/notifications/unsubscribe-auth/ AARzN5-w4EO9_FwNMDpvtYkGUuQKGt-Kks5qmHOHgaJpZM4HUm_-.

E ao trabalhar em uma solicitação de pull para o repositório Rust, na verdade, tive que trabalhar com o código que usava o? operador e na verdade isso realmente prejudicou a legibilidade para mim, como apenas gosto; estava super escondido (mentalmente, porque é apenas ruído que é filtrado no cérebro) e eu o esqueci muito. E acho isso bastante assustador.

@steveklabnik chamamos isso de "retorno implícito", porque nós não somos os únicos ones .

@hauleth huh, em todos os meus anos de Ruby, nunca ouvi falar de ninguém chamando isso de retorno implícito. Ainda sustento que é a maneira errada de pensar a respeito.

Usei ? em alguns projetos e preferi try! , principalmente porque está na posição postfix. Em geral, o código Rust "real" praticamente entra na Result 'mônada' em main e nunca o deixa, exceto em nós de folha ocasionais; e espera-se que esse código sempre propague erros. Na maioria das vezes, não importa quais expressões estão gerando erros - todas elas estão apenas sendo enviadas de volta para a pilha, e não quero ver isso quando estou lendo a lógica principal do código.

Minha principal preocupação com ? é que eu poderia obter o mesmo benefício - posição postfix - com macros de método, se elas existissem. Tenho outras preocupações de que talvez ao estabilizar a formulação atual estejamos limitando a expressividade futura no tratamento de erros - a conversão de Result atual não é suficiente para tornar o tratamento de erros em Rust tão ergonômico quanto eu gostaria; já cometemos vários erros de design com o tratamento de erros do Rust que parecem difíceis de consertar, e isso pode estar nos aprofundando; embora eu não tenha nenhuma evidência concreta.

Já escrevi isso muitas vezes, mas estou absolutamente apaixonado por ? e pelas possibilidades do traço de portador. Eu converti um projeto para o uso de ? inteiramente e ele tornou muitas coisas possíveis (particularmente com relação ao encadeamento) que eram muito complexas com try! . Eu também, por diversão, examinei alguns outros projetos para ver como fariam com ? e, no geral, não encontrei nenhum problema com isso.

Como tal, dou um grande +1 para estabilizar ? com base em um traço melhor denominado Carrier que idealmente também cobre alguns dos outros casos que mencionei em outra discussão sobre ele.

Minha principal preocupação com? é que eu poderia obter o mesmo benefício - posição pós-fixada - com macros de método, se elas existissem.

Talvez precisemos de um RFC para isso? A maioria das pessoas parece gostar da funcionalidade do?, Mas não do? em si.

Talvez precisemos de um RFC para isso? A maioria das pessoas parece gostar da funcionalidade do?, Mas não do? em si.

_Is_ um RFC com muita discussão para isso. Além disso, não sei de onde você está tirando essa "maioria das pessoas". Se for dos participantes desta edição, é claro que você verá mais pessoas argumentando contra, porque a estabilização _já_ é a ação padrão da equipe.
O ? foi amplamente discutido antes da fusão do RFC e, como defensor, é meio cansativo ter que fazer a mesma coisa quando a estabilização é discutida.

De qualquer forma, colocarei meu +1 para os sentimentos de @mitsuhiko aqui.

Há uma RFC com muita discussão para isso. Além disso, não sei de onde você está tirando essa "maioria das pessoas". Se for dos participantes desta edição, é claro que você verá mais pessoas argumentando contra, porque estabilizar já é a ação padrão da equipe.

Desculpe, meu comentário foi muito breve. Eu estava me referindo à criação de um RFC para algum tipo de "macros de método", por exemplo func1().try!().func2().try!() (até onde eu sei, isso não é possível no momento).

Pessoalmente, gosto do? operadora, mas compartilho as mesmas preocupações que @brson , e acho que seria bom explorar alternativas antes de estabilizar esse recurso. Incluindo a conversão RFC, este thread e o thread interno que @nikomatsakis vinculou, definitivamente ainda há alguma controvérsia sobre esse recurso, mesmo que sejam os mesmos argumentos repetidamente. No entanto, se não houver alternativas viáveis, estabilizar faz mais sentido.

Parece prematuro estabilizar um recurso sem tê-lo totalmente implementado - neste caso, a expressão catch {..}.

Já expressei minha preocupação com esse recurso antes e ainda acredito que é uma má ideia. Acho que ter um operador de retorno condicional pós-fixado é diferente de tudo em qualquer outra linguagem de programação e está empurrando o Rust além de seu orçamento de complexidade já esticado.

@mcpherrinm Em vez disso, outras linguagens ocultaram caminhos de desenrolamento em cada chamada para tratamento de erros. Você chamaria operator() "operador de retorno condicional"?

Quanto ao orçamento de complexidade, ele é apenas sintaticamente diferente de try! , pelo menos a parte da qual você está reclamando.
O argumento é contra o código try! -pesado, que ? apenas torna mais legível?
Em caso afirmativo, eu concordaria se houver uma alternativa séria diferente de "não tem nenhuma automação de propagação de erro _at all_".

Sugerindo um meio-termo: https://github.com/rust-lang/rfcs/pull/1737

Pode não haver chances de ser aceito, mas estou tentando mesmo assim.

Eu gosto da ideia de @keeperofdakeys sobre "macros de método". Não acho que a sintaxe ? deva ser aceita pela mesma razão que o operador ternário não está enferrujado - legibilidade. O próprio ? não diz nada. Em vez disso, eu preferiria ver a capacidade de generalizar o comportamento de ? com as "macros de método".

a.some_macro!(b);
// could be syntax sugar for
some_macro!(a, b); 
a.try!();
// could be syntax sugar for
try!(a); 

Dessa forma, ficaria claro qual é o comportamento e permite um encadeamento fácil.

Macro de método como result.try!() parece ser uma melhoria mais genérica para a ergonomia da linguagem e parece menos ad-hoc do que um novo operador ? .

@brson

Tenho outras preocupações de que talvez ao estabilizar a formulação atual estejamos limitando a expressividade futura no tratamento de erros - a conversão de Resultado atual não é suficiente para tornar o tratamento de erros em Rust tão ergonômico quanto eu gostaria

Este é um ponto interessante. Valeria a pena gastar algum tempo focado nisso (talvez você e eu possamos conversar um pouco). Concordo que poderíamos fazer melhor aqui. O design proposto para um traço Carrier (consulte https://github.com/rust-lang/rfcs/issues/1718) pode ajudar aqui, especialmente se combinado com especialização, uma vez que torna as coisas mais flexíveis.

Eu realmente duvido que macros de método sejam uma boa extensão para a linguagem.

macro_rules! macros são atualmente declarados de forma análoga às funções livres e se tornarão ainda mais análogos quando o novo sistema de importação for adotado para eles. O que quero dizer é que eles são declarados como itens de nível superior e chamados como itens de nível superior e, em breve, também serão importados como itens de nível superior.

Não é assim que os métodos funcionam. Os métodos têm estas propriedades:

  1. Não pode ser declarado em um escopo de módulo, mas deve ser declarado dentro de um bloco impl .
  2. São importados com o tipo / característica ao qual o bloco impl está associado, em vez de importados diretamente.
  3. São despachados com base em seu tipo de receptor, em vez de serem despachados com base em ser um símbolo único e inequívoco neste escopo.

Como as macros são expandidas antes da verificação de tipo, nenhuma dessas propriedades pode ser verdadeira para macros que usam a sintaxe do método, pelo que eu sei. É claro que poderíamos apenas ter macros que usam sintaxe de método, mas são despachadas e importadas da mesma forma que macros 'livres', mas acho que a disparidade tornaria isso um recurso muito confuso.

Por essas razões, não acho que seja uma boa escolha atrasar ? na crença de que "macros de método" podem aparecer algum dia.

Além disso, acho que há uma linha na qual alguns construtos são tão amplamente usados ​​e importantes que deveriam ser promovidos de macros a açúcar. for loops são um bom exemplo. O comportamento ? é parte integrante da história de tratamento de erros do Rust, e acho que é apropriado que seja um açúcar de primeira classe em vez de uma macro.

@hauleth , @CryZe

Para responder àqueles que sugerem que ? deve ser um operador and_then , isso funciona bem em linguagens como Kotlin (não estou familiarizado com o coffeescript) devido ao uso extensivo de funções de extensão, mas não é tão simples na ferrugem. Basicamente, a maioria dos usos de and_then não são maybe_i.and_then(|i| i.foo()) , eles são maybe_i.and_then(|i| Foo::foo(i)) O primeiro poderia ser expresso como maybe_i?.foo() mas o último não. Pode-se dizer que Foo::foo(maybe_i?, maybe_j?) transforma em maybe_i.and_then(|i| maybe_j.and_then(|j| Foo::foo(i, j))) mas isso parece ainda mais confuso do que apenas dizer que a ferrugem retorna antes de atingir o primeiro ? que resulta em um erro. No entanto, isso seria sem dúvida mais poderoso.

@Stebalien No RFC aceito, catch { Foo::foo(maybe_i?, maybe_j?) } faz o que você quer.

@eddyb Bom ponto. Acho que posso deixar de fora o "No entanto, isso seria sem dúvida mais poderoso". Tudo se resume a tentativa de captura / explícita implícita versus tentativa de captura / tentativa implícita explícita:

let x: i32 = try Foo::foo(a?.b?.c()?));
let y: Result<i32, _> = Foo::foo(a?.b?.c()?);

Versus:

let x: i32 = Foo::foo(a?.b?.c()?);
let y: Result<i32, _> = catch  Foo::foo(a?.b?.c()?);

(sintaxe do módulo)

@Stebalien Outro exemplo: se eu quisesse passar Foo para uma função bar , com sua proposta eu precisaria:

bar(Foo::foo(a?.b?.c()?)?)

É isso que você tem em mente? Observe o ? extra, sem ele bar obteria Result vez de Foo .

@eddyb Provavelmente. Nota: Na verdade, não estou propondo isso! Estou argumentando que usar ? como um operador de tubo não é particularmente útil em ferrugem sem alguma maneira de lidar com o caso Foo::foo(bar?) .

Apenas para notar que eu odeio a ideia de macros de método e não consigo pensar em um recurso de linguagem ao qual me oporia mais fortemente. Eles confundem o faseamento do compilador e, a menos que façamos mudanças realmente fundamentais na linguagem, não há como eles existirem e ter um comportamento que não surpreende. Eles também são difíceis de analisar de forma sensata e quase certamente não são compatíveis com versões anteriores.

@Stebalien , com ? como operador de tubo Foo::foo(bar?) ficaria assim: Foo::foo(try!(bar)) e bar(Foo::foo(a?.b?.c()?)?) (assumindo que Foo::foo : fn(Result<_, _>) -> Result<_, _> ): bar(try!(Foo::foo(a?.b?.c()?))) .

@hauleth, meu ponto é que Foo::foo(bar?)? é _muito_ mais comum do que bar?.foo()? em ferrugem. Portanto, para ser útil, ? teria que apoiar este caso (ou algum outro recurso teria que ser introduzido). Eu estava postulando uma maneira de fazer isso e mostrando que assim, pelo menos, seria uma confusão. O objetivo principal de ? é evitar escrever try!(foo(try!(bar(try!(baz()))))) (2x os parênteses!); geralmente não é possível reescrever isso como try!(baz()?.bar()?.foo()) .

Mas você sempre pode fazer:

try!(baz().and_then(bar).and_then(foo))

Łukasz Niemier
[email protected]

Wiadomość napisana przez Steven Allen [email protected] w dniu 05.09.2016, o godz. 15:39:

@hauleth https://github.com/hauleth meu ponto foi que Foo :: foo (bar?)? é muito mais comum que bar? .foo ()? em ferrugem. Portanto, para ser útil ,? teria que apoiar este caso (ou algum outro recurso teria que ser introduzido). Eu estava postulando uma maneira de fazer isso e mostrando que assim, pelo menos, seria uma confusão. O ponto inteiro? é evitar a escrita try! (foo (try! (bar (try! (baz ())))))) (2x os parênteses!); geralmente não é possível reescrever isso como try! (baz () ?. bar () ?. foo ()).

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244749275 ou ignore o tópico https://github.com/notifications/unsubscribe-auth/ AARzN1Hdk6uk5-SoYawtgAbJUDf_8MsMks5qnBumgaJpZM4HUm_-.

Em uma nota ligeiramente relacionada, parece que o recurso? É usado principalmente por construtores, portanto, poderíamos evitar a necessidade do recurso? Fornecendo uma maneira fácil de construir construtores que envolvam um Resultado. Eu propus algo aqui, mas pode precisar de um pouco mais de trabalho.

https://github.com/colin-kiegel/rust-derive-builder/issues/25

Obrigado por seus pensamentos sobre a ideia de macro do método @nrc e @withoutboats , é bom ouvir algumas razões concretas pelas quais eles não funcionariam.

@nielsle Não creio que seja correto dizer que ? está "principalmente" sendo usado por construtores. Embora os construtores sejam um exemplo em que eu acho que a vantagem de um operador leve e pós-fixado realmente transparece, eu prefiro ? a try! em cada contexto.

@nielsle com relação aos futuros, originalmente fiquei preocupado por motivos semelhantes. Mas, depois de pensar sobre isso, acho que async / await substituiria qualquer necessidade de ? nesse contexto. Na verdade, eles são bastante ortogonais: você poderia fazer coisas como (await future)?.bar() .

(Talvez seja bom ter um operador de sufixo em vez da palavra-chave await para que os parênteses não sejam necessários. Ou talvez uma precedência cuidadosamente ajustada seja suficiente.)

Eu definitivamente gostaria de ver alguma documentação escrita antes de estabilizarmos. Procurei na referência e não encontrei nenhuma menção. Onde devemos documentar esse recurso?

@cbreeden Eu sei que @steveklabnik geralmente evita documentar recursos instáveis, já que há uma chance de ser uma perda de tempo se eles nunca forem estabilizados. Não sei se já bloqueamos a estabilização na escrita da documentação antes.

@solson Você está certo, este provavelmente não era o lugar para trazer isso à tona - ou pelo menos não deveria estar relacionado a questões de estabilização. Acho que estava apenas imaginando uma situação em que poderíamos decidir sobre a estabilização de um recurso, mas também exigir a documentação antes de ser lançado para o stable rustc. Há um RFC relacionado à integração da documentação com estabilização e lançamento de recursos, então vou apenas esperar que o processo se estabilize (mas não sem a documentação adequada primeiro, é claro)

Acho que a parte importante deste RFC é ter algo do lado direito de uma expressão que atue como try! , porque isso torna a leitura de usos sequenciais / encadeados de "tentar" _much_ mais legível e "pegar" . Originalmente, eu apoiava 100% o uso de ? como sintaxe, mas recentemente me deparei com algum código (limpo!) Que já usava ? que me deixou ciente de que fora de exemplos simples ? é extremamente fácil de ignorar . O que agora me faz acreditar que usar ? como sintaxe para "a nova tentativa" pode ser um grande erro.

Portanto, proponho que pode ser uma boa ideia _putar alguma enquete_ antes de finalizá-la (com notificação sobre isso nos Fóruns) para obter um feedback sobre o uso de ? ou algum outro símbolo (s) como sintaxe . Idealmente com exemplo de uso em uma função mais longa. Observe que estou apenas considerando que pode ser uma boa ideia renomear ? para não alterar mais nada. A enquete poderia listar outros nomes possíveis que surgiram no passado, como ?! (ou ?? ) ou apenas algo como "usar? Vs. usar mais do que no caractere".

Btw. não usar ? também pode satisfazer as pessoas que não gostam porque é a mesma sintaxe que o tipo opcional de outras linguagens. E aqueles que desejam torná-la uma sintaxe opcional para os tipos de opções de ferrugem. (Com isso não são preocupações que eu compartilho).

Além disso, acho que com essa votação, as pessoas que normalmente não participam do processo RFC podem ser alcançadas. Normalmente, isso pode não ser necessário, mas try! => ? é uma mudança muito grande para qualquer pessoa que escreva e / ou leia código ferrugem.

PS:
Eu coloquei uma essência com a função gostada acima em diferentes variações ("?", "?!", "??") através de não sei se havia mais. Também havia um RFC para renomear ? para ?! que foi redirecionado para esta discussão.

Desculpe, sobre a possibilidade de reiniciar uma discussão já longa em andamento: smiley_cat:.

(Observe que ?? é ruim se você ainda quiser introduzir ? como Opção porque expr??? seria ambíguo)

? é extremamente fácil esquecido

O que estamos discutindo aqui? Código totalmente sem destaque? Navegação regular do código realçado?
Ou procurando ativamente por ? em uma função?

Se eu selecionar um ? no meu editor, _todos_ os outros ? no arquivo são destacados com um fundo amarelo brilhante e a função de pesquisa também funciona, então não vejo o último como apresentando _qualquer_ dificuldade para mim.

Quanto a outros casos, prefiro resolver isso destacando melhor do que terminar com ?? ou ?! .

@dathinab eu acho que .try! () ou algo seria ainda melhor, mas isso exigiria UFCS para macros.

let namespace = namespace_opt.ok_or(Error::NoEntry).try!();

Dessa forma, é difícil não perceber, mas é tão fácil, senão mais fácil de digitar do que .unwrap() .

@eddyb : trata-se do código realçado normal, por exemplo, no github. Por exemplo, ao ler uma base de código. Do meu ponto de vista, parece meio errado se eu precisar de um realce forte para não ignorar facilmente um ? que tanto introduziria outro caminho de retorno (/ caminho para pegar) quanto possíveis alterações no tipo de uma variável de Result<T> a T

@CryZe : Concordo com você, mas não acho que conseguiremos isso em um futuro próximo. E ter algo um pouco menor que .try!() também não é tão ruim.

@CryZe Eu gosto dessa sintaxe também, mas @withoutboats mencionou algumas razões sólidas pelas quais macros de método podem prejudicar a linguagem.
Por outro lado, temo que, se as macros de método aparecerem, não acho que funcionariam bem com ? .

Não sou intrinsecamente contra o uso de dois caracteres para este sigilo, mas olhei os exemplos e não achei a mudança que fez ? parecer mais visível para mim.

Sim mesmo. Acho que precisa ser algum tipo de palavra-chave, pois os símbolos em geral são mais usados ​​para estruturação, tanto na linguagem normal quanto nas linguagens de programação.

@CryZe : Talvez algo como ?try seja uma espécie de palavra-chave. Mas, para ser honesto, não gosto disso ( ?try ).

@eddyb

e a função de pesquisa também funciona

Pesquisar ? resultará em falsos positivos, enquanto versões mais longas provavelmente não.

Procurando por ? irá gerar falsos positivos, enquanto versões mais longas provavelmente não.

Espero que os realçadores de sintaxe cuidem disso internamente (evitando falsos positivos). Na verdade, eles podem até inserir alguma forma de marcador "pode ​​retornar" na margem (ao lado dos números das linhas).

Por exemplo,
screen-2016-09-15-175131

Uau, isso definitivamente ajuda muito. Mas então você realmente precisa ter certeza de
ter seu editor configurado corretamente, o que você não pode fazer o tempo todo (como
no GitHub, por exemplo).

15/09/2016 23:52 GMT + 02: 00 Steven Allen [email protected] :

Por exemplo,
[image: screen-2016-09-15-175131]
https://cloud.githubusercontent.com/assets/310393/18568833/1deed796-7b6d-11e6-99af-75f0d7ddd778.png

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -247465972,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/ABYmbjyrt07NXKMUdmlBfaciRZq7uBVEks5qqb4sgaJpZM4HUm_-
.

@CryZe Tenho certeza de que o GitHub está apenas usando um realce de sintaxe de código aberto; podemos corrigi-lo :-).

Acho que, em geral, é uma boa ideia para o projeto Rust fazer uma recomendação de que os realçadores de sintaxe do Rust usem um estilo altamente visível em ? , para equilibrar as preocupações sobre visibilidade.

Acho que ? é a melhor escolha que podemos fazer aqui, e temos usado muito na Miri .

Voltando à motivação original, try!(...) é intrusivo e torna difícil ignorar o fluxo de erros e apenas ler o caminho feliz. Isso é uma desvantagem em comparação com as exceções invisíveis das línguas tradicionais. Expandir ? para uma palavra-chave mais envolvida teria a mesma desvantagem.

Por outro lado, com ? , quando não me importo com o fluxo de erros, posso ignorá-lo e deixá-lo desaparecer em segundo plano. E quando realmente me importo com o fluxo de erros, ainda posso ver ? perfeitamente. Destacar ? brilhantemente não é nem necessário para mim, mas se ajuda outras pessoas, ótimo. Este é um aprimoramento tanto das exceções invisíveis quanto de try! .

Mudar para um sigilo trivialmente maior como ?! não me ajudaria de forma alguma, mas tornaria a leitura e escrita de código de tratamento de erros um pouco pior.

Obrigado a todos pelo caloroso período de comentários finais (bem como por um tópico interno anterior ). Visto que estamos falando sobre a RFC mais comentada até agora , não estou surpreso em ver que a discussão em torno da estabilização também tem sido bastante ativa.

Deixe-me ir direto ao assunto primeiro: a equipe @ rust-lang / lang decidiu estabilizar o operador ? quando aplicado a valores do tipo Result . Observe que o recurso catch não está sendo estabilizado (e, de fato, ainda não foi implementado); da mesma forma, o chamado "traço portador", que é um meio de estender ? a tipos como Option , ainda está possamos adicionar o Carrier traço mais tarde (que tratar de algumas das minhas preocupações anteriores sobre a interacção potencial com inferência).

Gostaria de resumir a discussão que ocorreu desde o início do FCP em 22 de agosto . Muitos desses temas também ocorreram no thread RFC original . Se você estiver interessado em ler o tópico, o comentário FCP e o comentário de recapitulação nesse tópico tentam cobrir a conversa em profundidade. Em alguns casos, vou criar um link para comentários no tópico original se eles forem mais aprofundados do que os correspondentes deste tópico.

O escopo do operador ? deve ser a expressão atual, não a função atual.

Quando ocorre um erro, a macro try! propaga esse erro incondicionalmente para a função de chamada (em outras palavras, ela executa um return com o erro). Conforme projetado atualmente, o operador ? segue este precedente, mas com a intenção de oferecer suporte a uma palavra-chave catch que permite ao usuário especificar um escopo mais limitado. Isso significa, por exemplo, que x.and_then(|b| foo(b)) pode ser escrito como catch { foo(x?) } . Em contraste, várias línguas recentes usam o operador ? para significar algo mais análogo a and_then , e existe a preocupação de que isso possa ser confuso para novos usuários.

Em última análise, uma vez que catch é implementado, isso é uma questão de padrões . E há vários motivos pelos quais acreditamos que o padrão de "break out of function" (com a opção de personalizar) é mais apropriado para ? em Rust :

O ? obscurece o fluxo de controle porque é difícil de detectar.

Uma preocupação comum é que o operador ? é muito fácil de ignorar . Este é claramente um ato de equilíbrio. Ter um operador leve torna mais fácil focar no "caminho feliz" quando você deseja - mas é importante ter alguma indicação de onde os erros podem ocorrer (em contraste com as exceções, que introduzem o fluxo de controle implícito). Além disso, é fácil tornar ? mais fácil de localizar por meio do realce de sintaxe (por exemplo, 1 , 2 ).

Por que não macros de método?

Um dos grandes benefícios de ? é que ele pode ser usado na posição pós-correção, mas
poderíamos obter benefícios semelhantes de "macros de método" como foo.try! . Embora verdadeiras, as macros de método abrem muita complexidade , especialmente se você quiser que se comportem como métodos (por exemplo, despachadas com base no tipo de receptor, e não usando escopo léxico). Além disso, usar uma macro de método como foo.try! tem uma sensação de peso significativamente maior do que foo? (consulte o ponto anterior).

Que contratos From oferecer?

Na discussão RFC original, decidimos adiar a questão de [se deveria haver "contratos" para From ] ((https://github.com/rust-lang/rust/issues/31436#issuecomment -180558025). O consenso geral da equipe lang é que se deve ver o operador ? como invocando o traço From e que os impls do traço From podem naturalmente fazer tudo é permitido por suas assinaturas de tipo. Observe que o papel do traço From é bastante limitado aqui de qualquer maneira: ele é simplesmente usado para converter de um tipo de erro para outro (mas sempre no contexto de um Result ).

No entanto , gostaria de observar que a discussão sobre um traço "portador" está em andamento , e a adoção de convenções fortes é mais importante nesse cenário . Em particular, o traço do portador define o que constitui "sucesso" e "falha" para um tipo, bem como se um tipo de valor (por exemplo, um Option ) pode ser convertido em outro (por exemplo, um Result ). Muitos argumentaram que não queremos oferecer suporte a interconversões arbitrárias entre tipos "semelhantes a erros" (por exemplo, ? não deve ser capaz de converter Option em Result ). Obviamente, uma vez que os usuários finais podem implementar o traço Carrier para seus próprios tipos conforme eles escolherem, esta é, em última análise, uma diretriz, mas acho que é importante.

porta tente! usar ?

Não acho que possamos fazer isso de forma compatível com versões anteriores e devemos deixar a implementação de try! sozinha. Devemos descontinuar try! ?

@nrc Por que não é compatível com versões anteriores?

@withoutboats try!(x) é (x : Result<_, _>)? e provavelmente poderíamos implementá-lo dessa forma se _ quiséssemos_, mas em geral x? poderia inferir a qualquer coisa que suporte Carrier traço (no futuro), um exemplo é iter.collect()? que com try! seria apenas Result mas pode ser realisticamente Option .

Isso faz sentido. Achei que aceitamos que adicionar impls a std poderia causar ambiguidades de inferência; por que não aceitar isso também aqui?

De qualquer forma, acho que try! deve ser descontinuado.

? é mais útil em um padrão de construtor como contexto, enquanto try é mais útil em um método aninhado como contexto. Acho que não deve ser descontinuado, seja o que for que isso signifique.

@ est31 Eles fazem exatamente a mesma coisa agora, exceto para inferência. Não tenho certeza do que você quer dizer exatamente, mas ? geralmente é estritamente mais limpo (novamente, inferência de módulo). Você poderia dar exemplos?

@eddyb foo()?.bar()?.baz() é melhor do que try!(try!(foo()).bar()).baz() , e try!(bar(try!(foo()))) é melhor do que bar(foo()?)?

Acho ? mais legível em ambos os casos. Ele reduz a desordem desnecessária de parênteses.

Quando isso vai parar no estável?

@ofek isso é para a coisa toda, que ainda não está completa, então é difícil dizer. https://github.com/rust-lang/rust/pull/36995 estabilizou a sintaxe básica ? , que deve ser estável no 1.14.

Não se esqueça de adicionar o? operador para a referência agora que está estável: https://doc.rust-lang.org/nightly/reference.html#unary -operator-expression

E @bluss apontou que o livro também está desatualizado: https://doc.rust-lang.org/nightly/book/syntax-index.html

@tomaka

Eu me oponho a estender? em Opções.

Não precisa ser usado para fins de erro. Por exemplo, se eu tenho um método wrapper para pegar get e mapeá-lo por alguma função, então eu gostaria de poder propagar o None case acima.

? foi apresentado como sendo apenas para erros; a notação mais geral para propagação geral como essa não era um objetivo.

Em 29 de outubro de 2016, 11:08-0400, ticki [email protected] , escreveu:

@tomaka (https://github.com/tomaka)

Eu me oponho a estender? em Opções.

Não precisa ser usado para fins de erro. Por exemplo, se eu tenho um método wrapper para pegar get e mapear por alguma função, então gostaria de poder propagar o caso None.

-
Você está recebendo isso porque foi mencionado.
Responda a este e-mail diretamente, visualize-o no GitHub (https://github.com/rust-lang/rust/issues/31436#issuecomment-257096575) ou ignore o tópico (https://github.com/notifications/unsubscribe -auth / AABsipGIpTF1-7enk-z_5JRYYtl46FLPks5q42DCgaJpZM4HUm_-).

Este (= ? si) agora é um recurso estável! (De Rust 1.13)

Dois problemas de documentação:

  • [x] Atualizar capítulo de tratamento de erros no livro # 37750
  • [x] Atualizar documentos de Carrier trait para a situação atual # 37751

Observe que nem catch nem o traço Carrier foram implementados corretamente ainda, apenas o recurso ? .

Carrier existe, e as mensagens de erro referem-se a ele ao usar ? , então seria melhor se o problema da operadora tivesse sido corrigido em vez de rejeitado. Seus documentos também precisam ser atualizados, já que os documentos referem-se a ele sendo implementado para Option. (O que é falso).

35946 deve remover qualquer menção de Carrier das mensagens de erro. Ele faz som como devemos pelo menos remover o Option menção dos Carrier docs.

Estou adicionando T-libs a este problema devido à interação com o traço Carrier

Oi; em # 31954 o upcast para o tipo de erro é feito usando From (e semelhante ao mesmo na cabeça atual ), mas o RFC 243 afirma claramente que Into deve ser usado para a conversão.

Existe alguma razão pela qual From foi usado em vez disso? Ao tentar fazer o upcast para algum tipo de erro genérico (ou seja, io::Error , ou algo mais em uma caixa externa) From não pode ser implementado para tipos de erros locais.

(FWIW, como autor de RFC 243, não pensei muito sobre se From ou Into é preferível e pode ou não ter feito a escolha certa. O que é apenas para dizer que a questão deve ser decidida com base nos méritos (que neste ponto podem incluir compatibilidade com versões anteriores), em vez do que está escrito no RFC.)

Infelizmente, haveria uma regressão. Se nenhuma instância (não trivial) From<...> para um tipo de erro for implementada, o compilador pode deduzir o tipo em certos casos, onde não pode deduzi-lo ao usar Into (o conjunto de From instâncias são limitadas pela caixa atual, o conjunto completo de Into instâncias não é conhecido durante a compilação de uma caixa).

Consulte https://play.rust-lang.org/?gist=6d3ee9f93c8b40094a80d3481b12dd00 ("simplificado" de um problema do mundo real envolvendo fmt::Error em src / librustc / util / ppaux.rs # L81 )

Há um novo RFC que substituirá o antigo em termos de descrição de ? : rust-lang / rfcs / pull / 1859

Estou -1 em ter catch blocos em tudo, quando eles já são muito mais concisos com os fechamentos.

Os fechamentos interferem nas declarações de controle de fluxo. ( break , continue , return )

Agora que https://github.com/rust-lang/rfcs/pull/1859 foi mesclado e afirma que estamos reutilizando este problema como seu problema de rastreamento, gostaria de propor que reavaliamos o catch Parte

Não estou propondo que nos livremos dele imediatamente, mas o entusiasmo por catch nunca foi tão grande quanto foi por ? e acho que vale a pena ter certeza de que ainda faz sentido à luz das expressões idiomáticas que surgiram para ? e que se espera que surjam à luz de Try .

Ainda estou ansioso para pegar; hoje cedo eu tive que contorcer algum código de uma forma que poderia ter sido evitada se o catch estivesse estável.

O recurso foi implementado todas as noites com a sintaxe do catch , não foi?

@withoutboats Sim, atualmente é do catch , pois catch { ... } conflito com literais de estrutura ( struct catch { }; catch { } ).
.
@archshift Como @SimonSapin apontou acima, fechamentos interferem em break , continue e return .

@bstrie Acho que freqüentemente quero catch atualmente; surge muito durante a refatoração.

Não percebi que planejávamos exigir do catch como sintaxe. Dado que o risco de quebra no mundo real parece excessivamente baixo (ambos violam as diretrizes de nomenclatura de estrutura e teriam que ter o construtor sendo a primeira expressão na instrução (o que é raro fora da posição de retorno)), poderíamos talvez alavancar rustfmt para reescrever quaisquer identificadores ofensivos para catch_ ? Se é necessário que o Rust 2.0 faça isso, então, bem, sempre fui do tipo que disse que o Rust 2.0 deveria conter apenas alterações de interrupção triviais ...: P

@bstrie Não queremos de forma alguma exigir do catch longo prazo. A discussão que levou ao uso dessa sintaxe por enquanto está aqui: https://github.com/rust-lang/rust/pull/39921

Excelente, obrigado pelo contexto.

Só vim aqui porque esperava que catch fosse estável e tive que aprender que não é - então sim, com certeza, agora que ? está estável, seria ótimo também ter catch .

Eu queria ver o quão longe havíamos chegado no resto disso e ver a discussão do catch.

Eu tenho considerado uma idéia provavelmente boba que ajudaria em casos como este: permitir opcionalmente prefixar palavras-chave com @ ou algum outro sigilo, então fazer com que todas as novas palavras-chave usem apenas o sigilo. Também tivemos um problema semelhante com a discussão da co-rotina. Não me lembro se considerei isso como uma solução - talvez tenha - mas parece que isso pode continuar aparecendo.

FWIW, C # realmente suporta o oposto: @ para usar palavras-chave como identificadores. Isso é comumente visto no Razor, onde você passa coisas como new { <strong i="6">@class</strong> = "errorbox" } para definir propriedades em nós HTML.

@scottmcm
Isso é interessante. Eu não sabia sobre isso. Mas, para o Rust, temos que seguir o outro caminho por causa da compatibilidade.

Boa ideia da parte deles, no entanto. O ecossistema .net tem muitos idiomas, todos com palavras-chave díspares e todos capazes de chamar uns aos outros.

Outra possibilidade para o futuro das palavras-chave: colocá-las atrás de um atributo, como uma espécie de #[feature] estável.

Acho que esse problema precisará de uma solução mais geral no longo prazo.

@camlorn Este tópico pode interessar a você, especificamente a ideia de Aaron sobre Rust "épocas": https://internals.rust-lang.org/t/pre-rfc-stable-features-for-breaking-changes/5002

Acho que devemos vincular https://github.com/rust-lang/rust/issues/42327 a partir da descrição do problema aqui. (Além disso, talvez o texto RFC deva ser atualizado para criar um link para lá em vez de aqui.)

(EDITAR: Eu postei um comentário lá, não tenho certeza de quem está ou ainda não se inscreveu nele!)

@camlorn Poderia, entretanto, abrir o caminho "rustfmt faz uma reescrita trivial por um tempo, depois se torna um caminho de palavra-chave". O AKA aproveita a vantagem de "não está quebrando se houver uma anotação extra que poderia ter sido escrita para fazê-lo funcionar em ambos" escapatória na garantia de estabilidade. E, semelhante ao UFCS para mudanças de inferência, um modelo hipotético de "loja em forma totalmente elaborada" poderia manter velhas caixas funcionando.

Eu não me importaria se a sintaxe para o bloco catch fosse apenas do { … } ou mesmo ?{ … }

do tem a boa propriedade de já ser uma palavra-chave. Ele tem a propriedade duvidosa (dependendo da sua perspectiva) de invocar a anotação do semelhante a Haskell, embora isso nunca tenha interrompido seus usos anteriores e este seja um pouco mais próximo no caso de uso.

também parece, mas se comporta de maneira diferente do que os javascripts propostos para expressões

Essa proposta não é realmente relevante para Rust, que já usa blocos nus como expressões.

Eu estava apenas apontando a possível confusão, uma vez que ambos seriam parecidos, mas farão coisas totalmente diferentes.

Ele tem a propriedade duvidosa (dependendo da sua perspectiva) de invocar a notação do tipo Haskell

@rpjohnst Resultado e Opção são mônadas, então são pelo menos conceitualmente semelhantes. Também deve ser compatível com versões futuras para estendê-lo no futuro para suportar todas as mônadas.

Certo, no passado a objeção era que se adicionarmos do para algo conceitualmente semelhante às mônadas, mas sem apoiá-las totalmente, isso deixaria as pessoas tristes.

Ao mesmo tempo, embora provavelmente pudéssemos torná-lo compatível com a notação de doação completa, provavelmente não deveríamos adicionar a notação de doação completa. Não é composível com estruturas de controle como if / while / for / loop ou break / continue / return , que devemos ser capazes de usar dentro e através dos blocos catch (ou neste caso do ). (Isso ocorre porque a do-notação completa é definida em termos de funções de ordem superior e, se colocarmos o conteúdo de um bloco catch em uma série de fechamentos aninhados, de repente controlaremos o fluxo de todas as quebras.)

Portanto, no final, a desvantagem de do é que ele se parece com do-notação sem realmente ser do-notação e sem um bom caminho para se tornar do-notação. Pessoalmente, estou totalmente bem com isso porque Rust não vai receber a notação do qualquer maneira - mas essa é a confusão.

@nikomatsakis , # 42526 foi mesclado, você pode marcá-lo como concluído na lista de rastreamento :)

É possível tornar a palavra-chave catch contextual, mas desabilitá-la se struct catch estiver no escopo e emitir um aviso de descontinuação?

Não tenho certeza se isso é apropriado, mas eu encontrei um problema que talvez precise ser resolvido, pois às vezes você deseja abortar um ok em vez de um valor de erro que é atualmente possível quando você usa return na medida em que você deseja frequentemente abortar um erro _inner_ por meio de um _outer_ ok. Como quando uma função diz retorna Option<Result<_,_>> que é bastante comum em um iterador.

Em particular, tenho uma macro em um projeto que uso copiosamente agora:

macro-rules! option_try {
    ( $expr:expr ) => {
        match $expr {
            Ok(x)  => x,
            Err(e) => return Some(Err(e.into())),
        }
    }
}

É muito comum que uma função chamada dentro de uma implementação Iterator::next precise abortar imediatamente com Some(Err(e)) em caso de falha. Esta macro funciona dentro de um corpo de função normal, mas não dentro de um bloco catch porque catch não pega return categoricamente, mas apenas a sintaxe especial ? .

Embora no final os retornos rotulados tornassem toda a idéia do bloco catch redundante, não é?

Parece que # 41414 está pronto. Alguém poderia atualizar o OP?

Atualização: RFC rust-lang / rfcs # 2388 agora está mesclado e, portanto, catch { .. } deve ser substituído por try { .. } .
Veja o problema de rastreamento logo acima deste comentário.

O que isso significa para a edição 2015, se a sintaxe do catch { .. } ainda está em um caminho para a estabilização ou será abandonada e apenas suportada por try { .. } na edição 2018+?

@ Nemo157 Este último.

Há duas declarações try ... catch ... na proposta atual? Nesse caso, não entendo a semântica.

De qualquer forma, se esta proposta é apenas sobre desugar, estou bem com ela. ou seja, um bloco catch apenas muda para onde o operador ? sai?

Como todas as caixas de seleção estão marcadas na postagem superior, quando avançaremos? Se ainda houver problemas não resolvidos, precisamos adicionar novas caixas de seleção.

Provavelmente, há muitas questões restantes não resolvidas não registradas.
Por exemplo, o comportamento ok-wrapping não é estabelecido dentro da equipe lang, o design de Try não está finalizado e assim por diante. Provavelmente deveríamos dividir esse problema em vários outros específicos, pois provavelmente já não é mais útil.

Hmm ... me incomoda que essas perguntas não tenham sido gravadas.

@ mark-im Então, para esclarecer, acho que eles estiveram em algum lugar; mas não em um local; é um atm um pouco disperso em vários RFCs e questões e tal, então o que precisamos fazer é gravá-los nos locais adequados.

O design do traço de apoio é rastreado em https://github.com/rust-lang/rust/issues/42327; há uma ampla discussão sobre os pontos fracos do atual e uma possível nova direção. (Estou planejando fazer um pré-RFC para uma mudança lá, uma vez que 2018 se estabilize um pouco.)

Portanto, acho que apenas try{} resta aqui, e a única discordância que conheço são coisas que foram acertadas na RFC e reconfirmadas em um dos problemas mencionados acima. Ainda pode ser bom ter um problema de rastreamento distinto, no entanto.

Vou adicionar uma caixa de seleção para a tarefa de implementação pendente que sei que ainda precisa ser feita ...

@scottmcm Eu sei que @joshtriplett tinha preocupações sobre o empacotamento OK (anotado na try RFC) e eu pessoalmente gostaria de restringir break na estabilização inicial de try { .. } portanto que você não pode fazer loop { try { break } } e tal.

@Centril

para que você não possa fazer loop { try { break } }

No momento, você não pode usar break em um bloco não-loop, e está correto: break só deve ser usado em loops. Para sair mais cedo de um bloco try , a maneira padrão é escrever Err(e)? . e força que as primeiras folhas estejam sempre no caminho de controle "anormal".

Portanto, minha proposta é que o código que você mostrou deve ser permitido e deve quebrar o loop , não apenas deixando o try .

O benefício imediato, é quando você vê break você sabe que vai quebrar de um loop, e você sempre pode substituí-lo por um continue . Além disso, elimina a necessidade de rotular o ponto de interrupção ao usar blocos try dentro de um loop e você deseja sair do loop.

@Centril Obrigado por aumentar isso.

Com relação a break , eu pessoalmente ficaria bem em simplesmente dizer que try não se importa com break e passa para o loop que o contém. Só não quero que break interaja com try alguma.

Quanto a Ok -embalagem, sim, gostaria de abordar isso antes de estabilizar try .

@centril Sim, estou ciente. Mas é importante lembrar que isso é re-re-levantar a questão. A RFC decidiu tê-lo , foi implementado sem ele, mas então a intenção original foi tomada _novamente_ , e a implementação mudou para seguir o RFC. Portanto, minha grande questão é se algum fato material mudou, especialmente considerando que este é um dos tópicos mais barulhentos que já vi ser discutido em RFCs + IRLO.

@scottmcm Claro, como você sabe, eu concordo em manter Ok -wrapping;) e concordo que a questão deve ser considerada resolvida.

Eu só queria comentar sobre isso, não tenho certeza se isso é a coisa certa:

Essencialmente, uma situação que tenho são callbacks em um framework GUI - em vez de retornar Option ou Result , eles precisam retornar UpdateScreen , para informar ao framework se a tela precisa ser atualizado ou não. Freqüentemente, não preciso de nenhum registro (simplesmente não é prático fazer logon em todos os erros menores) e simplesmente retornar UpdateScreen::DontRedraw quando um erro ocorrer. No entanto, com o atual operador ? , tenho que escrever isso o tempo todo:

let thing = match fs::read(path) {
    Ok(o) => o,
    Err(_) => return UpdateScreen::DontRedraw,
};

Como não consigo converter de Result::Err em UpdateScreen::DontRedraw por meio do operador Try, isso se torna muito tedioso - muitas vezes tenho pesquisas simples em mapas de hash que podem falhar (o que não é um erro ) - com tanta frequência em um retorno de chamada, tenho de 5 a 10 usos do operador ? . Como o texto acima é muito prolixo de escrever, minha solução atual é impl From<Result<T>> for UpdateScreen assim e, em seguida, usar uma função interna no retorno de chamada como este:

fn callback(data: &mut State) -> UpdateScreen {
     fn callback_inner(data: &mut State) -> Option<()> {
         let file_contents = fs::read_to_string(data.path).ok()?;
         data.loaded_file = Some(file_contents);
         Some(())
     }

    callback_inner(data).into()
}

Como o retorno de chamada é usado como um ponteiro de função, não posso usar um -> impl Into<UpdateScreen> (por algum motivo, retornar impl não é permitido atualmente para ponteiros de função). Portanto, a única maneira de usar o operador Try é fazer o truque da função interna. Seria bom se eu pudesse simplesmente fazer algo assim:

impl<T> Try<Result<T>> for UpdateScreen {
    fn try(original: Result<T>) -> Try<T, UpdateScreen> {
        match original {
             Ok(o) => Try::DontReturn(o),
             Err(_) => Try::Return(UpdateScreen::DontRedraw),
        }
    }
}

fn callback(data: &mut State) -> UpdateScreen {
     // On any Result::Err, convert to an UpdateScreeen::DontRedraw and return
     let file_contents = fs::read_to_string(data.path)?;
     data.loaded_file = Some(file_contents);
     UpdateScreen::Redraw
}

Não tenho certeza se isso seria possível com a proposta atual e apenas gostaria de adicionar meu caso de uso para consideração. Seria ótimo se um operador de teste personalizado pudesse oferecer suporte a algo assim.

EDITAR:
Eu cometi um erro.


Ignorar esta postagem


Isso poderia funcionar melhor com a inferência de tipo, ele falha mesmo em casos simples.

fn test_try(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x // : Option<_> // why is this type annotation necessary
    = try { div? + 1 };

    println!("{:?}", x);
}

Se isso for reescrito para usar um fecho em vez do bloco try (e no processo de quebra automática solta), obteremos

fn test_closure(a: u32, b: u32) {
    let div = if b != 0 {
        Some(a / b)
    } else {
        None
    };

    let x =  (|| (div? + 1).into())();

    println!("{:?}", x);
}

O que não requer uma anotação de tipo, mas requer que envolvamos o resultado.

Parque infantil

@KrishnaSannasi seu exemplo baseado em encerramento também tem uma falha de inferência de tipo ( playground ) porque Into não restringe a saída e você não a usa em qualquer lugar que o faça depois.

Isso parece ser principalmente um problema com o traço Try vez de blocos try , semelhante a Into ele não propaga nenhuma informação de tipo das entradas para a saída, então o tipo de saída deve ser determinável por seu uso posterior. Há _muito_ discussão em https://github.com/rust-lang/rust/issues/42327 sobre o traço, não li, então não tenho certeza se alguma das propostas poderia corrigir esse problema.

@ Nemo157

Sim, fiz uma alteração de última hora em meu código para torná-lo útil e não testei. Foi mal.

A que distância estamos de estabilizar os blocos try? É o único recurso que preciso da noite: D

@Arignir

Acredito que, uma vez feito isso, pode ser estabilizado.

block try {} catch (ou outras idents a seguir) para deixar o espaço de design aberto para o futuro e mostrar às pessoas como fazer o que desejam com a correspondência

Não existe um design intermediário para permitir o recurso agora, embora ainda deixe a possibilidade de deixar o espaço de design aberto para o futuro (e, portanto, um eventual bloco catch )?

O PR que fiz deve marcar essa caixa de qualquer maneira, CC @nikomatsakis

Tentei usar isso pela primeira vez ontem e fiquei um pouco surpreso que isto:

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    }?;
    Ok(x)
}

não compila devido a

error[E0284]: type annotations required: cannot resolve `<_ as std::ops::Try>::Ok == _`

Em vez disso, eu tive que fazer

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: Result<(), ()> = try {
        Err(())?
    };
    let x = x?;
    Ok(x)
}

em vez de.

Isso foi confuso no início, então talvez valha a pena alterar a mensagem de erro ou mencionar em --explain ?

Se você mover o ponto de interrogação em seu primeiro exemplo um pouco para baixo, para

#![feature(try_blocks)]
fn main() -> Result<(), ()> {
    let x: () = try {
        Err(())?
    };
    Ok(x?)
}

Você obtém uma mensagem de erro melhor. O erro surge porque Rust não consegue decidir para qual tipo resolver o try { ... } , devido ao quão geral ele é. Como ele não pode resolver esse tipo, ele não pode saber o que é o tipo <_ as Try>::Ok , e é por isso que você recebeu o erro. (porque o operador ? desembrulha o tipo Try e devolve o tipo Try::Ok ). A ferrugem não pode funcionar com o tipo Try::Ok por conta própria; ela deve ser resolvida por meio da característica Try e do tipo que implementa essa característica. (que é uma limitação da forma atual de verificação de tipo)

Tudo para este recurso está implementado, correto? Em caso afirmativo, por quanto tempo queremos sentar nisso antes de estabilizar?

Achei que ainda era uma questão em aberto se queríamos isso ou não. Em particular, houve alguma discussão sobre se queremos usar a linguagem de exceções aqui (tente, pegue).

Pessoalmente, sou fortemente contra tentar criar a impressão de que Rust tem algo como exceções. Eu acho que o uso da palavra catch em particular é uma má ideia porque qualquer um vindo de uma linguagem com exceções irá presumir que isso se desenrola, e não acontece. Eu esperaria que fosse confuso e doloroso de ensinar.

Em particular, houve alguma discussão sobre se queremos usar a linguagem de exceções aqui (tente, pegue).

Acho que https://github.com/rust-lang/rfcs/pull/2388 definiu definitivamente se try como nome é aceitável. Esta não é uma questão aberta. Mas a definição do traço Try , bem como Ok -embalagem parece ser.

Ok -wrapping já foi decidido no RFC original, então removido durante a implementação e finalmente adicionado novamente mais tarde. Não vejo como é uma questão em aberto.

@rpjohnst Bem, é em virtude de Josh discordar da decisão do RFC original ... :) É um assunto resolvido para mim . Consulte https://github.com/rust-lang/rust/issues/31436#issuecomment -427096703, https://github.com/rust-lang/rust/issues/31436#issuecomment -427252202 e https: // github.com/rust-lang/rust/issues/31436#issuecomment -437129491. De qualquer forma ... o ponto do meu comentário foi que try como "linguagem de exceções" é uma questão resolvida.

Woah, quando isso aconteceu? A última coisa de que me lembro foram as discussões sobre internos. Eu também sou contra o Ok-wrapping :(

Eca. Não posso acreditar que isso aconteceu. Ok -envolvimento é tão horrível (quebra a intuição muito sensata de que todas as expressões de retorno em uma função devem ser do tipo de retorno da função). Então sim, definitivamente com @ mark-im nisso. A discordância de Josh é suficiente para manter este um assunto aberto e obter mais discussão sobre ele? Eu ficaria feliz em ajudá-lo a lutar contra isso, não que isso signifique algo como um membro não pertencente à equipe.

Ok -envolvimento conforme aceito no RFC 243 (literalmente aquele que definiu o operador ? , se você estiver se perguntando quando isso aconteceu) não muda nada sobre os tipos de expressões de retorno de função. É assim que o RFC 243 o definiu: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#catch -expressions

Este RFC também introduz uma forma de expressão catch {..} , que serve para "definir o escopo" do operador ? . O operador catch executa seu bloco associado. Se nenhuma exceção for lançada, o resultado será Ok(v) onde v é o valor do bloco. Caso contrário, se uma exceção for lançada, o resultado será Err(e) .

Observe que catch { foo()? } é essencialmente equivalente a foo() .

Ou seja, ele pega um bloco do tipo T e o envolve incondicionalmente para produzir um valor do tipo Result<T, _> . Qualquer instrução return no bloco é completamente afetada; se o bloco for a expressão final de uma função, a função deve retornar Result<T, _> .

Ele foi implementado dessa forma todas as noites para idades: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=88379a1607d952d4eae1d06394b50959. Isso foi feito após muita discussão pela equipe da lang em, e com links a partir, deste tópico: rust-lang / rust # 41414 (e isso também está relacionado no início desta edição).

Em 28 de maio de 2019, 17:48:27 PDT, Alexander Regueiro [email protected] escreveu:

Eca. Não posso acreditar que isso aconteceu. Ok embrulhar é tão horrível (é
quebra a intuição muito sensata de que todas as expressões de retorno em um
função deve ser do tipo de retorno da função). Então sim, definitivamente
com @ mark-im sobre isso. O desacordo de Josh é suficiente para manter este
questão aberta e obter mais discussão sobre ela? Eu ficaria feliz em ajudá-lo
na luta contra isso, não que isso signifique algo como um não membro da equipe.

Obrigado. Não estou discordando disso apenas por mim; Também estou representando as inúmeras pessoas que vi expressando a mesma posição que eu.

@joshtriplett @ mark-im @alexreg

Um de vocês pode explicar por que acham a embalagem Ok tão desagradável ou fornecer um link para algum lugar que já foi explicado antes? Fui procurar, mas de uma forma superficial não vi nada. Eu não tenho nenhum cavalo nisso (eu apenas comentei literalmente sobre isso porque eu vi todas as caixas marcadas e nenhuma discussão por um mês), mas agora que eu chutei este ninho de vespas, quero entender melhor os argumentos.

Na terça-feira, 28 de maio de 2019 às 03:40:47 PM -0700, Russell Johnston escreveu:

Ok -wrapping já foi decidido no RFC original, então removido durante a implementação e finalmente adicionado novamente mais tarde. Não vejo como é uma questão em aberto.

Acho que você respondeu parcialmente à sua própria pergunta. Eu não acho que todos
envolvido na discussão RFC original estava na mesma página; try era
absolutamente algo que muitas pessoas queriam, mas não houve consenso
para embalagem Ok.

Na terça-feira, 28 de maio de 2019 às 03:44:46 PM -0700, Mazdak Farrokhzad escreveu:

De qualquer forma ... o ponto do meu comentário foi que try como "linguagem de exceções" é uma questão resolvida.

Para esclarecer, não acho a metáfora de "exceções" atraente,
e muitas das tentativas de coisas como try-fn e Ok-wrapping parecem
tentativa de fazer a linguagem falsa, tendo um mecanismo de exceção.
Mas o próprio try , como um meio de capturar ? em algo diferente do
limite de função, faz sentido como uma construção de fluxo de controle.

Na terça-feira, 28 de maio de 2019 às 23h37min33s -0700, Gabriel Smith escreveu:

Um de vocês pode explicar por que acham a embalagem Ok tão desagradável

Por alguns motivos:

Na terça-feira, 28 de maio de 2019 às 05:48:27 PM -0700, Alexander Regueiro escreveu:

quebra a intuição muito sensata de que todas as expressões de retorno em uma função devem ser do tipo de retorno da função

Isso quebra várias abordagens que as pessoas usam para raciocínio direcionado ao tipo
sobre funções e estrutura de código.

Eu certamente tenho minhas próprias idéias aqui, mas poderíamos, por favor, não reabrir este tópico agora? Acabamos de ter uma polêmica conversa de mais de 500 posts sobre sintaxe, então eu gostaria de evitar minas terrestres por um tempo.

Se isso for bloqueado na equipe lang discutindo isso, a caixa de seleção "resolver se os blocos de captura devem" quebrar "o valor do resultado (# 41414)" deve ser desmarcada novamente (talvez com um comentário que está bloqueado na equipe lang) para que as pessoas olhando para esse problema de rastreamento sabe o status?

Desculpe, não estou tentando reabrir nada - apenas reafirmar o que está marcado como decidido no problema de rastreamento e quando + como isso aconteceu.

@rpjohnst Obrigado pela informação!

@yodaldevoid Josh praticamente resumiu meus pensamentos.

Eu me oponho um pouco menos a ok-wrapping confinado a um bloco (em oposição a afetar o tipo de uma função), mas acho que ainda estabelece um mau precedente: como disse Josh, “Não acho a metáfora das exceções atraente”

@joshtriplett também resumiu essencialmente minhas visões: as questões são a adequação da metáfora da "exceção" (possivelmente pânico + catch_unwind é muito mais análogo) e o raciocínio baseado em tipo. De fato, estou bem com blocos try como escopo e mecanismo de fluxo de controle também, mas não os pontos mais radicais.

Ok, é justo, não vamos ter todo o debate aqui ... talvez apenas desmarque a caixa conforme sugerido e coloque-o de volta no debate da equipe lang (em seu próprio tempo), usando alguns dos fundamentos mencionados neste tópico? Contanto que a estabilização não seja apressada, isso parece razoável, suponho.

Uma sintaxe para anotações de tipo foi acordada? Eu esperava por algum try { foo()?; bar()?; }.with_context(|_| failure::err_msg("foon' n' barn'")?; , que não está nem remotamente interessado em compilar: error[E0282]: type annotations needed .

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

Eu li os comentários um pouco atrás (e carregar 300 comentários novamente no github é muito tedioso), mas eu me lembro que a maioria (se não todos) dos exemplos relacionados ao debate sobre Try::Ok empacotamento usado Ok no exemplo. Considerando que Option implementa Try , gostaria de saber como isso afeta a posição da equipe em que lado do debate se encontra.

Cada vez que uso Rust, fico pensando "cara, eu realmente gostaria de poder usar um bloco try aqui", mas cerca de 30% das vezes é porque eu realmente gostaria de poder usar try por Option s (como Costumava usar no Scala, que usava a sintaxe for para se aplicar às mônadas em geral, mas é muito semelhante a try aqui).

Hoje mesmo, eu estava usando a caixa json e ela expõe os métodos as_* que retornam opções.

Usando as duas sintaxes, meu exemplo seria:

match s {
  "^=" => |a, b| try { a.as_str()?.starts_with(b.as_str()?) }.unwrap_or(false),
  "$=" => |a, b| try { Some(a.as_str()?.ends_with(b.as_str()?)) }.unwrap_or(false),
  // original
  "$=" => |a, b| {
    a.as_str()
      .and_then(|a| b.as_str().map(|b| (a, b)))
      .map(|(a, b)| a.starts_with(b))
      .unwrap_or(false)
    },
}

Eu acho que, contextualmente, se o tipo de retorno é Option ou Result é bem claro, e mais, não importa muito (no que diz respeito à compreensão do código). De forma transparente, o significado é claro: "Preciso verificar se essas duas coisas são válidas e fazer uma operação nelas." Se eu tivesse que escolher uma delas, escolheria a primeira, porque não acredito que haja qualquer perda de compreensão quando você considera que esta função está inserida em um contexto maior, pois try irá sempre seja.

Quando comecei a olhar para este tópico, fui contra Ok embrulhar porque achei que seria melhor ser explícito, mas desde então, comecei a prestar atenção às vezes que dizia "Gostaria de poderia usar um bloco try aqui "e cheguei à conclusão de que Ok -embalagem é boa.

Originalmente, pensei que não Ok empacotamento seria melhor no caso em que sua última instrução é uma função que retorna o tipo que implementa Try , mas a diferença na sintaxe seria

try {
  fallible_fn()
}

try {
  fallible_fn()?
}

E, neste caso, eu acho que Ok -envolvimento é melhor porque deixa claro que fallible_fn é uma função de retorno Try , então é realmente mais explícito.

Quero saber o que a oposição pensa sobre isso e, como não consigo ver muitos outros neste tópico, @joshtriplett.

EDIT: Devo mencionar que estava apenas olhando para isso de uma perspectiva de ergonomia / compreensão de leitura. Não tenho ideia se um tem mais méritos técnicos do que o outro em termos de implementação, como inferência mais fácil.

Eu também queria dar a try uma tentativa de análise aninhada de Option :

#![feature(try_blocks)]

struct Config {
    log: Option<LogConfig>,
}

struct LogConfig {
    level: Option<String>,
}

fn example(config: &Config) {
    let x: &str = try { config.log?.level? }.unwrap_or("foo");
}

Isso falha com

error[E0282]: type annotations needed
  --> src/lib.rs:12:19
   |
12 |     let x: &str = try { config.log?.level? }.unwrap_or("foo");
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type
   |
   = note: type must be known at this point

O mais perto que cheguei foi

fn example(config: &Config) {
    let x: Option<&str> = try { &**config.log.as_ref()?.level.as_ref()? };
    let x = x.unwrap_or("foo");
}

O as_ref é bastante lamentável. Eu sei que Option::deref ajudará alguns aqui, mas não o suficiente. Parece que a ergonomia de alguma forma (ou ideia relacionada) deve entrar em jogo.

As múltiplas linhas também são lamentáveis.

try poderia usar um fallback de inferência de Result como literais inteiros? Isso permitiria que a primeira tentativa de @shepmaster inferisse Result<&str, NoneError> ? Que problemas restantes haveria - provavelmente encontrar um tipo de erro comum para ? s para conversão? (Já perdi a discussão sobre isso em algum lugar?)

@shepmaster Concordo com a inferência de tipo. Curiosamente, porém, tentei seu código exato com uma implementação um tanto ingênua de try_ e funcionou bem: https://github.com/norcalli/koption_macros/blob/4362fba8fa9b6c62fdaef4df30060234381141e7/src/lib.rs#L23

    let x = try_! { config.log?.level? }.unwrap_or("foo".to_owned());
    assert_eq!(x, "debug");

funciona muito bem.

uma implementação um tanto ingênua de try_

Sim, mas sua invocação de macro retorna String , não &str , exigindo propriedade. Você não mostra o código ao redor, mas isso falhará porque não temos propriedade de Config :

fn example(config: &Config) {
    let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
}
error[E0507]: cannot move out of captured variable in an `Fn` closure
  --> src/lib.rs:20:21
   |
19 | fn example(config: &Config) {
   |            ------ captured outer variable
20 |     let x = try_! { config.log?.level? }.unwrap_or_else(|| String::from("foo"));
   |                     ^^^^^^^^^^ cannot move out of captured variable in an `Fn` closure

Ele também aloca incondicionalmente um String ; Usei unwrap_or_else neste exemplo para evitar essa ineficiência.

É uma pena que esse recurso não tenha sido estabilizado antes dos blocos de async/await . IMO, teria sido mais consistente ter

let fut = async try {
    fut1().await?;
    fut2().await?;
    Ok(())
};

em vez de permitir que funcione sem try . Mas suponho que esse navio já tenha partido há muito.

Re: empacotamento automático, não acho que será possível ser consistente com blocos de async agora. async blocos fazem "auto-wrapping" de uma espécie, em um tipo anônimo implementando Future . Mas isso é verdade mesmo com retornos iniciais, o que não seria possível com blocos de try .

Pode ser duplamente confuso se tivermos um bloco hipotético async try . Isso deve envolver automaticamente o resultado?

Não acho que perdemos nenhuma chance de consistência no sentido de auto-envolvimento. Tanto os blocos async quanto as funções podem usar ? , e ambos devem fazer seu próprio Ok -wrapping manual, para os retornos iniciais e finais.

Um bloco try , por outro lado, poderia usar ? com embrulho automático Ok , incluindo para "devoluções antecipadas" assumindo um recurso de retorno antecipado - talvez quebra de rótulo valor . Uma função de tentativa hipotética poderia facilmente fazer Ok -envolvimento automático em retornos iniciais e finais.

Um bloco hipotético async try poderia simplesmente combinar a funcionalidade de dois auto- Ok -wrap e, em seguida, auto- Future -wrap. (O contrário é impossível de implementar e, sem dúvida, seria escrito try async qualquer maneira.)

A inconsistência que vejo é que confundimos async blocos com funções. (Isso aconteceu no último minuto, ao contrário do RFC, nada menos.) O que isso significa é que return em async bloqueia o bloco, enquanto return em try Blocos async sem retorno antecipado ou valor de quebra de rótulo seriam muito mais difíceis de usar.

Alguma coisa está impedindo a estabilização disso, ou simplesmente ninguém se deu ao trabalho de fazer isso ainda? Estou interessado em criar os PRs necessários de outra forma 🙂

Em 18 de novembro de 2019, 2:03:36 PST, Kampfkarren [email protected] escreveu:

Qualquer coisa impedindo a estabilização disso, ou simplesmente ninguém tomou o
hora de fazer isso ainda? Estou interessado em criar os PRs necessários
caso contrário 🙂>
>
->
Você está recebendo isto porque foi mencionado.>
Responda a este e-mail diretamente ou visualize-o no GitHub:>
https://github.com/rust-lang/rust/issues/31436#issuecomment -554944079

Sim, o bloqueador da estabilização está trabalhando na decisão sobre o empacotamento OK. Isso não deve ser estabilizado até que tenhamos um consenso sobre como deve se comportar.

Pessoalmente, sou contra o acondicionamento de Ok, mas estou me perguntando como seria difícil adicionar depois do fato. O no-ok-wrapping forward é compatível com o ok-wrapping?

Posso imaginar casos complicados, como a ambigüidade de Result<Result<T,E>> , mas em tais casos ambíguos, poderíamos simplesmente voltar ao não-empacotamento. O usuário poderia então explicitamente usar Ok-wrap para eliminar a ambigüidade. Isso não parece tão ruim, já que não espero que esse tipo de ambigüidade apareça com muita frequência ...

Não deve haver nenhuma ambigüidade, porque não é Ok -coercion, mas Ok -wrapping. try { ...; x } renderia Ok(x) tão inequivocamente quanto Ok({ ...; x }) .

@joshtriplett Isso não foi resolvido? O problema de rastreamento resolve whether catch blocks should "wrap" result value foi verificado, citando https://github.com/rust-lang/rust/issues/41414

@rpjohnst Desculpe, deveria ter sido mais claro. O que quero dizer é que se estabilizássemos try agora sem o empacotamento OK, acredito que poderia ser adicionado posteriormente de forma compatível.

Ou seja, acho que a maioria das pessoas concorda que devemos ter blocos de try , mas nem todos concordam sobre catch ou ok-wrapping. Mas não acho que essas discussões precisam bloquear try ...

@Kampfkarren Sim. A conversa acima detalha a progressão desse assunto. Foi prematuramente assinalado sem consultar totalmente a todos. @joshtriplett em particular tinha preocupações, que vários outros (incluindo eu) compartilhavam.

@ mark-im Como exatamente você vê o Ok-wrapping sendo adicionado no futuro? Estou tentando descobrir como isso pode ser feito, mas não consigo ver.

Vou começar dizendo que não sei se é uma boa ideia ou não ...

Nós estabilizaríamos o bloco try sem empacotamento ok. Por exemplo:

let x: Result<usize, E> = try { 3 }; // Error: expected Result, found usize
let x: Result<usize, E> = try { Ok(3) }; // Ok (no pun intended)

Mais tarde, suponha que chegamos a um consenso de que deveríamos ter Ok-wrapping, então poderíamos permitir alguns casos que não funcionavam antes:

let x: Result<usize, E> = try { 3 }; // Ok
let x: Result<usize, E> = try { Ok(3) }; // Also Ok for backwards compat
let x: Result<Result<usize, E1>, E2> = try { Ok(3) }; // Ok(Ok(3))
let x: Result<Result<usize, E1>, E2> = try { Ok(Ok(3)) }; // Ok(Ok(3))

A questão é se isso pode tornar algo ambíguo que não era antes. Por exemplo:

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Embora, talvez o Ok-wrapping já tenha que lidar com esse problema?

De qualquer forma, minha intuição é que esses casos estranhos não aparecem com tanta frequência, então pode não importar muito.

Que tal usar Ok-wrapping, exceto onde o tipo retornado é Result ou Option ? Isso permitiria um código mais simples na maioria dos casos, mas permitiria especificar o valor exato quando necessário.

// Ok-wrapped
let v: Result<i32, _> = try { 1 };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Ok(1) };

// not Ok-wrapped since the returned type is Result
let v: Result<i32, _> = try { Err("error") };

// Ok-wrapped
let v: Option<i32> = try { 1 };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { Some(1) };

// not Ok-wrapped since the returned type is Option
let v: Option<i32> = try { None };

Adicionar Ok -coerção ou algum tipo de envoltório dependente de sintaxe Ok (que é o que precisaria acontecer para suportar a estabilização sem ele e introduzi-lo mais tarde) seria muito ruim para a legibilidade, e foi amplamente contestado várias vezes em i.rl.o (normalmente por pessoas que não entendem bem o Ok -wrapping simples que é implementado).

Pessoalmente, sou fortemente a favor de Ok -wrapping conforme implementado, mas seria ainda mais fortemente contra qualquer forma de coerção ou dependência de sintaxe que torne o entendimento das situações que envolverão difícil (eu consideraria ter que escrever inútil Ok(...) está em toda parte por ter que tentar descobrir se foi coagido ou não).

let x = try { Err(3) }; // If x: Result<Result<T1, usize>, usize>, then it is not clear if user meant Ok(Err(3)) or Err(3)...

Embora, talvez o Ok-wrapping já tenha que lidar com esse problema?

Não, isso é inequivocamente Ok(Err(3)) , Ok -envolvimento é independente da sintaxe ou dos tipos, ele apenas envolve qualquer que seja a saída do bloco na variante Try::Ok .

@ mark-im Não acho que possamos mover razoavelmente de um para o outro após a estabilização. Tão ruim quanto eu considero que o envoltório Ok seja, o envoltório OK inconsistente que tenta adivinhar se você quer ou não seria ainda pior.

Em minha base de código, estou lidando com muitos valores opcionais, então introduzi meu próprio bloco try como macro há muito tempo. E quando eu estava apresentando, eu tinha várias variantes com e sem Ok-wrapping, e a versão Ok-wrapping acabou sendo muito mais ergonômica, que é a única macro que acabei usando.

Tenho uma tonelada de valores opcionais com os quais preciso trabalhar e, em sua maioria, são numéricos, portanto, tenho toneladas de situações como esta:

let c = try { 2 * a? + b? };

Sem o agrupamento de Ok, isso seria muito menos ergonômico a ponto de eu provavelmente ficar em meu próprio macro do que usar os blocos de teste reais.

Dada a venerável história desse problema de rastreamento, sua confluência original e lamentável com o operador ? e o obstáculo sobre o problema de Ok -wrapping, sugiro encerrar este problema imediatamente e enviar try volta ao início do processo RFC, onde esta discussão pode obter a visibilidade que merece e (com sorte) chegar a algum tipo de conclusão.

Sem a embalagem Ok, isso seria muito menos ergonômico

Você poderia explicar o que exatamente não ergonômico ele introduziria?

Sem Ok-wrapping, seu exemplo ficaria assim:

let c = try { Ok(2 * a? + b?) };

o que é muito bom na minha opinião.

Quero dizer, com um pequeno exemplo como este pode parecer um exagero, mas quanto mais código o bloco try contém, menos impacto este Ok(...) wrapper causa.

Na sequência do comentário de Ok -wrapping se o bloco try não o fizer (e alguém certamente criará uma caixa padrão para esta pequena macro), mas o inverso não é assim.

Essa macro não é possível enquanto o traço Try estiver instável.

Por quê? De qualquer forma, quando ele se estabilizar (hipoteticamente não muito longe no futuro), será muito possível.

@ Nemo157 try blocos também estão ativados todas as noites agora e provavelmente não serão estabilizados no caso improvável de decidirmos arrancar Try . Isso significa que eles provavelmente não estarão estabilizados antes de Try . Portanto, dizer que a macro não é possível não faz sentido.

@KrishnaSannasi Estou curioso para Try pode ser retirado?

@ mark-im Acho que não, estou apenas explicando por que preocupar-se com Try estar ligado todas as noites para tentar blocos não é uma preocupação realista. Estou ansioso para Try no estável.

Dado que ? já foi estabilizado e os blocos try têm um design claro abrangendo Result e Option da mesma forma que ? sim, não há nenhuma razão que eu possa ver para bloquear a estabilização deles na estabilização de Try . Não tenho acompanhado isso de perto, mas minhas impressões foram que havia muito menos consenso sobre o design de Try que para blocos de try , então pude ver try bloqueia a estabilização anos antes do traço Try (como aconteceu com ? ). E mesmo se o traço Try for abandonado, não vejo razão para bloquear try blocos sendo estabilizados como trabalhando com apenas Result e Option como ? então seria.

(Por _porque_ você não poderia escrever aquela macro dados os blocos estabilizados de try e uma característica instável de Try , a macro teria se expandido para try { Try::from_ok($expr) } ; você poderia criar macros por tipo por apenas Result e Option , mas IMO, isso não atenderia ao [...] ponto "muito fácil de emular").

Dado que ? já é um estábulo especial, embora o traço Try não possa ser usado no estável, não vejo por que Try sendo instável bloquearia os blocos try sendo implementado no estável, porque se o traço Try for removido ainda temos Opção e Resultado suportando ? no estável, mas sem a ergonomia.

Eu sugeriria o seguinte conceito para a semântica try catch ...

Considere o seguinte código:

union SomeFunctionMultipleError {
    err0: Error1,
    err1: Error2,
}

struct SomeFunctionFnError {
    index: u32,
    errors: SomeFunctionMultipleError,
}

fn some_function() -> Result<i32, SomeFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(SomeFunctionFnError{ index: 0, errors: SomeFunctionMultipleError {err1: Error2 {id0: 0, id1: 0, id3: 0}}})
    }
}

union OtherFunctionMultipleError {
    err0: Error1,
    err1: Error2,
    err2: Error3,
}

struct OtherFunctionFnError {
    id: u32,
    errors: OtherFunctionMultipleError,
}

fn other_function() -> Result<i32, OtherFunctionFnError> {
    if 0 == 0 {
        Ok(2)
    } else {
        Err(OtherFunctionFnError {id: 0, errors: OtherFunctionMultipleError {err0: Error1 {id0: 0, id1: 0}}})
    }
}

Este é o código que pode ser gerado por exceções Zero-Overhead no Rust com o seguinte recurso de sintaxe:

fn some_function() -> i32 throws Error1, Error2 {
    if 0 == 0 {
        2
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function() -> i32 throws Error1, Error2, Error3 {
    if 0 == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

ou mesmo esses erros podem ser deduzidos pelo compilador implicitamente:

fn some_function(i: i32) -> i32 throws { // Implicitly throws Error1, Error2
    if i == 0 {
        2
    } else if i == 1 {
        Error1 {id0: 0, id1: 0, id3: 0}.throw
    } else {
        Error2 {id0: 0, id1: 0, id3: 0}.throw
    }
}

fn other_function(i: i32) -> i32 throws { // Implicitly throws Error1
    if i == 0 {
        2
    } else {
        Error1{id0: 0, id1: 0}.throw
    }
}

Este nada mais açúcar sintático !! O comportamento é o mesmo !!

Olá a todos,

Alguém viu minha proposta com relação às exceções de sobrecarga zero acima?

@redradist Um dos pontos principais do bloco try é que poderíamos usá-lo dentro de uma função , sem a necessidade de criar uma função para cada bloco. Sua proposta não está totalmente relacionada aqui, tanto quanto eu vejo.

Ainda hoje senti a necessidade de blocos de try . Eu tenho uma grande função que tem muitas operações ? . Eu queria adicionar contexto aos erros, mas fazer isso para cada ? exigiria uma grande quantidade de clichês. Capturar os erros com try e adicionar o contexto em um lugar teria evitado isso.

Btw. envolver as operações em uma função interna teria sido difícil neste caso, porque o contexto não tem vida útil óbvia e dividir as coisas em várias funções quebra o NLL.

Gostaria de mencionar que na implementação atual um bloco try não é uma expressão. Pense que isso é um descuido.

Gostaria de mencionar que, na implementação atual, um bloco try não é uma expressão. Pense que isso é um descuido.

Você poderia postar o código que não está funcionando para você? Posso usá-lo em um contexto de expressão aqui: ( Rust Playground )

#![feature(try_blocks)]

fn main() {
    let s: Result<(), ()> = try { () };
}

Claro, aqui está .

E aqui está outro que mostra que a inferência de tipo em blocos try ainda não está completa. O que é especialmente irritante, uma vez que as atribuições de tipo não são suportadas em blocos if let .

@ Nokel81 o problema com seu exemplo anterior é que a expressão em if let $pat = $expr não é um contexto de expressão regular, mas um contexto especial de "expressão sem colchetes". Para obter um exemplo de como isso funciona com expressões de estrutura, consulte este exemplo em que é sintaticamente claro que existe uma expressão de estrutura e este exemplo em que não existe. Portanto, o erro não é que try não seja uma expressão, mas sim que o erro está errado e deveria dizer " try expressão não é permitida aqui; tente colocá-la entre parênteses" (e o aviso incorreto sobre parênteses desnecessários suprimidos).

Seu último exemplo é realmente ambíguo para inferência de tipo. O tipo de e é _: From<usize> neste caso, o que não é informação suficiente para lhe dar um tipo concreto. Você precisaria usá-lo de alguma maneira para fornecer um tipo concreto para permitir o sucesso da inferência de tipo. Este não é um problema específico de try ; é assim que a inferência de tipos funciona no Rust.

Agora, se você imediatamente tentar fazer a correspondência como Ok e descartar o caso Err , você terá um caso para uma mensagem de erro subótima sem nenhuma maneira simples de resolvê-la .

Muito obrigado pela explicação detalhada. Acho que ainda estou confuso porque o último a ambíguo para inferência de tipo. Por que o tipo da expressão não é Result<isize, usize> ?

O operador ? pode realizar conversões de tipo entre diferentes tipos de erro usando o traço From . Ele se expande aproximadamente para o seguinte código de ferrugem (ignorando o traço Try ):

match expr {
    Ok(v) => v,
    Err(e) => return From::from(e),
}

A chamada From usa o tipo da expressão final retornada para determinar quais conversões de tipo de erro devem ser feitas, e não padronizará para o tipo do valor passado automaticamente.

Peço desculpas se isso já foi abordado, mas me parece estranho que:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result = try { // no type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

falha ao compilar com:

error[E0282]: type annotations needed

mas:

#![feature(try_blocks)]

fn main() -> Result<(), ()> {
    let result : Result<_, _> = try { // partial type annotation
        Err(())?;
    };
    result.map_err(|err| err)
}

esta bem.

Se isso fosse um problema porque os argumentos de tipo de Result não pudessem ser deduzidos eu entenderia, mas, como mostrado acima, esse não é o caso e rustc é capaz de realizar inferência uma vez que é informado que o resultado de uma expressão try é algum tipo de Result , que deve ser capaz de inferir de core::ops::Try::into_result .

Pensamentos?

@nwsharp isso é porque try / ? é genérico sobre Try tipos. Se você tivesse algum outro tipo que fosse impl Try<Ok=_, Error=()> , o bloco try poderia avaliar aquele tipo tão bem quanto Result . Desugared, seu exemplo é aproximadamente

#![feature(try_trait, label_break_value)]

use std::ops::Try;

fn main() -> Result<(), ()> {
    let result /*: Result<_, _>*/ = 'block: {
        match Try::into_result(Err(())) {
            Ok(ok) => Try::from_ok(ok),
            Err(err) => {
                break 'block Try::from_error(From::from(err));
            }
        }
    };
    result.map_err(|err| err)
}

@ CAD97 Obrigado pela explicação.

Dito isso, não esperava que try fosse efetivamente capaz de causar uma espécie de conversão entre diferentes Try impls.

Eu esperaria um desgargamento onde o mesmo Try impl é selecionado para into_result , from_ok e from_error .

Na minha opinião, a perda ergonômica de ser incapaz de realizar a inferência de tipo (especialmente considerando que nenhum impl Try alternativo existe) não supera o benefício de permitir essa conversão.

Poderíamos permitir a inferência removendo a ambigüidade e manter a capacidade de aceitar a conversão por meio de algo como:

try { ... }.into()

Com o implante de cobertor correspondente:

impl<T: Try, E: Into<T::Err>> From<Result<T::Ok, E>> for T {
    fn from(result: Result<T::Ok, E>) -> Self {
        match result {
            Ok(ok) => T::from_ok(ok),
            Err(err) => T::from_err(err.into()),
        }
    }
}

(O que, honestamente, suponho que faça sentido mesmo assim, embora eu pessoalmente duvide da conversão automática de tipos de erro aqui. Se desejar, o usuário deve .map_err() no Result .)

Em geral, acho que essa remoção de açúcar é "muito inteligente". Ele esconde muito e sua semântica atual pode confundir as pessoas. (Especialmente considerando que a implementação atual pede anotações de tipo em algo que não as suporta diretamente!)

Ou, indo ainda mais longe com o implante de cobertor, suponho.

impl <T: Try, U: Try> From<U> for T 
    where U::Ok : Into<T::Ok>, U::Err : Into<T::Err>
{
    fn from(other: U) -> Self {
        match other.into_result() {
            Ok(ok) => Self::from_ok(ok.into()),
            Err(err) => Self::from_err(err.into()),
        }
    }
}

Como queiras...

Dito isso, não esperava que try fosse efetivamente capaz de causar uma espécie de conversão entre Try impls diferentes.

Eu esperaria um desgargamento onde o mesmo Try impl é selecionado para into_result , from_ok e from_error .

Na minha opinião, a perda ergonômica de ser incapaz de realizar a inferência de tipo (especialmente considerando que nenhum impl Try alternativo existe) não supera o benefício de permitir essa conversão.

Existem quatro tipos de Try estáveis: Option<T> , Result<T, E> , Poll<Result<T, E>> e Poll<Option<Result<T, E>> .

NoneError é instável, então Option<T> está preso tentando em Option<T> enquanto NoneError está instável. (Observe que os documentos chamam explicitamente From<NoneError> como "habilitar option? para o seu tipo de erro.")

Os impls Poll , entretanto, definem seu tipo de erro como E . Por causa disso, a "transformação do tipo" de Try é estável, porque você pode ? a Poll<Result<T, E>> em um -> Result<_, E> para obter um Poll<T> e devolva antecipadamente a caixa E .

Na verdade, isso alimenta um ajudante "fofo" :

fn lift_err<T, E>(x: Poll<Result<T, E>>) -> Result<Poll<T>, E> { Ok(x?) }

@ CAD97 Obrigado por me agradar. Isso será algo difícil de ensinar aos recém-chegados e exigirá um pouco de amor em termos de mensagens de erro.

Já pensou em permitir a especificação do impl Try desejado para amenizar o comportamento não intuitivo aqui?

Por exemplo, andar de bicicleta por um tempo, try<T> { ... } . Ou ainda há algo em que tropeçar com isso?

Para talvez adicionar um pouco mais de cor aqui, o fato de que try { } sobre um monte de Result não "apenas" produz um Result é inesperado e me deixa triste . Eu entendo o porquê , mas não gosto disso.

Sim, tem havido discussão sobre a combinação de "atribuição de tipo generalizado" (este é o seu termo a ser pesquisado) e try . Acho que, pela última vez que ouvi, try: Result<_, _> { .. } deveria funcionar eventualmente.

Mas concordo com você: os blocos try merecem alguns diagnósticos direcionados para garantir que seu tipo de saída seja especificado.

Por favor, veja este problema separado para uma Ok -embalagem.

Por favor, leia o comentário de abertura desse tópico antes de comentar e, em particular, observe que esse tópico é apenas sobre aquela pergunta, não qualquer outro assunto relacionado a try ou ? ou Try .

Não vejo por que o bloco try é necessário. Esta sintaxe

fn main() -> Result<(), ()> {
    try {
        if foo() {
            Err(())?
        }
        ()
    }
}

pode ser substituído por este:

fn main() -> Result<(), ()> {
    Ok({
        if foo() {
            Err(())?
        }
        ()
    })
}

Ambos usam o mesmo número de caracteres, mas o segundo já é estável.

Ao atribuir o resultado a uma variável, isso pode indicar que uma função auxiliar deve ser criada para retornar o resultado. Se isso não for possível, um fecho pode ser usado em seu lugar.

@dylni try blocos são especialmente úteis quando eles não contêm o corpo inteiro de uma função. O operador ? em caso de erro faz com que o controle de fluxo vá para o final do bloco try mais interno, sem retornar da função.

`` `ferrugem
fn main () / * nenhum resultado aqui * / {
deixe o resultado = tente {
foo () ?. bar () ?. baz ()?
};
match result {
//…
}
}

@SimonSapin Isso

fn main() /* no result here */ {
    let result  = foo()
        .and_then(|x| x.bar())
        .and_then(|x| x.baz());
    match result {
        // …
    }
}

Isso é mais detalhado, mas acho que uma solução mais simples seria uma sintaxe de encerramento de método:

fn main() /* no result here */ {
    let result  = foo()
        .and_then(::bar)
        .and_then(::baz);
    match result {
        // …
    }
}

O tipo também é inferido corretamente com and_then , onde você precisa de anotações de tipo para try . Eu já vi isso com tanta frequência que não acho que uma sintaxe concisa valeria o prejuízo da legibilidade.

O RFC aceito tem mais raciocínio: https://rust-lang.github.io/rfcs/0243-trait-based-exception-handling.html

De qualquer forma, os argumentos a favor das construções de linguagem para o fluxo de controle com o operador ? (e .await ) sobre métodos de encadeamento como and_then já foram discutidos extensivamente.

De qualquer forma, os argumentos a favor das construções de linguagem para o fluxo de controle com o operador ? (e .await ) sobre métodos de encadeamento como and_then já foram discutidos extensivamente.

@SimonSapin Obrigado. Isso e reler a RFC me convenceram de que isso pode ser útil.

Achei que poderia usar os blocos try para adicionar contexto aos erros facilmente, mas não tive sorte até agora.

Escrevi uma pequena função que funciona bem. Observe que File::open()? falha com std::io::Error enquanto a linha seguinte falha com anyhow::Error . Apesar dos tipos diferentes, o compilador descobre como converter ambos em Result<_, anyhow::Error> .

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> Result<(usize, usize), anyhow::Error> {
    let path = path.as_ref();
    let mut file = BufReader::new(File::open(path)?);
    Ok(config.root_store.add_pem_file(&mut file)
        .map_err(|_| anyhow!("Bad PEM file"))?)
}

Eu queria adicionar algum contexto de erro, então tentei usar um bloco try e, de qualquer forma, with_context() :

fn tls_add_cert(config: &ClientConfig, path: impl AsRef<Path>) -> anyhow::Result<(usize, usize)> {
    let path = path.as_ref();
    try {
        let mut file = BufReader::new(File::open(path)?);
        Ok(config.root_store.add_pem_file(&mut file)
            .map_err(|_| anyhow!("Bad PEM file"))?)
    }
    .with_context(|| format!("Error adding certificate {}", path.display()))
}

Mas agora a inferência de tipo falha:

error[E0282]: type annotations needed
  --> src/net.rs:29:5
   |
29 | /     try {
30 | |         let mut file = BufReader::new(File::open(path)?);
31 | |         Ok(config.root_store.add_pem_file(&mut file)
32 | |             .map_err(|_| anyhow!("Bad PEM file"))?)
33 | |     }
   | |_____^ cannot infer type
   |
   = note: type must be known at this point
   ```

I don't understand why a type annotation is needed here but not in the first case. Nor do I see any easy way to add one, as opposed to using an [IIFE](https://en.wikipedia.org/wiki/Immediately_invoked_function_expression) which does let me add an annotation:

```rust
(|| -> Result<_, anyhow::Error> {
    let domain = DNSNameRef::try_from_ascii_str(host)?;
    let tcp = TcpStream::connect(&(host, port)).await?;

    Ok(tls.connect(domain, tcp).await?)
})()
.with_context(|| format!("Error connecting to {}:{}", host, port))

@jkugelman

Novamente,

isso porque try / ? é genérico em relação aos tipos de Try . Se você tivesse algum outro tipo que fosse [ impl Try<Ok=_, Error=anyhow::Error> ], o bloco de teste poderia avaliar para aquele tipo tão bem quanto Result .

(Além disso, você não precisa Ok sua expressão final em um bloco try (# 70941).)

Acho que o fato de isso continuar a aparecer significa que

  • Antes da estabilização, try precisa oferecer suporte a uma atribuição de tipo ( try: Result<_,_> { ou qualquer outro) ou atenuar este problema,
  • Isso definitivamente precisa de diagnósticos direcionados para quando a inferência de tipo de um bloco try falha, e
  • Devemos considerar fortemente dar try um substituto de tipo para Result<_,_> quando não houver outra restrição. Sim, isso é difícil, subespecificado e potencialmente problemático, mas _seria_ resolveria o caso de 80% de try blocos que precisavam de uma anotação de tipo porque $12: Try<Ok=$5, Error=$8> não era específico o suficiente.

Além disso, dado que # 70941 parece estar resolvido no sentido de "sim, queremos (alguma forma de) ' Try::from_ok wrapping'", provavelmente _também_ queremos um diagnóstico direcionado para quando a expressão final de um try block está retornando Ok(x) quando x funcionaria.

Eu suspeito que o comportamento certo para tentar é

  • estenda a sintaxe para permitir uma atribuição manual como try: Result<_, _> { .. } , try as Result<> ou qualquer outra coisa (acho que try: Result provavelmente está bom? parece ser a sintaxe preferida)
  • examine o "tipo esperado" que vem do contexto - se algum estiver presente, prefira isso como o tipo de resultado de try
  • caso contrário, o padrão é Result<_, _> - este não é um fallback de inferência de tipo como com i32 , aconteceria antes, mas isso significaria que coisas como try { }.with_context(...) compilariam.

No entanto, estou preocupado que possamos obter erros em torno de ? e a coerção into , pelo menos desde que o tipo de erro não seja especificado. Em particular se você escrever código onde ? o resultado de um bloco try , assim:

#![feature(try_blocks)]

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x: Result<_, _> = try {
        std::fs::File::open("foo")?;
    };

    x?;

    Ok(())
}

fn main() { 
}

Você ainda obtém erros ( playground ) e com razão, porque não está claro em qual ? a coerção "em" deve ser acionada.

Não tenho certeza de qual é a melhor solução aqui, mas provavelmente envolve algum tipo de fallback de inferência que me deixará nervoso.

provavelmente envolve algum tipo de fallback de inferência que me deixará nervoso.

O mais simples: se todos os usos de ? em um determinado bloco Try contiverem o mesmo tipo Try::Error , use esse tipo de erro para o bloco contendo try (a menos de outra forma vinculado).

O "(a menos que vinculado de outra forma)" é, naturalmente, a parte sutil e assustadora.

Espero não estar sendo muito construtivo com esta postagem. No entanto, eu queria comparar o exemplo de @nikomatsakis com um de um mundo paralelo onde ? não faz uma conversão forçada e não há quebra automática do resultado do bloco try :

use std::error::Error;
fn foo() -> Result<(), Box<dyn Error>> {
    let x = try {
        std::fs::File::open("foo").err_convert()?;
        Ok(())
    };

    x?;

    Ok(())
}

Neste mundo:

  • É fácil ver que tanto o escopo try quanto o próprio fn resultam em sucesso sem nenhum valor. Também é fácil ver, sem nem mesmo tentar, que eles produzem Result s.
  • É óbvio onde ocorre a conversão de erro.
  • A conversão de erro pode ser movida para a expressão x? , tornando o escopo try específico para as operações std::fs::File .
  • Todas as dicas de tipo são transmitidas fluentemente a partir da assinatura do tipo. Tanto para a máquina quanto para nós, humanos.
  • A sugestão de tipo pelo usuário só é necessária nos casos em que realmente queremos dobrar os erros em outro, independente.

Eu ficaria muito feliz nesse universo paralelo.

@phaylon Embora eu aprecie a maneira cuidadosa como você escreveu esse comentário, temo que seja pouco construtivo. A conversão de erro é parte de ? e isso não vai mudar e, à luz disso, ok-wrapping é basicamente ortogonal do resto desta discussão.

Se try funções (com tipos return e throw) devem ser consideradas, então talvez valha a pena considerar também a sintaxe para atribuir o bloco try como algo semelhante.

por exemplo

try fn foo() -> u32 throw String {
  let result = try: u32 throw String {
    123
  };
  result?
}

Desculpe se isso foi discutido, mas quais são as vantagens de usar

try fn foo() -> u32 throw String { ... }

ou semelhante em oposição a

fn foo() -> Result<u32, String> { ... }

?
Parece uma sintaxe duplicada.

@gorilskij Pelo que entendi, a principal vantagem é conseguir Ok -embalagem. Caso contrário, você deve escrever:

fn foo() -> Result<u32, String> {
    try {
        // function body
    }
}

Algumas pessoas também preferem throws , pois consideram a terminologia das exceções identificável.

Pessoalmente, quero ficar o mais longe possível do aparecimento de exceções.

Este não é o tópico para discutir try fn , então, por favor, não prossiga com essa tangente. Este tópico é para o recurso aceito de blocos de try , não o recurso potencial (e ainda não RFCd) try fn .

Acabei de notar que o RFC original para ? propunha usar Into , não From . Afirma:

A presente RFC usa o traço std::convert::Into para este propósito (que tem um encaminhamento de implante de From ).

Embora deixe o método exato de upcasting como uma questão não resolvida . Into foi (presumivelmente) preferido com base na orientação de From :

Prefira usar Into vez de From ao especificar limites de características em uma função genérica. Dessa forma, os tipos que implementam diretamente Into podem ser usados ​​como argumentos.

No entanto, no RFC do traço Try , Into não é mais mencionado e a conversão é feita usando From . Isso também é o que o código agora usa, mesmo para ?
https://github.com/rust-lang/rust/blob/b613c989594f1cbf0d4af1a7a153786cca7792c8/src/librustc_ast_lowering/expr.rs#L1232

Isso parece lamentável, já que não há nenhuma implementação abrangente indo de Into a From . Isso significa que os erros que implementam Into (em oposição a From ) por erro, por motivos legados ou por alguma outra necessidade, não podem ser usados ​​com ? (ou Try ). Isso também significa que qualquer implementação que siga a recomendação da biblioteca padrão de usar Into nos limites não pode usar ? . Com um exemplo, a biblioteca padrão recomenda que eu escreva:

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where E: Into<MyError>

mas se o fizer, não posso usar ? no corpo da função. Se eu quiser fazer isso, tenho que escrever

fn with_user_err<E>(op: impl Fn() -> Result<(), E>) -> Result<(), MyError>
where MyError: From<E>

mas se eu fizer isso, os usuários com tipos de erro que implementam Into vez de From não podem usar esta função. Observe que o inverso não é verdadeiro, devido à cobertura implícita de Into base em From .

Provavelmente é (?) Tarde demais para consertar ? agora (o que é _muito_ lamentável - talvez na próxima edição?), Mas devemos pelo menos nos certificar de não aprofundar esse caminho nos Try traço.

@jonhoo @cuviper tentou mudar o desugar de From para Into em # 60796 para verificar # 38751 e resultou em uma grande quebra de inferência exatamente por causa de From -> Into cobertor impl tornou mais difícil para o rustc lidar com o caso comum de conversão de identidade. Foi decidido que não valia tanto a inferência de quebra de custos.

@jonhoo você também pode achar este comentário informativo de niko:

Também existe um limite embutido no sistema de características. Se você tiver que resolver uma meta como ?X: Into<ReturnType> , não conseguiremos resolver, mas se você tiver que resolver uma meta como ReturnType: From<?X> , teremos potencialmente sucesso e inferiremos um valor para ?X .

Editar: aqui, ?X refere-se a alguma variável de inferência desconhecida. O limite embutido no sistema de características de hoje é que o tipo Self deve ser pelo menos parcialmente inferido para que possamos explorar essa opção.

O TL; DR é que inferir com Into é mais difícil e de uma forma inerente à maneira como o resolvedor de traços funciona.

@KrishnaSannasi @ CAD97 Obrigado, isso é útil! Eu ainda me preocupo em estabilizar demais com base em From visto que estamos deixando os implementadores de Into permanentemente de fora. A expectativa é que a inferência aqui possa eventualmente melhorar? A orientação para preferir Into nos limites deve ser alterada? Estamos esperando que com as novas regras de coerência de traço em 1.41 (eu acho que era) não haja mais razão para implementar apenas Into , e considerar todos esses bugs impls?

Se a inferência for boa o suficiente, deve ser compatível com versões futuras para alterar para Into mais tarde. O pior que podemos quebrar é a inferência, dado que From implica Into

Isso (o traço Try ) cobre permitindo que ? trabalhe com os traços Into / From com limites genéricos para tipos que implementam Try (por exemplo, Result si)?

ou seja, ? em um encerramento ou função que retorna, por exemplo, impl Into<Result>

(Não parece quando tento isso todas as noites?)

Estou ansioso para ver isso estabilizado. Depois de ler este tópico e # 70941, acho que o resumo deve ser atualizado da seguinte forma:

  1. "resolver se os blocos de captura devem" quebrar "o valor do resultado" deve ser marcado, "Resolvido como sim "

  2. Nova preocupação adicionada sobre esses problemas de inferência. Talvez algo como:

    • [] Dificuldades ergonômicas devido a problemas com inferência de tipo.

ISTM que esta última preocupação poderia ser abordada, entre outras maneiras:

(Algumas dessas opções não são mutuamente exclusivas.)

Obrigado pela sua atenção e espero que esta mensagem seja útil.

A atribuição de tipo tem uma série de problemas (sintática e outros) e parece improvável que seja implementada em breve, muito menos estabilizada; bloquear try bloqueia na sintaxe de atribuição de tipo não parece apropriado.

Um fallback para Result pode ajudar, mas não resolve os problemas de inferência de tipo com tipos de erro: try { expr? }? (ou, na prática, equivalentes mais complexos) têm efetivamente duas chamadas para .into() , o que dá ao compilador muita flexibilidade no tipo intermediário.

@ijackson obrigado por tomar a iniciativa de resumir o estado atual. Acho que você está certo ao dizer que há várias maneiras de melhorar os blocos try, mas um dos problemas é que não temos certeza de qual fazer, em parte porque cada solução tem suas próprias desvantagens.

Com relação à atribuição de tipo, porém, sinto que os desafios de implementação não são tão difíceis. Esse pode ser um bom candidato para colocar um pouco de atenção e tentar empurrá-lo além da linha de chegada de qualquer maneira. Não me lembro se houve muita controvérsia sobre a sintaxe ou algo parecido.

Na quarta-feira, 5 de agosto de 2020 às 02:29:06 PM -0700, Niko Matsakis escreveu:

Com relação à atribuição de tipo, porém, sinto que os desafios de implementação não são tão difíceis. Esse pode ser um bom candidato para colocar um pouco de atenção e tentar empurrá-lo além da linha de chegada de qualquer maneira. Não me lembro se houve muita controvérsia sobre a sintaxe ou algo parecido.

Pelo que me lembro, a principal preocupação era permitir a atribuição de tipo
em todos os lugares haveria uma mudança substancial de gramática e, potencialmente, um
limitando um. Não me lembro de todos os detalhes, apenas que a preocupação era
levantado.

Pessoalmente, acho que os problemas ergonômicos não são tão graves a ponto de não valer a pena estabilizar esse recurso agora. Mesmo sem a atribuição de tipo de expressão, a introdução de uma ligação let não é uma solução alternativa tão feia.

Além disso, blocos try podem ser úteis em macros. Em particular, gostaria de saber se biblioteca @withoutboats excelente Fehler sofreria menos problemas com deficiências no nosso sistema de macro, se pudesse embrulhar os corpos de procs em try .

Encontro lugares onde adoraria usar muito os blocos try. Seria bom acabar com isso. Pessoalmente, eu, com certeza, sacrificaria 100% a atribuição de tipo se fosse necessário para obter blocos try além da linha. Eu ainda não me encontrei em uma situação em que disse "droga, eu adoraria ter uma atribuição de tipo aqui", mas acabo fazendo um IIFE para simular muitos blocos try. Deixar instável um recurso útil de longo prazo porque ele entra em conflito com outro recurso instável de longo prazo é uma situação realmente infeliz.

Para ser um pouco mais específico, eu me pego fazendo isso quando estou dentro de uma função que retorna Result, mas quero fazer algum tipo de processamento em coisas que retornam uma Option. Dito isso, se o Try em geral fosse estável, eu provavelmente ainda preferiria os blocos try, já que não quero retornar da função principal para fazer isso, mas em vez disso, forneça algum tipo de valor padrão se algo na cadeia for Nenhum. Isso tende a acontecer comigo no código de estilo de serialização.

Pessoalmente, eu queria a atribuição de tipo com muito mais frequência do que tentar blocos (embora eu tenha desejado os dois às vezes). Em particular, sempre lutei com a "depuração de tipo", em que o compilador infere um tipo diferente do que eu esperava. Normalmente, você precisa adicionar uma nova ligação let em algum lugar, o que é realmente perturbador e faz com que rustfmt interrompa o histórico de desfazer. Além disso, há muitos lugares onde a atribuição de tipo evitaria um peixe turbo extra.

Em contraste, posso apenas usar and_then ou outros combinadores para terminar mais cedo sem sair. Talvez não tão limpos aa try blocks, mas não é tão ruim assim.

@steveklabnik @ mark-im Os blocos de teste e a atribuição de tipo não estão de forma alguma em conflito, não é uma questão de um recurso ou outro. É apenas try blocos têm falhas de inferência de tipo não ergonômicas e a atribuição generalizada de tipo pode ser uma maneira de resolver esse problema, mas uma vez que a atribuição generalizada de tipo não é um recurso de curto prazo ou mesmo uma coisa certa, @joshtriplett (e eu concordar) não deseja que esse recurso bloqueie na ocorrência de atribuição de tipo generalizada.

Isso nem mesmo significa que não faríamos da atribuição generalizada de tipos a solução para o problema; uma opção que merece investigação é "estabilizar a tentativa como está, esperando que algum dia a atribuição generalizada de tipo resolva esse problema". Tudo o que foi dito é não bloqueie a estabilização na atribuição de tipo.


@ rust-lang / lang Tenho que admitir que é um pouco difícil entender a nuance da falha de inferência de tipo deste tópico, por causa das limitações do GitHub e de muitos outros assuntos que são discutidos aqui. Visto que chegar a uma decisão sobre como lidar com as falhas de inferência é a única coisa que impede que os bloqueios de tentativa se estabilizem, acho que seria benéfico se tivéssemos uma reunião para discutir isso, e alguém pudesse tomar o ponto de obter um entendimento profundo do problema de inferência .

Uma pergunta que me ocorre, por exemplo: este problema de inferência é especificamente devido à flexibilidade de conversão que permitimos em Try ? Eu sei que essa decisão foi discutida até a morte, mas se for assim, esta parece ser uma nova informação pertinente que poderia justificar a mudança da definição do traço Try .

@semoutboats Concordo com a necessidade de coletar todas as informações em um só lugar e o desejo de levar esse recurso até a linha de chegada. Dito isso, acho que a última vez que investigamos aqui também ficou claro que alterações em Try podem ser difíceis por causa da compatibilidade com versões anteriores - @cramertj mencionou alguns Pin impls específicos, IIRC.

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