Problema de seguimiento para https://github.com/rust-lang/rfcs/pull/1542
Hacer:
TryFrom
impl general para usar Into
en lugar de From
¿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.
std
se implementarán?impl TryFrom<T> for T
?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 implFrom<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.
@SimonSapin https://github.com/rust-lang/rfcs/pull/1216
@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
yInto
a los tipos de biblioteca estándar, espero que hagamos lo mismo paraTryFrom
yTryInto
.
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 unimpl 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
enTryFrom::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<...>
oTryFrom<...>
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:
TryFrom<str>
en la firma, que correctamente está en el módulo de conversión y es más obvio lo que haceTryFrom<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:
SomeType::default()
SomeType::from(other)
está implementado (puede escribirlo y ver si compila, sin buscar documentación)clone()
try_from
- oh espera :PTodos 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 impl
para U probablemente debería implicar TryFrom para 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 impl
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 soloT::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:
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:
TryFrom
está diseñado para conversiones falibles _solo_, por lo que no tiene cosas como u8 -> u128
o usize -> usize
.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 tenerimpl<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:
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)Infallible
como el tipo Error
en la impl generaltype Infallible = !;
como parte de la estabilización del tipo nuncaMe 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 ejemplotry!
)!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
oTryFrom
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 tenerTryInto<...>
oTryFrom<...>
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>
yAsRef<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:
string::ParseError
a convert::Infallible
pub use
o pub type
(¿esto hace alguna diferencia?)TryFrom
string::ParseError
y convert::Infallible
con type _ = !
y use !
directamente donde se usaron.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
contype 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:
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>
.
Ya tenemos estos dos impls:
https://doc.rust-lang.org/1.31.0/std/convert/trait.TryFrom.html#impl -TryFrom%3CU%3E
https://doc.rust-lang.org/1.31.0/std/convert/trait.TryInto.html#impl -TryInto%3CU%3E
Ah, 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 deInto<T> for U
, perowhere 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).
@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.
@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.
Referencias cruzadas: https://github.com/rust-lang/rust/pull/58302
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:
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.
@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.
Comentario más útil
Me gustaría que
!
se convierta en un tipo de vocabulario suficiente para queResult<_, !>
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.