Rust: Problema de seguimiento para el operador `?` Y los bloques `try` (características de RFC 243,` question_mark` y `try_blocks`)

Creado en 5 feb. 2016  ·  340Comentarios  ·  Fuente: rust-lang/rust

Problema de seguimiento para rust-lang / rfcs # 243 y rust-lang / rfcs # 1859.

Problemas de implementación:

  • [x] ? operador que equivale aproximadamente a try! - # 31954
  • [x] try { ... } expresión - https://github.com/rust-lang/rust/issues/39849

    • [x] resuelve do catch { ... } pregunta de sintaxis


    • [x] resolver si los bloques de captura deben "ajustar" el valor del resultado (primero se abordó en https://github.com/rust-lang/rust/issues/41414, ahora se establece de nuevo en https://github.com/rust- lang / rust / issues / 70941)

    • [] Abordar los problemas con la inferencia de tipo ( try { expr? }? actualmente requiere una anotación de tipo explícita en algún lugar).

  • [x] resolver el diseño del rasgo Try (https://github.com/rust-lang/rfcs/pull/1859)

    • [x] implementar el nuevo rasgo Try (en lugar de Carrier ) y convertir ? para usarlo (https://github.com/rust-lang/rust/pull / 42275)

    • [x] agregue impls por Option y así sucesivamente, y una familia adecuada de pruebas (https://github.com/rust-lang/rust/pull/42526)

    • [x] mejorar los mensajes de error como se describe en el RFC (https://github.com/rust-lang/rust/issues/35946)

  • [x] reserva try en la nueva edición
  • [x] bloquea try{}catch (u otros identificadores siguientes) para dejar el espacio de diseño abierto para el futuro, y señala a las personas cómo hacer lo que quieren con match lugar
A-error-handling B-RFC-approved B-RFC-implemented B-unstable C-tracking-issue F-try_blocks Libs-Tracked T-lang T-libs

Comentario más útil

@ mark-im No creo que podamos pasar razonablemente de uno a otro después de la estabilización. Por muy malo que considero que sea el ajuste correcto, un inconsistente que intenta adivinar si lo quieres o no sería aún peor.

Todos 340 comentarios

El RFC adjunto discute un desugaring basado en return / break etiquetado, ¿lo estamos obteniendo también o habrá un tratamiento especial para ? y catch en el compilador?

EDITAR: Creo que la etiqueta retorno / descanso es una idea excelente separada de ? y catch , por lo que si la respuesta es no, probablemente abriré un RFC separado para ello.

La devolución / rotura etiquetada es únicamente para fines explicativos.

El viernes 5 de febrero de 2016 a las 3:56 p.m., Jonathan Reem [email protected]
escribió:

El RFC adjunto analiza un desugaring basado en la etiqueta de retorno / descanso,
¿Estamos recibiendo eso también o solo habrá un tratamiento especial para? y
atrapar en el compilador?

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -180551605.

Otra pregunta sin resolver que tenemos que resolver antes de estabilizarnos es cuál debería ser el contrato que impl s de Into deben obedecer, o si Into es incluso el rasgo correcto para usar para la actualización de errores aquí. (¿Quizás este debería ser otro elemento de la lista de verificación?)

@reem

Creo que la etiqueta de retorno / descanso es una excelente idea ... Probablemente abriré un RFC separado para ello.

¡Por favor, hazlo!

Sobre el tema del rasgo Carrier , aquí hay un ejemplo básico de tal rasgo que escribí al principio del proceso de RFC.
https://gist.github.com/thepowersgang/f0de63db1746114266d3

¿Cómo se trata esto durante el análisis?

struct catch {
    a: u8
}

fn main() {

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

}

@petrochenkov Bueno, la definición no podría afectar el análisis, pero creo que todavía tenemos una regla de anticipación, basada en el segundo token después de { , : en este caso, por lo que aún debería ser analizado como una estructura literal.

también

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

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

+ https://github.com/rust-lang/rfcs/issues/306 si (¡cuándo!) implementado.
Parece que no hay conflictos además de los literales de estructura.

Dados los ejemplos anteriores, estoy a favor de la solución más simple (como de costumbre): siempre trate catch { en las posiciones de expresión como el comienzo de un bloque catch . Nadie llama a sus estructuras catch todos modos.

Sería más fácil si se usara una palabra clave en lugar de catch .

Esta es la lista de palabras clave: http://doc.rust-lang.org/nightly/grammar.html#keywords

@bluss sí, admito que ninguno de ellos es genial ... override parece el único que está cerca. O podríamos usar do , je. O una combinación, aunque no veo grandes de inmediato. do catch ?

do es el único que parece estar cerca en mi opinión. ¿Una sopa de palabras clave con do como prefijo es un poco irregular, no similar a ninguna otra parte del idioma? ¿ while let una sopa de palabras clave? Ese se siente bien ahora, cuando estás acostumbrado.

puerto try! para usar ?

¿No se puede portar ? para usar try! lugar? Esto permitiría el caso de uso en el que desea obtener una ruta de retorno Result , por ejemplo, al depurar. Con try! esto es bastante fácil, simplemente anula la macro al principio del archivo (o en lib / main.rs):

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

Obtendrá un seguimiento de pila de pánico a partir de la primera aparición de try! en la ruta de retorno Result . De hecho, si hace try!(Err(sth)) si descubre un error en lugar de return Err(sth) , incluso obtendrá el seguimiento completo de la pila.

Pero al depurar bibliotecas extranjeras escritas por personas que no han implementado ese truco, uno confía en el uso de try! algún lugar más alto de la cadena. Y ahora, si la biblioteca usa el operador ? con un comportamiento codificado, obtener un seguimiento de pila se vuelve casi imposible.

Sería genial si anular try! también afectara al operador ? .

Más adelante, cuando el sistema de macros obtenga más funciones, ¡incluso puede entrar en pánico! solo para tipos específicos.

Si esta propuesta requiere un RFC, hágamelo saber.

Idealmente, ? podría extenderse para proporcionar soporte de seguimiento de pila directamente, en lugar de depender de la capacidad de anular try! . Entonces funcionaría en todas partes.

Los seguimientos de pila son solo un ejemplo (aunque me parece muy útil).
Si se hace que el rasgo de portador funcione, ¿tal vez eso pueda cubrir tales extensiones?

El 7 de febrero de 2016 a las 4:14 p.m., Russell Johnston [email protected]
escribió:

Idealmente? podría ampliarse para proporcionar soporte de seguimiento de pila directamente,
en lugar de confiar en la capacidad de anular try !. Entonces funcionaria
En todas partes.

-
Responda a este correo electrónico directamente o véalo en GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -181118499.

Sin querer especular, creo que podría funcionar, aunque con algunos problemas.

Considere el caso habitual en el que uno tiene un código que devuelve un Result<V,E> . Ahora tendríamos que permitir que coexistan múltiples implementaciones del rasgo Carrier . Para no encontrarse con E0119 , uno tiene que hacer todas las implementaciones fuera del alcance (posiblemente a través de diferentes rasgos que no se importan por defecto), y cuando se usa el operador ? , se requiere que el usuario importe el implementación.

Esto requeriría que todos, incluso aquellos que no quieran depurar, importen la implementación de sus características deseadas al usar ? , no habría opción para un valor predeterminado predefinido.

Posiblemente, E0117 también puede ser un problema si desea realizar implementaciones personalizadas Carrier para Result<V,E> , donde todos los tipos están fuera de los límites, por lo que libstd debería proporcionar ya un conjunto de implementaciones de Carrier trait con los casos de uso más utilizados (implementación trivial e implementación panic! ing, quizás más).

Tener la posibilidad de anular a través de una macro proporcionaría una mayor flexibilidad sin la carga adicional sobre el implementador original (no tiene que importar la implementación deseada). Pero también veo que rust nunca antes había tenido un operador basado en macros, y que implementar ? través de una macro no es posible si se supone que funciona catch { ... } , al menos no sin elementos de idioma adicionales ( return_to_catch , throw , etiquetado break con param como se usa en RFC 243).

Estoy de acuerdo con cualquier configuración que le permita a uno obtener Result stacktraces con una ruta de retorno Err , teniendo solo que modificar una cantidad muy pequeña de código, preferiblemente en la parte superior del archivo. La solución también debería funcionar sin relación con cómo y dónde se implementa el tipo Err .

Solo para intervenir en el desprendimiento de bicicletas: catch in { ... } fluye bastante bien.

catch! { ... } es otra opción de backcompat.

Además, no es que espero que esto cambie algo, pero una nota de que esto va a romper macros de múltiples brazos que aceptaban $i:ident ? , de la misma manera que la asignación de tipo rompió $i:ident : $t:ty .

No exagere la compatibilidad con versiones anteriores, simplemente trate catch como una palabra clave cuando esté seguida de { (posiblemente solo en la posición de expresión, pero no estoy seguro de si eso cambia mucho en cuanto a compatibilidad).

También puedo imaginar algunos problemas posibles que no involucran literales de estructura (por ejemplo, let catch = true; if catch {} ); pero prefiero un cambio rotundo menor a una sintaxis más fea.

¿No teníamos un para agregar nuevas palabras clave, de todos modos? Podríamos ofrecer algún tipo de opción from __future__ para una nueva sintaxis; o especifique un número de versión del idioma rust en la línea de comandos / en Cargo.toml.
Dudo mucho que, a largo plazo, podamos trabajar solo con aquellas palabras clave que ya están reservadas. No queremos que nuestras palabras clave tengan tres significados diferentes cada una, según el contexto.

Estoy de acuerdo. Este ni siquiera es el primer RFC en el que ha surgido (https://github.com/rust-lang/rfcs/pull/1444 es otro ejemplo). Espero que no sea el último. (También default de https://github.com/rust-lang/rfcs/pull/1210, aunque no estoy a favor de un RFC). Creo que debemos encontrar una manera de agregar palabras clave sinceras en lugar de tratar de averiguar cómo hackear ad-hoc la gramática para cada caso nuevo.

¿No era todo el argumento para no reservar varias palabras clave antes de 1.0 que definitivamente estaríamos introduciendo una forma de agregar nuevas palabras clave al idioma de manera compatible con versiones anteriores (posiblemente al optar por participar explícitamente), por lo que no tenía sentido? Parece que ahora sería un buen momento.

@japaric ¿Estás interesado en revivir tus antiguas relaciones públicas y asumir esto?

@aturon Mi implementación simplemente desugó foo? de la misma manera que try!(foo) . También solo funcionó en llamadas a métodos y funciones, es decir, foo.bar()? y baz()? funcionan pero quux? y (quux)? no. ¿Estaría bien para una implementación inicial?

@japaric ¿Cuál fue la razón para restringirlo a métodos y llamadas a funciones? ¿No sería más fácil analizarlo como un operador de sufijo general?

¿Cuál fue la razón para restringirlo a métodos y llamadas a funciones?

forma más fácil (para mí) de probar la expansión ?

¿No sería más fácil analizarlo como un operador de sufijo general?

probablemente

@japaric Probablemente sería bueno generalizarlo a un operador postfix completo (como sugiere @eddyb ), pero está bien aterrizar ? con el simple desugaring y luego agregar catch más tarde.

@aturon Muy bien, veré la versión postfix la semana que viene si nadie me gana :-).

Rebasado / actualizado mi PR en # 31954 :-)

¿Qué pasa con el soporte para proporcionar seguimientos de pila? ¿Eso está planeado?

Odio ser el tipo +1, pero los rastros de pila me han ahorrado una buena parte de mi tiempo en el pasado. Tal vez en compilaciones de depuración, y al llegar a la ruta de error, ¿El operador podría agregar el archivo / línea a un Vec en Resultado? ¿Quizás el Vec también podría ser solo de depuración?

Y eso podría quedar expuesto o convertirse en parte de la descripción del error ...

Sigo encontrándome con la situación en la que quiero usar try! / ? dentro de iteradores que devuelven Option<Result<T, E>> . Desafortunadamente, eso actualmente no funciona. Me pregunto si el rasgo de portador podría sobrecargarse para respaldar esto o ¿iría eso a un From más genérico en su lugar?

@hexsel Realmente deseo que el tipo Result<> lleve un vec de punteros de instrucción en debug y? le añadiría. De esa manera, la información de DWARF podría usarse para construir un seguimiento de pila legible.

@mitsuhiko Pero, ¿cómo puedes crear y combinar patrones Result ? Es _sólo_ un cajero automático de enum .

En cuanto a la envoltura Option , creo que quieres Some(catch {...}) .

Actualmente, mi hábito en este momento es hacer try!(Err(bla)) lugar de return Err() , para poder anular la macro de prueba más adelante con una que entre en pánico, con el fin de obtener un seguimiento. Me funciona bien, pero el código con el que trato es de muy bajo nivel y, en su mayoría, origina los errores. Aún tendré que evitar ? si uso un código externo que devuelve Result .

@eddyb necesitaría soporte de lenguaje para llevar valores ocultos además de que necesita manipular por otros medios. Me preguntaba si se puede hacer de otras formas, pero no veo cómo. La única otra forma habría sido un cuadro de error estandarizado que puede tener datos adicionales, pero no es necesario tener errores en el cuadro y la mayoría de la gente no lo hace.

@mitsuhiko Puedo pensar en un nuevo método (predeterminado) en el rasgo Error y TLS.
Este último es utilizado por desinfectantes a veces.

@eddyb eso solo funciona si se puede identificar el resultado y eso requiere que esté en una caja o se moverá en la memoria a medida que pasa hacia arriba en la pila.

@mitsuhiko ¿ El TLS? En realidad, no, solo necesita poder comparar el error por valor.

O incluso solo por tipo (con la vinculación de From entradas y salidas), ¿de cuántos errores concurrentes desea que se propaguen simultáneamente los rastros de pila?

Estoy en contra de agregar Result hacks de compiladores específicos, personalmente, cuando funcionan soluciones más simples.

@eddyb el error pasa hacia arriba en la pila. Lo que desea es el EIP en todos los niveles de pila, no solo donde se origina. Además, los errores a) actualmente no son comparables yb) el hecho de que se comparen iguales no significa que sean el mismo error.

cuántos errores concurrentes de los que desea que los rastros de pila tengan que propagarse simultáneamente

Cualquier error detectado y relanzado como un error diferente.

Personalmente, estoy en contra de agregar hacks de compiladores específicos de resultados cuando funcionan soluciones más simples.

No veo cómo funciona una solución más simple, pero tal vez me falta algo allí.

Puede guardar el puntero de instrucción en cada ? y correlacionarlo con el tipo de error.
"Cualquier error capturado y relanzado como un error diferente". Pero, ¿cómo conservaría esa información si estuviera oculta en Result ?

Pero, ¿cómo preservaría esa información si estuviera oculta en Result?

No es necesario que almacene esa información en el resultado. Sin embargo, lo que sí necesita almacenar es una identificación única para el origen de la falla para que pueda correlacionarla. Y debido a que el rasgo de error es solo un rasgo y no tiene almacenamiento, podría almacenarse en el resultado. El puntero de instrucción vec en sí mismo no tendría que almacenarse en el resultado, que podría ir a TLS.

Una forma sería invocar un método failure_id(&self) en el resultado y devuelve un i64 / uuid o algo que identifique el origen de la falla.

Esto necesitaría soporte de lenguaje sin importar qué, porque lo que necesita es que a medida que el resultado pasa hacia arriba a través de la pila, el compilador inyecta una instrucción para registrar el marco de la pila por el que cae. Entonces, la devolución de un resultado se vería diferente en las compilaciones de depuración.

"el compilador inyecta una instrucción para registrar el marco de pila por el que cae", pero ? es explícito, esto no es nada parecido a excepciones, o ¿no le gusta grabar _sólo_ los ? que pasó?

De todos modos, si descomprime manualmente el error y luego lo vuelve a colocar en un Err , ¿cómo se conservaría esa identificación?

"Y debido a que el rasgo de error es solo un rasgo y no tiene almacenamiento, podría almacenarse en el resultado"
Hay una variante de esto: la implementación del rasgo Error podría tener un formato especial en el compilador para agregar un campo entero adicional al tipo, la creación del tipo activaría la generación de una ID, y copiar / soltar incrementar / disminuir efectivamente el refcount (y eventualmente borrarlo del "conjunto de errores en vuelo" de TLS si no se usa Result::unwrap ).

Pero eso entraría en conflicto con el rasgo Copy . Quiero decir, también lo haría su plan, agregar cualquier comportamiento especial a Result que _no_ sea activado por ? u otras acciones específicas del usuario puede invalidar los invariantes Copy .

EDITAR : En este punto, también podría incrustar un Rc<ErrorTrace> allí.
EDIT2 : ¿Qué estoy diciendo? Puede borrar el seguimiento de error asociado en catch .
EDIT3 : En realidad, al soltar, vea a continuación una mejor explicación.

"el compilador inyecta una instrucción para registrar el marco de pila por el que cae", pero? es explícito, esto no se parece en nada a las excepciones, o no le gusta grabar solo el? pasó a través?

Eso no funciona porque hay demasiados marcos en los que puede caer y que no usan ? . Y mucho menos que no todo el mundo va a manejar errores con solo ? .

De todos modos, si descomprime manualmente el error y luego lo vuelve a poner en Err, ¿cómo se conservaría esa identificación?

Por eso tendría que ser compatible con el compilador. El compilador tendría que realizar un seguimiento de las variables locales que son resultados y hacer lo mejor para propagar el ID de resultado en adelante para volver a ajustar. Si esto es demasiado mágico, entonces podría restringirse a un subconjunto de operaciones.

Eso no funciona porque hay demasiados marcos en los que puede caer y que no usan ? . Y mucho menos que no todo el mundo va a manejar errores con solo ? .

De acuerdo, pude ver que la devolución de Result directamente se aplicará de manera especial en funciones complejas con múltiples rutas de retorno (algunas de las cuales serían devoluciones anticipadas de ? ).

Si esto es demasiado mágico, entonces podría restringirse a un subconjunto de operaciones.

O hecho completamente explícito. ¿Tiene ejemplos de reenvasado no ? que el compilador debería rastrear mágicamente?

@eddyb El caso común de manejo manual de errores es un IoError donde desea manejar un subconjunto:

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

@mitsuhiko Entonces, mantener la ID dentro de io::Error definitivamente funcionaría.

Entonces, para recapitular, un Vec<Option<Trace>> "mapa entero disperso" en TLS, tecleado por struct ErrorId(usize) y al que se accede por 3 elementos de idioma:

  • fn trace_new(Location) -> ErrorId , al crear un valor de error que no sea const
  • fn trace_return(ErrorId, Location) , justo antes de regresar de una función _declarada_ como -> Result<...> (es decir, no es una función genérica en la devolución que _ pasa_ a usarse con un tipo Result allí)
  • fn trace_destroy(ErrorId) , al eliminar un valor de error

Si la transformación se realiza en MIR, Location se puede calcular a partir del Span de la instrucción que desencadena la construcción de un valor de error o la escritura en Lvalue::Return , que es mucho más confiable que un puntero de instrucción en mi opinión (no es una manera fácil de obtener eso en LLVM de todos modos, tendría que emitir en línea asm para cada plataforma específica).

@eddyb

¿No conducirá eso a una hinchazón de tamaño binario?

@ arielb1 Lo harías en modo de depuración solo donde debuginfo hincha el binario de todos modos; también podrías reutilizar la debuginfo inteligentemente _shrug_.

@eddyb, ¿qué es una ubicación en ese caso? No estoy seguro de qué es lo difícil de leer la IP. Claro, necesita JS personalizado para cada objetivo, pero eso no es tan difícil.

@mitsuhiko , podría ser la misma información que usamos para verificaciones de desbordamiento y otros pánicos emitidos por el compilador. Ver core::panicking::panic .

¿Por qué empaquetar tanta información de la pila en un Error / Result mientras la pila está desenrollada? ¿Por qué no ejecutar el código mientras la pila todavía está allí? Por ejemplo, ¿qué pasa si está interesado en variables en la ruta de la pila? Simplemente habilite a las personas a ejecutar código personalizado cuando se invoca un Err , por ejemplo, para llamar a un depurador de su elección. Esto es lo que ya proporciona try! debido a que es una macro (anulable). La forma más fácil es entrar en pánico en un caso Err que imprime la pila siempre que se haya iniciado el programa con los parámetros correctos.

Con try puede hacer lo que quiera en un caso de Err , y puede anular la caja de macros amplia, o muy limitada para no tocar el código de rendimiento crítico, por ejemplo, si el error es difícil de reproducir y necesita ejecutar una gran cantidad de código crítico de rendimiento.

Nadie necesitaría preservar una fracción de la información en una pila artificial que uno acumula porque la verdadera va a ser destruida. Todo el método de anulación de macros podría mejorarse con:

  • ? siendo reemplazable de manera similar, la forma más fácil sería definir ? como azúcar para try! - especialmente necesario para detectar errores no originarios en el borde de la caja como descrito en mi comentario anterior.
  • tener un sistema de macros más potente, como hacer coincidir el tipo para permitir aún más flexibilidad. Sí, uno podría pensar en poner esto en el sistema de rasgos tradicional, pero el óxido no permite anular las implementaciones de rasgos, por lo que será un poco complicado.

@ est31 La pila no se "desenrolla" _automáticamente_ como en un estado de pánico, esto es azúcar sintáctico para devoluciones tempranas.
Y sí, podría hacer que el compilador inserte llamadas de algunas funciones vacías con nombres fijos en los que puede interrumpir, esto es bastante fácil (y también tiene información que le permite hacerlo, por ejemplo, call dump(data) - donde dump y data son argumentos que los depuradores pueden ver).

@eddyb Creo que las funciones vacías no permitirían un caso de uso de, por ejemplo, mantener algunas instancias de depuración "canarias" en una implementación grande para ver si aparecen errores en los registros, para luego volver atrás y arreglar las cosas. Entiendo que es preferible ser proactivo que reactivo, pero no todo es fácil de predecir.

@hexsel Sí, por eso prefiero el método basado en TLS donde Result::unwrap o algún nuevo método ignore (¿o incluso siempre cuando suelta el error?) vuelca el seguimiento a stderr.

@eddyb Si agrega el puntero de instrucción o algo derivado del valor a una estructura de datos tipo pila en TLS, básicamente reconstruye su versión pequeña de la pila de la vida real. Una devolución reduce la pila en una entrada. Entonces, si lo hace mientras devuelve _unwind_ parte de la pila, mientras construye una versión limitada en algún otro lugar de la RAM. Quizás "desenrollar" es el término incorrecto para el comportamiento resultante de devoluciones "legales", pero si todo el código lo hace ? o try! y al final interceptas, el resultado final es el mismo. Es genial que rust no haga que la propagación de errores sea automática, realmente me gustó cómo Java había requerido que todas las excepciones se enumeraran después de la palabra clave throws , rust es una buena mejora en eso.

@hexsel un try! ) permitiría esto: puede ejecutar cualquier código que desee e iniciar sesión en cualquier sistema de registro. Sin embargo, necesitaría alguna detección de "incautos" cuando varios try detectan el mismo error a medida que se propaga por la pila.

@ est31 Anular try! solo funciona en su propio código (es literalmente un sombreado de importación de macros), tendría que ser algo diferente, como nuestros "elementos de lenguaje débil".

@ est31 Eso no es realmente correcto (sobre el desenrollado), las trazas y la pila real no necesariamente tienen ninguna relación, porque mover Result s no tiene que subir en el trazo original, puede ir de lado también.
Además, si el binario está optimizado, la mayor parte del backtrace desaparecerá, y si no tiene información de depuración, entonces Location es estrictamente superior. Incluso podría estar ejecutando un complemento de ofuscación que reemplaza toda la información de origen con hashes aleatorios que pueden ser igualados por los desarrolladores de algún producto de código cerrado.

Los depuradores son útiles (y aparte, me encanta la salida de rastreo inverso más limpia de lldb ), pero no son una panacea, y ya mostramos algo de información sobre pánico para que pueda obtener algunas pistas sobre que esta pasando.

Sobre eso, tuve algunas ideas sobre un truco a nivel de sistema de tipos donde {Option,Result}::unwrap tendría un argumento de tipo adicional, por defecto a un tipo dependiente de la ubicación desde la que se llamó a la función, de modo que los pánicos de esos tendrían forma información de ubicación más útil.

Con el progreso en la parametrización del valor, esa podría seguir siendo una opción, pero definitivamente no es la única, y no quiero descartar por completo los rastros de Result , sino que estoy tratando de encontrar un modelo que sea _implementable_.

Anular try! no es una solución en absoluto porque está contenido dentro de su propia caja. Eso es inaceptable como experiencia de depuración. Ya probé mucho de eso tratando de lidiar con el actual try! . Especialmente si tiene muchas cajas involucradas y los errores se transmutan varias veces en el camino a través de la pila, es casi imposible averiguar dónde se origina el error y por qué. Si una tirita como esa es la solución, entonces tendremos que revisar el manejo de errores en general para grandes proyectos de Rust.

@eddyb, ¿ entonces su sugerencia es que incrustemos literalmente el nombre del archivo, el nombre de la función, el número de línea y el número de columna en ese vector? Eso parece un desperdicio enorme, particularmente porque esa información ya está contenida en un formato mucho más procesable en DWARF. Además, DWARF nos permite usar el mismo proceso a un costo razonable en producción, mientras que este tipo de información de ubicación parece ser tan derrochador que nadie podría ejecutar un binario de lanzamiento con él.

¿Cómo sería mucho más derrochador que la información de ENANOS? Los nombres de archivo se deduplicarían y en x64 todo tiene el tamaño de 3 punteros.

@mitsuhiko , básicamente, ¿estás de acuerdo con la dirección general, pero no con los detalles técnicos de la misma?

¿Qué tan fácil es exponer la información de DWARF a una API general de Rust?

@eddyb porque la información DWARF no está contenida en el binario de la versión sino en archivos separados. Entonces puedo tener los archivos de depuración en servidores de símbolos y enviar un binario despojado a los usuarios.

@mitsuhiko Oh, ese es un enfoque completamente diferente al que estaba asumiendo. Actualmente, Rust no admite ese cajero automático, AFAIK, pero estoy de acuerdo en que debería hacerlo.

¿Crees que los punteros de instrucción son realmente tan útiles en comparación con los identificadores aleatorios generados por tu sistema de compilación con el propósito de depurar los binarios de la versión?

Mi experiencia ha sido que, con cualquier depurador integrado, es difícil recuperar gran parte del seguimiento de la pila, a excepción de la recursividad mutua / propia explícita y funciones muy grandes.

Sí, su caja contiene try! . Si pasa un puntero de función o similar al código de la biblioteca y hay un error en su función, el enfoque de prueba seguirá funcionando. Si una caja que usa tiene un error o error interno, es posible que necesite su código fuente para depurarlo ya, si la información devuelta Err no ayuda. Los seguimientos de pila solo son útiles si tiene acceso al código, por lo que las bibliotecas de código cerrado (cuyo código no puede modificar) tendrán que superar el soporte de una forma u otra.

¿Qué tal simplemente habilitar ambos enfoques y dejar que el desarrollador decida qué es lo mejor para ellos? No niego que el enfoque basado en TLS no tiene ninguna ventaja.

El modelo de prueba se puede implementar muy fácilmente, solo desugar ? a try , solo se necesitan extensiones de idioma adicionales si catch entra (habría agregado ese comportamiento dentro del comportamiento codificado de ? todos modos)

@eddyb "¿Crees que los punteros de instrucción son realmente tan útiles en comparación con los identificadores aleatorios generados por tu sistema de compilación con el fin de depurar los binarios de la versión?"

Así es como funciona la depuración de binarios nativos en general. Nosotros (Sentry) lo estamos usando casi por completo para el soporte de iOS en este momento. Obtenemos un volcado de memoria y resolvemos las direcciones con llvm-symbolizer a los símbolos reales.

@mitsuhiko Como ya incrustamos libbacktrace , podríamos usar eso para resolver los punteros de código a las ubicaciones de origen, por lo que no me opongo del todo.

@eddyb, sí. Solo miré el código de pánico. Sería bueno si en realidad fuera una API que pudiera usar el código de Rust. Puedo ver que esto es útil en algunos lugares más.

Acerca de eso, tuve algunas ideas sobre un truco a nivel de sistema de tipos donde {Option, Result} :: desenvolver tendría un argumento de tipo adicional, por defecto a un tipo que depende de la ubicación desde la que se llamó a la función, de modo que tener información de ubicación mucho más útil.

Hablando de que...

@glaebhoerl Hah, ¿tal vez valga la pena perseguir eso? Al menos como un experimento inestable.

@eddyb No

Solo un pensamiento: sería útil tener un interruptor que haga que rustc a un caso especial construya un Err modo que llame a una función fn(TypeId, *mut ()) con la carga útil antes de regresar . Eso debería ser suficiente para comenzar con cosas básicas como filtrar errores en función de la carga útil, atrapar en un depurador si ve un error de interés o capturar trazas para ciertos tipos de errores.

PR 33389 agrega soporte experimental para el rasgo Carrier . Dado que no formaba parte del RFC original, debería tener un período de examen y discusión particularmente estrecho antes de pasar a FCP (que probablemente debería estar separado del FCP para el resto del operador ? ). Vea este hilo de discusión para más detalles.

Me opongo a extender ? a Option s.

La redacción de la RFC es bastante clara sobre el hecho de que el operador ? trata de propagar errores / "excepciones".
Usar Option para informar un error es la herramienta incorrecta. Devolver None es parte del flujo de trabajo normal de un programa exitoso, mientras que devolver Err siempre indica un error.

Si alguna vez queremos mejorar algunas áreas de manejo de errores (por ejemplo, agregando rastros de pila), implementar ? en Option significa que tendremos que excluir ? de los cambios .

@tomaka ¿ podríamos mantener la discusión en el hilo de discusión ? (Ya he resumido su objeción ) Personalmente, encuentro que las discusiones largas sobre GH se vuelven bastante difíciles de manejar, y también sería bueno poder separar la discusión de este punto en particular de otros puntos o preguntas futuras que puedan surgir.

@eddyb Aquí están los documentos de la versión publicada de la función GHC de pila de llamadas implícita que mencioné anteriormente .

No hay actualizaciones aquí en un tiempo. ¿Alguien está trabajando para hacer avanzar esto? ¿Las tareas restantes en el PO siguen siendo precisas? ¿Se puede pedir ayuda electrónica en algo aquí?

Jugué el fin de semana pasado si sería posible escribir un cliente Sentry para Rust que tuviera algún sentido. Dado que la mayor parte del manejo de errores se basa en los resultados ahora en lugar de en el pánico, noté que la utilidad de esto está severamente limitada al punto en que decidí abandonar esto por completo.

Fui a la base de código crates.io como ejemplo para intentar integrar un sistema de informes de errores en él. Esto me trajo de regreso a este RFC porque realmente creo que a menos que podamos registrar el puntero de instrucción de alguna manera a medida que los resultados se transmiten y se convierten en los diferentes trazos de pila, será imposible obtener un informe de errores adecuado. Ya veo que esto es un gran dolor simplemente depurar fallas lógicas complejas locales donde hoy en día recurro a agregar pánicos de donde creo que proviene el error.

Desafortunadamente, actualmente no veo cómo se podría registrar la IP sin cambios masivos en el funcionamiento de los resultados. ¿Alguien más ha jugado con esta idea antes?

Estuvimos discutiendo esto en la reunión de @ rust-lang / lang. Algunas cosas que surgieron:

Primero, existe un interés definido en ver que ? estabilice lo más rápido posible. Sin embargo, creo que a la mayoría de nosotros también nos gustaría ver ? operando en Option y no solo Result (pero no, creo, bool , como también se ha propuesto). Una preocupación sobre la estabilización es que si estabilizamos sin ofrecer ningún tipo de rasgo que permita usar tipos distintos de Result , entonces no es compatible con versiones anteriores agregarlo más adelante.

Por ejemplo, yo mismo escribo un código como este con cierta regularidad:

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

donde confío en try! para informar la inferencia de tipo que espero que collect devuelva un Result<Vec<_>, _> . Si se usara ? , esta inferencia _podría_ fallar en el futuro.

Sin embargo, en discusiones anteriores también decidimos que se necesitaba una enmienda RFC para discutir los puntos más finos de cualquier tipo de rasgo "portador". Claramente, este RFC debe escribirse lo antes posible, pero preferiríamos no bloquear el progreso en ? en esa discusión.

Un pensamiento que teníamos fue que si tomamos la implementación de @nrc y hacemos que el rasgo sea inestable y lo implementamos solo por Result y algún tipo ficticio privado, eso debería suprimir la inferencia mientras solo generamos ? utilizable con Result .

Un último punto: creo que la mayoría de nosotros preferiría que si usa ? en un Option , requiere que el tipo de retorno de su función también sea Option (no p. Ej. Result<T,()> ). Es interesante notar que esto ayudará con las limitaciones de inferencia, ya que en última instancia podemos inferir del tipo de retorno declarado en muchos casos qué tipo se requiere.

La razón para no querer la interconversión es que parece probable que conduzca a una lógica suelta, algo similar a cómo C permite if x incluso cuando x tiene un tipo integral. Es decir, si Option<T> denota un valor donde None es parte del dominio de ese valor (como debería), y Result<> representa (típicamente) la falla de una función para tener éxito, entonces asumir que None significa que la función debería salir del error parece sospechosa (y como una especie de convención arbitraria). Pero esa discusión puede esperar al RFC, supongo.

La razón para no querer la interconversión es que parece probable que conduzca a una lógica suelta, algo parecido a cómo C permite if x incluso cuando x tiene un tipo integral. Es decir, si Option<T> denota un valor donde None es parte del dominio de ese valor (como debería), y Result<> representa (típicamente) la falla de una función para tener éxito, entonces asumir que None significa que la función debería salir del error parece sospechosa (y como una especie de convención arbitraria). Pero esa discusión puede esperar al RFC, supongo.

Estoy totalmente de acuerdo con esto.

Otra pregunta en la que habíamos acordado la estabilización de la puerta era concretar los contratos que impl s del rasgo From deberían obedecer (o cualquier rasgo que terminemos usando para Err -upcasting ).

@glaebhoerl

Otra pregunta sobre la que habíamos acordado la estabilización de la puerta era concretar los contratos que las implicaciones del rasgo De deberían obedecer (o cualquier rasgo que terminemos usando para la conversión de Err).

En efecto. ¿Puedes refrescarme la memoria y comenzar con algunos ejemplos de cosas que crees que deberían estar prohibidas? ¿O quizás solo las leyes que tiene en mente? Debo admitir que desconfío de "leyes" como esta. Por un lado, tienen una tendencia a ser ignorados en la práctica: la gente se aprovecha del comportamiento real cuando se adapta a sus propósitos, incluso si va más allá de las limitaciones previstas. Entonces eso lleva a otra pregunta: si tuviéramos leyes, ¿las usaríamos para algo? Optimizaciones? (Aunque me parece poco probable).

Por cierto, ¿cuál es el estado de las expresiones catch ? ¿Están implementados?

Tristemente no :(

El martes 26 de julio de 2016 a las 06:41:44 AM -0700, Alexander Bulaev escribió:

Por cierto, ¿cuál es el estado de las expresiones catch ? ¿Están implementados?


Estás recibiendo esto porque eres el autor del hilo.
Responda a este correo electrónico directamente o véalo en GitHub:
https://github.com/rust-lang/rust/issues/31436#issuecomment -235270663

¿Quizás debería planificar su implementación? No hay nada más deprimente que las RFC aceptadas pero no implementadas ...

cc # 35056

Para su información, https://github.com/rust-lang/rfcs/pull/1450 (tipos para variantes de enumeración) abriría algunas formas interesantes de implementar Carrier . Por ejemplo, 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(),
    }
}

Solo quería publicar algunos pensamientos de https://github.com/rust-lang/rust/pull/35056#issuecomment -240129923. Ese PR introduce un rasgo Carrier con tipo ficticio. La intención era salvaguardar, en particular, queríamos estabilizar ? sin tener que estabilizar su interacción con la inferencia de tipos. Esta combinación de rasgo más tipo ficticio parecía ser conservadora.

La idea era (creo) que luego escribiríamos un RFC discutiendo Carrier y trataríamos de modificar el diseño para que coincidiera, estabilizándonos solo cuando estuviéramos contentos con la forma general (o posiblemente eliminando Carrier total, si no podemos alcanzar un diseño que nos guste).

Ahora, hablando un poco más especulativamente, _ anticipo_ que, si adoptamos un rasgo Carrier , querríamos no permitir la interconversión entre portadores (mientras que este rasgo es básicamente una forma de convertir a / desde Result ). Entonces, intuitivamente, si aplica ? a un Option , está bien si fn devuelve Option ; y si aplica ? a Result<T,E> , está bien si fn devuelve Result<U,F> donde E: Into<F> ; pero si aplica ? a un Option y la fn devuelve Result<..> , no está bien.

Dicho esto, este tipo de regla es difícil de expresar en el sistema de tipos de hoy. El punto de partida más obvio sería algo como HKT (que por supuesto no tenemos realmente, pero ignorémoslo por ahora). Sin embargo, eso no es obviamente perfecto. Si lo usáramos, se supondría que el parámetro Self para Carrier tiene el tipo type -> type -> type , ya que Result puede implementar Carrier . Eso nos permitiría expresar cosas como Self<T,E> -> Self<U,F> . Sin embargo, _no_ se aplicaría necesariamente a Option , que tiene el tipo type -> type (todo esto, por supuesto, dependería precisamente del tipo de sistema HKT que adoptemos, pero no creo que Iré hasta "lambdas de tipo general"). Incluso más extremo podría ser un tipo como bool (aunque no quiero implementar Carrier para bool, esperaría que algunas personas quieran implementar Carrier para un nuevo tipo bool).

Lo que había considerado es que las reglas de escritura para ? podrían ser en sí mismas especiales: por ejemplo, podríamos decir que ? solo se puede aplicar a un tipo nominal Foo<..> de _some_ kind, y que coincidirá con el rasgo Carrier con este tipo, pero requerirá que el tipo de retorno del fn adjunto sea también Foo<..> . Así que básicamente crearíamos Foo instancia de ? ni el tipo de fn adjunto, no podemos hacer cumplir esta restricción sin agregar algún nuevo tipo de obligación de rasgo. También es bastante ad-hoc. :) Pero funcionaría.

Otro pensamiento que tuve es que podríamos reconsiderar el rasgo Carrier . La idea sería tener Expr: Carrier<Return> donde Expr es el tipo al que se aplica ? y Return es el tipo de entorno. Por ejemplo, tal vez podría verse así:

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
}

Luego expr? des azúcares para:

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

La diferencia clave aquí es que Target no sería el tipo _error_, sino un nuevo tipo Result . Entonces, por ejemplo, podríamos agregar la siguiente 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())) }
}

