Rust: Problema de seguimiento para los rasgos TryFrom/TryInto

Creado en 5 may. 2016  ·  240Comentarios  ·  Fuente: rust-lang/rust

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


Hacer:

B-unstable C-tracking-issue T-libs

Comentario más útil

Me gustaría que ! se convierta en un tipo de vocabulario suficiente para que Result<_, !> se lea intuitivamente como "resultado infalible" o "resultado que (literalmente) nunca se equivoca". Usar un alias (¡no importa un tipo separado!) me parece un poco redundante y puedo ver que me causa al menos una pausa momentánea al leer las firmas de tipo: "espera, ¿en qué se diferencia esto de ! otra vez ?" YMMV, por supuesto.

Todos 240 comentarios

¿Hay alguna manera de imprimir genéricamente un error con el valor original si la conversión falla sin requerir Clone por lo que un método relacionado que entra en pánico en caso de falla podría tener buenos mensajes de error?

Disculpas si esto está cubierto en otro lugar, pero ¿qué nos gustaría ver antes de marcar esto como estable? Estoy bastante seguro de que he vuelto a implementar esta funcionalidad varias veces en varios proyectos, por lo que un rasgo reutilizable común me haría 😄

Podemos traerlo a discusión para el próximo ciclo.

🔔 Este problema ahora está entrando en un período de comentario final de un ciclo para su estabilización 🔔

Como punto de estabilización, al equipo de libs también le gustaría agregar estos rasgos al preludio como parte de su estabilización. Esto requerirá que una carrera de cráter esté 100% limpia _como mínimo_, pero estamos relativamente seguros de que el estado actual de resolución hace que sea compatible con versiones anteriores para agregar rasgos al preludio.

Tengo algunas preguntas sobre el propósito de estos rasgos.

  1. ¿Para qué tipos en std se implementarán?
  2. ¿Qué tipos deberían obtener implementaciones como impl TryFrom<T> for T ?
  3. ¿Qué tipos deberían obtener implementaciones como impl TryFrom<U> for T si ya tienen impl From<U> for T ?

cc @sfackler , ¿podría ampliar el conjunto actual de impls y también algunas de las razones?

Creo que, en general, la intuición debería ser exactamente la misma que la de From / Into , excepto cuando la conversión puede fallar. Al igual que agregamos gradualmente implementaciones de From y Into a los tipos de biblioteca estándar, espero que hagamos lo mismo para TryFrom y TryInto . La pauta más importante aquí es que la conversión debe ser "canónicamente obvia": si hay más de una forma razonable de convertir un tipo a otro, TryFrom o From pueden no ser los correctos. cosas para usar

_En general_, no creo que deba esperarse que todos los tipos deban impl TryFrom<T> for T o levantar manualmente un impl From<U> for T . En particular, a menudo no está claro qué tipo de error elegir. Sin embargo, ese tipo de implementaciones son en gran medida las que pueden y deben definirse para hacer que una API en particular funcione. Por ejemplo, tenemos ambos tipos de implementaciones para varias combinaciones de tipos de enteros primitivos por las razones descritas en el RFC. Como otro ejemplo, un rasgo ad-hoc que TryFrom / TryInto reemplazará es postgres::IntoConnectParams , que tiene una implementación reflexiva para convertir un ConnectParams en sí mismo.

En general, no creo que deba esperarse que todos los tipos impl TryFrom<T> for T o levanten manualmente un impl From<U> for T .

Cuando una conversión TryFrom resulta ser infalible, el tipo de error debería ser enum Void {} , ¿verdad? (O una enumeración similar con algún otro nombre). Lo que, por cierto, me parece una buena razón para tener un tipo de Void propósito general en std .

@SimonSapin que rompería una API de estilo IntoConnectParams , así como el caso de uso de conversión de enteros descrito en el RFC, ya que los tipos de error de las conversiones infalibles y falibles no coincidirían.

@sfackler ¡ No si usa From simples para los tipos de error (por ejemplo try! )! impl<T> From<!> for T puede y debe existir para ese propósito.

Al igual que agregamos gradualmente implementaciones de From y Into a los tipos de biblioteca estándar, espero que hagamos lo mismo para TryFrom y TryInto .

Eso no parece haber sucedido todavía, así que no sé si es porque nadie lo ha entendido o porque no hay tipos aplicables en std . Si hay tipos aplicables en std , sería bueno ver algunas implementaciones para ellos. Por ejemplo, ¿alguna de las siguientes implementaciones es buena?

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

_En general_, no creo que deba esperarse que todos los tipos deban impl TryFrom<T> for T o levantar manualmente un impl From<U> for T .

El problema es que si desea usar TryInto<T> y T no está en su caja, solo tiene que esperar que se haya implementado impl TryFrom<T> for T . Esa es una limitación desafortunada, una que no existe para From / Into .

@SimonSapin que rompería una API de estilo IntoConnectParams , así como el caso de uso de conversión de enteros descrito en el RFC, ya que los tipos de error de las conversiones infalibles y falibles no coincidirían.

¿Significa esto que el tipo de error se corrige de alguna manera según el tipo al que se está convirtiendo?

¿Por qué TryFrom::Err y TryInto::Err no están limitados por std::error::Error ?

no limitado por std::error::Error?

Eso evitaría que Err sea () , que es un tipo de error viable para conversiones infalibles.

Si () es el tipo de error, la función podría devolver Err(()) . Esto no hace que la función sea infalible. Simplemente no proporciona un error útil cuando falla. Al escribir código que usa conversiones que pueden o no ser falibles, creo que la mejor manera es limitarse a Into y escribir una especialización que use TryInto .

Una vez que se implemente RFC 1216 , ! será el tipo de error apropiado para conversiones infalibles. Creo que eso significaría que un límite de Error en TryFrom::Err / TryInto::Err estaría bien.

Sin duda, me gustaría que la implementación From<T> produjera una implementación TryFrom<T, Err = !> (y lo mismo para Into , por supuesto).

Creo que eso significaría que un límite de Error en TryFrom::Err / TryInto::Err estaría bien.

Sí, definitivamente tendremos impl Error for ! { ... } precisamente para este tipo de casos.

Estoy un poco preocupado por las diferencias de tipos en el caso de uso de conversiones de enteros, pero parece que probablemente deberíamos intentar estabilizar esto hasta que ! -as-a-type aterriza para tener la oportunidad de experimentar.

En mi POV, todos los tipos asociados que representan errores deben estar delimitados por Error . Lamentablemente, ya dejamos un tipo sin él: FromStr::Err (Los únicos otros tipos públicos son TryInto y TryFrom , marcados con ack 'type Err;' y ack 'type Error;' ). No hagamos esto de nuevo.

Discutido recientemente, el equipo de libs decidió apostar por estabilizar estos rasgos hasta que se resuelva el tipo ! .

Supongo que ahora esto ya no está bloqueado, ¿no?

Eliminar la nominación como @sfackler investigará los tipos de ! y estos rasgos.

¿Tendrá esto un cambio para estabilizarse en el futuro previsible?

¿Por qué TryFrom::Err y TryInto::Err no están limitados por std::error::Error?

std::err::Error no existe en las compilaciones de #![no_std] . Además, en muchos casos no hay ningún beneficio en implementar std::err::Error para un tipo de error incluso cuando está disponible. Por lo tanto, preferiría no tener ningún límite de este tipo.

He experimentado implementando esto yo mismo en mi propia biblioteca. Me gustaría reiterar la preocupación expresada por @SimonSapin en https://github.com/rust-lang/rfcs/pull/1542#issuecomment -206804137: try!(x.try_into()); es confuso porque la palabra "intentar" es utilizado de dos maneras diferentes en la misma declaración.

Entiendo que muchas personas piensan que tales cosas deberían escribirse x.try_into()?; , sin embargo, soy una de las muchas personas (basado en todos los debates) que prefieren no usar la sintaxis ? por... todas las razones mencionadas en todos los debates.

Personalmente, creo que aún deberíamos intentar encontrar algún patrón que no requiera el prefijo try_ en los nombres.

No me siento particularmente fuerte con el nombre, pero personalmente no puedo pensar en nada que sea mejor.

Ya se ha vuelto semiestándar usar try_ para tipos de funciones que devuelven un Result .

sin embargo, soy uno de un número considerable de personas (basado en todos los debates) que prefieren no usar la sintaxis ? debido a... todas las razones mencionadas en todos los debates.

Ese debate ha terminado ahora, ¿no es así? Quiero decir, simpatizo con ese lado del debate: no sé por qué la gente dice que encuentra try!() tan molesto, ? es menos visible, fomenta el manejo perezoso de errores y parece un desperdicio de sintaxis para algo para lo que ya teníamos una macro perfectamente buena (a menos que se amplíe para convertirse en algo mucho más general en el futuro).

Pero eso está en el pasado ahora. ? es estable y no va a desaparecer. Entonces, todos podríamos cambiar a él para que todos usemos lo mismo y podamos dejar de preocuparnos por los conflictos de nombres con try! .

Me gustaría reiterar la preocupación expresada por @SimonSapin en rust-lang/rfcs#1542 (comentario)

Como me citan por mi nombre, permítanme decir que ya no tengo esa preocupación. En el momento en que hice este comentario, el operador ? era una propuesta cuyo futuro era incierto, pero ahora llegó para quedarse.

Además, creo que estabilizarse más temprano que tarde es más importante que otra ronda de nombres de desprendimiento de bicicletas. Han pasado meses desde que se aceptó el RFC y esto se ha implementado #[unstable] .

Ya se ha vuelto semiestándar usar try_ para tipos de funciones que devuelven un resultado.

Esto es lo que encuentro más extraño de esta función. La mayoría de las funciones que escribo devuelven un Result pero nunca he nombrado ninguna de esas funciones con un prefijo try_ excepto cuando intento experimentar con este rasgo.

Además, no he encontrado ninguna ventaja práctica al escribir esto:

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

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

En cambio, siempre puedo escribir esto, mucho menos sobrecarga sintáctica:

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

Nunca he tenido que escribir código genérico parametrizado por TryInto o TryFrom a pesar de tener muchas conversiones, por lo que la última forma es suficiente para todos mis usos en los tipos que defino. Creo que tener TryInto<...> o TryFrom<...> parece una forma cuestionable.

Además, descubrí que nombrar el tipo de error asociado Err en lugar de Error era propenso a errores ya que siempre escribía Error . Noté que este error se cometió incluso durante la redacción del RFC: https://github.com/rust-lang/rfcs/pull/1542#r60139383. Además, el código que usa Result ya usa el nombre Err extensamente ya que es un constructor Result .

Había una propuesta alternativa que se enfocaba específicamente en los tipos de números enteros y usaba terminología como "ampliar" y "estrechar", por ejemplo, x = try!(x.narrow()); que también había implementado. Descubrí que la propuesta era suficiente para mis usos de la funcionalidad propuesta aquí en mi uso real, ya que solo terminé haciendo tales conversiones en tipos de enteros incorporados. También es más ergonómico y más claro (IMO) para los casos de uso para los que es suficiente.