Y luego podríamos agregar:

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

Y finalmente podríamos implementar para bool así:

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

Ahora esta versión es más flexible. Por ejemplo, _podríamos_ permitir la interconversión entre los valores Option para convertirlos a Result agregando un impl como:

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

Pero, por supuesto, no tenemos que hacerlo (y no lo haríamos).

@Stebalien

Para su información, rust-lang / rfcs # 1450 (tipos para variantes de enumeración) abriría algunas formas interesantes de implementar Carrier

Mientras escribía esa idea que acabo de escribir, estaba pensando en tener tipos para las variantes de enumeración y cómo eso podría afectar las cosas.

Una cosa que noté al escribir un código que usa ? es que es un poco molesto no tener ningún tipo de palabra clave "throw", en particular, si escribe Err(foo)? , el compilador no t _sabe_ que esto volverá, así que tienes que escribir return Err(foo) . Está bien, pero luego no obtiene las conversiones de into() sin escribirlas usted mismo.

Esto surge en casos como:

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

Oh, debería agregar otra cosa. El hecho de que permitamos que las impls influyan en la inferencia de tipo _debería_ significar que foo.iter().map().collect()? , en un contexto en el que fn devuelve un Result<..> , sospecho que no se requerirían anotaciones de tipo, ya que si sabemos que el El tipo de retorno fn es Result , solo una implícita se aplicaría potencialmente (localmente, al menos).