Además, no he encontrado ninguna ventaja práctica en escribir esto...

Estoy más o menos de acuerdo. Este rasgo es para cuando una cosa se puede usar para crear otra cosa, pero a veces ese proceso puede fallar, pero eso suena como casi todas las funciones. Quiero decir, ¿deberíamos tener estos impls?:

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

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

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

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

Este rasgo parece casi demasiado genérico. En todos los ejemplos anteriores, sería mucho mejor decir explícitamente lo que realmente está haciendo en lugar de llamar a try_into .

Creo que tener TryInto<...> o TryFrom<...> parece una forma cuestionable.

También de acuerdo. ¿Por qué no haría simplemente la conversión y manejaría el error antes de pasar el valor a la función?

std::err::El error no existe en las compilaciones #![no_std]. Además, en muchos casos no hay ningún beneficio en implementar std::err::Error para un tipo de error incluso cuando está disponible. Por lo tanto, preferiría no tener ningún límite de este tipo.

El único beneficio de estar limitado por std::error::Error es que puede ser el valor de retorno de cause() de otro error. Realmente no sé por qué no hay un core::error::Error , pero no lo he investigado.

Además, descubrí que nombrar el tipo de error asociado Err en lugar de Error era propenso a errores, ya que siempre escribía Error. Me di cuenta de que este error se cometió incluso durante la redacción del propio RFC: rust-lang/rfcs#1542 (comentario). Además, el código que usa Result ya usa el nombre Err de forma extensiva ya que es un constructor de Result.

FromStr , que es estable, también usa Err para su tipo asociado. Ya sea que sea el mejor nombre o no, creo que es importante mantenerlo consistente en toda la biblioteca estándar.

Ya sea que TryFrom y TryInto sean demasiado generales o no, realmente me gustaría ver conversiones falibles, al menos entre tipos enteros, en la biblioteca estándar. Tengo una caja para esto, pero creo que los casos de uso van lo suficientemente lejos como para que sea estándar. Cuando Rust era alfa o beta, recuerdo usar FromPrimitive y ToPrimitive para este propósito, pero esos rasgos tenían problemas aún mayores.

Error no se puede mover a core debido a problemas de coherencia.

También debido a problemas de coherencia, Box<Error> no implementa Error , otra razón por la que no vinculamos el tipo Err .

Realmente no hay necesidad de vincularlo en la definición del rasgo, de todos modos, siempre puede agregar ese límite usted mismo más tarde:

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

No suelo comentar en estos hilos, pero he estado esperando esto por un tiempo y, como expresaron varios anteriormente, no estoy seguro de cuál es el retraso; Siempre quiero un rasgo from que pueda fallar. Tengo un código por todas partes que se llama try_from ... Este rasgo _perfectamente_ encapsula esa idea. Por favor, déjame usarlo en óxido estable.

También escribí un montón de otras cosas, pero desde entonces lo eliminé porque, lamentablemente, la coherencia de rasgos evita que este rasgo sea tan útil como podría ser para mí. Por ejemplo, esencialmente he vuelto a implementar una versión especializada de este mismo rasgo exacto para los tipos primitivos para un analizador altamente genérico de esos tipos. No te preocupes, despotricaré sobre esto en otro momento.

Dicho esto, creo que str::parse se beneficiará enormemente de esto, ya que también especializa un rasgo FromStr como límite, que es exactamente ( TryFrom<str> ) especializado a mano.

Así que corrígeme si me equivoco, pero creo que estabilizar y usar esto por str::parse hará lo siguiente:

  1. eliminar la reimplementación repetitiva y, por lo tanto, eliminar un rasgo menos familiar y especializado
  2. agregue TryFrom<str> en la firma, que correctamente está en el módulo de conversión y es más obvio lo que hace
  3. permitirá a los clientes upstream implementar TryFrom<str> para sus tipos de datos y obtener str.parse::<YourSweetDataType>() gratis, junto con otros try_from que deseen implementar, lo que permite una mejor organización lógica del código.

Por último, aparte de las abstracciones, la reutilización de código, bla, bla, creo que uno de los beneficios discretos de rasgos como este es la compra semántica que brindan tanto a principiantes como a veteranos. Esencialmente, brindan (o están comenzando a brindar, cuanto más nos estabilizamos) un paisaje uniforme con un comportamiento familiar canonicalizado. Default , From , Clone son muy buenos ejemplos de esto. Proporcionan un panorama de funciones memorable al que los usuarios pueden acceder al realizar ciertas operaciones, y cuyo comportamiento y semántica ya conocen bien (aprender una vez, aplicar en todas partes). P.ej:

  1. Quiero una versión predeterminada; oh, déjame alcanzar SomeType::default()
  2. Quiero convertir esto, me pregunto si SomeType::from(other) está implementado (puede escribirlo y ver si compila, sin buscar documentación)
  3. Quiero clonar esto, clone()
  4. Quiero intentar sacar esto de esto, y dado que los errores son una parte integral de rust, puede fallar en la firma, así que déjame try_from - oh espera :P

Todos estos métodos se vuelven comunes y forman parte del conjunto de herramientas de los usuarios de Rust, que en mi humilde opinión reduce la carga lógica al permitirnos alcanzar conceptos familiares (¡y ese es solo otro nombre para un rasgo!) cuya documentación y comportamiento semántico ya hemos interiorizado.

Por supuesto, no siempre coinciden, en cuyo caso especializamos nuestras estructuras de datos, pero características como esta reducen la carga de API de usuarios y programadores al darles acceso a conceptos que ya han estudiado, en lugar de leer documentación/encontrar su from_some_thing , etc. Sin ni siquiera tener que leer su documentación, mediante el uso de características estándar, los usuarios pueden navegar su API y estructuras de datos de una manera lógica, robusta y eficiente.

En cierto sentido, es formalizar un acuerdo de gentileza entre nosotros, y hace que sea más fácil y más familiar para nosotros realizar ciertas operaciones familiares.

Y eso es todo lo que tengo que decir al respecto ;)

Esto se bloqueó previamente en una investigación sobre la posibilidad de usar ! como tipo de error para conversiones de enteros infalibles. Como la función está implementada actualmente, falla incluso en los casos más simples: https://is.gd/Ws3K7V.

¿Todavía estamos pensando en cambiar los nombres de los métodos, o deberíamos poner esta característica en FCP?

@sfackler Ese enlace del patio de recreo funciona para mí si cambio el tipo de devolución en la línea 29 de Result<u32, ()> a Result<u32, !> : https://is.gd/A9pWbU No reconoce que let Ok(x) = val; es un patrón irrefutable cuando val tiene el tipo Err!, pero eso no parece ser un problema de bloqueo.

@Ixrec , una motivación principal para estos rasgos fueron las conversiones hacia y desde definiciones de tipos enteros de C. Si tengo una funcion

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

Esto compilará en objetivos i686 pero no en objetivos x86_64.

Del mismo modo, supongamos que quiero reemplazar el tipo IntoConnectParams : https://docs.rs/postgres/0.13.4/postgres/params/trait.IntoConnectParams.html. ¿Cómo puedo hacer eso si hay una manta impl<T> TryFrom<T> for T { type Error = ! } ? Necesito la implementación reflexiva para ConnectParams , pero con un tipo de error concreto diferente a ! .

¡No reconoce que let Ok(x) = val; es un patrón irrefutable cuando val tiene el tipo Err!

Tenga en cuenta que hay un PR abierto para eso .

Si tengo una función...

Aunque esto debería funcionar

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

A riesgo de ser un molesto comentario de +1, solo quiero mencionar que después de que las macros 1.1 lleguen a Rust 1.15, try_from será la última característica que mantendrá a Ruma en Rust nocturno. ¡Se espera con ansias el try_from estable!

En una nota más sustancial...

Esto es lo que encuentro más extraño de esta función. La mayoría de las funciones que escribo devuelven un resultado, pero nunca he nombrado ninguna de esas funciones con un prefijo try_ excepto cuando trato de experimentar con este rasgo.

Esa es una buena observación, pero creo que el motivo del prefijo try_ no es que sea necesario identificar el tipo de retorno como Result , sino distinguirlo del equivalente no falible.

Además, descubrí que nombrar el tipo de error asociado Err en lugar de Error era propenso a errores ya que siempre escribía Error. Me di cuenta de que este error se cometió incluso durante la redacción del propio RFC: rust-lang/rfcs#1542 (comentario). Además, el código que usa Result ya usa el nombre Err de forma extensiva ya que es un constructor de Result.

Estoy de acuerdo en este. La mayoría de los otros tipos de error que he encontrado en las bibliotecas se denominan "Error" y me gusta que hasta ahora "Err" solo haya significado Result::Err . Parece que estabilizarlo como "Err" dará como resultado (sin juego de palabras) que las personas constantemente se equivoquen con el nombre.

@canndrew Por supuesto que es posible hacer que funcione, pero el objetivo principal de esa función para esta función es facilitar el manejo correcto de este tipo de diferencias de plataforma: queremos evitar todo el espacio de "compila en x86 pero no ARM" .

Creo que elegí Err por consistencia con FromStr pero me encantaría cambiar a Error antes de estabilizarme.

la última característica que mantiene a Ruma en Rust nocturno

Del mismo modo, el backend del patio de recreo alternativo solo necesita todas las noches para acceder a TryFrom ; las cosas de serde nocturnas eran demasiado inestables para mi gusto, así que pasé a la configuración del script de compilación. Con 1.15, regresaré a #[derive] . ¡Espero ansiosamente que esta función se estabilice!

@sfackler Lo siento, no estaba siguiendo. En el caso de las conversiones de enteros, parece que lo que realmente necesitamos es no tipear c_ulong en u32 o u64 dependiendo de la plataforma, sino convertirlo de alguna manera en un nuevo tipo. De esa forma, una TryFrom<c_ulong> no puede interferir con una impl TryFrom<u{32,64}> .
Después de todo, siempre vamos a tener problemas de "compila una plataforma pero no la otra" si definimos tipos de manera diferente en diferentes plataformas. Es una pena tener que sacrificar el impl de TryFrom<T> for U where U: From<T> que de otro modo sería completamente lógico, solo para que podamos respaldar lo que parece una práctica cuestionable.

No estoy sugiriendo seriamente que bloqueemos este RFC hasta que obtengamos un RFC de tipo nuevo entero escrito, fusionado y estabilizado. Pero debemos tenerlo en cuenta para el futuro.

Del mismo modo, supongamos que quiero reemplazar el tipo IntoConnectParams:

¿Cuál es el problema aquí? ¿Por qué no usaría un tipo de error para TryFrom<ConnectParams> y otro para TryFrom<&'a str> ?

No recomendaría que rompamos literalmente todo el código FFI del mundo. Habiendo intentado y fallado en recoger envoltorios de tipo nuevo de enteros similares como Wrapping , existen costos ergonómicos masivos.