Ah, y una versión ligeramente mejor de mi rasgo Carrier probablemente sería:

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

donde lo implementaría 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()) }
    }
}

Y expr? generaría un código como:

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

Una desventaja (o una ventaja ...) de esto, por supuesto, es que las conversiones Into se insertan en las impls, lo que significa que es posible que la gente no las use cuando tenga sentido. Pero también significa que puede desactivarlos si (para su tipo en particular) no los desea.

@nikomatsakis IMO, el rasgo debería ser IntoCarrier y IntoCarrier::into_carrier debería devolver un Carrier (una nueva enumeración) en lugar de reutilizar un resultado como este. Es decir:

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

@Stebalien seguro, parece bien.

Nominación para discusión (y posible FCP del operador ? solo) en la reunión del equipo de lang. Supongo que necesitamos conseguir algún tipo de rasgo de portador temporal en los próximos días a FCP.

Abrí rust-lang / rfcs # 1718 para discutir el rasgo Carrier.

¡Escuchen, escuchen! El operador ? específicamente ahora está ingresando al período de comentarios final . Esta discusión dura aproximadamente este ciclo de lanzamiento que comenzó el 18 de agosto. La inclinación es estabilizar el operador ? .

Con respecto al rasgo de portador , una versión temporal aterrizó en # 35777 que debería asegurar que tengamos la libertad de decidir de cualquier manera al prevenir la interacción no deseada con la inferencia de tipos.

@rust-lang / lang miembros, marquen su nombre para indicar que están de acuerdo. Deje un comentario con inquietudes u objeciones. Otros, por favor dejen comentarios. ¡Gracias!

  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @aturon
  • [x] @eddyb
  • [] @pnkfelix (de vacaciones)

Me pregunto si las bibliotecas basadas en tokio terminarán usando mucho and_then . Ese sería un argumento para permitir que foo().?bar() sea ​​la abreviatura de foo().and_then(move |v| v.bar()) , de modo que Resultados y Futuros puedan usar la misma notación.

Para que quede claro, este FCP se trata de la función question_mark , no de captura , ¿correcto? El título de este número implica el seguimiento de ambas características en este número.

@seanmonstar, este último ni siquiera se ha implementado, así que, sí. Presumiblemente, si el FCP da como resultado la aceptación, esto se cambiará para rastrear catch .

Para ser claros, este FCP se trata de la función question_mark, no de captura, ¿correcto? El título de este número implica el seguimiento de ambas características en este número.

Sí, solo la función question_mark .

Siguiendo en https://github.com/rust-lang/rfcs/issues/1718#issuecomment -241764523. Pensé que el comportamiento actual ? podría generalizarse en "vincular continuación actual", pero la parte map_err(From::from) de ? hace que sea un poco más que unir: /. Si agregamos una instancia From para el resultado por completo, entonces supongo que la semántica podría ser v? => from(v.bind(current-continuation)) .

Ha habido mucha discusión sobre los méritos relativos de ? en este hilo interno:

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

No tengo tiempo para hacer un resumen profundo en este momento. Lo que recuerdo es que los comentarios se centran en la cuestión de si ? es lo suficientemente visible para llamar la atención sobre los errores, pero probablemente estoy pasando por alto otras facetas de la discusión. Si alguien más tiene tiempo para resumir, ¡sería genial!

No he comentado antes y esto tal vez sea demasiado tarde, pero también encuentro confuso el operador ? , si se usa como una declaración de devolución oculta, como @hauleth señaló en la discusión que ha vinculado @ nikomatsakis.

Con try! , era obvio que podría haber un retorno en alguna parte, porque una macro puede hacer eso. Con ? como return oculto, tendríamos 3 formas de devolver valores de una función:

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

Sin embargo, me gusta esto, como dijo @CryZe :

De esta manera, es familiar para todos, reduce el error hasta el final, donde puede manejarlo, y no hay devoluciones implícitas. Por lo que podría verse aproximadamente así:

deje que a = intente! (x? .y? .z);

Eso ayuda a que el código sea más conciso y no oculta una devolución. Y es familiar en otros idiomas como coffeescript .

¿Cómo afectaría a los futuros la resolución de ? en el nivel de expresión en lugar del nivel de función? Para todos los demás casos de uso, me parece bien.

Sin embargo, me gusta esto, como dijo @CryZe :

De esta manera, es familiar para todos, reduce el error hasta el final, donde puede manejarlo, y no hay devoluciones implícitas. Por lo que podría verse aproximadamente así:

deje que a = intente! (x? .y? .z);

He postulado esto. Creo que sería la solución perfecta.

Con try !, era obvio que podría haber un retorno en alguna parte, porque una macro puede hacer eso.

Solo es obvio porque está familiarizado con el funcionamiento de las macros en Rust. Lo cual será exactamente lo mismo una vez que ? sea ​​estable, generalizado y explicado en cada introducción a Rust.

@conradkleinespel

tendríamos 3 formas de devolver valores de una función:

  • retorno implícito

Rust no tiene "retornos implícitos", tiene expresiones que evalúan un valor. Esta es una diferencia importante.

if foo {
    5
}

7

Si Rust tuviera un "retorno implícito", este código se compilaría. Pero no es así, necesitas return 5 .

¿Cuál será exactamente el mismo una vez? es estable, generalizado y explicado en cada introducción a Rust.

Para ver un ejemplo de cómo se vería, https://github.com/rust-lang/book/pull/134

Con try !, era obvio que podría haber un retorno en alguna parte, porque una macro puede hacer eso.
Solo es obvio porque está familiarizado con el funcionamiento de las macros en Rust. ¿Cuál será exactamente el mismo una vez? es estable, generalizado y explicado en cada introducción a Rust.

En cualquier idioma que yo sepa, "macros" significa "aquí hay dragones" y que allí puede pasar cualquier cosa. Así que lo reformularía como "porque estás familiarizado con el funcionamiento de las macros", sin la parte "en Rust".

@hauleth

deje que a = intente! (x? .y? .z);

He postulado esto. Creo que sería la solución perfecta.

Estoy totalmente en desacuerdo. Como obtendrá un símbolo mágico que solo funciona en try! y no en el exterior.

@hauleth

deje que a = intente! (x? .y? .z);
He postulado esto. Creo que sería la solución perfecta.
Estoy totalmente en desacuerdo. ¡Como obtendrás un símbolo mágico que solo funciona en el intento! y no afuera.

No he dicho que ? debería funcionar solo en try! . Lo que estaba diciendo es que ? debería funcionar como un operador de tubería que empujaría los datos hacia abajo y devolvería el error tan pronto como ocurriera. try! no sería necesario en ese caso, pero podría usarse en el mismo contexto en el que se usa ahora.

@steveklabnik Creo que Rust es un lenguaje que tiene un retorno implícito. En su ejemplo, 5 no se devolvió implícitamente, pero tomemos esto:

fn test() -> i32 {
    5
}

Aquí, 5 se devuelve implícitamente, ¿no es así? A diferencia de return 5; que necesitaría en su ejemplo. Esto lo convierte en 2 formas diferentes de devolver un valor. Lo que encuentro algo confuso sobre Rust. Agregar un tercero no ayudaría a la OMI.

No lo es. Es el resultado de una expresión, específicamente, el cuerpo de la función. "retorno implícito" implica que de alguna manera puede regresar implícitamente desde cualquier lugar, pero eso no es cierto. Ningún otro lenguaje basado en expresiones llama a esto un "retorno implícito", ya que ese sería mi ejemplo de código anterior.

@steveklabnik Muy bien, gracias por tomarse el tiempo para explicar esto: +1:

¡Está todo bien! Puedo ver totalmente de dónde vienes, son solo dos cosas diferentes que la gente usa de manera incorrecta a menudo. He visto a personas asumir que "retorno implícito" significa que puede dejar el ; descuento en cualquier lugar de la fuente para devolver ... eso _ sería_ muy malo: sonrisa:

@hauleth El? operador sería azúcar sintáctico para and_then en ese caso. De esa manera, podría usarlo en muchos más casos y no tendría que ser una devolución difícil de perder. Esto también es lo que tienen todos los demás idiomas que tienen un? operador. Rust's? El operador en la implementación actual sería exactamente OPUESTO de lo que hacen todos los demás lenguajes. También and_then es el enfoque funcional y se recomienda de todos modos, ya que tiene un flujo de control claro. Entonces, ¿solo haciendo? azúcar sintáctico para and_ then y luego manteniendo el intento actual! para explícitamente "desenvolver y devolver", parece ser la situación mucho más limpia, al hacer que las devoluciones sean más visibles y el? operador más flexible (al poder usarlo en casos de no devolución como la coincidencia de patrones).

Exactamente.

Łukasz Niemier
[email protected]

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

@hauleth https://github.com/hauleth ¿El? operador sería azúcar sintáctico para and_then en ese caso. De esa manera, podría usarlo en muchos más casos y no tendría que ser una devolución difícil de perder. Esto también es lo que tienen todos los demás idiomas que tienen un? operador. Rust's? El operador en la implementación actual sería exactamente OPUESTO de lo que hacen todos los demás lenguajes. También and_then es el enfoque funcional y se recomienda de todos modos, ya que tiene un flujo de control claro. Entonces, ¿solo haciendo? azúcar sintáctico para and_ then y luego manteniendo el intento actual! para explícitamente "desenvolver y devolver", parece ser la situación mucho más limpia, al hacer que las devoluciones sean más visibles y el? operador más flexible (al poder usarlo en casos de no devolución como la coincidencia de patrones).

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244461722, o silencie el hilo https://github.com/notifications/unsubscribe-auth/ AARzN5-w4EO9_FwNMDpvtYkGUuQKGt-Kks5qmHOHgaJpZM4HUm_-.

Y cuando trabajaba en una solicitud de extracción para el repositorio de Rust, en realidad tenía que trabajar con un código que usaba? operador y, de hecho, me dolió mucho la legibilidad, como me gusta; estaba súper escondido (mentalmente, porque es solo ruido que se filtra en el cerebro) y lo pasé por alto mucho. Y eso me da bastante miedo.

@steveklabnik lo llamamos "rendimiento implícito", debido a que no somos los únicos queridos .

@hauleth eh, en todos mis años de Ruby, nunca escuché que nadie lo llamara retorno implícito. Sigo manteniendo que es la forma incorrecta de pensarlo.

He usado ? en algunos proyectos y lo he preferido a try! , principalmente porque está en posición de postfijo. En general, el código "real" de Rust entra en la 'mónada' Result en main y nunca lo deja excepto en los nodos hoja ocasionales; y se espera que dicho código siempre propague errores. En su mayor parte, no importa qué expresiones están generando errores, todas simplemente se están enviando de regreso a la pila, y no quiero ver eso cuando estoy leyendo la lógica principal del código.

Mi principal preocupación con ? es que podría obtener el mismo beneficio (posición postfija) con macros de métodos, si existieran. Tengo otras preocupaciones de que quizás al estabilizar la formulación actual estamos limitando la expresividad futura en el manejo de errores: la conversión actual Result no es suficiente para que el manejo de errores en Rust sea tan ergonómico como me gustaría; ya hemos cometido varios errores de diseño con el manejo de errores de Rust que parecen difíciles de solucionar, y esto puede estar cavando más profundamente; aunque no tengo pruebas concretas.

Escribí esto muchas veces antes, pero estoy absolutamente enamorado de ? y las posibilidades del rasgo de portador. Convirtí un proyecto para usar ? completo e hizo posibles muchas cosas (particularmente con respecto al encadenamiento) que era demasiado complejo con try! . También, por diversión, repasé algunos otros proyectos para ver cómo les iría con ? y, en general, no he tenido ningún problema con eso.

Como tal, doy un +1 enorme a la estabilización de ? sobre la base de un rasgo Carrier mejor nombrado que idealmente también cubre algunos de los otros casos que mencioné en la otra discusión al respecto.

Mi principal preocupación con? es que podría obtener el mismo beneficio - posición postfix - con macros de método, si existieran.

¿Quizás necesitamos un RFC para esto? A la mayoría de la gente parece gustarle la funcionalidad de?, Pero no? sí mismo.

¿Quizás necesitamos un RFC para esto? A la mayoría de la gente parece gustarle la funcionalidad de?, Pero no? sí mismo.

Hay un RFC con mucha discusión sobre esto. Además, no sé de dónde sacas esa "mayoría de la gente". Si es de los participantes en este número, por supuesto, verá a más personas argumentando en contra porque la estabilización es _ya_ la acción predeterminada del equipo.
El ? se ha debatido mucho antes de que se fusionara el RFC y, como partidario, es un poco agotador tener que hacer lo mismo cuando se habla de estabilización.

De todos modos, pondré mi +1 para los sentimientos de @mitsuhiko aquí.

Hay un RFC con mucha discusión al respecto. Además, no sé de dónde sacas esa "mayoría de la gente". Si es de los participantes en este número, por supuesto, verá a más personas discutiendo en contra porque la estabilización ya es la acción predeterminada del equipo.

Lo siento, mi comentario fue demasiado breve. Me refería a la creación de un RFC para algún tipo de "macros de métodos", por ejemplo func1().try!().func2().try!() (que yo sepa, esto no es posible actualmente).

Personalmente me gusta el? operador, pero comparto las mismas preocupaciones que @brson , y creo que sería bueno explorar alternativas antes de estabilizar esta función. Incluyendo la conversión RFC, este hilo y el hilo interno que @nikomatsakis vinculó, definitivamente todavía hay algo de controversia sobre esta característica, incluso si se trata de los mismos argumentos una y otra vez. Sin embargo, si no hay alternativas viables, la estabilización tiene más sentido.

Parece prematuro estabilizar una función sin haberla implementado por completo, en este caso, la expresión catch {..}.

Ya he expresado mis preocupaciones sobre esta función y todavía creo que es una mala idea. Creo que tener un operador de retorno condicional postfix no se parece a nada en ningún otro lenguaje de programación y está empujando a Rust más allá de su presupuesto de complejidad ya estirado.

@mcpherrinm En cambio, otros lenguajes han ocultado las rutas de desenrollado en cada llamada para el manejo de errores, ¿llamaría operator() un "operador de retorno condicional"?

En cuanto al presupuesto de complejidad, solo es sintácticamente diferente de try! , al menos la parte de la que se está quejando.
¿Es el argumento en contra de try! -código pesado, que ? solo hace más legible?
Si es así, estaría de acuerdo si hay una alternativa seria que no sea "no tengo ninguna automatización de propagación de errores _ en absoluto_".

Sugerir un compromiso: https://github.com/rust-lang/rfcs/pull/1737

Puede que no tenga posibilidades de ser aceptado, pero lo intento de todos modos.

Me gusta la idea de @keeperofdakeys sobre las "macros de métodos". No creo que la sintaxis ? deba aceptarse por la misma razón por la que el operador ternario no está en oxidación: legibilidad. El ? sí mismo no dice nada. En cambio, preferiría ver la capacidad de generalizar el comportamiento de ? con las "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); 

De esta forma, quedaría claro cuál es el comportamiento y permite un encadenamiento sencillo.

La macro de método como result.try!() parece ser una mejora más genérica de la ergonomía del lenguaje y se siente menos ad-hoc que un nuevo operador ? .

@brson

Tengo otras preocupaciones de que tal vez al estabilizar la formulación actual estamos limitando la expresividad futura en el manejo de errores: la conversión de resultados actual no es suficiente para que el manejo de errores en Rust sea tan ergonómico como me gustaría

Este es un punto interesante. Valdría la pena dedicar un tiempo a esto (quizás tú y yo podamos charlar un poco). Estoy de acuerdo en que podríamos hacerlo mejor aquí. El diseño propuesto para un rasgo Carrier (ver https://github.com/rust-lang/rfcs/issues/1718) puede ayudar aquí, particularmente si se combina con la especialización, ya que hace las cosas más flexibles.

Realmente dudo que las macros de métodos sean una buena extensión del lenguaje.

macro_rules! macros

No es así como funcionan los métodos. Los métodos tienen estas propiedades:

  1. No se puede declarar en el ámbito de un módulo, pero se debe declarar dentro de un bloque impl .
  2. Se importan con el tipo / rasgo con el que está asociado el bloque impl , en lugar de importarse directamente.
  3. Se envían en función de su tipo de receptor, en lugar de ser enviados en función de ser un símbolo único e inequívoco en este ámbito.

Debido a que las macros se expanden antes de la verificación de tipo, ninguna de estas propiedades podría ser cierta para las macros que utilizan la sintaxis del método hasta donde yo sé. Por supuesto, podríamos tener macros que usen la sintaxis de método pero que se envíen e importen de la misma manera que las macros "gratuitas", pero creo que la disparidad lo convertiría en una característica muy confusa.

Por estas razones, no creo que sea una buena opción retrasar ? con la creencia de que algún día pueden aparecer "macros de métodos".

Además, creo que hay una línea en la que algún constructo se usa tan ampliamente y es tan importante que debería promoverse de las macros al azúcar. for bucles ? es parte integral de la historia de manejo de errores de Rust, y creo que es apropiado que sea azúcar de primera clase en lugar de una macro.

@hauleth , @CryZe

Para responder a aquellos que sugieren que ? debería ser un operador and_then , esto funciona bien en lenguajes como Kotlin (no estoy familiarizado con coffeescript) debido a su amplio uso de funciones de extensión, pero no lo es tan sencillo en óxido. Básicamente, la mayoría de los usos de and_then no son maybe_i.and_then(|i| i.foo()) , son maybe_i.and_then(|i| Foo::foo(i)) El primero podría expresarse como maybe_i?.foo() pero el segundo no. Se podría decir que Foo::foo(maybe_i?, maybe_j?) convierte en maybe_i.and_then(|i| maybe_j.and_then(|j| Foo::foo(i, j))) pero esto se siente aún más confuso que simplemente decir que el óxido regresa temprano al golpear el primer ? que evalúa un error. Sin embargo, podría decirse que esto sería más poderoso.

@Stebalien En el RFC aceptado, catch { Foo::foo(maybe_i?, maybe_j?) } hace lo que quiere.

@eddyb Buen punto. Supongo que puedo dejar de lado el "Sin embargo, podría decirse que esto sería más poderoso". Todo se reduce a captura implícita / intento explícito versus captura explícita / intento implícito:

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()?);

(sintaxis de módulo)

@Stebalien Otro ejemplo: si quisiera pasar Foo a una función bar , con tu propuesta necesitaría:

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

¿Es esto lo que tienes en mente? Tenga en cuenta los ? adicionales, sin ellos bar obtendría un Result lugar de un Foo .

@eddyb Probablemente. Nota: ¡en realidad no estoy proponiendo esto! Estoy argumentando que usar ? como operador de tubería no es particularmente útil en el óxido sin alguna forma de manejar el caso Foo::foo(bar?) .

Solo para notar que odio la idea de macros de métodos y no puedo pensar en una característica del lenguaje a la que me opondría con más fuerza. Eliminan las fases del compilador y, a menos que hagamos cambios bastante fundamentales en el lenguaje, no hay forma de que existan y tengan un comportamiento poco sorprendente. También son difíciles de analizar con sensatez y es casi seguro que no son compatibles con versiones anteriores.

@Stebalien , con ? como operador de tubería Foo::foo(bar?) se vería así: Foo::foo(try!(bar)) y bar(Foo::foo(a?.b?.c()?)?) (asumiendo que Foo::foo : fn(Result<_, _>) -> Result<_, _> ): bar(try!(Foo::foo(a?.b?.c()?))) .

@hauleth, mi punto fue que Foo::foo(bar?)? es _mucho_ más común que bar?.foo()? en óxido. Por lo tanto, para ser útil, ? tendría que admitir este caso (o se tendría que introducir alguna otra característica). Estaba postulando una forma de hacerlo y demostrando que de esa manera al menos sería complicado. El objetivo de ? es poder evitar escribir try!(foo(try!(bar(try!(baz()))))) (¡el doble de paréntesis!); normalmente no es posible volver a escribir esto como try!(baz()?.bar()?.foo()) .

Pero siempre puedes hacer:

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 mi punto fue que Foo :: foo (¿bar?)? es mucho más común que bar? .foo ()? en óxido. Por tanto, para ser útil? tendría que soportar este caso (o se tendría que introducir alguna otra característica). Estaba postulando una forma de hacerlo y demostrando que de esa manera al menos sería complicado. ¿Todo el punto de? es poder evitar escribir try! (foo (try! (bar (try! (baz ()))))) (¡2x los paréntesis!); normalmente no es posible volver a escribir esto como try! (baz () ?. bar () ?. foo ()).

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub https://github.com/rust-lang/rust/issues/31436#issuecomment -244749275, o silencie el hilo https://github.com/notifications/unsubscribe-auth/ AARzN1Hdk6uk5-SoYawtgAbJUDf_8MsMks5qnBumgaJpZM4HUm_-.

En una nota ligeramente relacionada, parece que los constructores utilizan principalmente la función? -Por lo que posiblemente podríamos evitar la necesidad de la función?. Propuse algo aquí, pero es posible que necesite un poco más de trabajo.

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

Gracias por sus pensamientos sobre la idea de macro de método @nrc y @withoutboats , es bueno escuchar algunas razones concretas por las que no funcionarían.

@nielsle No creo que sea exacto decir que ? está siendo utilizado "principalmente" por los constructores. Si bien los constructores son un ejemplo en el que creo que la ventaja de un operador de posfijo liviano realmente se destaca, prefiero ? a try! en todos los contextos.

@nielsle con respecto a los futuros, originalmente estaba preocupado por razones similares. Pero, después de pensarlo, creo que async / await reemplazaría cualquier necesidad de ? en ese contexto. En realidad, son bastante ortogonales: podrías hacer cosas como (await future)?.bar() .

(Tal vez sería bueno tener un operador de sufijo en lugar de la palabra clave await para que los paréntesis no sean necesarios. O tal vez una precedencia cuidadosamente ajustada sería suficiente).

Definitivamente me gustaría ver alguna documentación escrita antes de estabilizarnos. Busqué en la referencia y no pude encontrar ninguna mención. ¿Dónde deberíamos documentar esta característica?

@cbreeden Sé que @steveklabnik generalmente evita documentar características inestables, ya que existe la posibilidad de que sea una pérdida de tiempo si nunca se estabilizan. No sé si alguna vez hemos bloqueado la estabilización en la redacción de documentación antes.

@solson Tienes razón, probablemente este no era el lugar para mencionarlo, o al menos no debería estar relacionado con cuestiones de estabilización. Supongo que solo estaba imaginando una situación en la que podríamos decidir estabilizar una función, pero luego también requerir documentación antes de ser lanzados a rustc estable. Existe un RFC relacionado con la integración de la documentación con la estabilización y el lanzamiento de funciones, por lo que solo esperaré a que ese proceso se estabilice (pero no sin la documentación adecuada primero, por supuesto)

Creo que la parte importante de este RFC es tener algo del lado derecho de una expresión que actúe como try! , porque eso hace que la lectura de usos secuenciales / encadenados de "try" _much_ sea más legible y tener "catch" . Originalmente, era un partidario del 100% para usar ? como sintaxis, pero recientemente encontré un código (¡limpio!) Que ya usaba ? que me hizo consciente de que, fuera de ejemplos simples ? es extremadamente fácil pasarlo por alto . Lo que ahora me hace creer que usar ? como sintaxis para "el nuevo intento" podría ser un gran error.

Por lo tanto, propongo que podría ser una buena idea _publicar alguna encuesta_ antes de finalizarla (con una notificación al respecto en los foros) para obtener una retroalimentación sobre el uso de ? o algún otro símbolo (s) como sintaxis . Óptimamente con ejemplo de uso en una función más larga. Tenga en cuenta que solo estoy considerando que podría ser una buena idea cambiar el nombre de ? para no cambiar nada más. La encuesta podría enumerar otros nombres posibles que aparecieron en el pasado como ?! (o ?? ) o simplemente algo como "¿usar? Vs. usar más que en el personaje".

Por cierto. no usar ? también podría satisfacer a las personas a las que no les gusta porque es la misma sintaxis que el tipo opcional de otros idiomas. Y aquellos que quieran convertirlo en una sintaxis opcional para los tipos de Opción rust. (A través de esto no son preocupaciones que comparto).

Además, creo que con una encuesta de este tipo se podría llegar a personas que normalmente no participan en el proceso de RFC. Normalmente esto puede no ser necesario, pero try! => ? es un cambio muy grande para cualquiera que escriba y / o lea código rust.

PD:
Puse una esencia con la función que me gusta arriba en diferentes variaciones ("?", "?!", "??") hasta no sé si había habido más. También hubo un RFC para cambiar el nombre de ? a ?! que fue redirigido a esta discusión.

Lo siento, por la posibilidad de reiniciar una discusión en curso ya larga: smiley_cat:.

(Tenga en cuenta que ?? es malo si aún desea introducir ? para Option porque expr??? sería ambiguo)

? es extremadamente fácil pasarlo por alto

¿Qué estamos discutiendo aquí? ¿Código completamente sin resaltar? ¿Navegación regular de códigos resaltados?
¿O busca activamente ? en una función?

Si selecciono un ? en mi editor, _todos_ los otros ? en el archivo se resaltan con un fondo amarillo brillante, y la función de búsqueda también funciona, así que no veo el último como plantearme cualquier dificultad.

En cuanto a otros casos, prefiero resolver esto resaltando mejor que terminar con ?? o ?! .

@dathinab Creo que .try! () o algo sería aún mejor, pero eso requeriría UFCS para macros.

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

De esa forma es difícil pasarlo por alto, pero es igual de fácil, si no más fácil de escribir que .unwrap() .

@eddyb : se trata de código resaltado normal, por ejemplo, en github. Por ejemplo, al leer a través de una base de código. Desde mi punto de vista, se siente un poco mal si necesito un resaltado fuerte para no pasar por alto fácilmente un ? que introduciría otra ruta de retorno (/ ruta para capturar) y posibles cambios en el tipo de una variable de Result<T> a T

@CryZe : Estoy de acuerdo contigo, pero no creo que podamos conseguir esto en un futuro próximo. Y tener algo que sea un poco más corto que .try!() tampoco es tan malo.

@CryZe También me gusta esa sintaxis, pero @withoutboats mencionó algunas razones sólidas por las que las macros de métodos pueden dañar el lenguaje.
Por otro lado, me temo que si aparecen macros de métodos, no creo que funcionen bien con ? .

No estoy intrínsecamente en contra de usar dos caracteres para este sigilo, pero miré los ejemplos y no encontré que el cambio hiciera que ? pareciera más visible.

Si, igual. Creo que debe ser algún tipo de palabra clave, ya que los símbolos en general se usan más para estructurar, tanto en el lenguaje normal como en los lenguajes de programación.

@CryZe : Quizás algo como ?try sería una especie de palabra clave. Pero para ser honesto, no me gusta ( ?try ).

@eddyb

y la función de búsqueda también funciona

La búsqueda de ? arrojará falsos positivos, mientras que las versiones más largas probablemente no lo harán.

Buscando ? arrojará falsos positivos, mientras que las versiones más largas probablemente no lo harán.

Espero que los resaltadores de sintaxis se encarguen de esto internamente (evitando falsos positivos). De hecho, incluso pueden insertar algún tipo de marcador de "podría devolver" en el margen (junto a los números de línea).

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

Vaya, eso definitivamente ayuda mucho. Pero entonces realmente necesitas asegurarte
tener su editor configurado correctamente, lo que no puede hacer todo el tiempo (como
en GitHub, por ejemplo).

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

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

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/rust-lang/rust/issues/31436#issuecomment -247465972,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/ABYmbjyrt07NXKMUdmlBfaciRZq7uBVEks5qqb4sgaJpZM4HUm_-
.

@CryZe Estoy bastante seguro de que GitHub solo está usando un resaltador de sintaxis de código abierto; podemos parchearlo :-).

Creo que, en general, es una buena idea que el proyecto Rust haga una recomendación de que los resaltadores de sintaxis para Rust deben usar un estilo muy visible en ? , para equilibrar las preocupaciones sobre la visibilidad.

Creo que ? es la mejor elección que podemos hacer aquí, y lo hemos estado usando bastante en Miri .

Volviendo a la motivación original, try!(...) es molesto y dificulta ignorar el flujo de errores y simplemente leer el camino feliz. Esta es una desventaja en comparación con las excepciones invisibles de los idiomas tradicionales. Expandir ? a una palabra clave más involucrada tendría el mismo inconveniente.

Por otro lado, con ? , cuando no me importa el flujo de errores, puedo ignorarlo y dejar que se desvanezca en un segundo plano. Y cuando realmente me preocupa el flujo de errores, todavía puedo ver ? muy bien. Resaltar ? brillantemente ni siquiera es necesario para mí, pero si ayuda a otras personas, eso es genial. Esta es una mejora con respecto a las excepciones invisibles y try! .

Cambiar a un sigilo trivialmente más grande como ?! no me ayudaría de ninguna manera, pero empeoraría un poco el código de manejo de errores de lectura y escritura.

Gracias a todos por un cordial período de comentarios finales (así como por un hilo interno anterior ). Dado que estamos hablando del RFC más comentado hasta la fecha , no me sorprende ver que la discusión sobre la estabilización también ha sido bastante activa.

Permítanme ir al grano primero: el equipo de @ rust-lang / lang ha decidido estabilizar el operador ? cuando se aplica a valores de tipo Result . Tenga en cuenta que la función catch no se está estabilizando (y, de hecho, aún no se ha implementado); de manera similar, el llamado "rasgo de portador", que es un medio para extender ? a tipos como Option , todavía se podemos añadir el Carrier rasgo más tarde (que se refieren a algunas de mis preocupaciones anteriores sobre la posible interacción con la inferencia).

Me gustaría tomarme un poco de tiempo para resumir la discusión que ha tenido lugar desde que comenzó el FCP el 22 de agosto . Muchos de estos temas también aparecieron en el hilo de RFC original . Si está interesado en leer el hilo, el comentario de FCP y el comentario de recapitulación en ese hilo intentan cubrir la conversación en profundidad. En algunos casos, vincularé los comentarios en ese hilo original si son más profundos que los correspondientes de este hilo.

El alcance del operador ? debe ser la expresión actual, no la función actual.