¿Hay un impl<T> From<!> for T en la biblioteca estándar? No lo veo en los documentos, pero eso podría ser un error de rustdoc. Si no está allí, entonces no hay forma de convertir el TryFrom<ConnectParams> impl ! Error en el que realmente necesito.

Habiendo intentado y fallado en recoger envoltorios de tipo nuevo de enteros similares como Wrapping, existen costos ergonómicos masivos.

Estaba pensando en algo más como poder definir sus propios tipos de enteros a. la. C++:

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

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

extern fn foo(x: c_ulong);

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

¿Eso resolvería la mayoría de los problemas ergonómicos? En realidad, no me gustan los literales definidos por el usuario de C ++, o las características en general que le dan a las personas el poder de cambiar el idioma de manera confusa, pero supongo que podría terminar siendo el menor de los dos males.

¿Hay un impl<T> From<!> for T en la biblioteca estándar?

No actualmente porque entra en conflicto con el From<T> for T impl. Sin embargo, tengo entendido que la especialización impl eventualmente debería poder manejar esto.

Eso suena como algo a lo que deberíamos regresar en un par de años.

¿Cuál es el cronograma de estabilización de la especialización y ! ?

Por especialización no sé. Para ! en sí mismo, es principalmente una cuestión de cuándo puedo fusionar mis parches.

@canndrew Definitivamente estoy de acuerdo en que no debería implementarse para todo. Los documentos dicen _intento de construir Self a través de una conversión_, pero ¿qué cuenta como una conversión? ¿Qué tal…_cambiar lo mismo de una representación a otra, o agregar o quitar un envoltorio_? Esto cubre su Vec<u8> -> String y Mutex<T> -> MutexGuard<T> , así como cosas como u32 -> char y &str -> i64 ; mientras que excluye SocketAddr -> TcpStream y process::Child -> process::Output .

Siento que impl From<T> for U probablemente debería implicar TryFrom<T, Err=!> for U . Sin eso, las funciones que toman TryFrom<T> s no pueden tomar también From<T> s. (Usar try_from() | try_into() para una conversión concreta e infalible probablemente solo sería un antipatrón. Podrías hacerlo, pero... sería una tontería, así que simplemente no lo hagas). .)

Me siento como implpara U probablemente debería implicar TryFrompara ti.

Acordado.

Podrías hacerlo, pero... sería una tontería, así que simplemente no lo hagas.

Suena como una pelusa recortada potencial.

@BlacklightShining Creo que debería implementarse para tipos donde, dado el tipo de salida, la "conversión" es obvia. Tan pronto como sea posible realizar varias conversiones (¿utf8/16/32? ¿serialización frente a conversión? etc.), debe evitarse.

EN MI HUMILDE OPINIÓN:

En este momento, podríamos proporcionar fácilmente una implementación general de TryFrom<&str> para todo lo que implemente FromStr :

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

Me gustaría ver algo como esto agregado antes de que se estabilice try_from , o al menos alguna referencia en los documentos. De lo contrario, puede resultar confuso tener implementaciones de TryFrom<&'a str> y FromStr que difieran.

Estoy de acuerdo en que sería una buena inclusión, pero podría entrar en conflicto con la propuesta Try -> TryFrom impl?

@sfackler potencialmente. Estaría perfectamente bien si TryFrom , TryInto y FromStr se hicieran referencia entre sí en los documentos, pero mi principal preocupación es que no existe un estándar que diga si str::parse y str::try_into devuelven el mismo valor.

Estaría bien con una solicitud suave en los documentos de que las personas deberían implementarlos para tener el mismo comportamiento, aunque definitivamente puedo ver casos en los que alguien podría pensar que pueden ser diferentes.

Por ejemplo, supongamos que alguien crea una estructura Password para un sitio web. Podrían suponer que "password".parse() comprobaría la validez de la contraseña y luego la convertiría en un hash, mientras que Password::try_from("1234abcd") podría suponer que "1234abcd" ya es un hash almacenado en la base de datos e intentar para analizarlo directamente en un hash que se puede comparar.

Esto tiene sentido, teniendo en cuenta que la redacción de parse implica que se realiza cierto nivel de análisis, mientras que try_from es solo una conversión de tipos. Sin embargo, en realidad, podríamos querer aclarar que ambas funciones pretenden realizar lo mismo.

Aunque el equipo de idiomas ha propuesto cerrar el RFC por desaprobar los parámetros anónimos , todos parecen estar de acuerdo en que, idealmente, dejaríamos de crear código nuevo que use parámetros anónimos. Con eso en mente, ¿podríamos actualizar la firma de try_from / try_into para dar nombres a los parámetros? ¿O sería más importante mantener la simetría con from / into ?

Además, ¿sería útil escribir un resumen de las principales preguntas sin respuesta que aún quedan? Realmente espero que podamos decidir estabilizar esto para el próximo ciclo de lanzamiento. Como mencioné, es la única función nocturna restante que uso mucho. :}

¡@jimmycuadra ciertamente sí! ¿Quiere enviar un PR agregando algunos nombres de parámetros?

Con respecto al estado actual de las cosas, creo que la única pregunta sin respuesta es si debería haber una

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

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

Esto agrega una buena cantidad de simetría, pero hace que las cosas sean un poco más molestas en muchos casos, ya que ya no tiene un solo tipo de error para las cosas que desea convertir.

escriba Error = !;

Sugiero hacerlo en un RFC separado que tenga en cuenta el resultado de lo que se decida sobre todas las cosas indecisas con respecto a ! .

@sfackler Creo que también sería importante tener en cuenta las cosas que mencioné sobre FromStr . ¿Deberíamos tener un impl similar para los implementadores de FromStr , o debería permitirse que sean distintos, o simplemente deberíamos documentar que deberían ser iguales pero no necesariamente?

Soy de la opinión de que TryFrom<str> y FromStr deberían ser funcionalmente idénticos, y la documentación debería dejar en claro que las implementaciones de los dos están destinadas a ser idénticas. Implementar uno también debería brindarle el otro, al menos en términos de permitirle usar str::parse . Si Rust hubiera tenido TryFrom desde el principio, nunca se habría necesitado FromStr . Por esa razón, también documentaría TryFrom<str> como la forma preferida para el nuevo código.

@jimmycuadra en ese caso, deberíamos modificar parse para usar TryFrom y luego poner un impl general para FromStr -> TryFrom .