Cuando ocurre un error, la macro try! propaga ese error incondicionalmente a la función que llama (en otras palabras, ejecuta un return con el error). Como está diseñado actualmente, el operador ? sigue este precedente, pero con la intención de admitir una palabra clave catch que permita al usuario especificar un alcance más limitado. Esto significa, por ejemplo, que x.and_then(|b| foo(b)) se puede escribir como catch { foo(x?) } . En contraste, varios lenguajes recientes usan el operador ? para significar algo más análogo a and_then , y ha existido la preocupación de que esto pueda resultar confuso para los nuevos usuarios.

En última instancia, una vez que se implementa catch , esta es una cuestión de valores predeterminados . Y hay varias razones por las que creemos que el valor predeterminado de "salir de la función" (con la opción de personalizar) es más apropiado para ? en Rust :

? oscurece el flujo de control porque es difícil de detectar.

Una preocupación común es que el operador ? es demasiado fácil de pasar por alto . Este es claramente un acto de equilibrio. Tener un operador liviano hace que sea fácil concentrarse en el "camino feliz" cuando lo desee , pero es importante tener alguna indicación de dónde pueden ocurrir los errores (a diferencia de las excepciones, que introducen un flujo de control implícito). Además, es fácil hacer que ? sea ​​más fácil de detectar mediante el resaltado de sintaxis (por ejemplo, 1 , 2 ).

¿Por qué no macros de métodos?

Uno de los grandes beneficios de ? es que se puede usar en la posición posterior a la reparación, pero
podríamos obtener beneficios similares de "macros de métodos" como foo.try! . Si bien es cierto, las macros de métodos abren mucha complejidad por sí mismas , particularmente si desea que se comporten como métodos (p. Ej., Que se envíen en función del tipo de receptor y no utilicen un alcance léxico). Además, el uso de una macro de método como foo.try! tiene una sensación de peso significativamente mayor que foo? (consulte el punto anterior).

¿Qué contratos debería ofrecer From ?

En la discusión de RFC original, decidimos posponer la pregunta de [si debería haber "contratos" por From ] ((https://github.com/rust-lang/rust/issues/31436#issuecomment -180558025). El consenso general del equipo de lang es que uno debe ver el operador ? como invocando el rasgo From , y que los implícitos del rasgo From pueden hacer naturalmente cualquier cosa está permitido por sus firmas de tipo. Tenga en cuenta que el papel del rasgo From es bastante limitado aquí de todos modos: simplemente se usa para convertir de un tipo de error a otro (pero siempre en el contexto de un Result ).

Sin embargo , me gustaría señalar que la discusión sobre un rasgo de "portador" está en curso , y la adopción de convenciones sólidas es más importante en ese escenario . En particular, el rasgo de portador llega a definir qué constituye "éxito" y "fracaso" para un tipo, así como si un tipo de valor (por ejemplo, un Option ) se puede convertir en otro (por ejemplo, un Result ). Muchos han argumentado que no queremos admitir interconversiones arbitrarias entre tipos "similares a errores" (por ejemplo, ? no debería poder convertir Option en Result ). Obviamente, dado que los usuarios finales pueden implementar el rasgo Carrier para sus propios tipos según lo deseen, esta es en última instancia una guía, pero creo que es importante.

puerto prueba! usar ?

No creo que podamos hacer esto de manera compatible con versiones anteriores y deberíamos dejar la implementación de try! sola. ¿Deberíamos desaprobar try! ?

@nrc ¿

@withoutboats try!(x) es (x : Result<_, _>)? y probablemente podríamos implementarlo de esa manera si _ quisiéramos_, pero en general x? podría inferir cualquier cosa que admita Carrier rasgo (en el futuro), un ejemplo es iter.collect()? que con try! solo sería Result pero, de manera realista, puede ser Option .

Eso tiene sentido. Pensé que aceptamos que agregar impls a std podría causar ambigüedades en la inferencia; ¿Por qué no aceptar eso también aquí?

De cualquier manera, creo que try! debería quedar obsoleto.

? es más útil en un patrón de construcción como context, mientras que try es más útil en un método anidado como context. Creo que no debería estar en desuso, sea lo que sea que eso signifique.

@ est31 Ellos hacen exactamente lo mismo ahora mismo excepto por inferencia. No estoy seguro de lo que quiere decir exactamente, pero ? suele ser estrictamente más limpio (de nuevo, inferencia de módulo). ¿Podría dar ejemplos?

@eddyb foo()?.bar()?.baz() es mejor que try!(try!(foo()).bar()).baz() , y try!(bar(try!(foo()))) es mejor que bar(foo()?)?

Encuentro ? más legible en ambos casos. Reduce el desorden innecesario de paréntesis.

¿Cuándo aterrizará en estable?

@ofek esto es para todo, que aún no está completo, por lo que es difícil de decir. https://github.com/rust-lang/rust/pull/36995 estabilizó la sintaxis básica ? , que debería ser estable en 1.14.

No olvide agregar el? operador a la referencia ahora que es estable: https://doc.rust-lang.org/nightly/reference.html#unary -operator-expression

Y @bluss señaló que el libro también está desactualizado: https://doc.rust-lang.org/nightly/book/syntax-index.html

@tomaka

Me opongo a ampliar a Opciones.

No es necesario que se utilice con fines de error. Por ejemplo, si tengo un método de envoltura para tomar get y asignarlo mediante alguna función, entonces me gustaría poder propagar el caso None .

? fue presentado como solo por errores; la notación do más general para la propagación general como esta no era un objetivo.

El 29 de octubre de 2016, 11:08 -0400, ticki [email protected] , escribió:

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

Me opongo a ampliar a Opciones.

No es necesario que se utilice con fines de error. Por ejemplo, si tengo un método contenedor para tomar get y mapearlo mediante alguna función, me gustaría poder propagar el caso None hacia arriba.

-
Estás recibiendo esto porque te mencionaron.
Responda a este correo electrónico directamente, véalo en GitHub (https://github.com/rust-lang/rust/issues/31436#issuecomment-257096575), o silencie el hilo (https://github.com/notifications/unsubscribe -auth / AABsipGIpTF1-7enk-z_5JRYYtl46FLPks5q42DCgaJpZM4HUm_-).

¡Esto (= ? sí mismo) es ahora una característica estable! (De Rust 1.13)

Dos problemas de documentación:

  • [x] Actualización del capítulo sobre manejo de errores del libro n.º 37750
  • [x] Actualizar Carrier trait docs para la situación actual # 37751

Tenga en cuenta que ni catch ni el rasgo Carrier se han implementado correctamente todavía, solo la función ? .

Carrier existe, y los mensajes de error se refieren a él cuando se usa ? , por lo que sería mejor si el problema del operador se hubiera solucionado en lugar de rechazado. Sus documentos también deben actualizarse, ya que los documentos se refieren a que se está implementando para Option. (Lo cual es falso).

35946 debería eliminar cualquier mención de Carrier de los mensajes de error. Parece que al menos deberíamos eliminar la mención Option de los documentos Carrier .

Estoy agregando T-libs a este problema debido a la interacción con el rasgo Carrier

Hola; en # 31954, el upcast para el tipo de error se realiza usando From (y similar lo mismo en el encabezado actual ), pero RFC 243 establece claramente que Into debe usarse para la conversión.

¿Hay alguna razón por la que se utilizó From su lugar? Al intentar realizar una conversión ascendente a algún tipo de error genérico (es decir, io::Error , o algo más en una caja externa) From no se puede implementar para tipos de error locales.

(FWIW, como autor de RFC 243, no pensé mucho sobre si From o Into es preferible, y puedo o no haber tomado la decisión correcta. Lo cual es solo para decir que la pregunta debe decidirse en función de los méritos (que en este punto pueden incluir compatibilidad con versiones anteriores), en lugar de lo que está escrito en el RFC).

Lamentablemente, habría una regresión. Si no se implementa una instancia From<...> (no trivial) para un tipo de error, el compilador puede deducir el tipo en ciertos casos, donde no puede deducirlo cuando usa Into (el conjunto de From instancias está limitado por la caja actual, el conjunto completo de Into instancias no se conoce al compilar una caja).

Ver https://play.rust-lang.org/?gist=6d3ee9f93c8b40094a80d3481b12dd00 ("simplificado" de un problema del mundo real que involucra fmt::Error en src / librustc / util / ppaux.rs # L81 )

Hay un nuevo RFC que reemplazará al anterior en términos de describir ? : rust-lang / rfcs / pull / 1859

Tengo -1 por tener bloques catch , cuando ya son mucho más concisos con los cierres.

Los cierres interfieren con las declaraciones de control de flujo. ( break , continue , return )

Ahora que https://github.com/rust-lang/rfcs/pull/1859 se ha fusionado y afirma que estamos reutilizando este problema como problema de seguimiento, me gustaría proponer que reevaluamos el catch porción de RFC 243.

No estoy proponiendo que nos deshagamos de él por completo, pero el entusiasmo por catch nunca fue tan grande como lo fue por ? y creo que vale la pena asegurarse de que todavía tenga sentido a la luz de los modismos que han surgido por ? , y que se espera que surjan a la luz de Try .

Todavía estoy ansioso por atraparlo; hoy temprano tuve que contorsionar algo de código de una manera que podría haberse evitado si la captura fuera estable.

La función se ha implementado todas las noches con la sintaxis do catch , ¿no es así?

@withoutboats Sí, actualmente es do catch , ya que catch { ... } entra en conflicto con los literales de estructura ( struct catch { }; catch { } ).
.
@archshift Como @SimonSapin señaló anteriormente, los cierres interfieren con break , continue y return .

@bstrie Encuentro que con bastante frecuencia quiero catch estos días; surge mucho durante la refactorización.

No me di cuenta de que estábamos planeando requerir do catch como sintaxis. Dado que el riesgo de rotura en el mundo real parece extremadamente bajo (ambos violan las pautas de nomenclatura de estructuras y debería tener el constructor como la primera expresión en la declaración (lo cual es raro fuera de la posición de retorno)), ¿podríamos quizás aprovechar rustfmt para reescriba cualquier identificador ofensivo en catch_ ? Si requiere que Rust 2.0 lo haga, entonces, bueno, siempre he sido de los que dicen que Rust 2.0 debería contener solo cambios triviales importantes de todos modos ...: P

@bstrie No do catch largo plazo. La discusión que llevó a usar esa sintaxis por ahora está aquí: https://github.com/rust-lang/rust/pull/39921

Excelente, gracias por el contexto.

Vine aquí porque esperaba que catch fuera estable, y tuve que aprender que no lo es, así que sí, absolutamente, ahora que ? es estable, sería genial tener también catch .

Quería ver qué tan lejos habíamos llegado con el resto de esto y vi la discusión de la captura.

He estado considerando una idea probablemente tonta que ayudaría en casos como este: permitir opcionalmente prefijar palabras clave con @ o algún otro sigilo, luego hacer que todas las palabras clave nuevas usen solo el sigilo. También tuvimos un problema similar con la discusión de la corrutina. No recuerdo si entré en esto como una solución allí, podría haberlo hecho, pero parece que esto puede seguir apareciendo.

FWIW, C # en realidad admite lo contrario: @ para usar palabras clave como identificadores. Eso se ve comúnmente en Razor, donde pasa cosas como new { <strong i="6">@class</strong> = "errorbox" } para establecer propiedades en los nodos HTML.

@scottmcm
Eso es interesante. Yo no lo sabía. Pero para Rust tenemos que ir al revés debido a la compatibilidad.

Sin embargo, una buena idea de su parte. El ecosistema .net tiene muchos idiomas, todos con palabras clave dispares y todos pueden llamarse entre sí.

Otra posibilidad para el futuro de las palabras clave: colóquelas detrás de un atributo, como una especie de #[feature] estable.

Creo que este problema necesitará una solución más general a largo plazo.

@camlorn Este hilo puede interesarte, específicamente la idea de Aaron sobre las "épocas" de Rust: https://internals.rust-lang.org/t/pre-rfc-stable-features-for-breaking-changes/5002

Creo que deberíamos vincular https://github.com/rust-lang/rust/issues/42327 desde la descripción del problema aquí. (También tal vez el texto RFC debería actualizarse para vincular allí en lugar de aquí).

(EDITAR: publiqué un comentario allí, ¡no estoy seguro de quién está suscrito o no!)

@camlorn Podría, sin embargo, abrir la ruta "rustfmt hace una reescritura trivial por un tiempo, luego se convierte en una palabra clave". AKA aproveche la escotilla de escape "no se está rompiendo si hay una anotación adicional que podría haber sido escrita que lo haría funcionar en ambos" en la garantía de estabilidad. Y, similar al UFCS para los cambios de inferencia, un modelo hipotético de "tienda en forma totalmente elaborada" podría mantener las cajas viejas en funcionamiento.

No me importaría si la sintaxis para el bloque de captura fuera solo do { … } o incluso ?{ … }

do tiene la buena propiedad de ser una palabra clave. Tiene la propiedad dudosa (dependiendo de su perspectiva) de invocar una notación do similar a Haskell, aunque eso nunca detuvo sus usos anteriores, y este es algo más cercano en el caso de uso.

También se parece, pero se comporta de manera diferente a los javascripts propuestos.

Esa propuesta no es realmente relevante para Rust, que ya usa bloques desnudos como expresiones.

Solo estaba señalando la posible confusión, ya que ambos se verían iguales pero harían cosas completamente diferentes.

Tiene la dudosa propiedad (dependiendo de su perspectiva) de invocar la notación do tipo Haskell

@rpjohnst Result y Option son mónadas, por lo que es al menos conceptualmente similar. También debería ser compatible con versiones posteriores para ampliarlo en el futuro para admitir todas las mónadas.

Correcto, en el pasado la objeción ha sido que si agregamos do por algo conceptualmente similar a las mónadas, pero sin apoyarlas completamente, entristecería a la gente.

Al mismo tiempo, aunque probablemente podríamos hacerlo compatible con versiones posteriores con la notación do completa, probablemente no deberíamos agregar la notación do completa. No se puede componer con estructuras de control como if / while / for / loop o break / continue / return , que debemos poder usar dentro y entre los bloques catch (o en este caso do ). (Esto se debe a que la notación do completa se define en términos de funciones de orden superior, y si colocamos el contenido de un bloque catch en una serie de cierres anidados, de repente controlamos el flujo de todas las interrupciones).

Entonces, al final, esta desventaja de do es que parece una notación do sin ser realmente notación, y sin un buen camino a seguir para convertirse en notación do. Personalmente, estoy totalmente de acuerdo con esto porque Rust no obtendrá la notación do modos, pero esa es la confusión.

@nikomatsakis , # 42526 está combinado, puede marcarlo como hecho en la lista de seguimiento :)

¿Es posible hacer que la palabra clave catch contextual pero deshabilitarla si un struct catch está dentro del alcance y emitir una advertencia de obsolescencia?

No estoy seguro de cuán apropiado es esto, pero me encontré con un problema que tal vez deba resolverse, ya que a veces desea cancelar un valor correcto en lugar de error, que actualmente es posible cuando usa return en la medida en que a menudo desea abortar un error _inner_ a través de un _outter_ okay. Como cuando una función say devuelve Option<Result<_,_>> cual es bastante común en, digamos, un iterador.

En particular, tengo una macro en un proyecto que uso copiosamente en este momento:

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

Es muy común que una función llamada dentro de una implementación Iterator::next necesite abortar inmediatamente con Some(Err(e)) en caso de falla. Esta macro funciona dentro de un cuerpo de función normal pero no dentro de un bloque de captura porque la captura no captura categóricamente return sino solo la sintaxis especial ? .

Aunque al final los retornos etiquetados harían redundante toda la idea del bloque catch , ¿no?

Parece que # 41414 está listo. ¿Alguien podría actualizar el OP?

Actualización: RFC rust-lang / rfcs # 2388 ahora está combinado y, por lo tanto, catch { .. } se reemplazará con try { .. } .
Vea el problema de seguimiento justo encima de este comentario.

¿Qué significa esto para la Edición 2015? ¿La sintaxis do catch { .. } todavía está en camino hacia la estabilización, o se eliminará y solo se admitirá a través de try { .. } en la Edición 2018+?

@ Nemo157 Este último.

¿Hay un try ... catch ... dos declaraciones en la propuesta actual? Si es así, no entiendo la semántica.

De todos modos, si esta propuesta solo se trata de desugaring, estoy bien. es decir, ¿un bloque catch simplemente cambia hacia donde sale el operador ? ?

Como todas las casillas de verificación están marcadas en la publicación superior, ¿cuándo avanzaríamos? Si todavía hay problemas sin resolver, debemos agregar nuevas casillas de verificación.

Probablemente hay muchas preguntas pendientes sin resolver que no se registraron.
Por ejemplo, el comportamiento de ajuste correcto no está resuelto dentro del equipo de lang, el diseño de Try no está finalizado, y así sucesivamente. Probablemente deberíamos dividir este problema en varios más específicos, ya que probablemente haya dejado de ser útil.

Hmm ... me molesta que estas preguntas no hayan sido grabadas.

@ mark-im Entonces, para aclarar, creo que han estado en alguna parte; pero no en un solo lugar; es un cajero automático un poco disperso en varios RFC y problemas, por lo que lo que debemos hacer es registrarlos en las ubicaciones adecuadas.

El diseño del rasgo de respaldo se rastrea en https://github.com/rust-lang/rust/issues/42327; allí hay una amplia discusión sobre las debilidades del actual y una posible nueva dirección. (Estoy planeando hacer un pre-RFC para un cambio una vez que 2018 se asiente un poco).

Así que creo que aquí solo queda try{} , y el único desacuerdo que conozco son las cosas que se resolvieron en el RFC y se volvieron a confirmar en uno de los problemas mencionados anteriormente. Sin embargo, aún podría ser bueno tener un problema de seguimiento distinto.

Agregaré una casilla de verificación para la única tarea de implementación pendiente que sé que aún debe realizarse ...

@scottmcm Sé que @joshtriplett tenía inquietudes sobre el try RFC) y personalmente me gustaría restringir break en la estabilización inicial de try { .. } así que que no puedes hacer loop { try { break } } y tal.

@Centril

para que no puedas hacer loop { try { break } }

En este momento, no puede usar break en un bloque sin bucle, y es correcto: break solo debe usarse en bucles. Para salir antes de un bloque try , la forma estándar es escribir Err(e)? . y obliga a que las primeras hojas estén siempre en el camino de control "anormal".

Así que mi propuesta es que el código que mostraste debería estar permitido, y debería romper el loop , no solo dejar el try .

El beneficio inmediato es que cuando ves break sabes que se va a romper de un bucle y siempre puedes reemplazarlo con un continue . Además, elimina la necesidad de tener que etiquetar el punto de interrupción cuando se usan bloques try dentro de un loop y desea salir del ciclo.

@Centril Gracias por

Con respecto a break , personalmente estaría bien si simplemente dijera que try no se preocupa por break y pasa al ciclo contenedor. Simplemente no quiero que break interactúe con try en absoluto.

En cuanto al envoltorio de Ok , sí, me gustaría abordarlo antes de estabilizar try .

@centril Sí, lo sé. Pero es importante recordar que eso es re-re-plantear la cuestión. El RFC decidió tenerlo , se implementó sin él, pero luego se retomó la intención original _ de nuevo_ y la implementación cambió para seguir el RFC. Entonces, mi gran pregunta es si algún hecho material ha cambiado, especialmente dado que este es uno de los temas más ruidosos que he visto discutidos en RFC + IRLO.

@scottmcm Por supuesto, como saben, estoy de acuerdo con retener Ok -wrapping;) y estoy de acuerdo en que el problema debe considerarse resuelto.

Solo quería comentar sobre esto, no estoy seguro de si esto es lo correcto:

Esencialmente, una situación que tengo son las devoluciones de llamada en un marco de GUI: en lugar de devolver un Option o Result , necesitan devolver un UpdateScreen , para decirle al marco si la pantalla necesita ser actualizado o no. A menudo no necesito iniciar sesión en absoluto (simplemente no es práctico iniciar sesión en cada error menor) y simplemente devolver un UpdateScreen::DontRedraw cuando ha ocurrido un error. Sin embargo, con el operador actual ? , tengo que escribir esto todo el tiempo:

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

Como no puedo convertir de Result::Err en UpdateScreen::DontRedraw través del operador Try, esto se vuelve muy tedioso; a menudo tengo búsquedas simples en mapas hash que pueden fallar (lo cual no es un error ) - tan a menudo en una devolución de llamada tengo de 5 a 10 usos del operador ? . Debido a que lo anterior es muy detallado de escribir, mi solución actual es impl From<Result<T>> for UpdateScreen así , y luego usar una función interna en la devolución de llamada como esta:

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()
}