Si vamos a cambiar str::parse para que se implemente en términos de TryFrom , ¿deberíamos cambiar también otras implementaciones de FromStr para tipos concretos de forma similar (es decir, todos los implementadores de esta lista : https://doc.rust-lang.org/stable/std/str/trait.FromStr.html)? ¿Deberían actualizarse los documentos de FromStr para sugerir el uso TryFrom en su lugar? ¿Deberían trasladarse los documentos de las implementaciones concretas actuales de FromStr a la versión TryFrom ?

Creo que por compatibilidad con versiones anteriores no podemos cambiar FromStr , ¿verdad?

Eliminar FromStr a favor de TryFrom<&str> es definitivamente algo a tener en cuenta para Rust 2.0.

Sí, una vez que esta función se estabilice, presentaremos un problema y lo etiquetaremos con 2.0-breakage-wishlist.

Una cosa a considerar también es agregar un método parse_into a String que usa TryFrom<String> . Me encuentro implementando TryFrom para ambos a menudo si un tipo almacena internamente un String pero aún requiere validación.

Si vamos a dejar implTryFrom para T donde T: Desde para un futuro RFC, ¿esta función está lista para estabilizarse ahora? Realmente no quiero perderme otro ciclo de lanzamiento, así que espero que algunas personas en el equipo de Rust tengan el ancho de banda para discutir y tomar una decisión.

Creo que el problema es que sería difícil estabilizar esa función después de que se haya estabilizado y la gente haya proporcionado impls para ambas.

Esperaría que ya tener un T : From<U> pusiera U : TryFrom<T> en la categoría de cambio " agujero API obvio " cuando la implementación es razonable.

Eso implica que al menos debería haber un T : TryFrom<T> con Error = ! , pero la versión para cualquier From infalible es claramente mejor que eso.

En mi opinión, no hay realmente una distinción clara entre si From debería proporcionar una implementación TryFrom o si TryFrom debería proporcionar una implementación From .

Porque por un lado, podrías considerar que T::from(val) son solo T::try_from(val).unwrap() y, por otro lado, podrías considerar que T::try_from(val) son solo Ok(T::from(val)) . ¿Cual es mejor? No sé.

podrías considerar T::from(val) como solo T::try_from(val).unwrap()

No estoy de acuerdo con eso. No se espera que las implementaciones From entren en pánico. Sólo al revés tiene sentido.

@clarcharr Debido a que From no debería entrar en pánico, las opciones son From en términos de TryFrom<Error=!> o al revés. Pero odiaría tener el consejo habitual de "Debería implementar TryFrom con type Error = ! " en lugar de "Debería implementar From ".

¿Alguna forma de conseguir algo de movimiento para estabilizar esto? Nos estamos quedando sin tiempo antes de que 1.18 entre en beta. @sfackler?

fusión @rfcbot fcp

El miembro del equipo @sfackler ha propuesto fusionar esto. El siguiente paso es la revisión por parte del resto de equipos etiquetados:

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

No hay preocupaciones actualmente en la lista.

Una vez que estos revisores lleguen a un consenso, entrará en su período final de comentarios. Si detecta un problema importante que no se ha planteado en ningún momento de este proceso, ¡dígalo!

Consulte este documento para obtener información sobre los comandos que pueden darme los miembros del equipo etiquetados.

@sfackler : solo para verificar, ¿estamos bien con varias preocupaciones sobre ! e impls generales? Sé que hablamos de esto en la reunión de libs, pero sería útil obtener un resumen aquí.

@aturon La última discusión al respecto fue @sfackler preguntándose si impl From<T> for U debería proporcionar impl TryFrom<T> for U donde TryFrom::Error = ! .

@briansmith sugirió que una decisión sobre eso sea un RFC separado una vez que se resuelvan las preguntas no resueltas sobre el tipo nunca.

¿No es el principal problema de estabilizar esto ahora que dicho cambio no se puede realizar sin romper la compatibilidad con versiones anteriores? ¿O la solución es simplemente no seguir adelante con ese cambio?

Creo que el conjunto actual de impls es insostenible. Podría entender cualquiera de estas posiciones:

  1. TryFrom está diseñado para conversiones falibles _solo_, por lo que no tiene cosas como u8 -> u128 o usize -> usize .
  2. TryFrom está destinado a _todas_ las conversiones, algunas de las cuales son infalibles y, por lo tanto, tienen un tipo TryFrom::Error deshabitado.

Pero en este momento las cosas se encuentran en un extraño estado híbrido en el que el compilador insertará un código de verificación para una conversión i32 -> i32 y, sin embargo, no puede realizar una conversión String -> String .

¿Cuáles son las objeciones a ! como tipo de error? Lo único que noté en un vistazo rápido fue "pero hace que las cosas sean un poco más molestas de manejar en muchos casos, ya que ya no tiene un solo tipo de error para las cosas que desea convertir", pero no estoy convencido Estoy de acuerdo con eso, ya que debe asumir que pasó algo personalizado con un tipo de error personalizado en un contexto genérico sin importar qué.

¿No es el principal problema de estabilizar esto ahora que dicho cambio no se puede realizar sin romper la compatibilidad con versiones anteriores? ¿O la solución es simplemente no seguir adelante con ese cambio?

Soy de la opinión de que es demasiado entusiasta agregar una implementación genérica de TryFrom cuando se implementa From . Aunque es semánticamente cierto que si hay una implementación From , hay una implementación de TryFrom que no puede producir un error, no veo que esta implementación proporcionada sea prácticamente útil en absoluto, y mucho menos un necesidad lo suficientemente común como para que se proporcione de forma predeterminada. Si alguien realmente necesita ese comportamiento para su tipo por alguna razón, está a solo una implementación simple de distancia.

Si hay un ejemplo de un caso en el que alguna vez usaría try_from en lugar de from para una conversión infalible, ciertamente podría cambiar de opinión.

¿Cuáles son las objeciones a ! como un tipo de error?

! está a meses de la estabilización. Elija uno de estabilizar TryFrom en un futuro cercano o tener impl<T, U> TryFrom<U> for T where T: From<U> .

¿Hemos considerado usar alias de rasgos (rust-lang/rfcs#1733) aquí? Cuando eso aterriza, podemos alias From<T> a TryFrom<T, Error=!> , haciendo que los dos rasgos sean uno y el mismo.

@lfairy Eso rompería al usuario impl s de From , por desgracia.

@glaebhoerl Sí, tienes razón 😥 La sección de motivación de ese RFC menciona el alias impl s, pero la propuesta real no los permite.

(Incluso si no fuera así, los métodos tienen nombres diferentes, etc.)

Eso podría pasar por debajo de una lista de deseos 2.0, pero independientemente de eso, no sucederá sin romper nada.

En primer lugar, gracias @sfackler por una excelente conversación sobre esto en IRC. Después de dejar que las cosas se asentaran en mi cabeza por un tiempo, aquí es donde terminé.

Elija uno de estabilizar TryFrom en un futuro cercano o tener impl<T, U> TryFrom<U> for T where T: From<U> .

Creo que la pregunta central aquí es si las conversiones infalibles pertenecen al rasgo. Creo que lo hacen, por cosas como las conversiones infalibles en el RFC y para uso genérico (análogo a cómo está el T:From<T> aparentemente inútil). Dado eso, lo que más quiero es evitar un mundo en el que se espera que cada implementador de tipo impl TryFrom<MyType> for MyType , y cada implementación From también debería resultar en una implementación de TryFrom . (O archivar los errores más tarde por no proporcionarlos).

Entonces, ¿podríamos tener el impl general sin estabilizar ! ? Creo que hay una forma, dado que ya tenemos tipos similares a ! en la biblioteca, como std::string::ParseError . (" Esta enumeración es un poco incómoda: en realidad nunca existirá").

Un boceto de cómo podría funcionar esto:

  • Un nuevo tipo, core::convert::Infallible , implementado exactamente como std::string::ParseError . (Tal vez incluso cambie el último a un tipo de alias para el primero).
  • impl<T> From<Infallible> for T para que sea compatible en ? con cualquier tipo de error (ver las cosas c_foo más adelante)
  • Use Infallible como el tipo Error en la impl general
  • Más tarde, considere type Infallible = !; como parte de la estabilización del tipo nunca

Me ofreceré como voluntario para hacer un PR haciendo eso, si fuera útil para concretarlo.

En cuanto a c_foo : lo anterior continuaría permitiendo código como este:

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

Pero convertiría un código como este en una "pistola" de portabilidad, debido a los diferentes tipos de error

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

Personalmente, no me preocupa esa diferencia porque mientras c_int sea ​​un alias de tipo, hay una pistola "totalmente automática":

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

Y, en general, el código que espera que el tipo asociado en un rasgo sea el mismo para diferentes impls me parece un olor a código. Leo TryFrom como "generalizar From "; si el objetivo fuera "mejores conversiones entre subconjuntos de enteros", lo que también se siente útil, entonces "siempre el mismo tipo de error" es lógico, pero esperaría algo dirigido a std::num en su lugar, probablemente como num::cast::NumCast (o boost::numeric_cast ).

(Aparte: con #[repr(transparent)] en la fusión de FCP, tal vez los tipos c_foo puedan convertirse en tipos nuevos, momento en el cual estas conversiones podrían ser más consistentes. Los impls From&TryFrom podrían codificar el C "char <= short < = int <= long", así como los tamaños mínimos estándar requeridos para ellos como c_int:From<i16> o c_long:TryFrom<i64> . Entonces la conversión anterior sería i32:TryFrom<c_int> en todos plataformas, con siempre el mismo tipo Error , y el problema desaparece.)

Con respecto a "Esta enumeración es un poco incómoda: en realidad nunca existirá".

¿Hay alguna razón por la que el tipo de error no pueda ser simplemente unidad? ¿Por qué molestarse con la estructura ParseError única si la conversación nunca puede fallar?

@sunjay () es la representación del sistema de tipos de "esto puede suceder, pero no hay nada interesante que decirle cuando sucede". Los tipos deshabitados (como ! y std::string::ParseError ) son lo contrario, la forma en que el sistema de tipos dice "esta situación no puede ocurrir nunca, por lo que no es necesario que se ocupe de ella".

@jimmycuadra

Si hay un ejemplo de un caso en el que alguna vez usaría try_from en lugar de from para una conversión infalible, ciertamente podría cambiar de opinión.

@scottmcm

Creo que la pregunta central aquí es si las conversiones infalibles pertenecen al rasgo.

Aquí está mi caso de uso: tengo un formato de archivo de configuración donde los valores pueden ser booleanos, numéricos o de cadena, y una macro para escribir valores de configuración literales donde las claves pueden ser variantes de enumeración o cadena. Por ejemplo:

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

Para resumir, la macro termina expandiéndose a una lista de llamadas ($k, $v).into() . Me gustaría verificar la conversión de las claves de cadena para asegurarme de que nombran una opción de configuración válida, es decir, implementar TryFrom<(String, ???)> y cambiar la macro para usar ($k, $v).try_into() . Sería más difícil hacer todo esto si no hubiera un solo nombre de método para que la macro usara para todas las conversiones.

:bell: Esto ahora está entrando en su período final de comentarios , según la revisión anterior . :campana:

De hecho, me gusta mucho la idea de:

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

Porque cualquiera que quiera TryFrom<Error=!> puede implementarlo, pero la gente aún puede implementar From si lo desea. Tal vez, en última instancia, podríamos desaprobar From , pero no es necesario.

El plan de @scottmcm para usar una enumeración vacía me parece genial.

@Ericson2314 escribiste :

¡No si usa From simple para los tipos de error (por ejemplo try! )! impl<T> From<!> for T puede y debe existir para ese propósito.

¿Cómo funcionaría esto en la práctica? Digamos que estoy tratando de escribir una función como esta:

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

Excepto que esto, por supuesto, no funciona, necesito especificar Error= en TryInto . Pero, ¿qué tipo debo escribir allí? MyError parece obvio pero no puedo usar MyType con la manta TryFrom impl.

¿Estás sugiriendo lo siguiente?

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

Eso parece bastante detallado.

Hay una pregunta más general de cómo se supone que funciona esto si quiero múltiples conversiones "infalibles" para el mismo tipo pero con diferentes tipos de error.

Tal vez la definición TryFrom debería cambiarse a la siguiente:

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

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

Puede restringir el error para convertirlo en MyError sin tener que darle un nombre explícito, como

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

todavía es un poco detallado, pero realmente establece las restricciones para llamar a la función muy bien (patio de recreo )

EDITAR: Y probar con una variante como P::Error: Into<MyError> en realidad no funciona con ? ya que no hay una implementación general de From para Into . Cambiando TryFrom como has mostrado, esperaría encontrarme con el mismo problema.

El período final de comentarios ya está completo.

@jethrogb je, me citaste hace un año, así que tuve que pensar un poco. @ Nemo157 tiene toda la razón y eso parece razonable.

En el caso específico de ! deberíamos tener un impl general, pero recuerdo que se superpone a otro. Es una superposición molesta ya que ambas implementaciones están de acuerdo en la implementación --- comportamiento indefinido/código muerto.

Un comentario sobre esto de otro número: https://github.com/rust-lang/rust/pull/41904#issuecomment -300908910

¿Alguien del equipo de libs tiene alguna idea sobre la idea de scottmcm ? Suena como un gran enfoque para mí, y me gustaría continuar impulsando esta función después de perderme otro ciclo de lanzamiento.

¡El equipo de libs volvió a hablar de esto hace un par de semanas (perdón por la demora en escribir esto)! Llegamos a la conclusión de que la fuente de los problemas relacionados con esta función era principalmente que el caso de FFI realmente no encaja con ningún otro caso de uso de estas funciones: es único en el sentido de que lo llama en tipos concretos a través de alias que varían basado en el objetivo.

Entonces, el plan de acción básico es agregar impl<T, U> TryFrom<T> for U where U: From<T> y eliminar los impls explícitos para conversiones de enteros que son infalibles. Para manejar el caso de uso de FFI, crearemos una API separada.

Usar un alias de tipo para evitar el bloqueo en ! es interesante. Mi única preocupación es si ! es más "especial" que los tipos deshabitados normales que causarían roturas cuando cambiamos el alias de una enumeración deshabitada a ! .

Abrí un PR para la "API separada" para tipos integrales: https://github.com/rust-lang/rust/pull/42456

Nunca he tenido que escribir código genérico parametrizado por TryInto o TryFrom a pesar de tener muchas conversiones, por lo que la última forma es suficiente para todos mis usos en los tipos que defino. Creo que tener TryInto<...> o TryFrom<...> parece una forma cuestionable.

Tengo la intención de usar TryFrom una vez que sea estable como parte de un rasgo derivado, y sería realmente extraño llamar a métodos intrínsecos ad-hoc en algunos tipos como parte de una macro derive .

Por favor, no elimines esto.

Nunca he tenido que escribir código genérico parametrizado por TryInto o TryFrom

Incluso si ese es el caso, no creo que eso haga que TryInto y TryFrom sean mucho menos útiles. Uso Into y From por todas partes en contextos no genéricos. Agregar impl s de rasgos de biblioteca estándar se siente mucho más "normal" y "esperado" que un montón de métodos de conversión inherentes ad-hoc.

Nunca he tenido que escribir código genérico parametrizado por TryInto o TryFrom

De uno de mis proyectos:

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

¿Se espera que las implementaciones de estos rasgos sigan alguna ley en particular? Por ejemplo, si podemos convertir A en B y B en A, ¿se requiere que la conversión sea invertible cuando tiene éxito?:

#![feature(try_from)]

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

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

editar: s/reflexivo/invertible/

@briansmith Dado cómo funciona From , diría que no es invertible de esta manera.

use std::collections::BinaryHeap;

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

Así que me pregunto acerca de las implementaciones actuales de este rasgo. Como surgió en #43127 (ver también #43064), no sé si es simplemente porque la implementación usa i/u128, pero parece que las llamadas TryInto no están en línea. Esto no es muy óptimo, y no está realmente en el espíritu de las abstracciones de costo cero. Por ejemplo, el uso <u32>::try_into<u64>() debería optimizarse hasta una simple extensión de signo en el ensamblaje final (siempre que la plataforma sea de 64 bits), pero en su lugar resulta en una llamada de función.

¿Existe algún requisito de que las implementaciones del rasgo sean las mismas para todos los tipos de enteros?

De acuerdo con #42456, probablemente no tengamos impl TryFrom directamente en los tipos enteros, pero aún se está redactando cómo se ve ese rasgo "NumCast" (al que #43127 debería cambiar).

Independientemente de si terminan moviéndose a otro rasgo, estas conversiones se implementan hoy en libcore y se pueden usar en libcore. Creo que usar u128 / i128 para todas las conversiones de enteros se hizo para simplificar el código fuente. En su lugar, probablemente deberíamos tener un código diferente dependiendo de si el tipo de origen es más ancho o más estrecho que el destino. (Posiblemente con llamadas de macro diferentes basadas en #[cfg(target_pointer_width = "64")] vs #[cfg(target_pointer_width = "32")] vs #[cfg(target_pointer_width = "16")] ).

Recordatorio: ¡no se necesita mucho para desbloquear este! Si está dispuesto a hacerlo, eche un vistazo al resumen de @sfackler y siéntase libre de enviarme un ping a mí o a otros miembros del equipo de libs para obtener orientación.

No me di cuenta de que el equipo de libs estaba convencido de que podíamos usar la idea de @scottmcm como una solución alternativa para que el tipo de explosión fuera inestable. Puedo trabajar en un PR para hacer los cambios que mencionó @sfackler usando la solución alternativa.

Impresionante, gracias @jimmycuadra!

La mayor parte de esta discusión parece girar en torno a la implementación TryFrom para tipos enteros. Si TryFrom se estabiliza o no, no debería deberse solo a estos tipos, en mi opinión.

Hay otras conversiones interesantes que se beneficiarían de estas características, como TryFrom<&[T]> por &[T; N] . Recientemente envié un PR para implementar exactamente esto: https://github.com/rust-lang/rust/pull/44764.

Conversiones como estas son lo suficientemente importantes para mí como para estabilizar TryFrom .

Con #44174 combinado, creo que ahora está desbloqueado.

Ese PR eliminó la implementación automática de FromStr para cualquier tipo que implemente TryFrom<&str> porque el sistema de tipo no puede admitirlo actualmente, incluso con especialización. La intención es que FromStr y parse queden obsoletos en favor de TryFrom<&str> y try_into una vez que se estabilice esta función. Es desafortunado perder la compatibilidad provisional entre los dos; si alguien tiene ideas para una solución provisional, por favor hable.

Si no hay que hacer más cambios y alguien en el equipo de libs da luz verde a esto para la estabilización, puedo hacer el PR de estabilización y un PR para desaprobar FromStr / parse .

y un PR para desaprobar FromStr/parse.

Las advertencias de obsolescencia no deben agregarse a Nightly hasta que el reemplazo esté disponible en Stable (o hasta que se implemente https://github.com/rust-lang/rust/issues/30785), de modo que sea posible en cualquier momento hacer una construcción de cajas sin advertencias en los tres canales de liberación.

Me perdí el otro PR ya que las referencias no dan como resultado notificaciones por correo electrónico. Me doy cuenta de que hay un impl From<Infallible> for TryFromIntError específico. ¿No debería ser impl<T> From<Infallible> for T como se discutió?

@jethrogb Lamentablemente, eso entra en conflicto con impl<T> From<T> for T por lo que no se puede hacer (¿hasta que obtengamos impls de intersección? -- y usar ! tampoco funciona allí).

@scottmcm ah, por supuesto.

¿No creo que necesites impls de intersección? ¿No es eso solo una especialización directa?

No he leído los otros comentarios, pero TryFrom está roto para mí en este momento (funcionaba bien antes).

versión rustc:

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

La sección de código relevante de la que se queja el óxido está aquí: https://github.com/fschutt/printpdf/blob/master/src/types/plugins/graphics/two_dimensional/image.rs#L29 -L39 y https://github .com/fschutt/printpdf/blob/master/src/types/plugins/graphics/xobject.rs#L170 -L200 - se compiló bien hace unas semanas, razón por la cual la biblioteca todavía tiene la insignia de "compilación aprobada".

Sin embargo, en la última compilación nocturna, TryFrom parece fallar:

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

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

error: aborting due to 2 previous errors

error: Could not compile `printpdf`.

Entonces, supuestamente tiene una implementación duplicada en el cajón core . Si alguien pudiera investigar esto, sería genial, gracias.

@fschutt El impl conflictivo es probablemente impl<T, U> TryFrom<T> for U where U: From<T> , agregado en https://github.com/rust-lang/rust/pull/44174. Podría existir un T como T: ImageDecoder y Image: From<T> .

¿Hay algo que todavía se necesita para que esto salga de la puerta de características?

Si https://github.com/rust-lang/rust/issues/35121 se estabiliza primero, podríamos eliminar el tipo Infallible introducido en https://github.com/rust-lang/rust/pull/ 44174 y use ! en su lugar. No sé si esto se considera un requisito.

Creo que el principal bloqueador aquí sigue siendo los tipos enteros. O usamos un rasgo Cast separado para tipos enteros https://github.com/rust-lang/rust/pull/42456#issuecomment -326159595, o hacemos que la pelusa de portabilidad #41619 suceda primero.

Así que solía tener una enumeración que implementaba TryFrom para AsRef<str> , pero esto se rompió hace unos meses. Pensé que era un error introducido en nightly que desaparecería con el tiempo, pero parece que este no es el caso. ¿Este ya no es un patrón compatible con TryFrom ?

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

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

¿Qué otras opciones hay para convertir tanto &str como String ?

@kybishop ¿ MyEnum implementa FromStr ? Esa puede ser la fuente de su rotura.

@nvzqz no lo hace, aunque me recomendaron usar TryFrom a través de Rust IRC ya que es una solución más generalizada. TryFrom inicialmente funcionó muy bien hasta que se produjo un cambio importante todas las noches hace unos meses.

EDITAR: ¿Quiere decir que debería cambiar a implementar FromStr , o que si _hizo_ impl FromStr , eso podría causar roturas?

EDIT 2: Aquí está el impl completo, bastante corto y simple para aquellos curiosos: https://gist.github.com/kybishop/2fa9e9d32728167bed5b1bc0b9becd97

@kybishop , ¿hay alguna razón en particular por la que desee una implementación para AsRef<str> en lugar de &str ?

@sfackler Tenía la impresión de que permite la conversión de &str y String , aunque todavía soy un poco novato en Rust, así que tal vez no entiendo exactamente cómo AsRef<str> Se usa &str y ver si AsRef estaba permitiendo algo que &str no permite.

@kybishop Igual que https://github.com/rust-lang/rust/issues/33417#issuecomment -335815206, y como dice el mensaje de error del compilador, este es un conflicto real con el impl<T, U> std::convert::TryFrom<U> for T where T: std::convert::From<U> impl que fue añadido a libcore.

Podría haber un tipo T (posiblemente en una caja aguas abajo) que implemente ambosFrom<MyEnum> y AsRef<str>y tiene MyEnum: From<T> . En ese caso, ambos impls se aplicarían, por lo que ambos impls no pueden existir juntos.

@SimonSapin entendió. Entonces, ¿cuáles son las opciones para las personas que desean convertir desde &str y &String ? ¿Solo tengo que impl TryFrom para ambos?

EDITAR: Supongo que puedo comerme el trabajo extra y llamar a .as_ref() en String s. Entonces puedo tener un único TryFrom impl por str .

Sí, dos impls (posiblemente con uno basado en el otro, como señaló) deberían funcionar siempre que MyEnum no implemente From<&str> o From<String> .

@kybishop String implementa Deref<str> , por lo que la inferencia de tipo debe permitir que &String coaccione a &str al pasarlo a la implementación TryFrom . Es posible que no siempre sea necesario llamar a as_ref .

Si alguien está buscando un ejemplo rápido de esto: https://play.rust-lang.org/?gist=bfc3de0696cbee0ed9640a3f60b33f5b&version=nightly

Con https://github.com/rust-lang/rust/pull/47630 a punto de estabilizar ! , ¿hay apetito para que un PR reemplace Infallible con ! aquí? ?

Una mejor ruta a seguir sería hacer un alias. Conserva la expresividad y utiliza la característica del lenguaje adaptado.

type Infallible = !;

Solo saltando. Estoy con @scottmcm en eso.

Sin embargo, esto agrega la sobrecarga adicional de que esta función ( TryInto / TryFrom ) ahora depende de otra función inestable: never_type .

Además, Infallible tiene la ventaja de que brinda más información/semántica sobre por qué no se puede construir el tipo. Soy obstinado conmigo mismo en este momento.

Me gustaría que ! se convierta en un tipo de vocabulario suficiente para que Result<_, !> se lea intuitivamente como "resultado infalible" o "resultado que (literalmente) nunca se equivoca". Usar un alias (¡no importa un tipo separado!) me parece un poco redundante y puedo ver que me causa al menos una pausa momentánea al leer las firmas de tipo: "espera, ¿en qué se diferencia esto de ! otra vez ?" YMMV, por supuesto.

@jdahlstrom Estoy totalmente de acuerdo. Tendríamos que introducir eso en el libro de Rust o en el nomicon para que sea una "verdad básica" y amigable.

Han pasado dos años desde que se envió el RFC para esta API.

~ @briansmith : Hay una estabilización de relaciones públicas en curso.~

EDITAR : O tal vez estoy demasiado cansado...

Vinculaste el PR de la estabilización de tipo ! .

Dado que el PR de estabilización ! acaba de fusionarse, he enviado un PR para reemplazar convert::Infallible con ! : #49038

https://github.com/rust-lang/rust/pull/49038 se fusiona. Creo que este fue el último bloqueador para la estabilización, avíseme si me perdí un problema sin resolver.

fusión @rfcbot fcp

rfcbot no responde porque ya se completó otro FCP en https://github.com/rust-lang/rust/issues/33417#issuecomment -302817297.

Uhmm, hay algunos impls que deberían revisarse. Ahora que tenemos impl<T, U> TryFrom<U> for T where T: From<U> , las impls restantes de TryFrom que tienen type Error = ! deben reemplazarse con impls de From , eliminarse o volverse falibles ( cambiando el tipo de error a algún tipo no deshabitado).

Aquellos en ese caso que puedo encontrar involucran usize o isize . Supongo que los impls From correspondientes no existen porque su falibilidad depende del tamaño del puntero de destino. De hecho, los TryFrom impls se generan de manera diferente para diferentes objetivos: https://github.com/rust-lang/rust/blob/1.24.1/src/libcore/num/mod.rs#L3103 -L3179 ​​Esto es probablemente un riesgo de portabilidad.

generado de manera diferente para diferentes objetivos

Para aclarar: diferentes cuerpos de método para diferentes target_pointer_width en el mismo impl están bien (y probablemente sean necesarios), diferentes API (tipos de error) no lo están.

Estabilización PR: #49305. Después de un poco de discusión allí, este PR también elimina algunos impls TryFrom que involucran usize o isize porque no hemos decidido entre dos formas diferentes de implementarlos. (Y, por supuesto, solo podemos tener uno).

Problema de seguimiento dedicado para aquellos: https://github.com/rust-lang/rust/issues/49415

TryFrom funcionó perfectamente en rustc 1.27.0-nightly (ac3c2288f 2018-04-18) sin ningún tipo de activación de funciones, pero se rompió cuando se compiló con rustc 1.27.0-nightly (66363b288 2018-04-28) .

¿Ha habido alguna regresión a la estabilización de esta característica en los últimos 10 días?

@kjetilkjeka Esta función se ha desestabilizado recientemente: #50121.

(Reapertura ya que se ha revertido la estabilización)

@kjetilkjeka Se ha revertido la estabilización de TryFrom en https://github.com/rust-lang/rust/pull/50121 junto con la estabilización del tipo ! , debido a la TryFrom<U, Error=!> for T where T: From<U> impl. El tipo ! no se estabilizó debido a https://github.com/rust-lang/rust/issues/49593.

Gracias por la explicación. ¿Significa esto que esta característica está esencialmente bloqueada en algunos cambios en la coerción del tipo de compilador? No puedo encontrar un problema que explique qué cambios se requieren, ¿se conoce la magnitud de los cambios en este momento?

¿Hay alguna razón fundamental por la que no podamos seguir adelante y estabilizar el rasgo TryFrom en sí mismo, y cualquier impl que no involucre ! , y simplemente posponer la estabilización de impls que involucren ! ? hasta después de la posible estabilización de ! ?

https://github.com/rust-lang/rust/pull/49305#issuecomment -376293243 clasifica las diversas implementaciones de rasgos posibles.

@joshtriplett Según tengo entendido, impl<T, U> TryFrom<T> for U where U: From<T> { type Err = !; } en particular no es compatible con versiones anteriores para agregar si TryFrom ya ha sido estable.

@SimonSapin
¿Por qué no es compatible con versiones anteriores para agregar?

(¿Y realmente necesitamos el impl de la manta?)

¿Por qué no es compatible con versiones anteriores para agregar?

Creo que no es compatible con versiones anteriores, ya que TryFrom podría haberse implementado manualmente entre TryFrom estabilizado y ! estabilizado. Cuando se agregó la implementación general, entraría en conflicto con la implementación manual.

(¿Y realmente necesitamos el impl de la manta?)

Creo que la impl general realmente tiene sentido cuando se escribe código genérico sobre TryFrom . Al referirse a todos los tipos que tienen la posibilidad de convertirse en T , la mayoría de las veces también querrá incluir todos los tipos que se pueden convertir en T con seguridad. Supongo que una alternativa podría ser exigir a todos que también implementen TryFrom para todos los tipos que implementen From y esperar la especialización antes de realizar la implementación general. Todavía tendría el problema de qué Err convertir en Result genérico. ! parece una buena manera de expresar y hacer cumplir programáticamente la infalibilidad de la conversión y, con suerte, vale la pena esperar.

Siempre podemos esperar hasta que la especialización esté disponible para proporcionar la impl general y estabilizar las diversas conversiones numéricas en el ínterin.

He visto varias solicitudes para estos en el contexto de personas que buscan ayuda sobre cómo hacer conversiones de enteros idiomáticamente en Rust, y hoy me encontré con alguien que se preguntaba específicamente cómo deberían convertir u64 a u32 con detección de errores.

No estoy convencido de que la especialización permita mágicamente agregar el impl general después del hecho. Mi comprensión de las propuestas actuales es que un rasgo debe optar por ser especializable, lo que puede no ser compatible con versiones anteriores de los rasgos existentes.

Para cuando esto se estabilice: _por favor_ agregue TryFrom y TryInto al preludio.

@SergioBenitez Lo hemos hecho en Nightly por un tiempo, y desafortunadamente fue un cambio importante para las cajas que definen su propio rasgo TryFrom o TryInto (para usar mientras el std unos son inestables), lo suficiente como para que lo hayamos revertido.

Creo que la lección que debe aprender aquí el equipo de std libs es que cuando hay una característica que queremos agregar al preludio, debemos hacerlo tan pronto como se implemente y no esperar a que se estabilice.

Sin embargo, para TryFrom y TryInto , una solución sería tener un preludio diferente para la edición de 2018. Esto se ha discutido informalmente antes, pero no lo encontré archivado, así que abrí https://github.com/rust-lang/rust/issues/51418.

Se implementó una solución genérica a un problema similar para la extensión.
métodos en https://github.com/rust-lang/rust/issues/48919 , por lo que inestable
los métodos de extensión emiten advertencias cuando el código estable choca.

Parece que podría hacer algo similar con nuevos términos agregados al
¿preludio?

El jueves, 7 de junio de 2018 a las 11:47 a. m. Simon Sapin [email protected] escribió:

@SergioBenitez https://github.com/SergioBenitez Lo hemos hecho en
Todas las noches durante un tiempo, y desafortunadamente fue un cambio importante para las cajas.
que definen su propio rasgo TryFrom o TryInto (para usar mientras el std
son inestables), lo suficiente como para que lo hayamos revertido.

Creo que la lección que debe aprender aquí el equipo de std libs es que cuando
hay un rasgo que podríamos querer agregar al preludio, deberíamos hacerlo
tan pronto como se implemente y no espere a que se estabilice.

Sin embargo, para TryFrom y TryInto, una solución sería tener un diferente
antesala de la edición de 2018. Esto ha sido discutido informalmente antes, pero
No lo encontré archivado, así que abrí #51418
https://github.com/rust-lang/rust/issues/51418 .


Estás recibiendo esto porque estás suscrito a este hilo.
Responda a este correo electrónico directamente, véalo en GitHub
https://github.com/rust-lang/rust/issues/33417#issuecomment-395525170 ,
o silenciar el hilo
https://github.com/notifications/unsubscribe-auth/AAC2lNbHvgBjWBk48-1UO311-LuUY5lPks5t6XUvgaJpZM4IXpys
.

En este caso, lo que choca no son los rasgos en sí mismos (el nombre del elemento en el preludio), sino los métodos que ese rasgo pone en el alcance. Tal vez todavía haya algún ajuste similar que podamos hacer, pero es más sutil.

@SimonSapin , ¿cuál es el plan actual para la estabilización TryFrom/TryInto? No pude encontrar nada concreto más allá de que se revirtió el 29 de abril.

@nayato Está bloqueado al estabilizar (nuevamente) el tipo nunca https://github.com/rust-lang/rust/issues/35121 , que a su vez está bloqueado en https://github.com/rust-lang/rust/ problemas/49593.

@SimonSapin El tipo nunca solo se usa aquí . ¿Podríamos estabilizar TryFrom sin esa implementación y aterrizarlo cuando nunca se ha estabilizado? TryFrom es una interfaz bastante importante incluso aparte de la equivalencia con Try .

Lamentablemente no. Sería un cambio importante agregar este impl general después de que se haya estabilizado el rasgo y las bibliotecas de crates.io hayan tenido la oportunidad de implementar, por ejemplo TryFrom<Foo> for Bar mientras que también tienen From<Foo> for Bar , como los impls se superpondría.

La decisión hasta ahora ha sido que hacer TryFrom "compatible" de esta manera con From era lo suficientemente importante como para bloquear la estabilización.

Pensamiento probablemente ingenuo y tonto:

¿Qué pasa si mientras tanto, rustc tiene un error de pelusa siempre activo que se activó para Error<_, !> , evitando cualquier impls en el espacio del usuario, permitiendo la adición de impl s más adelante?

O un impl<T: From<U>, U> TryFrom<U> for T inestable con cualquier tipo de error. ¿Pueden los impls de rasgos ser inestables?

@jethrogb No

No estoy seguro de entender completamente por qué esto está bloqueado en el tipo nunca, pero el hecho de que lo esté me sugiere que falta un mecanismo importante en Rust. Parece que debería haber una manera de reservar la implementación deseada para una definición futura. Tal vez ese mecanismo sea algo así como hacerlo inestable, pero idealmente también sería un mecanismo utilizable por cajas no estándar. ¿Alguien sabe de una propuesta de solución a este problema?

Está bloqueado debido a este impl:

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

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

@rust-lang/libs ¿Qué piensas de volver a enum Infallible {} en lugar de nunca escribir, para desbloquear?

Personalmente, no me importa tener enum Infalliable {} y luego cambiarlo a type Infalliable = ! .

Hablamos de esto en la reunión de clasificación de libs de ayer, pero no podíamos recordar si ese cambio estaría bien después de la estabilización o si ya habíamos rechazado esa idea debido a una posible rotura, o cuál sería esa rotura.

Lo que sí recordamos es que solo podemos hacer uno de esos reemplazos: si dos o más enumeraciones vacías de la biblioteca estándar más estables (tipos separados) luego se convierten en el mismo tipo, una caja que tenía algunos impl s para ambos se rompería a medida que los impls se superponen (o son idénticos). Por ejemplo impl From<std::convert::Invallible> for MyError y impl From<std::string::ParseError> for MyError .

Por cierto, tenemos std::string::ParseError que, por lo que sé, es la única enumeración vacía en la biblioteca estándar 1.30.0. Entonces, si podemos estar seguros de que no hay ningún otro problema con este plan, podríamos:

  • Mover string::ParseError a convert::Infallible
  • Vuelva a exportarlo en su ubicación anterior con pub use o pub type (¿esto hace alguna diferencia?)
  • Úselo en el impl general TryFrom
  • Más tarde, en la misma versión que cuando se estabiliza el tipo nunca, reemplace string::ParseError y convert::Infallible con type _ = ! y use ! directamente donde se usaron.
  • (Opcional) Más tarde, emita advertencias de obsolescencia para los alias antiguos

Habiendo agregado a regañadientes un rasgo de marcador de posición de TryFrom a mi propia caja, y admito que sin comprender completamente las implicaciones del RFC y los esfuerzos de estabilización, me sorprende que una manta TryFrom por From con el tipo de error Infallible / ! es lo que está retrasando esto? ¿No son estos objetivos secundarios, después de establecer TryFrom estándar estables y rasgos generales TryInto ? Quiero decir, incluso sin el aumento de From a TryFrom (propósito del cual no entiendo completamente), ¿no sería menos agitado si cada caja que lo necesita no agrega su propio TryFrom ?

El problema obvio si TryFrom se envía sin la implementación general para From es que las cajas podrían implementar TryFrom y From para los mismos tipos (posiblemente exactamente porque el la impl general no está allí), y se romperían cuando la impl general se agregue a libstd.

Aunque, ahora que lo pienso, ¿tal vez no sería un cambio radical con la especialización?

Hmm, perdóname de nuevo si me estoy perdiendo lo obvio . Es casi como si estas odiseas de RFC/seguimiento de 1,5 años necesitaran una sección de preguntas frecuentes para comprender la progresión lógica. ¿Qué pasaría si la guía fuera simplemente implementar From solo para conversiones infalibles y TryFrom solo para conversiones falibles? ¿No da eso un resultado final práctico similar, al mismo tiempo que proporciona menos obstáculos para enviarlo en cajas estándar y ajustarlas?

Sí, como dijo @glandium , agregar el impl general después de que los rasgos ya se hayan estabilizado es un cambio importante. La estabilización aún no está lista, y no está claro si permitiría este tipo de impl de intersección de todos modos (en lugar de solo impls "estrictamente más específicos").

Proporcionar orientación (¿docs?) Que diga que no escriba programas que se romperían no es lo suficientemente bueno para justificar cambios de ruptura. La rotura definitivamente seguiría ocurriendo.

¿Qué se necesita para que el plan descrito en https://github.com/rust-lang/rust/issues/33417#issuecomment -423073898 comience a funcionar? ¿Qué se puede hacer para ayudar?

Es una tarea un poco ingrata, pero ayudaría si alguien puede revisar este problema de seguimiento y https://github.com/rust-lang/rust/issues/35121 para verificar si hay un problema con ese plan que estamos He discutido antes y olvidado desde entonces, en particular si reemplazar enum Infallible con type Infallible = !; después de que la enumeración se mantuvo estable en una versión anterior podría ser un cambio importante.

¿Es necesario estabilizar la enumeración Infallible junto con el rasgo? Si se mantiene inestable, nadie puede nombrarlo, por lo que cambiarlo por ! más tarde debería estar bien.

@seanmonstar No, podría referirse a él usando <u16 as TryFrom<u8>>::Error y se considera un nombre estable. Testigo:

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

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

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

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

trait T3 {}

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

fn main() {}

El primer impl T3 no causa ningún error. Solo la segunda impl T3 provoca el error E0658 "uso de función de biblioteca inestable".

Eso es.... Vaya, solo pido que me muerdan >_<

Yo personalmente uso el truco de hacer público un tipo en un módulo no exportado para devolver tipos innombrables, y aunque alguien que haga lo que dijiste significaría una ruptura si lo hiciera, mis sentimientos son "vergüenza para ellos", y no considero ajustando el tipo innombrable como ruptura.

Personalmente, no me importa asegurarme de que cada una de estas enumeraciones que nunca en libstd sean iguales, y luego cambiarlas a un alias de tipo para ! cuando eso se vuelve estable. Me parece razonable.

Aparte de todo este alboroto de nunca escribir, ¿cuál es la elección de diseño para una conversión falible en un tipo que no sea Copy ? Se debe prestar atención para evitar dejar caer la entrada dada, de modo que pueda recuperarse en caso de falla.

Por ejemplo, con String::from_utf8 , podemos ver que el tipo de error contiene la entrada propia Vec<u8> para poder devolverlo :

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

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

Entonces, si obtuviéramos una implementación de String: TryFrom<Vec<u8>> , se esperaría que <String as TryFrom<Vec<u8>>>::Error sean FromUtf8Error , ¿verdad?

Sí, dar el valor de entrada "atrás" en el tipo de error es una opción válida. Tomar una referencia con impl<'a> TryFrom<&'a Foo> for Bar es otra.

En este ejemplo específico, sin embargo, no estoy seguro de que una TryFrom sea ​​apropiada. UTF-8 es solo una de las posibles decodificaciones de bytes en Unicode, y from_utf8 refleja eso en su nombre.

Es una tarea un poco ingrata, pero ayudaría si alguien puede revisar este problema de seguimiento y #35121 para verificar si hay un problema con ese plan que hemos discutido antes y olvidado desde entonces, en particular si reemplaza enum Infallible con type Infallible = !; después de que la enumeración se mantuvo estable en una versión anterior podría ser un cambio importante.

No hubo problemas concretos señalados con eso en este número o #35121. Había una preocupación en torno a la posibilidad de que ! fuera especial de alguna manera, los tipos desinhibidos no lo son. Pero no hay preocupaciones en el PR y está claro a partir de los comentarios de revisión de código que la enumeración se estabilizó era una posibilidad (aunque eso nunca sucedió). Aquí hay enlaces a lo que encontré.

Concepto original
Una preocupación abstracta
Adelante del equipo lib

Seguido por:

44174 29 de septiembre: se agregó el tipo Infalible

47630 14 de marzo: ¡estabilizar el tipo nunca!

49038 22 de marzo: Infalible convertido al tipo nunca.

49305 27 de marzo: TryFrom estabilizado

49518 30 de marzo: eliminado del preludio

50121 21 de abril: desestabilizado

En la nota de impls generales para cuando ! se estabilice,

¿funcionaría esto?

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

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

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

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

De esta forma, todas las conversiones infalibles (con From y Into ) obtienen un impl correspondiente TryFrom y TryInto donde Err es ! y obtenemos una implementación de TryInto por cada implementación de TryFrom la misma manera que obtenemos una implementación de Into por cada implementación de From .

De esta manera, si quisiera escribir un impl, probaría From luego Into luego TryFrom luego TryInto , y uno de estos funcionaría para su escenario , si necesita conversiones infalibles, usaría From o Into (según las reglas de coherencia) y si necesita conversiones falibles, usaría TryFrom o TryInto (basado en reglas de coherencia). Si necesita restricciones, puede elegir TryInto o Into en función de si puede manejar conversiones falibles. TryInto serían todas las conversiones, y Into serían todas las conversiones infalibles.

Luego podemos hacer pelusa contra un impl TryFrom<T, Err = !> o impl TryInto<T, Err = !> con una pista para usar impl From<T> o impl Into<T> .

Ah, no los vi (demasiados otros impls abarrotaban los documentos). ¿Podemos cambiar?

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

a

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

porque esto permitirá estrictamente más impls, y no castigaría a las personas que solo pueden impl Into por razones de coherencia, de esa manera también obtienen la impl automática por TryFrom , y los From impl se aplicaría transitivamente debido a la impl general de Into .

¿Le gustaría probarlo, ver si compila y enviar una solicitud de extracción?

Sí, me gustaría probar.

¿Hay algún caso en el que From<U> for T no se pueda implementar, pero Into<T> for U y TryFrom<U> for T sí?

si, por ejemplo

use other_crate::OtherType;

struct MyType;

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

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

Esto se debe a las reglas huérfanas.
TryFrom y From son iguales en cuanto a las reglas de huérfanos, y de forma similar TryInto y Into son iguales en cuanto a las reglas de huérfanos

@KrishnaSannasi su ejemplo se compilará, solo mire este ejemplo de patio de recreo

Creo que la regla de coherencia más actualizada se describe en este RFC y se debe permitir ambas implementaciones.

struct MyType<T>(T);

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

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

patio de juegos
Ah, sí, pero tan pronto como agrega parámetros genéricos, se cae. Si intenta compilar cada impl individualmente, el From no se compilará, mientras que el Into se compilará.


Otra razón por la que quiero este cambio es porque actualmente, si escribes una impl Into , no lo haces y no puedes obtener automáticamente una TryInto . Veo esto como un mal diseño porque es inconsistente con From y TryFrom .

Estoy más preguntando si hay algún caso en el que desee que TryFrom<U> for T se derive automáticamente de Into<T> for U , pero donde From<U> for T no se pueda implementar. Me parece una tontería.

Ciertamente entiendo querer una impl general de Into a TryInto , pero desafortunadamente el sistema de tipos actualmente no permite tal impl general porque entra en conflicto con los otros From a TryFrom y From a Into impls generales (o, al menos, eso es lo que yo entiendo).

Estoy bastante seguro de que estos casos de uso eran exactamente lo que se suponía que debía solucionar el enlace RFC @kjetilkjeka . Después de implementar eso, nunca debería haber un caso en el que pueda implementar Into pero no From .

@scottjmaddox No necesariamente quiero que Into implique TryFrom tanto como quiero que Into implique TryInto . También From y Into son semánticamente equivalentes, el hecho de que no podamos expresar eso en nuestro sistema de rasgos no significa que

TryFrom<U> for T se derivará automáticamente de Into<T> for U , pero where From<U> for T no se puede implementar.

es una tontería. En un mundo perfecto, no tendríamos la distinción y solo habría un rasgo para convertir entre tipos, pero debido a las limitaciones del sistema, ahora hemos terminado con dos. Esto no va a cambiar debido a las garantías de estabilidad, pero podemos tratar estos dos rasgos como el mismo concepto y avanzar desde allí.

@clarcharr Incluso si ese es el caso, todavía no hay forma de que Into impliquen TryInto y TryFrom impliquen TryInto directamente, como los dos los impls entrarían en conflicto. Me gustaría que Into implique TryInto , y que TryFrom implique TryInto . La forma en que propongo lo hará, aunque indirectamente.


Hice un comentario en mi solicitud de incorporación de cambios donde expuse mis principales razones para este cambio aquí


Esto es menos importante que las razones anteriores, pero también me gusta que con este impl, todas las conversiones infalibles ahora tendrán una contraparte falible, donde el tipo Err de la versión falible es ! .

Para que quede claro, quiero estos cuatro impls

  • From implica Into (para estabilidad)
  • TryFrom implica TryInto o TryInto implica TryFrom (porque son semánticamente equivalentes)
  • From implica TryFrom
  • Into implica TryInto

No me importa si se hacen directa o indirectamente.

O bien, podríamos hacer TryInto un alias:

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

No tengo idea de si esto realmente funcionaría, pero parece bastante simple.

¿Dónde se definiría el método into ?

@clarcharr Como dijo @SimonSapin , ¿dónde se definiría el método into? No tenemos alias de rasgo. Eso tendría que ser otro RFC. Y necesitaríamos bloquear este en ese RFC, lo cual no es deseable.

@KrishnaSannasi Actualmente es imposible tener los cuatro de From => Into , From => TryFrom , TryFrom => TryInto y Into => TryInto , porque todo lo que implementa From sería tener dos impl's competidores por TryInto (uno de From => Into => TryInto , y otro de From => TryFrom => TryInto ).

Dado que From => Into y TryFrom => TryInto son críticos, se debe sacrificar Into => TryInto . O al menos eso es lo que entiendo.

Sí, no pensé que el alias TryInto fuera correcto, así que simplemente ignórame ><

Sin embargo, estoy de acuerdo con lo que dice @scottjmaddox .

@scottjmaddox

Solo para aclarar un posible malentendido, estoy eliminando From impls automáticos TryFrom y reemplazándolos con Into impls automáticos TryFrom .

Actualmente es imposible tener los cuatro From => Into, From => TryFrom, TryFrom => TryInto y Into => TryInto

Esto es simplemente falso, simplemente mire la implementación propuesta.

Éste:
From -> Into -> TryFrom -> TryInto

From implementaciones automáticas Into
Into implementaciones automáticas TryFrom
TryFrom implementaciones automáticas TryInto

Por extensión, debido a los impls automáticos transitivos obtenemos
From implica TryFrom (porque From impls automáticos Into y Into impls automáticos TryFrom )
Into implica TryInto (porque Into impls automáticos TryFrom y TryFrom impls automáticos TryInto )
cada uno con 1 nivel de direccionamiento indirecto (creo que esto está bien)

Así que todas mis condiciones se han cumplido.
Podríamos tener todo

impl | niveles de indirección
-----------------------|-------------------
From implica Into | Sin direccionamiento indirecto
TryFrom implica TryInto | Sin direccionamiento indirecto
From implica TryFrom | 1 nivel de indirección
Into implica TryInto | 1 nivel de indirección


From -> Into -> TryInto -> TryFrom
También funcionaría, pero me gustaría mantener la consistencia y la versión original (visto arriba) es más consistente que esta versión


Nota sobre la terminología: (cuando los uso)

-> significa implementación automática
auto impl se refiere al impl real que se escribe.
implica significa que si tengo este impl también obtendré ese impl


También tenga en cuenta que hice una solicitud de extracción y todas las pruebas pasaron, lo que significa que no hay un agujero en mi lógica, esto es posible. Solo tenemos que discutir si se desea este comportamiento. Creo que lo es, porque preservará la consistencia (que es muy importante).

solicitud de extracción

@KrishnaSannasi Ahhh, ahora entiendo tu punto. Sí, eso tiene sentido y ahora veo cómo su cambio propuesto resuelve el problema y proporciona el comportamiento deseado. ¡Gracias por la completa explicación!

Editar : está bien, espera, sin embargo ... Todavía no entiendo por qué los impl generales actuales no son suficientes. Presumiblemente, hay un caso en el que podría implementar Into pero no From . Y, sin embargo, es posible derivar automáticamente TryFrom ?

Edición 2 : Bien, acabo de regresar y leer su explicación sobre cómo la regla huérfana puede evitar implementaciones de From . Todo tiene sentido ahora. Apoyo totalmente este cambio. Todavía es un poco frustrante que la regla de los huérfanos tenga tantas consecuencias no deseadas, pero no vamos a solucionar eso aquí, por lo que tiene sentido sacar lo mejor de las cosas.

¿Alguien de @rust-lang/libs estaría dispuesto a iniciar FCP en esto ahora que https://github.com/rust-lang/rust/issues/49593 se ha solucionado y never_type es un candidato para la estabilización? ¿una vez más?

Esta característica ya ha pasado por FCP para su estabilización. ( Propuesta , finalización ). Creo que no es necesario pasar por otro período de comentarios de 10 días.

Después de que haya aterrizado un PR de estabilización para el tipo nunca, podemos hacer un (nuevo) PR de estabilización para TryFrom / TryInto y usar rfcbot allí para asegurarnos de que los miembros del equipo lo vean.

¿Sería posible cambiar los tipos de enumeración vacíos existentes como FromStringError para que sean alias a ! junto con la estabilización?

@clarcharr Sí. Debido a la coherencia de impl de rasgos, solo podemos hacer esto para un tipo. Por suerte, solo tenemos std::string::ParseError . (Y esta es exactamente la razón por la que lo usamos en lugar de agregar un nuevo tipo para impl FromString for PathBuf en https://github.com/rust-lang/rust/pull/55148).

Sin embargo, esto no está relacionado con TryFrom / TryInto . Este es un tema para https://github.com/rust-lang/rust/issues/49691 / https://github.com/rust-lang/rust/issues/57012 , ya que debe suceder en el mismo ciclo de lanzamiento como estabilización de ! .

@SimonSapin ¿Sería posible fusionar mi solicitud de extracción (# 56796) antes de que se estabilice?

Claro, lo he agregado a la descripción del problema para que no perdamos la pista.

¡Gracias!

¿Podría esto ser integrado en el establo? Es una dependencia de argdata para CloudABI.

@mcandre Sí. Actualmente está esperando en https://github.com/rust-lang/rust/issues/57012 , que se desbloqueó recientemente. Tengo la esperanza de que TryFrom llegue a Rust 1.33 o 1.34.

Estoy harto de esperar esto y tengo tiempo libre (por fin). Si hay algún tipo de código o documentación que pueda hacer para impulsar esto, me ofrezco para ayudar.

56796 se ha fusionado. Entonces podemos marcar eso.

@icefoxen Supongo que en este momento lo mejor que puede hacer es proporcionar comentarios (y cambios) sobre los documentos de estos rasgos. En este momento los encuentro un poco escasos en comparación con los rasgos From y Into .

Trabajando en la documentación. Pequeño bache en el camino: Quiero assert_eq!(some_value, std::num::TryFromIntError(())); . Sin embargo, dado que TryFromIntError no tiene constructor ni campos públicos, no puedo crear una instancia de él. ¿Algún consejo sobre cómo proceder? Por ahora solo estoy haciendo assert!(some_value_result.is_err()); .

Lo siento si este es el lugar equivocado, pero parece que hay un error en los documentos para TryFromIntError : enumera impl Debug for TryFromIntError , cuando en realidad no hay código para ello. El compilador genera un error actualmente cuando intenta desenvolver un resultado de un uso de TryFrom.

¿Se supone que hay un impl de fmt::Debug en esto?

@marco9999

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

Tiene un atributo #[derive(Debug)] .

Hmm, sí, hay... ¿algo no funciona correctamente?

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

@ marco9999 Probablemente te falte una restricción genérica. TryFromIntError solo lo usan algunos tipos, pero su T podría ser cualquier cosa:

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

De todos modos, esto está un poco fuera de tema, lo siento a todos. IRC podría ser un mejor lugar para hacer estas preguntas.

Quiero assert_eq!(some_value, std::num::TryFromIntError(()));

@icefoxen No hay un valor útil asociado con TryFromIntError , por lo que tal afirmación no parece tener mucho valor. Si tiene un Result<_, TryFromIntError> y es un Err , no hay otro valor que pueda tener.

assert!(some_value_result.is_err());

Esto me parece razonable.

Gracias @glaebhoerl.

Debido a que se solucionó un error de bloqueo (https://github.com/rust-lang/rust/issues/49593), esperaba que el tipo nunca se estabilizara pronto® https://github.com/rust-lang/ rust/issues/57012 y desbloquee esto. Sin embargo, ha surgido un nuevo problema (https://github.com/rust-lang/rust/issues/57012#issuecomment-460740678), y tampoco tenemos consenso sobre otro (https://github.com /rust-lang/rust/issues/57012#issuecomment-449098855).

Entonces, en una reunión de libs la semana pasada, mencioné nuevamente la idea, creo que fue propuesta por primera vez por @scottmcm en https://github.com/rust-lang/rust/issues/33417#issuecomment -299124605, para estabilizar TryFrom y TryInto sin el tipo nunca, y en su lugar tienen una enumeración vacía que luego podría convertirse en un alias para ! .

La última vez que discutimos esto (https://github.com/rust-lang/rust/issues/33417#issuecomment-423069246), no podíamos recordar por qué no lo habíamos hecho la vez anterior.

La semana pasada, @dtolnay nos recordó el problema: antes de que ! se convierta en un tipo completo, ya se puede usar en lugar del tipo de retorno de una función para indicar que nunca regresa. Esto incluye tipos de puntero de función. Entonces, asumiendo que https://github.com/rust-lang/rust/pull/58302 aterriza en este ciclo, un código como este será válido en Rust 1.34.0:

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

Debido a que fn() -> ! y fn() -> Infallible son dos tipos diferentes (puntero), los dos impls no se superponen. Pero si reemplazamos la enumeración vacía con un alias de tipo pub type Infallible = !; (cuando ! se convierte en un tipo completo), entonces los dos impls comenzarán a superponerse y el código como el anterior se romperá.

Entonces, cambiar la enumeración a un alias sería un cambio importante. En principio, no lo permitiríamos en la biblioteca estándar, pero en este caso sentimos que:

  • Uno tiene que hacer todo lo posible para construir un código que se rompa con este cambio, por lo que parece poco probable que suceda en la práctica.
  • Usaremos Crater para obtener señal adicional cuando llegue el momento.
  • Si terminamos juzgando que la ruptura es lo suficientemente significativa, tener tanto el tipo nunca como una enumeración vacía con el mismo rol es una inconsistencia con la que podemos vivir.

En base a esta discusión, envié https://github.com/rust-lang/rust/pull/58302 , que ahora se encuentra en el período de comentarios finales.

58015 debería estar listo para revisión/fusión ahora.

@kennytm ¿No es posible referirse a ! en el establo ya? Por ejemplo, considere lo siguiente:

trait MyTrait {
    type Output;
}

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

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

Después de hacer esto, Void se refiere al tipo ! .

Eso parece un error, lo que significa que las garantías de estabilidad no se extienden a él. El uso del tipo nunca ( ! ) como un tipo en cualquier capacidad sigue siendo inestable, al menos hasta que se fusione #57012.

¿Cómo puedo ayudar con la documentación? :-)

Oh, pensé que https://github.com/rust-lang/rust/pull/58015 había aterrizado, pero aún no... Discutámoslo allí.

¿Podría el rasgo TryFrom tener un método para verificar si el argumento se puede convertir sin consumirlo?

fn check(value: &T) -> bool

Una forma de trabajar con la conversión imposible sin consumo podría ser devolver el valor no convertible consumido junto con el error asociado.

Vaya, esto debería haber sido cerrado por https://github.com/rust-lang/rust/pull/58302. Cerrando ahora.


@o01eg La forma típica de realizar una conversión sin consumo es implementar, por ejemplo TryFrom<&'_ Foo> en lugar de TryFrom<Foo> .

Espera... esto no debería cerrarse hasta que aterrice en el establo el jueves, ¿verdad?

No, cerramos los problemas de seguimiento cuando el PR que estabiliza la función aterriza en el maestro.

No, generalmente cerramos el problema de seguimiento cuando la estabilización o eliminación aterriza en la rama master . Después de eso, no queda nada que rastrear. (A menos que surja un error recientemente informado, pero lo manejamos por separado).

Los problemas de seguimiento son cerrados por el PR que los estabiliza. Dependiendo del ciclo de lanzamiento, esto podría ser hasta 12 semanas antes de que se publique la versión estable.

Entendido. ¡Gracias por la aclaración a todos! :)

@gregdegruy actualice su versión de Rust a 1.34 o superior.

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