Dado que la devolución de llamada se usa como un puntero de función, no puedo usar un -> impl Into<UpdateScreen> (por alguna razón, actualmente no se permite devolver un impl para punteros de función). Entonces, la única forma en que puedo usar el operador Try es hacer el truco de la función interna. Sería bueno si pudiera simplemente hacer algo como esto:

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
}

No estoy seguro de si esto sería posible con la propuesta actual y solo quería agregar mi caso de uso para su consideración. Sería genial si un operador Try personalizado pudiera admitir algo como esto.

EDITAR:
Cometí un error.


Ignorar esta publicación


¿Podría esto funcionar mejor con la inferencia de tipos? Falla incluso en 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);
}

Si esto se reescribe para usar un cierre en lugar del bloque try (y en el proceso el ajuste automático suelto), obtenemos

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

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

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

Lo cual no requiere una anotación de tipo, pero sí requiere que envolvemos el resultado.

patio de recreo

@KrishnaSannasi su ejemplo basado en el cierre también tiene una falla de inferencia de tipo (área de juegos ) porque Into no restringe la salida y no lo usa en ningún lugar que lo haga más tarde.

Esto parece ser principalmente un problema con el rasgo Try lugar de try bloques, similar a Into no propaga ningún tipo de información de las entradas a la salida, por lo que el tipo de salida debe ser determinable por su uso posterior. Hay una gran cantidad de discusión en https://github.com/rust-lang/rust/issues/42327 sobre el rasgo, no lo he leído, así que no estoy seguro de si alguna de las propuestas allí podría solucionar este problema.

@ Nemo157

Sí, hice un cambio de último minuto en mi código, para usarlo, y no lo probé. Culpa mía.

¿Qué tan lejos estamos de estabilizar los bloques de prueba? Es la única función que necesito de nightly: D

@Arignir

Creo que una vez hecho esto, se puede estabilizar.

block try {} catch (u otros identificadores siguientes) para dejar el espacio de diseño abierto para el futuro y, en su lugar, indicar a las personas cómo hacer lo que quieran con match

¿No hay ningún diseño intermedio que permita la función ahora y al mismo tiempo permita la posibilidad de dejar el espacio de diseño abierto para el futuro (y, por lo tanto, un eventual bloque catch )?

El PR que hice debería marcar esa casilla de todos modos, CC @nikomatsakis

Intenté usar esto por primera vez ayer, y me sorprendió un poco que esto:

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

no se compila debido a

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

Más bien, tenía que hacer

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

en lugar.

Esto fue confuso al principio, así que tal vez valga la pena cambiar el mensaje de error o mencionar en --explain

Si mueve el signo de interrogación en su primer ejemplo un poco hacia abajo para

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

Obtiene un mensaje de error mejor. El error surge porque Rust no puede decidir a qué tipo resolver el try { ... } , debido a lo general que es. Debido a que no puede resolver este tipo, no puede saber cuál es el tipo <_ as Try>::Ok , razón por la cual recibió el error que recibió. (porque el operador ? desenvuelve el tipo Try y devuelve el tipo Try::Ok ). Rust no puede funcionar con el tipo Try::Ok por sí solo, debe resolverse mediante el rasgo Try y el tipo que implementa ese rasgo. (que es una limitación de la forma actual en que funciona la verificación de tipos)

Todo para esta función está implementado, ¿correcto? Si es así, ¿cuánto tiempo queremos estar sentados en esto antes de estabilizarnos?

Pensé que todavía era una pregunta abierta si queríamos esto o no. En particular, hubo alguna discusión sobre si queremos usar el lenguaje de las excepciones aquí (prueba, captura).

Personalmente, estoy totalmente en contra de intentar crear la impresión de que Rust tiene algo así como excepciones. Creo que el uso de la palabra catch en particular es una mala idea porque cualquiera que provenga de un idioma con excepciones asumirá que esto se desenrolla, y no es así. Espero que sea confuso y doloroso enseñar.

En particular, hubo alguna discusión sobre si queremos usar el lenguaje de las excepciones aquí (prueba, captura).

Creo que https://github.com/rust-lang/rfcs/pull/2388 resolvió definitivamente si try como nombre es aceptable. Ésta no es una pregunta abierta. Pero la definición del rasgo Try , así como la envoltura de Ok parecen serlo.

Ok -wrapping ya se decidió en el RFC original, luego se eliminó durante la implementación y finalmente se volvió a agregar más tarde. No veo cómo es una pregunta abierta.

@rpjohnst Bueno, es en virtud de que Josh no está de acuerdo con la decisión del RFC original ... :) Es un asunto resuelto para mí . Consulte https://github.com/rust-lang/rust/issues/31436#issuecomment -427096703, https://github.com/rust-lang/rust/issues/31436#issuecomment -427252202 y https: // github.com/rust-lang/rust/issues/31436#issuecomment -437129491. De todos modos ... el punto de mi comentario fue que try como "lenguaje de excepciones" es un asunto resuelto.

Woah, ¿cuándo sucedió esto? Lo último que recuerdo son las discusiones sobre los aspectos internos. Estoy muy en contra de la envoltura Ok también :(

Eww. No puedo creer que esto haya pasado. Ok -wrapping es tan horrible (rompe la intuición muy sensata de que todas las expresiones de retorno en una función deben ser del tipo de retorno de la función). Así que sí, definitivamente con @ mark-im en esto. ¿Es el desacuerdo de Josh lo suficiente como para mantener este tema abierto y generar más discusión al respecto? Con mucho gusto le prestaría apoyo para luchar contra esto, no es que signifique algo como no miembro del equipo.

Ok -wrapping como se acepta en RFC 243 (literalmente, el que definió el operador ? , si se preguntaba cuándo sucedió esto) no cambia nada sobre los tipos de expresiones de retorno de función. Así es como lo definió RFC 243: https://github.com/rust-lang/rfcs/blob/master/text/0243-trait-based-exception-handling.md#catch -expressions

Este RFC también introduce una forma de expresión catch {..} , que sirve para "alcance" el operador ? . El operador catch ejecuta su bloque asociado. Si no se lanza ninguna excepción, entonces el resultado es Ok(v) donde v es el valor del bloque. De lo contrario, si se lanza una excepción, el resultado es Err(e) .

Tenga en cuenta que catch { foo()? } es esencialmente equivalente a foo() .

Es decir, toma un bloque de tipo T y lo envuelve incondicionalmente para producir un valor de tipo Result<T, _> . Cualquier instrucción return en el bloque no se ve afectada en absoluto; si el bloque es la expresión de cola de una función, la función debe devolver Result<T, _> .

Se ha implementado de esta manera todas las noches durante años: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=88379a1607d952d4eae1d06394b50959. Esto se hizo después de mucha discusión por parte del equipo de lang en, y vinculado desde, este hilo: rust-lang / rust # 41414 (y esto también está vinculado al principio de este número).

El 28 de mayo de 2019 a las 5:48:27 p.m. PDT, Alexander Regueiro [email protected] escribió:

Eww. No puedo creer que esto haya pasado. Ok -envolver es tan horrible (es
rompe la intuición muy sensible de que todas las expresiones devuelven en un
función debe ser del tipo de retorno de la función). Así que sí, definitivamente
con @ mark-im en esto. ¿Es suficiente el desacuerdo de Josh para mantener esto
tema abierto y obtener más debate sobre él? Con mucho gusto le prestaría apoyo
en la lucha contra esto, no es que signifique algo como no miembro del equipo.

Gracias. No solo estoy en desacuerdo con esto solo para mí; También represento a las numerosas personas que he visto expresar la misma posición que yo.

@joshtriplett @ mark-im @alexreg

¿Puede alguno de ustedes explicar por qué considera que el envoltorio Ok es tan desagradable o proporcionar un enlace a algún lugar que se haya explicado antes? Fui a mirar, pero en una vista superficial no vi nada. No tengo ningún caballo en esto (literalmente, solo comenté esto porque vi todas las casillas marcadas y sin discusión durante un mes), pero ahora que he pateado este avispero quiero entender mejor los argumentos.

El martes 28 de mayo de 2019 a las 03:40:47 PM -0700, Russell Johnston escribió:

Ok -wrapping ya se decidió en el RFC original, luego se eliminó durante la implementación y finalmente se volvió a agregar más tarde. No veo cómo es una pregunta abierta.

Creo que respondió en parte a su propia pregunta. No creo que todos
involucrado en la discusión de RFC original estaba en la misma página; try era
absolutamente algo que muchas personas querían, pero no había consenso
para envolver bien.

El martes 28 de mayo de 2019 a las 03:44:46 PM -0700, Mazdak Farrokhzad escribió:

De todos modos ... el punto de mi comentario fue que try como "lenguaje de excepciones" es un asunto resuelto.

Como aclaración, no encuentro atractiva la metáfora de las "excepciones",
y muchos de los intentos de cosas como try-fn y Ok-wrapping parecen
Intente hacer que el lenguaje sea falso con un mecanismo similar a una excepción.
Pero try sí mismo, como un medio para atrapar ? en algo que no sea el
límite de función, tiene sentido como una construcción de flujo de control.

El martes 28 de mayo de 2019 a las 11:37:33 p. M. -0700, Gabriel Smith escribió:

¿Puede alguno de ustedes explicar por qué encuentra que el envoltorio Ok es tan desagradable?

Como una de las pocas razones:

El martes 28 de mayo de 2019 a las 05:48:27 PM -0700, Alexander Regueiro escribió:

rompe la intuición muy sensata de que todas las expresiones de retorno en una función deben ser del tipo de retorno de la función

Esto rompe varios enfoques que las personas usan para el razonamiento dirigido por tipos.
sobre funciones y estructura de código.

Ciertamente tengo mis propios pensamientos aquí, pero ¿podríamos no volver a abrir este tema ahora mismo? Acabamos de tener una polémica conversación de más de 500 publicaciones sobre sintaxis, así que me gustaría evitar las minas terrestres por un tiempo.

Si esto está bloqueado en el equipo de lang que lo discute, ¿la casilla de verificación "resolver si los bloques de captura deben" ajustar "el valor del resultado (# 41414)" debe estar desmarcada nuevamente (tal vez con un comentario de que está bloqueado en el equipo de lang) para que las personas que miran este problema de seguimiento conoce el estado?

Disculpas, no estoy tratando de reabrir nada, solo reafirmar lo que se marcó como decidido en el problema de seguimiento y cuándo + cómo sucedió.

@rpjohnst ¡ Gracias por la información!

@yodaldevoid Josh prácticamente resumió mis pensamientos.

Me opongo un poco menos a que el ajuste correcto se limite a un bloque (en lugar de afectar el tipo de función), pero creo que todavía sienta un mal precedente: como dijo Josh, "No encuentro atractiva la metáfora de las excepciones".

@joshtriplett también ha resumido esencialmente mis puntos de vista: los problemas son la idoneidad de la metáfora de la "excepción" (posiblemente pánico + catch_unwind es mucho más análogo) y el razonamiento basado en tipos. De hecho, estoy de acuerdo con los bloques try como mecanismo de flujo de control y alcance también, pero no los puntos más radicales.

De acuerdo, es justo, no tengamos todo el debate aquí ... ¿tal vez simplemente desmarque la casilla como se sugiere y vuelva a ponerlo en el debate del equipo de idiomas (en su propio tiempo), utilizando algunos de los fundamentos mencionados en este hilo? Siempre y cuando la estabilización no se apresure, eso suena razonable, supongo.

¿Se ha acordado una sintaxis para las anotaciones de tipo? Esperaba algo de try { foo()?; bar()?; }.with_context(|_| failure::err_msg("foon' n' barn'")?; , que no está ni remotamente interesado en compilar: error[E0282]: type annotations needed .

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

Leí los comentarios hace un rato (y cargar 300 comentarios nuevamente en github es demasiado tedioso), pero recuerdo que la mayoría (si no todos) de los ejemplos relacionados con el debate en torno a Try::Ok envoltorio usaron Ok en el ejemplo. Teniendo en cuenta que Option implementa Try , me gustaría saber cómo eso afecta la posición del equipo en qué lado del debate estar.

Cada vez que uso Rust, sigo pensando "hombre, realmente me gustaría poder usar un bloque try aquí", pero alrededor del 30% del tiempo es porque realmente deseo poder usar try por Option s Solía ​​hacerlo en Scala, que usaba la sintaxis for para aplicar a las mónadas en general, pero es muy similar a try aquí).

Hoy mismo, estaba usando la caja json y expone los métodos as_* que devuelven opciones.

Usando las dos sintaxis, mi ejemplo habría sido:

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)
    },
}

Creo que, contextualmente, si el tipo de retorno es Option o Result es bastante claro, y además, realmente no importa (en lo que respecta a la comprensión del código). Transparentemente, el significado es claro: "Necesito comprobar si estas dos cosas son válidas y operarlas". Si tuviera que elegir uno de estos, iría con el primero, porque no creo que haya ninguna pérdida de comprensión cuando se considera que esta función está incrustada en un contexto más amplio, como try siempre sé.

Cuando comencé a mirar este hilo, estaba en contra de Ok envolver porque pensé que sería mejor ser explícito, pero desde entonces, comencé a prestar atención a las veces que dije "Ojalá podría usar un bloque de prueba aquí "y he llegado a la conclusión de que Ok -wrapping es bueno.

Originalmente pensé que no Ok envoltorio sería mejor en el caso de que su última declaración sea una función que devuelva el tipo que implementa Try , pero la diferencia en la sintaxis sería

try {
  fallible_fn()
}

try {
  fallible_fn()?
}

Y en este caso, pienso de nuevo que Ok -wrapping es mejor porque deja claro que fallible_fn es una función de retorno Try , por lo que en realidad es más explícito.

Quiero saber qué piensa la oposición de esto y, como no puedo ver a muchos otros en este hilo, @joshtriplett.

EDITAR: Debo mencionar que solo estaba viendo esto desde una perspectiva de ergonomía / comprensión de lectura. No tengo idea de si uno tiene más méritos técnicos que el otro en términos de implementación, como una inferencia más fácil.

También quería darle a try una oportunidad para un análisis Option anidado:

#![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");
}

Esto falla con

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

Lo más cerca que estuve fue

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

El as_ref es bastante desafortunado. Sé que Option::deref ayudará a algunos aquí, pero no lo suficiente. Esto parece que de alguna manera debería entrar en juego la ergonomía coincidente (o una idea relacionada).

Las múltiples líneas también son lamentables.

¿Podría try usar un respaldo de inferencia de Result como literales enteros? ¿ Eso permitirá que el primer intento de Result<&str, NoneError> ? ¿Qué problemas restantes habría, probablemente encontrar un tipo de error común para ? s para convertir? (¿Me he perdido la discusión de esto en alguna parte?)

@shepmaster Estoy de acuerdo con la inferencia de tipos. Curiosamente, probé tu código exacto con una implementación try_ algo ingenua y funciona bien: 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 bien.

una implementación try_ algo ingenua

Sí, pero su invocación de macro devuelve String , no &str , que requiere propiedad. No muestra el código que lo rodea, pero esto fallará porque no tenemos la propiedad 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

También asigna incondicionalmente un String ; Usé unwrap_or_else en este ejemplo para evitar esa ineficiencia.

Es una pena que esta función no se haya estabilizado antes de los bloques async/await . En mi opinión, hubiera sido más consistente tener

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

en lugar de permitir que funcione sin try . Pero supongo que ese barco hace mucho que zarpó.

Re: ajuste automático, no creo que sea posible ser coherente con los bloques async ahora. async bloques Future . Pero esto es cierto incluso con devoluciones anticipadas, lo que no sería posible con bloques try .

Podría volverse doblemente confuso si alguna vez tenemos un bloque hipotético async try . En caso de que la auto-envolver el resultado?

No creo que hayamos perdido ninguna oportunidad de consistencia en el sentido del ajuste automático. Tanto los bloques como las funciones async pueden usar ? , y ambos deben hacer su propio envoltorio manual Ok , tanto para las devoluciones iniciales como para las finales.

Un bloque try , por otro lado, podría usar ? con envoltura automática Ok , incluso para "devoluciones anticipadas" asumiendo una función de devolución anticipada, tal vez rotura de etiqueta. valor . Una función de prueba hipotética podría fácilmente realizar un envoltorio automático de Ok en las devoluciones iniciales y finales.

Un bloque async try hipotético podría simplemente combinar la funcionalidad de dos- auto- Ok -wrap, y luego auto- Future -wrap. (Al revés es imposible de implementar, y podría decirse que se escribiría try async todos modos).

La inconsistencia que veo es que hemos combinado bloques async con funciones. (Esto sucedió en el último minuto, contrario a la RFC, nada menos.) Lo que esto significa es que return en async bloques sale del bloque, mientras que return en try blocks sale de la función contenedora. Sin embargo, estos al menos tienen sentido de forma aislada, y los bloques async sin retorno temprano o valor de rotura de etiqueta serían mucho más difíciles de usar.

¿Algo que detenga la estabilización de esto, o simplemente nadie se ha tomado el tiempo para hacerlo todavía? Estoy interesado en crear los PR necesarios de lo contrario 🙂

El 18 de noviembre de 2019 2:03:36 a.m.PST, Kampfkarren [email protected] escribió:

Cualquier cosa que detenga la estabilización de esto, o simplemente nadie ha tomado la
¿Es hora de hacerlo todavía? Me interesa crear las relaciones públicas necesarias
de lo contrario 🙂>
>
->
Estás recibiendo esto porque te mencionaron.>
Responda a este correo electrónico directamente o véalo en GitHub:>
https://github.com/rust-lang/rust/issues/31436#issuecomment -554944079

Sí, el bloqueador de la estabilización está trabajando en la decisión sobre el ajuste correcto. Esto no debería estabilizarse hasta que tengamos un consenso sobre cómo debería comportarse.

Personalmente, estoy en contra del ajuste correcto, pero me pregunto qué tan difícil sería agregar después de los hechos. ¿No-ok-wrapping forward es compatible con ok-wrapping?

Puedo imaginar casos complicados como la ambigüedad de Result<Result<T,E>> , pero en casos tan ambiguos, podríamos recurrir a la no envoltura. El usuario podría entonces explícitamente Ok-wrap para eliminar la ambigüedad. Eso no parece tan malo, ya que no espero que este tipo de ambigüedad surja con demasiada frecuencia ...

No debe haber ambigüedad en absoluto, porque no es Ok -coercion sino Ok -wrapping. try { ...; x } produciría Ok(x) tan inequívocamente como Ok({ ...; x }) .

@joshtriplett ¿Esto no está resuelto? El problema de seguimiento tiene resolve whether catch blocks should "wrap" result value marcados, citando https://github.com/rust-lang/rust/issues/41414

@rpjohnst Lo siento, debería haber sido más claro. Lo que quiero decir es que si estabilizamos try ahora sin un ajuste correcto, creo que podría agregarse más tarde de manera compatible con versiones anteriores.

Es decir, creo que la mayoría de la gente está de acuerdo en que deberíamos tener bloques try , pero no todo el mundo está de acuerdo con catch o el ajuste correcto. Pero no creo que esas discusiones necesiten bloquear try ...

@Kampfkarren Sí. La conversación anterior detalla la progresión de este asunto. Se marcó prematuramente sin consultar completamente a todos. @joshtriplett en particular tenía preocupaciones, que varios otros (incluido yo mismo) compartimos.

@ mark-im ¿Cómo ve exactamente que se agregará el ajuste Ok en el futuro? Estoy tratando de averiguar cómo se podría hacer eso, y no puedo verlo.

Así que comenzaré esto diciendo que no sé si es una buena idea o no ...

Estabilizaríamos el bloque try sin un ajuste correcto. Por ejemplo:

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

Más tarde, suponga que llegamos a un consenso de que deberíamos tener un ajuste correcto, luego podríamos permitir algunos casos que antes no funcionaban:

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

La pregunta es si esto puede hacer que algo se vuelva ambiguo que no era antes. Por ejemplo:

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)...

Aunque, ¿quizás Ok-wrapping ya tiene que lidiar con este problema?

De todos modos, mi intuición es que casos tan extraños no surgen con tanta frecuencia, por lo que puede que no importe mucho.

¿Qué pasa con el uso de Ok-wrap excepto donde el tipo devuelto es Result o Option ? Eso permitiría un código más simple en la mayoría de los casos, pero permitiría especificar el valor exacto donde sea necesario.

// 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 };

Agregar Ok -coercion o algún tipo de sintaxis dependiente de Ok -wrapping (que es lo que debería suceder para admitir la estabilización sin él e introducirlo más adelante) sería muy malo para la legibilidad, y ha sido ampliamente discutido varias veces en i.rl.o (comúnmente por personas que malinterpretan el sencillo envoltorio Ok que se implementa).

Personalmente, estoy firmemente a favor de Ok -wrapping tal como se implementó, pero estaría aún más fuertemente en contra de cualquier forma de coerción o dependencia de la sintaxis que dificulte la comprensión de las situaciones que envolverán (me tomaría inútil tener que escribir Ok(...) está en todas partes por tener que intentar averiguar si ha coaccionado o no)

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)...

Aunque, ¿quizás Ok-wrapping ya tiene que lidiar con este problema?

No, eso es inequívocamente Ok(Err(3)) , Ok -wrapping es independiente de la sintaxis o los tipos, simplemente envuelve cualquier salida del bloque en la variante Try::Ok .

@ mark-im No creo que podamos pasar razonablemente de uno a otro después de la estabilización. Por muy malo que considero que sea el ajuste correcto, un inconsistente que intenta adivinar si lo quieres o no sería aún peor.

En mi base de código estoy tratando con muchos valores opcionales, así que introduje mi propio bloque try como macro hace mucho tiempo. Y cuando lo estaba introduciendo, tenía varias variantes con y sin Ok-wrap, y la versión Ok-wrap resultó ser mucho más ergonómica, que es la única macro que terminé usando.

Tengo un montón de valores opcionales con los que necesito trabajar, y en su mayoría son numéricos, así que tengo un montón de situaciones como esta:

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

Sin el ajuste correcto, esto sería mucho menos ergonómico hasta el punto en que probablemente me quedaría en mi propia macro que usando los bloques de prueba reales.

Dada la venerable historia de este problema de seguimiento, su combinación original y lamentable con el operador ? y el obstáculo sobre el problema de envoltura Ok , sugeriría cerrar este problema por completo y enviar try volviendo al comienzo del proceso RFC, donde esta discusión puede obtener la visibilidad que merece y (con suerte) llegar a algún tipo de conclusión.

Sin la envoltura correcta, esto sería mucho menos ergonómico.

¿Podría explicar qué cosas no ergonómicas introduciría exactamente?

Sin el ajuste correcto, su ejemplo se vería así:

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

que es bastante bueno en mi opinión.

Quiero decir, con un pequeño ejemplo como este podría parecer una exageración, pero cuanto más código contiene el bloque try , menor impacto causa este contenedor Ok(...) .

Además del comentario de @CreepySkeleton , debe tenerse en cuenta que es muy fácil crear una macro que emule Ok -wrapping si el bloque try no lo hace (y seguramente alguien creará una caja estándar para esta pequeña macro), pero lo contrario no es así.

Esa macro no es posible mientras el rasgo Try sea ​​inestable.

¿Por qué? De todos modos, cuando se estabilice (hipotéticamente no muy lejos en el futuro), será muy posible.

Los bloques @ Nemo157 try también están activados solo por la noche en este momento, y es probable que no se estabilicen en el improbable caso de que decidamos arrancar Try . Esto significa que probablemente no se estabilizarán antes de Try . Entonces, decir que la macro no es posible no tiene sentido.

@KrishnaSannasi Tengo curiosidad por saber por qué Try podrían ser arrancados.

@ mark-im No creo que lo haga, solo estoy explicando por qué preocuparse de que Try esté en wrt todas las noches para probar bloques no es una preocupación realista. Estoy deseando que llegue Try estable.

Dado que ? se ha estabilizado, y los bloques try tienen un diseño claro que abarca tanto Result como Option de la misma manera que ? hace, no hay ninguna razón que pueda ver para bloquear estabilizándolos al estabilizar Try . No lo he estado vigilando de cerca, pero mis impresiones fueron que había mucho menos consenso sobre el diseño de Try que para los bloques try , por lo que pude ver try bloquea la estabilización años antes del rasgo Try (como ha sucedido con ? ). E incluso si se abandona el rasgo Try no veo ninguna razón que deba bloquear try bloques se estabilicen para que funcionen con solo Result y Option como ? entonces sería.

(Para _por qué_ no podría escribir esa macro dados try bloques estabilizados y un rasgo Try inestable, la macro se habría expandido a try { Try::from_ok($expr) } ; podría crear macros por tipo por solo Result y Option , pero IMO eso no cumpliría con el punto "muy fácil de [...] emular").

Dado que ? ya es estable en caja especial, aunque el rasgo Try no se puede usar en estable, no veo por qué Try siendo inestable bloquearía los bloques de prueba. implementado en estable, porque si se elimina el rasgo Try, todavía tenemos Opción y Resultado que admiten ? en estable, solo que sin la ergonomía.

Sugeriría el siguiente concepto para intentar atrapar semántica ...

Considere el siguiente 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 es el código que podría generar las excepciones Zero-Overhead en Rust con la siguiente característica de sintaxis:

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

o incluso estos errores podrían ser deducidos implícitamente por el compilador:

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 azúcar sintáctico nada más !! ¡¡El comportamiento es el mismo !!

Hola a todos,

¿Alguien ha visto mi propuesta con respecto a las excepciones de cero gastos generales que está arriba?

@redradist Uno de los puntos principales del bloque try es que podríamos usarlo dentro de una función , sin necesidad de crear una función para cada bloque. Por lo que yo veo, su propuesta no tiene ninguna relación aquí.

Hoy mismo sentí la necesidad de bloques try . Tengo una función grande que tiene muchas operaciones ? . Quería agregar contexto a los errores, pero hacerlo para cada ? habría necesitado una gran cantidad de texto estándar. Detectar los errores con try y agregar el contexto en un lugar habría evitado esto.

Por cierto. envolver las operaciones en una función interna hubiera sido difícil en este caso, porque el contexto no tiene una vida útil obvia, y dividir cosas en múltiples funciones rompe NLL.

Me gustaría mencionar que en la implementación actual un bloque try no es una expresión. Piensa que esto es un descuido.

Me gustaría mencionar que en la implementación actual, un bloque try no es una expresión. Piensa que esto es un descuido.

¿Podría publicar el código que no le funciona? Puedo usarlo en un contexto de expresión aquí: ( Rust Playground )

#![feature(try_blocks)]

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

Claro, aquí está .

Y aquí hay otro que muestra que la inferencia de tipos en bloques de prueba aún no está completa. Lo cual es especialmente molesto ya que las asignaciones de tipos no son compatibles con los bloques if let .

@ Nokel81 el problema con su ejemplo anterior es que la expresión en if let $pat = $expr no es un contexto de expresión regular, sino un contexto especial de "expresión sin corchetes". Para ver un ejemplo de cómo funciona esto con expresiones de estructura, vea este ejemplo donde está sintácticamente claro que hay una expresión de estructura ahí, y este ejemplo donde no lo está. Entonces, el error no es que try no sea una expresión, sino que el error es incorrecto y debería decir " try expresión no está permitida aquí; intente rodearla entre paréntesis" (y el advertencia incorrecta sobre paréntesis innecesarios suprimidos).

Su último ejemplo es realmente ambiguo para la inferencia de tipos. El tipo de e es _: From<usize> en este caso, lo cual no es suficiente información para darle un tipo concreto. Debería usarlo de alguna manera para darle un tipo concreto para permitir que la inferencia de tipos tenga éxito. Este no es un problema específico de try ; así es como funciona la inferencia de tipos en Rust.

Ahora, si inmediatamente intenta hacer coincidir como un Ok y descarta el caso Err , tiene un caso para un mensaje de error subóptimo sin una forma realmente

Ah, muchas gracias por la explicación en profundidad. Supongo que todavía estoy confundido por qué el último es ambiguo para la inferencia de tipos. ¿Por qué el tipo de expresión no es Result<isize, usize> ?

El operador ? puede realizar conversiones de tipos entre diferentes tipos de error utilizando el rasgo From . Se expande aproximadamente al siguiente código de oxidación (ignorando el rasgo Try ):

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

La llamada From usa el tipo de la expresión final devuelta para determinar qué tipo de conversiones de error realizar, y no se establecerá automáticamente en el tipo de valor pasado automáticamente.

Disculpas si esto ya se ha abordado, pero me parece extraño que:

#![feature(try_blocks)]

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

no se compila con:

error[E0282]: type annotations needed

pero:

#![feature(try_blocks)]

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

está bien.

Si esto fuera un problema porque los argumentos de tipo de Result no podrían deducirse, lo entendería, pero, como se muestra arriba, ese no es el caso y rustc puede realizar inferencias una vez que se le dice que el resultado de una expresión try es una especie de Result , que debería poder inferirse a partir de core::ops::Try::into_result .

Pensamientos

@nwsharp eso es porque try / ? es genérico sobre los tipos Try . Si tuviera algún otro tipo que fuera impl Try<Ok=_, Error=()> , el bloque try podría evaluar ese tipo así como Result . Desafiado, tu ejemplo es 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 Gracias por la explicación.

Dicho esto, no esperaba que try fuera efectivamente capaz de causar una especie de conversión entre diferentes Try impls.

Esperaría una eliminación en la que se seleccione el mismo Try impl para into_result , from_ok y from_error .

En mi opinión, la pérdida ergonómica de no poder realizar la inferencia de tipo (especialmente considerando que no existe un impl Try alternativo), no supera el beneficio de permitir esta conversión.

Podríamos permitir la inferencia eliminando la ambigüedad y mantener la capacidad de optar por la conversión a través de algo como:

try { ... }.into()

Con la manta correspondiente impl:

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()),
        }
    }
}

(Lo cual, honestamente, supongo que tiene sentido tenerlo independientemente, aunque personalmente dudo de la conversión automática de tipos de error aquí. Si se desea, el usuario debe .map_err() en el Result )

En general, creo que esta eliminación de azúcar es "demasiado inteligente". Oculta demasiado y su semántica actual puede confundir a la gente. (¡Especialmente considerando que la implementación actual solicita anotaciones de tipo en algo que no las admite directamente!)

O, yendo aún más lejos con la implicación general, supongo.

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()),
        }
    }
}

O lo que sea...

Dicho esto, no esperaba que el intento fuera efectivamente capaz de causar una especie de conversión entre diferentes Try impls.

Esperaría una eliminación en la que se seleccione el mismo Try impl para into_result , from_ok y from_error .

En mi opinión, la pérdida ergonómica de no poder realizar la inferencia de tipo (especialmente considerando que ni siquiera existe un impl Try alternativo), no supera el beneficio de permitir esta conversión.

Hay cuatro tipos estables Try : Option<T> , Result<T, E> , Poll<Result<T, E>> y Poll<Option<Result<T, E>> .

NoneError es inestable, por lo que Option<T> está atascado intentando en Option<T> mientras que NoneError es inestable. (Sin embargo, tenga en cuenta que los documentos llaman explícitamente From<NoneError> como "habilitar option? para su tipo de error").

Sin embargo, las impls Poll establecen su tipo de error en E . Debido a esto, el "tipo morphing" de Try es estable, porque puedes ? a Poll<Result<T, E>> en un -> Result<_, E> para obtener un Poll<T> y devolución anticipada del estuche E .

De hecho, esto impulsa a un pequeño ayudante "lindo" :

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

@ CAD97 Gracias por complacerme. Esto será algo complicado de enseñar a los recién llegados y requerirá un poco de amor en términos de mensajes de error.

¿Se ha pensado en permitir la especificación del impl Try deseado para mitigar el comportamiento poco intuitivo aquí?

Por ejemplo, bicis perdiendo un poco, try<T> { ... } . ¿O hay una vez más algo con lo que tropezar incluso con eso?

Tal vez para agregar un poco más de color aquí, el hecho de que try { } sobre un montón de Result no produzca "solo" un Result es inesperado y me entristece . Entiendo por qué , pero no me gusta.

Sí, se ha hablado de la combinación de "adscripción de tipo generalizado" (hay un término para buscar) y try . Creo que, lo último que supe, try: Result<_, _> { .. } estaba destinado a funcionar eventualmente.

Pero estoy de acuerdo con usted: los bloques try merecen algunos diagnósticos específicos para asegurarse de que se especifique su tipo de salida.

Consulte este número separado para una pregunta específica específica para resolver el consenso del equipo de idiomas sobre el asunto de Ok -wrapping.

Lea el comentario de apertura de ese hilo antes de comentar y, en particular, tenga en cuenta que ese hilo solo se trata de esa pregunta, no de ningún otro problema relacionado con try o ? o Try .

No veo por qué se necesita el bloque try . Esta sintaxis

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

se puede reemplazar con esto:

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

Ambos usan la misma cantidad de caracteres, pero el segundo ya es estable.

Al asignar el resultado a una variable, eso podría indicar que se debe crear una función auxiliar para devolver el resultado. Si eso no es posible, se puede usar un cierre en su lugar.

Los bloques @dylni try son especialmente útiles cuando no contienen todo el cuerpo de una función. El operador ? en caso de error hace que el control de flujo vaya al final del bloque try más interno, sin regresar de la función.

`` óxido
fn main () / * no hay resultado aquí * / {
deja resultado = probar {
foo () ?. bar () ?. baz ()?
};
resultado del partido {
//…
}
}

@SimonSapin ¿

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

Eso es más detallado, pero creo que una solución más simple sería una sintaxis de cierre de método:

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

El tipo también se infiere correctamente con and_then , donde necesita anotaciones de tipo para try . He tenido esto con poca frecuencia y no creo que una sintaxis más tersa valga la pena el daño de la legibilidad.

La RFC aceptada tiene más razonamientos: https://rust-lang.github.io/rfcs/0243-trait-based-exception-handling.html

De todos modos, los argumentos a favor de las construcciones de lenguaje para el flujo de control con el operador ? (y .await ) sobre métodos de encadenamiento como and_then ya se han discutido ampliamente.

De todos modos, los argumentos a favor de las construcciones de lenguaje para el flujo de control con el operador ? (y .await ) sobre métodos de encadenamiento como and_then ya se han discutido ampliamente.

@SimonSapin Gracias. Esto y releer el RFC me convencieron de que esto puede ser útil.

Pensé que podría usar bloques de prueba para agregar contexto fácilmente a los errores, pero hasta ahora no tuve suerte.

Escribí una pequeña función que funciona bien. Observe que File::open()? falla con std::io::Error mientras que la siguiente línea falla con anyhow::Error . A pesar de los diferentes tipos, el compilador descubre cómo convertir ambos a 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"))?)
}

Quería agregar un contexto de error, así que intenté usar un bloque de prueba y, de todos modos, 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()))
}

Pero ahora falla la inferencia de tipos:

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

Otra vez,

eso es porque try / ? es genérico sobre los tipos Try . Si tuviera algún otro tipo que fuera [ impl Try<Ok=_, Error=anyhow::Error> ], el bloque try podría evaluar ese tipo, así como Result .

(Además, no necesita Ok su expresión final en un bloque try (# 70941)).

Creo que el hecho de que esto siga apareciendo significa que

  • Antes de la estabilización, try debe admitir una adscripción de tipo ( try: Result<_,_> { o lo que sea) o mitigar este problema,
  • Esto definitivamente necesita diagnósticos específicos para cuando falla la inferencia de tipo de un bloque try , y
  • Deberíamos considerar seriamente dar try un tipo de respaldo a Result<_,_> cuando no esté restringido de otra manera. Sí, eso es difícil, poco especificado y potencialmente problemático, pero _ resolvería_ el caso del 80% de los bloques try necesitaban una anotación de tipo debido a que $12: Try<Ok=$5, Error=$8> no era lo suficientemente específico.

Además, dado que # 70941 parece estar resolviendo hacia "sí, queremos (alguna forma de) ' Try::from_ok envoltura'", probablemente _también_ queremos un diagnóstico específico para cuando la expresión final de un try block está devolviendo Ok(x) cuando x funcionaría.

Sospecho que el comportamiento correcto para probar es

  • extienda la sintaxis para permitir una asignación manual como try: Result<_, _> { .. } , try as Result<> , o lo que sea (creo que try: Result probablemente esté bien? Parece ser la sintaxis preferida)
  • examine el "tipo esperado" que proviene del contexto; si hay uno presente, prefiera ese como el tipo de resultado de un try
  • de lo contrario, el valor predeterminado es Result<_, _> ; esto no es un respaldo de inferencia de tipo como con i32 , sucedería antes, pero eso significaría que cosas como try { }.with_context(...) compilan.

Sin embargo, me preocupa que podamos obtener errores alrededor de ? y la coerción into , al menos siempre que no se especifique el tipo de error. En particular, si escribe código donde ? el resultado de un bloque try , así:

#![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() { 
}

Aún recibe errores ( patio de recreo ) y con razón, porque no está claro qué ? debería desencadenarse la coerción "hacia".

No estoy seguro de cuál es la mejor solución aquí, pero probablemente implica algún tipo de reserva de inferencia que me pondrá nervioso.

probablemente implica algún tipo de respaldo de inferencia que me pondrá nervioso.

El más simple: si todos los usos de ? en un bloque Try contienen el mismo tipo Try::Error , use ese tipo de error para el bloque try que lo contiene (a menos que de lo contrario vinculado).

El "(a menos que se establezca lo contrario)" es, por supuesto, la parte sutil de miedo.

Espero no ser demasiado poco constructivo con esta publicación. Sin embargo, quería contrastar el ejemplo de @nikomatsakis con uno de un mundo paralelo donde ? no hace una conversión forzada, y no hay un ajuste automático del resultado del bloque try :

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

    x?;

    Ok(())
}

En este mundo:

  • Es fácil ver que tanto el alcance try como el propio fn dan como resultado un éxito sin ningún valor. También es fácil de ver sin siquiera intentar que producen Result s.
  • Es obvio dónde ocurre la conversión de errores.
  • La conversión de error se podría mover a la expresión x? , haciendo que el alcance try específico para las operaciones std::fs::File .
  • Todos los tipos de sugerencias se encadenan con fluidez desde la firma del tipo. Tanto para la máquina como para nosotros los humanos.
  • Las sugerencias de tipo por parte del usuario solo son necesarias en los casos en los que realmente queremos convertir los errores en otro independiente.

Sería muy feliz en ese universo paralelo.

@phaylon Si bien aprecio la forma cuidadosa en la que escribiste ese comentario, me temo que es bastante poco constructivo. La conversión de errores es parte de ? y eso no va a cambiar y, a la luz de eso, el ajuste correcto es básicamente ortogonal del resto de esta discusión.

Si alguna vez se van a considerar las funciones try (con tipos de retorno y lanzamiento), entonces quizás valga la pena considerar también que la sintaxis para atribuir el bloque try sea algo similar.

p.ej

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

Lo siento si se ha hablado de esto, pero ¿cuáles son las ventajas de usar

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

o similar en contraposición a

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

?
Parece una sintaxis duplicada.

@gorilskij Según tengo entendido, la ventaja principal es obtener Ok -wrapping. De lo contrario, tienes que escribir:

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

Algunas personas también prefieren throws ya que encuentran que la terminología de excepciones se relaciona con ella.

Personalmente, quiero mantenerme lo más lejos posible de la aparición de excepciones.

Este no es el hilo para discutir try fn , así que por favor no lleve esta tangente más lejos. Este hilo es para la característica aceptada de los bloques try , no para la característica potencial (y hasta ahora, no RFCd) try fn .

Acabo de notar que el RFC original para ? propuso usar Into , no From . Afirma:

El RFC actual usa el rasgo std::convert::Into para este propósito (que tiene un reenvío implícito general desde From ).

Aunque deja el método exacto de upcasting como una pregunta sin resolver . Into fue (presumiblemente) preferido según la orientación de From :

Prefiere usar Into en lugar de usar From al especificar límites de rasgos en una función genérica. De esta forma, los tipos que implementan directamente Into se pueden usar como argumentos.

Sin embargo, en el RFC del rasgo Try , Into ya no se menciona, y la conversión se realiza usando From lugar. Esto también es lo que usa ahora el código, incluso por ?
https://github.com/rust-lang/rust/blob/b613c989594f1cbf0d4af1a7a153786cca7792c8/src/librustc_ast_lowering/expr.rs#L1232

Esto parece desafortunado, ya que no hay una implementación general que vaya de Into a From . Esto significa que los errores que implementan Into (a diferencia de From ) ya sea por error, por razones heredadas o por alguna otra necesidad, no se pueden usar con ? (o Try ). También significa que cualquier implementación que siga la recomendación de la biblioteca estándar de usar Into dentro de los límites no puede usar ? . Con un ejemplo, la biblioteca estándar recomienda que escriba:

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

pero si lo hago, no puedo usar ? en el cuerpo de la función. Si quiero hacer eso, tengo que escribir

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

pero si lo hago, los usuarios con tipos de error que implementan Into lugar de From no pueden usar esta función. Tenga en cuenta que la inversa no es cierta, debido a la implícita general de Into basada en From .

Probablemente sea (?) Demasiado tarde para arreglar ? ahora (lo cual es _muy_ desafortunado, ¿tal vez en la próxima edición?), Pero al menos deberíamos asegurarnos de no profundizar en ese camino en los Try rasgo.

@jonhoo @cuviper intentó cambiar el desugaring de From a Into en # 60796 para verificar # 38751 y resultó en una gran cantidad de rotura de inferencia exactamente debido al From -> Into blanket impl hizo más difícil para rustc manejar el caso común de la conversión de identidad. Se decidió que no valía tanto la pena romper el costo de la inferencia.

@jonhoo también puede encontrar este comentario de niko informativo:

También hay un límite codificado en el sistema de rasgos. Si tiene que resolver un objetivo como ?X: Into<ReturnType> , no lo resolveremos, pero si tiene que resolver un objetivo como ReturnType: From<?X> , potencialmente tendremos éxito e inferiremos un valor de ?X .

Editar: Aquí, ?X refiere a alguna variable de inferencia desconocida. El límite codificado en el sistema de características actual es que el tipo Self debe inferirse, al menos en parte, para que podamos explorar esa opción.

El TL; DR es que inferir con Into es más difícil y de una manera inherente a la forma en que funciona el solucionador de rasgos.

@KrishnaSannasi @ CAD97 ¡ Gracias, eso es útil! Todavía me preocupa que nos estabilicemos demasiado, eso está basado en From dado que estamos dejando implementadores de Into especie de permanentemente fuera. ¿Se espera que la inferencia aquí eventualmente mejore? ¿Debería cambiarse la guía para preferir Into dentro de los límites? ¿Estamos esperando que con las nuevas reglas de coherencia de rasgos en 1.41 (creo que lo fue) ya no hay una razón para implementar solo Into y considerar todos esos errores de impls?

Si la inferencia es lo suficientemente buena, debería ser compatible con versiones posteriores para cambiar a Into más tarde. Lo peor que podríamos romper es la inferencia dado que From implica Into

¿Esto (el rasgo Try ) cubre lo que permite que ? trabaje con los rasgos Into / From con límites genéricos para tipos que implementan Try (por ejemplo, Result sí mismo)?

es decir, ? en un cierre o función que devuelve, por ejemplo, impl Into<Result>

(¿No parece cuando me lo pruebo todas las noches?)

Estoy ansioso por ver esto estabilizado. Después de leer este hilo y el número 70941, creo que el resumen debería actualizarse de la siguiente manera:

  1. "resolver si los bloques de captura deben" ajustar "el valor del resultado" debe estar marcado, "Resuelto como "

  2. Se agregó una nueva preocupación sobre estos problemas de inferencia. Quizás algo como:

    • [] Dificultades ergonómicas debido a problemas con la inferencia de tipos.

ISTM que esta última preocupación podría abordarse, entre otras formas:

(Algunas de estas opciones no se excluyen mutuamente).

Gracias por su atención y espero que este mensaje le resulte útil.

La adscripción de tipo tiene una serie de problemas (sintácticos y de otro tipo), y parece poco probable que se implemente pronto, y mucho menos se estabilice; bloquear try bloques en la sintaxis de asignación de tipo no parece apropiado.

Un respaldo a Result podría ayudar, pero no resuelve los problemas de inferencia de tipos con tipos de error: try { expr? }? (o en la práctica equivalentes más complejos) tienen efectivamente dos llamadas a .into() , lo que le da al compilador demasiada flexibilidad en el tipo intermedio.

@ijackson gracias por tomar la iniciativa de resumir el estado actual. Creo que tiene razón en que hay varias formas en que podríamos mejorar los bloques de prueba, pero uno de los problemas es que no estamos seguros de cuál hacer, en parte porque cada solución tiene sus propios inconvenientes únicos.

Sin embargo, con respecto a la adscripción de tipos, siento que los desafíos de implementación no son tan difíciles. Ese podría ser un buen candidato para poner un poco de atención e intentar llevarlo hasta la línea de meta independientemente. No recuerdo si hubo mucha controversia sobre la sintaxis o algo por el estilo.

El miércoles, 05 de agosto de 2020 a las 02:29:06 PM -0700, Niko Matsakis escribió:

Sin embargo, con respecto a la adscripción de tipos, siento que los desafíos de implementación no son tan difíciles. Ese podría ser un buen candidato para poner un poco de atención e intentar llevarlo hasta la línea de meta independientemente. No recuerdo si hubo mucha controversia sobre la sintaxis o algo por el estilo.

Según recuerdo, la principal preocupación era que permitir la adscripción de tipos
en todas partes sería un cambio gramatical sustancial, y potencialmente un
limitando uno. No recuerdo todos los detalles, solo que la preocupación era
elevado.

Personalmente creo que los problemas ergonómicos no son tan graves que no valga la pena estabilizar esta característica ahora. Incluso sin la atribución del tipo de expresión, introducir un enlace let no es una solución tan desagradable.

Además, los bloques try pueden ser útiles en macros. En particular, me pregunto si @withoutboats excellent fehler library sufriría menos problemas con deficiencias en nuestro sistema de macros, si pudiera envolver los cuerpos de los procesos en try .

Me encuentro con lugares donde me encantaría usar mucho los bloques de prueba. Sería bueno superar esto. Personalmente, sacrificaría absolutamente el 100% la adscripción de tipo si fuera necesario para obtener bloques de prueba sobre la línea. Todavía tengo que encontrarme en una situación en la que dije "me encantaría tener una asignación de tipo aquí", pero termino haciendo un IIFE para simular mucho los bloques de prueba. Dejar una característica útil a largo plazo inestable porque entra en conflicto con otra característica inestable a largo plazo es una situación realmente desafortunada.

Para ser un poco más específico, me encuentro haciendo esto cuando estoy dentro de una función que devuelve Resultado, pero quiero hacer algún tipo de procesamiento en cosas que devuelven una Opción. Dicho esto, si Try en general fuera estable, probablemente seguiría prefiriendo los bloques de prueba, ya que en realidad no quiero volver de la función principal para hacerlo, sino dar algún tipo de valor predeterminado si algo en la cadena es Ninguna. Esto tiende a sucederme en el código de estilo de serialización.

Personalmente, he querido la adscripción de tipos con mucha más frecuencia que los bloques de prueba (aunque a veces he querido ambos). En particular, a menudo he tenido problemas con la "depuración de tipos" en la que el compilador infiere un tipo diferente al que esperaba. Por lo general, debe agregar un nuevo enlace let en algún lugar, lo que es realmente perjudicial y hace que rustfmt rompa el historial de deshacer. Además, hay muchos lugares donde la adscripción de tipo evitaría un pez turbo adicional.

En contraposición, puedo usar and_then u otros combinadores para terminar antes sin salir. Quizás no tan limpios como los bloques de prueba, pero tampoco tan mal.

@steveklabnik @ mark-im Los bloques de prueba y la try tienen fallas de inferencia de tipo no ergonómico, y la adscripción de tipo generalizada podría ser una forma de resolver ese problema, pero dado que la adscripción de tipo generalizada no es una característica a corto plazo o incluso algo seguro, @joshtriplett (y yo de acuerdo) no quiere que esta función se bloquee cuando se produzca una atribución de tipo generalizado.

Esto ni siquiera significa que no haríamos de la adscripción de tipos generalizados la solución al problema; una opción que merece ser investigada es "estabilizar el intento como está, esperando que algún día la adscripción de tipos generalizados resuelva ese problema". Todo lo que se ha dicho es que no bloquee la estabilización en la adscripción de tipo.


@ rust-lang / lang Tengo que admitir que es un poco difícil entender el matiz del error de inferencia de tipo de este hilo, debido a las limitaciones de GitHub y los muchos otros temas que se tratan aquí. Dado que tomar una decisión sobre cómo manejar las fallas de inferencia es lo único que bloquea la estabilización de los bloques de prueba, creo que sería beneficioso si tuviéramos una reunión para discutir esto, y alguien podría apuntar a obtener una comprensión profunda del problema de la inferencia .

Una pregunta que se me ocurre, por ejemplo: ¿este problema de inferencia se debe específicamente a la flexibilidad de conversión que hemos permitido en Try ? Sé que esa decisión ha sido discutida a muerte, pero si es así, parece una nueva información pertinente que podría justificar cambiar la definición del rasgo Try .

@withoutboats Estoy de acuerdo con la necesidad de recopilar toda la información en un solo lugar y el deseo de llevar esta función hasta la línea de meta. Dicho esto, creo que la última vez que investigamos aquí también quedó claro que los cambios en Try podrían ser difíciles debido a la compatibilidad con versiones anteriores: @cramertj mencionó algunos Pin impls específicos, IIRC.

¿Fue útil esta página
0 / 5 - 0 calificaciones