Tracking issue for https://github.com/rust-lang/rfcs/pull/1542
To do:
TryFrom
blanket impl to use Into
instead of From
Is there a way to generically print an error with the original value if the conversion fails without requiring Clone
so a related method which panics on failure could have nice error messages?
A discussion of whether this should go in the prelude is pending for when this goes stable.
Apologies if this is covered somewhere else, but what would we like to see before marking this as stable? I'm pretty sure I've reimplemented this functionality a few times in various projects, so a common reusable trait would make me đ
We can bring it up for discussion for the next cycle.
đ This issue is now entering a cycle-long final comment period for stabilization đ
As a point of stabilization, the libs team would also like to add these traits to the prelude as part of their stabilization. This will require a crater run being 100% clean _at minimum_, but we're relatively confident that the current state of resolve makes it backwards compatible to add traits to the prelude.
I have a few questions about the purpose of these traits.
std
will these be implemented for?impl TryFrom<T> for T
?impl TryFrom<U> for T
if they already have impl From<U> for T
?cc @sfackler, could you expand on the current set of impls and some of the rationale as well?
I think in general the intuition should be exactly the same for that of From
/Into
, except when the conversion can fail. Just like we gradually add implementations of From
and Into
to standard library types, I expect we'll do the same for TryFrom
and TryInto
. The most important guideline here is that the conversion should be the "canonically obvious" one - if there's more than one reasonable way to convert one type to another, TryFrom
or From
may not be the right things to use.
_In general_, I don't think it should be expected that all types should impl TryFrom<T> for T
or manually lift an impl From<U> for T
. In particular, it's often not clear which error type to pick. However, those kinds of implementations are very much ones that can and should be defined to make a particular API work. For example, we have both of these kinds of implementations for various combinations of primitive integer types for the reasons outlined in the RFC. As another example, one ad-hoc trait that TryFrom
/TryInto
will replace is postgres::IntoConnectParams, which has a reflexive implementation to convert a ConnectParams
into itself.
In general, I don't think it should be expected that all types should impl
TryFrom<T> for T
or manually lift an implFrom<U> for T
.
When a TryFrom
conversion happens to be infallible the error type should be enum Void {}
, right? (Or a similar enum with some other name.) Which by the way sounds to me like a good reason to have a general purpose Void
type in std
.
@SimonSapin that would break an IntoConnectParams
style API as well as the integer conversion use case outlined in the RFC since the error types of the infallible and fallible conversions wouldn't match.
@SimonSapin https://github.com/rust-lang/rfcs/pull/1216
@sfackler Not if you use plain From
for the error types(e.g. try!
)! impl<T> From<!> for T
can and should exist for that purpose.
Just like we gradually add implementations of
From
andInto
to standard library types, I expect we'll do the same forTryFrom
andTryInto
.
That doesn't appear to have happened yet so I don't know if it's because nobody has got round to it or there are no applicable types in std
. If there are applicable types in std
it would be nice to see some implementations for them. For example are any of the following good implementations?
impl TryFrom<u32> for char
impl TryFrom<char> for u8
impl TryFrom<Vec<u8>> for String
impl TryFrom<&[u8]> for &str
_In general_, I don't think it should be expected that all types should
impl TryFrom<T> for T
or manually lift animpl From<U> for T
.
The issue is that if you want to use TryInto<T>
and T
isn't in your crate then you have to just hope that impl TryFrom<T> for T
has been implemented. That's an unfortunate limitation, one which doesn't exist for From
/Into
.
@SimonSapin that would break an
IntoConnectParams
style API as well as the integer conversion use case outlined in the RFC since the error types of the infallible and fallible conversions wouldn't match.
Does this mean the error type is somehow fixed based on the type you're converting to?
Why TryFrom::Err
and TryInto::Err
is not bounded by std::error::Error
?
not bounded by std::error::Error?
That would prevent Err
being ()
, which is a viable error type for infallible conversions.
If ()
is the error type, the function could return Err(())
. This doesn't make the function infallible. It just fails to provide a useful error when it does fail. When writing code that uses conversions which may or may not be fallible, I think the best way is to bound by Into
and write a specialization that uses TryInto
.
Once RFC 1216 is implemented, !
will be the appropriate error type for infallible conversions. I _think_ that would mean that an Error
bound on TryFrom::Err
/TryInto::Err
would be OK.
Iâd certainly like it if implementing From<T>
yielded a TryFrom<T, Err = !>
implementation (and ditto for Into
, of course).
I _think_ that would mean that an
Error
bound onTryFrom::Err
/TryInto::Err
would be OK.
Yes, we'll definitely have impl Error for ! { ... }
for precisely these sorts of cases.
I'm a bit worried about the type differences in the integer conversions use case, but it does seem like we should probably punt on stabilizing this until !
-as-a-type lands to have a chance to experiment.
In my POV all associated types that represents errors should be bounded by Error
. Sadly, we already left one type without it: FromStr::Err
(The only other public types are TryInto
and TryFrom
, checked with ack 'type Err;'
and ack 'type Error;'
). Let not make this again.
Discussed recently, the libs team decided to punt on stabilizing these traits until the !
type is worked out.
I guess that now this is no longer blocked, right?
Removing nomination as @sfackler is going to investigate !
types and these traits.
Will this have a change to get stabilized in the foreseeable future?
Why TryFrom::Err and TryInto::Err is not bounded by std::error::Error?
std::err::Error
doesn't exist in #![no_std]
builds. Also, in many cases there is no benefit in implementing std::err::Error
for an error type even when it is available. Thus, I would prefer to not have any such bound.
I have experimented with implementing this myself in my own library. I'd like to reiterate the concern expressed by @SimonSapin in https://github.com/rust-lang/rfcs/pull/1542#issuecomment-206804137: try!(x.try_into());
is confusing because the word âtryâ is used two different ways in the same statement.
I understand that many people thing that such things should be written x.try_into()?;
, however I am one of a substantial number of people (based on all the debates) that strongly prefer to not use the ?
syntax because of...all the reasons mentioned in all the debates.
Personally I think we should still try to find some pattern that doesn't require the try_
prefix on the names.
I don't feel particularly strongly about the naming, but I can't personally think of anything that's better.
It's already become semi-standard to use try_
for flavours of functions that return a Result
.
however I am one of a substantial number of people (based on all the debates) that strongly prefer to not use the
?
syntax because of...all the reasons mentioned in all the debates.
That debate's over now though isn't it? I mean, I sympathize with that side of the debate: I don't know why people said they find try!()
so annoying, ?
is less visible, encourages lazy error-handling and it seems like a waste of syntax for something we already had a perfectly good macro for (unless it gets extended to become something much more general in the future).
But that's in the past now. ?
is stable and it's not going away. So we all might as well all switch to it so that we're all using the same thing and we can stop worrying about name conflicts with try!
.
I'd like to reiterate the concern expressed by @SimonSapin in rust-lang/rfcs#1542 (comment)
Since Iâm cited by name, let me say that I donât really have that concern anymore. At the time I made this comment the ?
operator was a proposal whose future was uncertain, but now itâs here to stay.
Also, I think stabilizing sooner rather than latter is more important than another round of names bikeshedding. Itâs been months since the RFC was accepted and this has been implemented #[unstable]
.
It's already become semi-standard to use try_ for flavours of functions that return a Result.
This is the thing I find most strange about this feature. Most functions I write returns a Result
but I've never named any of those functions with a try_
prefix except when trying to experiment with this trait.
Also, I haven't found any practical advantage to writing this:
impl TryInto<X> for Y {
type Err = MyErrorType;
fn try_into(self) -> Result<X, Self::Err> { ... }
}
Instead, I can always just write this, much less syntactic overhead:
fn into_x(self) -> Result<X, MyErrorType> { ... }
I've never had to write generic code that was parameterized by TryInto
or TryFrom
despite having lots of conversions, so the latter form is sufficient for all my uses in types I define. I think having TryInto<...>
or TryFrom<...>
parameters seems like questionable form.
Also, I found that naming the associated error type Err
instead of Error
was error-prone as I always typed Error
. I noticed that this mistake was made even during the drafting of the RFC itself: https://github.com/rust-lang/rfcs/pull/1542#r60139383. Also, code that uses Result
already uses the name Err
extensively since it is a Result
constructor.
There was an alternate proposal that focused on integer types specifically and used terminology like âwidenâ and ânarrow,â e.g. x = try!(x.narrow());
that I'd also implemented. I found that proposal was sufficient for my uses of the functionality proposed here in my actual usage as I only ended up doing such conversions on built-in integer types. It is also more ergonomic and clearer (IMO) for the use cases it is sufficient for.
Also, I haven't found any practical advantage to writing this...
I sort-of agree. This trait is for where a thing can be used to create another thing but sometimes that process can fail - but that sounds like just about every function. I mean, should we have these 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)
}
}
This trait seems almost too-generic. In all the above examples it would be much better to explicitly say what you're actually doing rather than call try_into
.
I think having
TryInto<...>
orTryFrom<...>
parameters seems like questionable form.
Also agree. Why wouldn't you just do the conversion and handle the error before passing the value to the function?
std::err::Error doesn't exist in #![no_std] builds. Also, in many cases there is no benefit in implementing std::err::Error for an error type even when it is available. Thus, I would prefer to not have any such bound.
The one benefit to being bounded by std::error::Error
is that it can be the return value of another error's cause()
. I really don't know why there isn't a core::error::Error
, but I haven't looked into that.
Also, I found that naming the associated error type Err instead of Error was error-prone as I always typed Error. I noticed that this mistake was made even during the drafting of the RFC itself: rust-lang/rfcs#1542 (comment). Also, code that uses Result already uses the name Err extensively since it is a Result constructor.
FromStr
, which is stable, also uses Err
for its associated type. Whether it's the best name or not, I think it's important to keep it consistent throughout the standard library.
Whether or not TryFrom
and TryInto
are too general, I would really like to see fallible conversions, at least between integer types, in the standard library. I have a crate for this, but I think the use cases go far enough to make it standard. Back when Rust was alpha or beta, I remember using FromPrimitive
and ToPrimitive
for this purpose, but those traits had even bigger problems.
Error
can't be moved into core
due to coherence issues.
Also due to coherence issues Box<Error>
doesn't implement Error
, another reason we don't bound the Err
type.
There really isn't any need to bound it at the trait definition, anyway - you can always add that bound yourself later:
where T: TryInto<Foo>, T::Err: Error
I don't usually comment on these threads, but I've been waiting for this for a while and as expressed by several above, I'm not sure what the hold up is; I always want a from trait that can fail. I have code all over that is called try_from
... This trait _perfectly_ encapsulates that idea. Please let me use it on stable rust.
I also wrote a whole bunch of other things, but I've since deleted it as unfortunately, trait coherence prevents this trait from being as useful as it could be for me. E.g., I've essentially reimplemented a specialized version of this exact same trait for the primitive types for a highly generic parser of those types. Don't worry, I will rant about this some other time.
That being said, I believe str::parse will benefit greatly from this, as it also specializes a FromStr
trait as the bound - which is exactly (TryFrom<str>
) hand specialized.
So correct me if I'm wrong, but I believe stablilizing and using this for str::parse
will:
TryFrom<str>
in the signature, which properly is in the convert module and is more readily obvious what it doesTryFrom<str>
for their data types and get str.parse::<YourSweetDataType>()
for free, along with other try_from
they feel like implementing, which makes for better logical code organization.Lastly, abstractions aside, code re-use, bla bla, I think one of the understated benefits of traits like this is the semantic purchase they provide for beginners and veterans alike. Essentially they provide (or are beginning to provide, the more we stabilize) a uniform landscape with familiar, canonicalized behavior. Default
, From
, Clone
are really great examples of this. They provide a memorable function landscape which users can reach to when performing certain operations, and whose behavior and semantics they already have a good grasp on (learn once, apply everywhere). E.g.:
SomeType::default()
SomeType::from(other)
is implemented (you can just type it and see if it compiles, without reaching for documentation)clone()
try_from
- oh wait :PAll of these methods become commonplace and become apart of the Rust users toolkit, which imho reduces logical burden by allowing us to reach for familiar concepts (and thats just another name for a Trait!) whose documentation and semantic behavior we've already internalized.
Of course they don't always match up, in which case we specialize our datastructures, but traits like this reduce the API burden of users and coders alike by giving them access to concepts they've already studied, instead of reading documentation/finding your from_some_thing
, etc. Without even having to read your documentation, by using std traits, users can navigate your api and datastructures in a logical, robust and efficient manner.
In some sense, it's formalizing a gentleperson's agreement amongst ourselves, and makes it easier and more familiar for us to perform certain familiar operations.
And that's all I have to say about that ;)
This was previously blocked on an investigation into the possibility of using !
as the error type for infallible integer conversions. As the feature is currently implemented, this fails in even the most simple cases: https://is.gd/Ws3K7V.
Are we still thinking about changing the method names, or should we put this feature into FCP?
@sfackler That playground link works for me if I change the return type on line 29 from Result<u32, ()>
to Result<u32, !>
: https://is.gd/A9pWbU It does fail to recognize that let Ok(x) = val;
is an irrefutable pattern when val has Err type !, but that doesn't seem like a blocking issue.
@Ixrec a primary motivation for these traits was conversions to and from C integer typedefs. If I have a function
fn foo(x: i64) -> Result<c_long, TryFromIntError> {
x.try_into()
}
This will compile on i686 targets but not on x86_64 targets.
Similarly, say I want to replace the IntoConnectParams
type: https://docs.rs/postgres/0.13.4/postgres/params/trait.IntoConnectParams.html. How can I do that if there's a blanket impl<T> TryFrom<T> for T { type Error = ! }
? I need the reflexive implementation for ConnectParams
, but with a different concrete error type than !
.
It does fail to recognize that
let Ok(x) = val;
is an irrefutable pattern when val has Err type !
Note that there's a PR open for that.
If I have a function ...
This should work though
fn foo(x: i64) -> Result<c_long, TryFromIntError> {
let val = x.try_into()?;
Ok(val)
}
At the risk of being an annoying +1 comment, I just want to mention that after macros 1.1 arrives in Rust 1.15, try_from will be the last feature keeping Ruma on nightly Rust. Stable try_from is eagerly anticipated!
On a more substantial note...
This is the thing I find most strange about this feature. Most functions I write returns a Result but I've never named any of those functions with a try_ prefix except when trying to experiment with this trait.
That's a good observation, but I think the reason for the try_ prefix is not that it's necessary to identify the return type as a Result
, but to distinguish it from the non-fallible equivalent.
Also, I found that naming the associated error type Err instead of Error was error-prone as I always typed Error. I noticed that this mistake was made even during the drafting of the RFC itself: rust-lang/rfcs#1542 (comment). Also, code that uses Result already uses the name Err extensively since it is a Result constructor.
I agree on this one. Most other error types I have come across in libraries are named "Error" and I like that so far "Err" has only ever meant Result::Err
. It seems like stabilizing it as "Err" will result (no pun intended) in people constantly getting the name wrong.
@canndrew Of course it's possible to make work, but the entire point of that motivation for this feature is to make it easy to correctly handle these kinds of platform differences - we want to avoid the entire space of "compiles on x86 but not ARM".
I think I chose Err
for consistency with FromStr
but I would be very happy to switch to Error
before stabilizing!
the last feature keeping Ruma on nightly Rust
Likewise, the alternate playground backend only needs nightly for access to TryFrom
; the nightly serde stuff was too unstable for my liking, so I moved to the build script setup. With 1.15, I'll be moving back to #[derive]
. I'm eagerly awaiting this feature becoming stable!
@sfackler Sorry I wasn't following. In the case of integer conversions it sounds like what we really need is to not typedef c_ulong
to either u32
or u64
depending on platform but to somehow make it a newtype. That way a TryFrom<c_ulong>
impl can't interfere with a TryFrom<u{32,64}>
impl.
After all, we're always going to have "compiles one platform but not the other" problems if we define types differently on different platforms. It's a shame to have to sacrifice the otherwise-entirely-logical TryFrom<T> for U where U: From<T>
impl just so that we can supporting what seems like questionable practice.
I'm not seriously suggesting we block this RFC until we get an integer newtype RFC written+merged+stabilized. But we should keep it in mind for the future.
Similarly, say I want to replace the IntoConnectParams type:
What's the problem here though? Why wouldn't you use one error type for TryFrom<ConnectParams>
and another for TryFrom<&'a str>
?
I would not advocate that we break literally all FFI code in the world. Having tried and failed to pick up similar integer newtype wrappers like Wrapping
, there are massive ergonomic costs.
Is there a impl<T> From<!> for T
in the standard library? I don't see it in the docs but that might just be a rustdoc bug. If it's not there, then there's no way to convert the TryFrom<ConnectParams>
impl's !
Error
into the one I actually need.
Having tried and failed to pick up similar integer newtype wrappers like Wrapping, there are massive ergonomic costs.
I was thinking something more like being able to define your own integer types 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
Would that solve most the ergonomic issues? I don't actually like C++ user defined literals - or features in general that give people the power to change the language in confusing ways - but I guess it could end up being the lesser of two evils.
Is there a
impl<T> From<!> for T
in the standard library?
Not currently because it conflicts with the From<T> for T
impl. My understanding is that impl specialization should eventually be able to handle this though.
That sounds like something we should circle back to in a couple of years.
What's the timeline on stabilization of specialization and !
?
For specialization I don't know. For !
itself it's mostly a matter of whenever I can get my patches merged.
@canndrew I definitely agree that it shouldn't be implemented for everything. The docs say _attempt to construct Self
via a conversion_, but what counts as a conversion? How aboutâŠ_changing the same thing from one representation to another, or adding or removing a wrapper_? This covers your Vec<u8> -> String
and Mutex<T> -> MutexGuard<T>
, as well as things like u32 -> char
and &str -> i64
; while excluding SocketAddr -> TcpStream
and process::Child -> process::Output
.
I feel like impl From<T> for U
should probably imply TryFrom<T, Err=!> for U
. Without that, functions that take TryFrom<T>
s can't also take From<T>
s. (Using try_from()
|try_into()
for a concrete, infallible conversion would probably just be an anti-pattern. You _would_ be able to do it, butâŠit would be silly, so just don't.)
I feel like impl From
for U should probably imply TryFrom for U.
Agreed.
You would be able to do it, butâŠit would be silly, so just don't.
Sounds like a potential clippy lint.
@BlacklightShining I think it should be implemented for types where, given the output type, the "conversion" is obvious. As soon as multiple conversions are possible (utf8/16/32? serialising vs casting? etc...), it should be avoided.
IMHO:
Right now, we could easily provide a blanket implementation of TryFrom<&str>
for everything that implements 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)
}
}
I would like to see something like this added before try_from
is stabilised, or at least some reference to it in the docs. Otherwise, it may be confusing to have implementations of TryFrom<&'a str>
and FromStr
that differ.
I agree that that would be a good inclusion, but it might conflict with the proposed Try -> TryFrom impl?
@sfackler potentially. It'd be perfectly fine if TryFrom
, TryInto
, and FromStr
all referenced each other in the docs, but my main concern is that there's no existing standard that says whether str::parse
and str::try_into
return the same value.
I'd be fine with a soft request in the docs that people should implement them to have the same behaviour, although I can definitely see cases where someone might think that they can be different.
For example, let's say that someone creates a Password
struct for a website. They might assume that "password".parse()
would check the password for validity, then convert it into a hash, whereas Password::try_from("1234abcd")
might assume that "1234abcd"
is already a hash stored in the database and try to parse it directly into a hash which can be compared.
This makes sense, considering how the wording of parse
implies that some level of parsing is done, whereas try_from
is just a type conversion. However, in reality, we might want to clarify that both of these functions intend to perform the same thing.
Even though the language team has proposed to close the RFC for deprecating anonymous parameters, they all seem to agree that ideally we would stop creating new code that uses anonymous parameters. With that in mind, could we update the signature of try_from
/try_into
to give the parameters names? Or would it be more important to maintain symmetry with from
/into
?
Also, would it be useful to write a summary of major unanswered questions still remaining? I'm really hoping we can decide to stabilize this for the next release cycle. As I mentioned, it's the only remaining nightly feature I use a lot. :}
@jimmycuadra certainly yeah! Want to send a PR adding some parameter names?
With respect to the current state of things, I believe the only unanswered question is if there should be a
impl<T, U> TryFrom<U> for T
where T: From<U>
{
type Error = !;
fn try_from(u: U) -> Result<T, !> {
Ok(T::from(u))
}
}
This adds some nice amount of symmetry, but makes things a bit more annoying to deal with in many cases since you no longer have a single error type for the things you want to convert.
type Error = !;
I suggest doing that it in a separate RFC that takes into account the result of whatever is decided about all the undecided stuff regarding !
.
@sfackler I think that it'd be important to factor in the stuff I mentioned about FromStr
too. Should we have a similar impl for FromStr
implementors, or should they be allowed to be distinct, or should we just document that they should be the same but don't have to be?
I'm of the opinion that TryFrom<str>
and FromStr
should be functionally identical, and the documentation should make it clear that implementations of the two are intended to be identical. Implementing one should also give you the other, at least in terms of allowing you to use str::parse
. If Rust had had TryFrom
from the start, FromStr
would never have been needed. For that reason, I would also document TryFrom<str>
to be the preferred form for new code.
@jimmycuadra in that case, we should modify parse
to use TryFrom
and then put a blanket impl for FromStr
-> TryFrom
.
If we're going change str::parse
to be implemented in terms of TryFrom
, should we also change other implementations of FromStr
for concrete types similarly (i.e. all the implementors in this list: https://doc.rust-lang.org/stable/std/str/trait.FromStr.html)? Should the docs for FromStr
be updated to suggest using TryFrom
instead? Should the current concrete implementations of FromStr
have their docs moved to the TryFrom
version?
I think for backwards compatibility we can't change FromStr
, right?
Removing FromStr
in favor for TryFrom<&str>
is definitely something to keep in mind for Rust 2.0.
Yep, once this feature stabilizes we'll file an issue and tag it with 2.0-breakage-wishlist.
One thing to also consider is adding a parse_into
method to String
which uses TryFrom<String>
. I find myself implementing TryFrom
for both often if a type internally stores a String
but still requires validation.
If we're going to leave impl
I think that the problem is that it'd be difficult to stabilise that feature after it's been stabilised and people have provided impls for both.
I would expect that already having a T : From<U>
would put U : TryFrom<T>
in the "obvious API hole" category of change when the implementation is reasonable.
That implies there should at least be a T : TryFrom<T>
with Error = !
, but the version for any infallible From
is clearly better than that.
IMO there isn't really a clear distinction between whether From
should provide a TryFrom
implementation or whether TryFrom
should provide a From
implementation.
Because on one hand, you could consider T::from(val)
to be just T::try_from(val).unwrap()
, and on the other, you could consider T::try_from(val)
to be just Ok(T::from(val))
. Which is better? I don't know.
you could consider
T::from(val)
to be justT::try_from(val).unwrap()
I disagree with that one. From
implementations are not expected to ever panic. Only the other way around makes sense.
@clarcharr Because From shouldn't panic, the options are From
in terms of TryFrom<Error=!>
or the other way around. But I'd hate to have the usual advice be "You should implement TryFrom
with type Error = !
" instead of "You should implement From
".
Any way to get some movement on stabilizing this? We're running out of time before 1.18 goes into beta. @sfackler?
@rfcbot fcp merge
Team member @sfackler has proposed to merge this. The next step is review by the rest of the tagged teams:
No concerns currently listed.
Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
@sfackler: just to check in, are we good on various concerns around !
and blanket impls? I know we talked about this in the libs meeting, but it'd be helpful to get a summary here.
Isn't the main problem with stabilising this now is that such a change can't be made without breaking backwards compatibility? Or is the solution to just not move forward with that change?
I think the current set of impls is untenable. I could understand either of these positions:
TryFrom
is intended for fallible conversions _only_, so doesn't have things like u8 -> u128
or usize -> usize
.TryFrom
is intended for _all_ conversions, some of which are infallible and thus have an uninhabited TryFrom::Error
type.But right now things are in an odd hybrid state where the compiler will insert checking code for an i32 -> i32
conversion and yet you can't do a String -> String
conversion.
What are the objections to !
as an Error type? The only thing I noticed in a quick skim was "but makes things a bit more annoying to deal with in many cases since you no longer have a single error type for the things you want to convert", but I'm not convinced I agree with that since you have to assume you got passed something custom with a custom error type in generic context no matter what.
Isn't the main problem with stabilising this now is that such a change can't be made without breaking backwards compatibility? Or is the solution to just not move forward with that change?
I'm of the opinion that it's overzealous to add a generic implementation of TryFrom
when From
is implemented. Although it's semantically true that if there is a From
implementation, there is a TryFrom
implementation that cannot produce an error, I don't see this provided implementation being practically useful at all, let alone a common enough need that it should be provided by default. If someone really needs that behavior for their type for some reason, it's just one simple implementation away.
If there's an example of a case when you'd ever use try_from
instead of from
for an infallible conversion, I could certainly change my mind.
What are the objections to ! as an Error type?
!
is months away from stabilization. Pick one of stabilizing TryFrom
in the near future or having the impl<T, U> TryFrom<U> for T where T: From<U>
.
Have we considered using trait aliases (rust-lang/rfcs#1733) here? When that lands, we can alias From<T>
to TryFrom<T, Error=!>
, making the two traits one and the same.
@lfairy That would break user impl
s of From
, alas.
@glaebhoerl Yeah, you're right đ„ The motivation section of that RFC mentions aliasing impl
s but the actual proposal disallows them.
(Even if it didn't, the methods have different names etc.)
That could go under a 2.0 wishlist but regardless it's not going to happen without breaking anything.
First of all, thank you @sfackler for a great conversation about this on IRC. After letting things sit in my head for a bit, here's where I ended up.
Pick one of stabilizing
TryFrom
in the near future or having theimpl<T, U> TryFrom<U> for T where T: From<U>
.
I think the core question here is whether infallible conversions belong in the trait. I think that they do, for things like the infallible conversions in the RFC and for generic use (analogous to how there's the seemingly-useless T:From<T>
). Given that, what I want most is to avoid a world where every type implementer is expected to impl TryFrom<MyType> for MyType
, and every From
impl should also result in a TryFrom
impl. (Or get bugs filed later for not providing them.)
So could we have the blanket impl without stabilizing !
? I think there's a way, since we already have !
-like types in the library, such as std::string::ParseError
. ("This enum is slightly awkward: it will never actually exist.")
A sketch of how this could work:
core::convert::Infallible
, implemented exactly like std::string::ParseError
. (Maybe even change the latter to a type alias for the former.)impl<T> From<Infallible> for T
so that it's compatible in ?
with any error type (see the c_foo
stuff later)Infallible
as the Error
type in the blanket impltype Infallible = !;
as part of stabilizing the never typeI'll volunteer to make a PR doing that, if it would be helpful to concretize it.
As for c_foo
: The above would continue to allow code like this:
fn foo(x: c_int) -> Result<i32, TryFromIntError> { Ok(x.try_into()?) }
But it would make code like this a portability "footgun", because of the different error types
fn foo(x: c_int) -> Result<i32, TryFromIntError> { x.try_into() }
Personally, I'm not concerned by that difference because so long as c_int
is a type alias, there's a "full-auto" footgun:
fn foo(x: c_int) -> i32 { x }
And in general, code expecting the associated type on a trait to be the same for different impls feels like a code smell to me. I read TryFrom
as "generalize From
"; if the goal were "better conversions between integer subsets"--which also feels useful--then the "always the same error type" is logical, but I'd expect something targeted in std::num
instead, probably like num::cast::NumCast
(or boost::numeric_cast
).
(Aside: with #[repr(transparent)]
in FCP merge, maybe the c_foo
types can become newtypes, at which point these conversions could be more consistent. The From&TryFrom impls could codify the C "char <= short <= int <= long" rules, as well as the standard-required minimum sizes for them as things like c_int:From<i16>
or c_long:TryFrom<i64>
. Then the conversion above would be i32:TryFrom<c_int>
on all platforms, with always the same Error
type, and the problem goes away.)
Regarding "This enum is slightly awkward: it will never actually exist."
Is there a reason why the error type couldn't just be unit? Why bother with the unique ParseError struct if the conversation can never error?
@sunjay ()
is the type system representation of "this can happen, but there's nothing interesting to tell you when it does". Uninhabited types (like !
and std::string::ParseError
) are the opposite, the way the type system says "this situation cannot happen ever, so you don't need to deal with it."
@jimmycuadra
If there's an example of a case when you'd ever use try_from instead of from for an infallible conversion, I could certainly change my mind.
@scottmcm
I think the core question here is whether infallible conversions belong in the trait.
Here's my use case: I have a config file format where values can be bool, numeric, or string, and a macro for writing literal config values where the keys can be either enum variants or string. For example:
let cfg = config![
BoolOpt::SomeCfgKey => true,
"SomeOtherCfgKey" => 77,
];
Long story short, the macro ends up expanding to a list of ($k, $v).into()
calls. I'd like to have a check in the conversion for string keys to make sure they name a valid config option, i.e. implementing TryFrom<(String, ???)>
and changing the macro to use ($k, $v).try_into()
. It would make it harder to do all this if there wasn't a single method name for the macro to use for all the conversions.
:bell: This is now entering its final comment period, as per the review above. :bell:
I actually really like the idea of:
impl<U: TryFrom<T, Error=!>> From<T> for U {
fn from(val: T) -> U {
val.unwrap()
}
}
Because anyone who wants TryFrom<Error=!>
can implement it, but people can still implement From
if they like. Perhaps we could ultimately deprecate From
but we don't have to.
@scottmcm's plan for using an empty enum sounds great to me.
@Ericson2314 you wrote:
Not if you use plain
From
for the error types(e.g.try!
)!impl<T> From<!> for T
can and should exist for that purpose.
How would this work in practice? Say I'm trying to write a function like this:
fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError>
Except this of course doesn't work, I need to specify Error=
on TryInto
. But what type should I write there? MyError
seems obvious but then I can't use MyType
with the blanket TryFrom
impl.
Are you suggesting the following?
fn myfn<E: Into<MyError>, P: TryInto<MyType, Error=E>>(p: P) -> Result<(), MyError>
That seems quite verbose.
There is a more general question of how this is supposed to work if I want multiple âinfallibleâ conversions for the same type but with different error types.
Maybe the TryFrom
definition should be changed to the following:
pub trait TryFrom<T, E>: Sized {
type Error: Into<E>;
fn try_from(t: T) -> Result<Self, E>;
}
You can constrain the error to be convertible into MyError
without having to give it an explicit name, like
fn myfn<P: TryInto<MyType>>(p: P) -> Result<(), MyError> where MyError: From<P::Error>
it's still a little verbose, but really does state the constraints for calling the function nicely (playground)
EDIT: And trying with a variant like P::Error: Into<MyError>
doesn't actually work with ?
since there is no blanket implementation of From
for Into
. Changing TryFrom
as you've shown I would expect to run into the same issue.
The final comment period is now complete.
@jethrogb heh you quoted me from a year ago so I had to think for a bit. @Nemo157 is absolutely right and that seems reasonable.
In the specific case of !
we ought to have a blanket impl, but I recall it overlaps another. It's an annoying overlap as both implementations agree on implementation---undefined behavior / dead code.
A comment on this from another issue: https://github.com/rust-lang/rust/pull/41904#issuecomment-300908910
Anyone from the libs team have thoughts on scottmcm's idea? It sounds like a great approach to me, and I'd like to continue pushing this feature forward after missing another release cycle.
The libs team talked about this again a couple of weeks ago (sorry for the delay in writing this up)! We came to the conclusion that the source of the troubles over this feature were primarily that the FFI case doesn't really fit with any other use case of these traits - it's unique in that you're calling it on concrete types through aliases that vary based on target.
So, the basic plan of action is to add the impl<T, U> TryFrom<T> for U where U: From<T>
and remove the explicit impls for integer conversions that are infallible. To handle the FFI use case we'll make a separate API.
Using a type alias to avoid blocking on !
is an interesting one. My only concern there is if !
is more "special" than normal uninhabited types that would cause breakage when we swapped the alias from an uninhabited enum to !
.
I've opened a PR for the "separate API" for integral types: https://github.com/rust-lang/rust/pull/42456
I've never had to write generic code that was parameterized by
TryInto
orTryFrom
despite having lots of conversions, so the latter form is sufficient for all my uses in types I define. I think havingTryInto<...>
orTryFrom<...>
parameters seems like questionable form.
I intend to use TryFrom
once it's stable as part of a derived trait, and it would be really weird to call ad-hoc intrinsic methods on some types as part of a derive
macro.
Please don't remove this.
I've never had to write generic code that was parameterized by TryInto or TryFrom
Even if thatâs the case, I donât think that makes TryInto
and TryFrom
much less useful. I use Into
and From
all over the place in non-generic contexts. Adding impl
s of standard library traits feels much more "normal" and "expected" than a bunch of ad-hoc inherent conversion methods.
I've never had to write generic code that was parameterized by TryInto or TryFrom
From one of my projects:
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,
});
}
}
Is it expected that implementations of these traits will follow any particular laws? For example, if we can convert A to B and B to A, is it required that the conversion is invertible when it succeeds?:
#![feature(try_from)]
use std::convert::{TryFrom, TryInto};
fn invertible<'a, A, B, E>(a: &'a A) -> Result<(), E>
where A: 'a + TryFrom<&'a B>,
A: PartialEq,
B: 'a + TryFrom<&'a A>,
E: From<<A as TryFrom<&'a B>>::Error>,
E: From<<B as TryFrom<&'a A>>::Error>,
{
let b = B::try_from(a)?;
let a2 = A::try_from(&b)?;
assert!(a == &a2);
Ok(())
}
edit: s/reflexive/invertible/
@briansmith Given how From
works, I'd say it is not invertible this way.
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);
}
So I'm wondering about the current implementations of this trait. As came up in #43127 (see also #43064), I don't know if it's purely because the implementation uses i/u128, but it looks like calls TryInto are not inlined. This is not very optimal, and not really in the spirit of zero-cost abstractions. E.g using <u32>::try_into<u64>()
should optimize down to a simple sign extend in the final assembly (provided the platform is 64-bit), but instead it's resulting in a function call.
Is there a requirement that the implementations of the trait has to be the same for all integer types?
According to #42456 we will probably not have impl TryFrom
directly on the integer types, but how that "NumCast" trait (which #43127 should switch to) looks like is still being drafted.
Regardless of whether they end up moving to another trait, these conversions are implemented today in libcore and can be used in libcore. I think that using u128
/ i128
for all integer conversions was done for source code simplicity. We probably should instead have different code depending of whether the source type is wider or narrower than the destination. (Possibly with different macro calls based on #[cfg(target_pointer_width = "64")]
v.s. #[cfg(target_pointer_width = "32")]
v.s. #[cfg(target_pointer_width = "16")]
.)
Reminder: not much is needed to unblock this one! If you're up for it, take a look at @sfackler's summary and feel free to ping me or other libs team members for guidance.
I didn't realize there was buy-in from the libs team that we could use @scottmcm's idea as a workaround for the bang type being unstable. I can work on a PR to make the changes @sfackler mentioned using the workaround.
Awesome, thanks @jimmycuadra!
Most of this discussion seems to revolve around implementing TryFrom
for integer types. Whether or not TryFrom
becomes stabilized shouldn't only be because of these types, in my opinion.
There are other neat conversions that would benefit from these traits such as TryFrom<&[T]>
for &[T; N]
. I recently submitted a PR to implement exactly this: https://github.com/rust-lang/rust/pull/44764.
Conversions like these are important enough to me for TryFrom
to be stabilized.
With #44174 merged, I believe this is now unblocked.
That PR removed the automatic implementation of FromStr
for any type that implements TryFrom<&str>
because the type system can't currently support it, even with specialization. The intent is that FromStr
and parse
be deprecated in favor of TryFrom<&str>
and try_into
once this feature is stabilized. It is unfortunate to lose interim compatibility between the twoâif anyone has ideas for a stopgap, please speak up.
If there are no more changes to be made and someone on the libs team green lights this for stabilization, I can do the stabilization PR and a PR to deprecate FromStr
/parse
.
and a PR to deprecate FromStr/parse.
Deprecation warnings should not be added to Nightly until the replacement is available on Stable (or until https://github.com/rust-lang/rust/issues/30785 is implemented), so that it is possible at any time to make a crate build without warnings on all three release channels.
I missed the other PR since references don't result in email notifications. I notice there's a specific impl From<Infallible> for TryFromIntError
. Shouldn't that be impl<T> From<Infallible> for T
as discussed?
@jethrogb Sadly, that conflicts with impl<T> From<T> for T
so can't be done (until we get intersection impls? -- and using !
doesn't work either there).
@scottmcm ah of course.
I don't think you need intersection impls? Isn't that just straight up specialization?
I haven't read the other comments, but TryFrom
is broken for me right now (it worked fine before).
rustc version:
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
The relevant code section that rust complains about is here: https://github.com/fschutt/printpdf/blob/master/src/types/plugins/graphics/two_dimensional/image.rs#L29-L39 and https://github.com/fschutt/printpdf/blob/master/src/types/plugins/graphics/xobject.rs#L170-L200 - it compiled fine a few weeks ago, which is why the library still has the "build passing" badge.
However, on the latest nightly build, TryFrom
seems to break:
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`.
So, supposedly it has a duplicated implementation in crate core
. If anyone could look into this, that'd be great, thanks.
@fschutt The conflicting impl is probably impl<T, U> TryFrom<T> for U where U: From<T>
, added in https://github.com/rust-lang/rust/pull/44174. There could exist a T
such as both T: ImageDecoder
and Image: From<T>
.
Is there anything that is still needed for this to leave the feature gate?
If https://github.com/rust-lang/rust/issues/35121 is stabilized first we could remove the Infallible
type introduced in https://github.com/rust-lang/rust/pull/44174 and use !
instead. I donât know if this is considered a requirement.
I think the main blocker here is still the integer types. Either we use a separate Cast
trait for integer types https://github.com/rust-lang/rust/pull/42456#issuecomment-326159595, or make the portability lint #41619 happen first.
So I used to have an enum that implemented TryFrom
for AsRef<str>
, but this broke a few months ago. I thought it was a bug introduced in nightly that would go away with time, but it appears this is not the case. Is this no longer a supported pattern for TryFrom
?
impl<S: AsRef<str>> TryFrom<S> for MyEnum {
type Error = &'static str;
fn try_from(string: S) -> Result<Self, Self::Error> {
// Impl here
}
}
...
What other options are there for converting from both &str
and String
?
@kybishop does MyEnum
implement FromStr
? That may be the source of your breakage.
@nvzqz it does not, though I was recommended to use TryFrom
via the Rust IRC since it is a more generalized solution. TryFrom
initially worked great until w/e breaking change occurred in nightly a few months ago.
EDIT: Do you mean I should switch to implementing FromStr
, or that if it _did_ impl FromStr
, that could cause breakage?
EDIT 2: Here is the full impl, rather short and simple for those curious: https://gist.github.com/kybishop/2fa9e9d32728167bed5b1bc0b9becd97
@kybishop is there a particular reason you want an implemention for AsRef<str>
rather than &str
?
@sfackler I was under the impression it allows conversion from both &str
and String
, though I'm still a bit of a Rust newbie, so maybe I'm misunderstanding exactly how AsRef<str>
is used. I'll try going off &str
and see if AsRef
was allowing something &str
does not.
@kybishop Same as https://github.com/rust-lang/rust/issues/33417#issuecomment-335815206, and as the compiler error message says, this is a real conflict with the impl<T, U> std::convert::TryFrom<U> for T where T: std::convert::From<U>
impl that was added to libcore.
There could be a type T
(possibly in a downstream crate) that implements both From<MyEnum>
andAsRef<str>
and has MyEnum: From<T>
. In that case, both impls would apply, so both impls are not allowed to exist together.
@SimonSapin understood. So what are the options for people wanting to convert from both &str
and &String
? Just have to impl TryFrom
for both?
EDIT: I guess I can just eat the extra work and call .as_ref()
on String
s. I can then just have a single TryFrom
impl for str
.
Yes, two impls (possibly with one based on the other, as you pointed out) should work as long as MyEnum
doesnât implement From<&str>
or From<String>
.
@kybishop String
implements Deref<str>
, so type inference should allow &String
to coerce into &str
when passing it into the TryFrom
impl. Calling as_ref
may not always be required.
If anyone is looking for a quick example of this: https://play.rust-lang.org/?gist=bfc3de0696cbee0ed9640a3f60b33f5b&version=nightly
With https://github.com/rust-lang/rust/pull/47630 about to stabilize !
, is there appetite for a PR to replace Infallible
with !
here?
A better route to follow would be to make an alias. It conserve expressiveness and use the adapted language feature.
type Infallible = !;
Just jumping in. Iâm with @scottmcm on that.
However, this adds the extra overhead that this feature (TryInto
/TryFrom
) now depends on another instable feature â never_type
.
Also, Infallible
has the advantage that it gives more information / semantic on why the type cannot be constructed. Iâm opinionated with myself right now.
I'd like !
to become enough of a vocabulary type so that Result<_, !>
would be intuitively read as "infallible result", or "result that's (literally) never Err." Using an alias (never mind a separate type!) seems to me a bit redundant and I can see it causing at least to myself a momentary pause when reading type signaturesâ"wait, how was this different from !
again?" YMMV, of course.
@jdahlstrom I totally agree. Weâd need to introduce that in the Rust book or the nomicon so that itâs âground truthâ and friendly.
It's now been two years since the RFC for this API was submitted.
~@briansmith: There is a stabilization PR in progress.~
EDIT: Or maybe I am too tired...
You linked the PR of the !
type stabilisation.
Since the !
stabilization PR just got merged, I've submitted a PR for replacing convert::Infallible
with !
: #49038
https://github.com/rust-lang/rust/pull/49038 is merged. I believe this was the last blocker to stabilization, please let me know if I missed an unresolved issue.
@rfcbot fcp merge
rfcbot is not responding because another FCP has already been completed before at https://github.com/rust-lang/rust/issues/33417#issuecomment-302817297.
Uhmm, there are a few impls that should be revisited. Now that we have impl<T, U> TryFrom<U> for T where T: From<U>
, remaining impls of TryFrom
that have type Error = !
should either be replaced with impls of From
, be removed, or be made fallible (changing the error type to some non-uninhabited type).
Those in that case that I can find involve usize
or isize
. I suppose that the corresponding From
impls do not exist because their fallibility depends on the target pointer size. Indeed the TryFrom
impls are generated differently for different targets: https://github.com/rust-lang/rust/blob/1.24.1/src/libcore/num/mod.rs#L3103-L3179 This is probably a portability hazard.
generated differently for different targets
To clarify: different method bodies for different target_pointer_width
in the same impl
are fine (and likely necessary), different APIs (error types) are not.
Stabilization PR: #49305. After some discussion there, this PR also removes some TryFrom
impls that involve usize
or isize
because we havenât decided between two different ways to implement them. (And we can of course only have one.)
Dedicated tracking issue for those: https://github.com/rust-lang/rust/issues/49415
TryFrom
worked perfectly on rustc 1.27.0-nightly (ac3c2288f 2018-04-18)
without any feature gating but broke when compiled with rustc 1.27.0-nightly (66363b288 2018-04-28)
.
Have there been any regressions to stabilization of this feature the last 10 days?
@kjetilkjeka This feature has been recently unstabilized: #50121.
(Reopening since stabilization has been reverted)
@kjetilkjeka The stabilization of TryFrom
has been reverted in https://github.com/rust-lang/rust/pull/50121 together with the stabilization of the !
type, because of the TryFrom<U, Error=!> for T where T: From<U>
impl. The !
type was unstabilized because of https://github.com/rust-lang/rust/issues/49593.
Thanks for explaining. Does this mean that this feature is essentially blocked on some changes to the compiler type coercion? I can't find an issue explaining what changes are required, is the magnitude of changes known at this point?
Is there some fundamental reason that we couldn't go ahead and stabilize the TryFrom
trait itself, and any impls that do not involve !
, and just defer stabilizing impls involving !
until after the possible stabilization of !
?
https://github.com/rust-lang/rust/pull/49305#issuecomment-376293243 classifies the various possible trait implementations.
@joshtriplett As far as I understand, impl<T, U> TryFrom<T> for U where U: From<T> { type Err = !; }
in particular is not backward-compatible to add if TryFrom
has already been stable.
@SimonSapin
Why is that not backward-compatible to add?
(And do we really need the blanket impl?)
Why is that not backward-compatible to add?
I think it's not backward compatible since TryFrom
could have been implemented manually between TryFrom
was stabilized and !
was stabilized. When the blanket implementation was added it would be conflicting with the manual implementation.
(And do we really need the blanket impl?)
I think the blanket impl really makes sense when writing generic code over TryFrom
. When referring to all types that have a possibility for conversion into T
you would most of the time also want to include all types that can be converted into T
for sure. I assume an alternative could be to require everyone to also implement TryFrom
for all types that implement From
and wait for specialization before making the blanket impl. You would still have the problem of what Err
to make Result
generic over. !
seems like a nice way to both express and programatically enforce the infallibility of the conversion, and hopefully worth the wait.
We could always wait until specialization is available to provide the blanket impl, and stabilize the various numeric conversions in the interim.
I've seen various requests for these in the context of people seeking help for how to do integer conversions idiomatically in Rust, and just today ran into someone who specifically wondered how they should convert u64 to u32 with error detection.
Iâm not convinced that specialization will magically allow adding the blanket impl after the fact. My understanding of current proposals is that a trait must opt into being specializable, which may not be backward-compatible to do on existing traits.
For whenever this is stabilized: _please_ add TryFrom
and TryInto
to the prelude.
@SergioBenitez Weâve done that on Nightly for a while, and unfortunately it was a breaking change for crates that define their own TryFrom
or TryInto
trait (for use while the std
ones are unstable), enough that weâve reverted it.
I think the lesson to learn here for the std libs team is that when thereâs a trait that we might want to add to the prelude, we should do so as soon as itâs implemented and not wait for stabilization.
For TryFrom
and TryInto
however, a solution would be to have a different prelude for the 2018 edition. This has been discussed informally before but I didnât find it filed, so I opened https://github.com/rust-lang/rust/issues/51418.
A generic solution to a similar problem was implemented for extension
methods in https://github.com/rust-lang/rust/issues/48919, whereby unstable
extension methods issue warnings when stable code collides.
It seems like you could do something similar with new terms added to the
prelude?
On Thu, Jun 7, 2018, 11:47 AM Simon Sapin notifications@github.com wrote:
@SergioBenitez https://github.com/SergioBenitez Weâve done that on
Nightly for a while, and unfortunately it was a breaking change for crates
that define their own TryFrom or TryInto trait (for use while the std
ones are unstable), enough that weâve reverted it.I think the lesson to learn here for the std libs team is that when
thereâs a trait that we might want to add to the prelude, we should do
so as soon as itâs implemented and not wait for stabilization.For TryFrom and TryInto however, a solution would be to have a different
prelude for the 2018 edition. This has been discussed informally before but
I didnât find it filed, so I opened #51418
https://github.com/rust-lang/rust/issues/51418.â
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/33417#issuecomment-395525170,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC2lNbHvgBjWBk48-1UO311-LuUY5lPks5t6XUvgaJpZM4IXpys
.
In this case whatâs colliding is not the traits themselves (the name of the item in the prelude) but the methods brought into scope by that trait. Maybe thereâs still some similar tweak we can do, but itâs more subtle.
@SimonSapin what's the current plan for TryFrom/TryInto stabilization? I couldn't find anything concrete beyond it being reverted on Apr 29.
@nayato It is blocked on stabilizing (again) the never type https://github.com/rust-lang/rust/issues/35121, which itself is blocked on https://github.com/rust-lang/rust/issues/49593.
@SimonSapin The never type is only used here. Could we stabilize TryFrom
without that implementation and land it when never is stabilized? TryFrom
is a pretty important interface even apart from the equivalence with Try
.
Unfortunately not. It would be a breaking change to add this blanket impl after the trait has been stabilized and crates.io libraries have had a chance to implement for example TryFrom<Foo> for Bar
while they also have From<Foo> for Bar
, as the impls would overlap.
The decision so far has been that making TryFrom
"compatible" in this way with From
was important enough to block stabilization.
Probably naĂŻve and silly thought:
What if rustc got an always-on error lint in the meanwhile that triggered for Error<_, !>
, preventing any impls in userland, allowing for addition of impl
s later on?
Or an unstable impl<T: From<U>, U> TryFrom<U> for T
with any error type. Can trait impls be unstable?
@jethrogb No
I'm not sure I entirely understand why this is blocked on the never type, but the fact that it is suggests to me that an important mechanism is missing from Rust. It seems that there should be a way to reserve the desired implementation for future definition. Perhaps that mechanism is something like making it unstable, but ideally it would be a mechanism usable by non-std crates, as well. Is anyone aware of a proposed solution to this problem?
It is blocked because of this 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 What do you think of going back to enum Infallible {}
instead of the never type, to unblock?
I personally don't mind having enum Infalliable {}
and then later changing it to type Infalliable = !
.
We discussed this in yesterdayâs libs triage meeting, but couldnât remember if such a change would be ok after stabilization or if we had already rejected that idea because of potential breakage, or what that breakage would be.
What we did remember is that we only get to do one such replacement: if two ore more stable standard library empty enums (separate types) later become the same type, a crate that had some impl
s for both would break as the impls become overlapping (or identical). For example impl From<std::convert::Invallible> for MyError
and impl From<std::string::ParseError> for MyError
.
By the way, we have std::string::ParseError
which as far as I can tell is the only empty enum in the 1.30.0 standard library. So if we can be confident thereâs no other issue with this plan, we could:
string::ParseError
to convert::Infallible
pub use
or pub type
(does this make a difference?)TryFrom
blanket implstring::ParseError
and convert::Infallible
with type _ = !
and use !
directly where they were used.Having just and reluctantly added a TryFrom
placeholder trait to my own crate, and admittedly without fully understanding the implications of the RFC and stabilization efforts, I'm surprised that a blanket TryFrom
for From
with the Infallible
/!
error type is what is holding this up? Aren't these secondary goals, after establishing stable std TryFrom
and blanket TryInto
traits? I mean, even lacking the uplift of From
to TryFrom
(purpose of which I don't fully understand), wouldn't it be less churn if every crate that needs it doesn't add its own TryFrom
?
The obvious problem if TryFrom
is shipped without the blanket impl for From
is that crates might implement TryFrom
and From
for the same types (possibly exactly because the blanket impl is not there), and they would break when the blanket impl is added to libstd.
Although, come to think of it, maybe it would not be a breaking change with specialization?
Hmm, please forgive me again if I'm missing the obvious. Its almost like these 1.5 year RFC/tracking odysseys need a running FAQ section to understand the logic progression. What if the guidance was to just implement From
only for infallible conversions, and TryFrom
only for fallible conversions? Doesn't that give similar practical end result, while providing less barrier to shipping it in std and adjusting crates?
Yes, as @glandium said adding the blanket impl after the traits have already been stable is a breaking change. Stabilization is not ready yet, and itâs not clear that it would allow this kind of intersection impl anyway (rather than only "strictly more specific" impls).
Providing guidance (docs?) that say donât write programs that would break is not good enough to justify breaking changes. Breakage would definitely still happen.
What is necessary for the plan outlined in https://github.com/rust-lang/rust/issues/33417#issuecomment-423073898 to start happening? What can be done to help?
Itâs a bit of a thankless task, but it would help if someone can go through this tracking issue and https://github.com/rust-lang/rust/issues/35121 to check if thereâs a problem with that plan that weâve discussed before and forgotten since, in particular whether replacing enum Infallible
with type Infallible = !;
after the enum had been stable in a previous release could be a breaking change.
Does the Infallible
enum need to be stabilized along with the trait? If it's kept unstable, no one can name it, and so swapping it for !
later should be fine?
@seanmonstar No you could refer to it using <u16 as TryFrom<u8>>::Error
and it is considered a stable name. Witness:
// 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() {}
The first T3 impl does not cause any error. Only the second T3 impl causes the E0658 "use of unstable library feature" error.
That's.... Wow, just asking to be bitten >_<
I personally use the trick of making a type public in a non-exported module to return unnameable types, and while someone doing what you said would mean breakage if they did that, my feelings are "shame on them", and don't consider adjusting the unnameable type as breaking.
I personally don't mind making sure that every one of these not-really-never enums in libstd is the same, then changing it to a type alias to ! when that becomes stable. Seems reasonable to me.
Unrelated to all this never-type-fuss, what's the design choice for a fallible conversion on a non-Copy
type? Attention should be paid to avoid dropping the given input, so that it can be taken back in case of failure.
For instance, with String::from_utf8
, we can see that the error type contains the owned input Vec<u8>
so as to be able to give it back:
// 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]);
},
};
So, if we were to get an implementation of String: TryFrom<Vec<u8>>
, it would be expected that <String as TryFrom<Vec<u8>>>::Error
be FromUtf8Error
right?
Yes, giving the input value "back" in the error type is a valid option. Taking a reference with impl<'a> TryFrom<&'a Foo> for Bar
is another.
In this specific example however Iâm not sure that a TryFrom
impl is appropriate. UTF-8 is only one of the possible decodings of bytes into Unicode, and from_utf8
reflects that in its name.
Itâs a bit of a thankless task, but it would help if someone can go through this tracking issue and #35121 to check if thereâs a problem with that plan that weâve discussed before and forgotten since, in particular whether replacing
enum Infallible
withtype Infallible = !;
after the enum had been stable in a previous release could be a breaking change.
There were no concrete problems pointed out with that in this issue or #35121. There was one concern around the possibility of !
being special in some way uninhibited types are not. But no concerns in the PR and it's clear from the code review comments that the enum becoming stable was a possibility (though that never happened). Here are links to what I found.
Original concept
One abstract concern
Go-ahead from the lib team
Followed by:
On the note of blanket impls for when !
gets stabilized,
would this work?
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) }
}
This way all infallible conversions (with From
and Into
) get a corresponding TryFrom
and TryInto
impl where the Err
is !
and we get aTryInto
impl for every TryFrom
impl like how we get a Into
impl for every From
impl.
This way if you wanted to write an impl, you would try From
then Into
then TryFrom
then TryInto
, and one of these would work for your scenario, if you need infallible conversions you would use From
or Into
(based on coherence rules) and if you need fallible conversions you would use TryFrom
or TryInto
(based on coherence rules). If you need constraints, you can pick TryInto
or Into
based on if you you can handle fallible conversions. TryInto
would be all conversions, and Into
would be all infallible conversions.
Then we can lint against an impl TryFrom<T, Err = !>
or impl TryInto<T, Err = !>
with a hint to use impl From<T>
or impl Into<T>
.
We already have both of these 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, didn't see them (too many other impls cluttered the docs). Could we change
impl<T, U> TryFrom<U> for T where    T: From<U>,
to
impl<T, U> TryFrom<U> for T where    U: Into<T>,
because this will allow strictly more impls, and wouldn't punish people who can only impl Into
for coherence reasons, that way they also get the automatic impl for TryFrom
, and the From
impl would be transitively applied due to the blanket impl for Into
.
Would you like to try it, see if it compiles, and send a pull request?
Yes I would like to try.
Is there a case where From<U> for T
cannot be implemented, but Into<T> for U
and TryFrom<U> for T
can be?
Yes, for example
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
}
}
This is due to orphan rules.
TryFrom
and From
are the same in terms of the orphan rules, and similarly TryInto
and Into
are the same in terms of the orphan rules
@KrishnaSannasi your example will compile, just look at this playground example
I think the most updated coherence rule is described in this RFC and both implementation should be allowed.
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!()
}
}
playground
Ah yes, but as soon as you add generic parameters it falls over. If you try and compile each impl individually the From
one will fail to compile, while the Into
one will compile.
Another reason I want this change is because currently, if you write an Into
impl, you don't, and can't automatically get a TryInto
impl. I see this as a bad design because it is inconsistent with From
and TryFrom
.
I'm more asking if there's ever a case where you would want TryFrom<U> for T
to be auto-derived from Into<T> for U
, but where From<U> for T
cannot be implemented. It seems nonsensical to me.
I certainly understand wanting a Into
to TryInto
blanket impl, but unfortunately the type system does not currently allow such a blanket impl because it conflicts with the other From
to TryFrom
and From
to Into
blanket impls (or, at least, that's my understanding).
I'm fairly certain that these use cases were exactly what the RFC @kjetilkjeka linked was supposed to fix. After that's implemented there should never be a case where you can implement Into
but not From
.
@scottjmaddox I don't necessarily want Into
to imply TryFrom
as much as I want Into
to imply TryInto
. Also From
and Into
are semantically equivalent, just because we can't express that in our trait system doesn't mean that
TryFrom<U> for T
to be auto-derived fromInto<T> for U
, butwhere From<U> for T
cannot be implemented.
is nonsensical. In a perfect world we wouldn't have the distinction and there would only be one trait to convert between types, but due to limitations in the system we have now ended up with two. This isn't going to change due to stability guarantees, but we can treat these two traits as the same concept and move from there.
@clarcharr Even if that is the case, there is still no way to have both Into
imply TryInto
and TryFrom
imply TryInto
directly, as the two blanket impls would conflict. I would like Into
to imply TryInto
, and for TryFrom
to imply TryInto
. The way I propose will do so, albeit indirectly.
I made a comment on my pull request where I laid my major reasons for this change here
This is less important that the above reasons, but I also like that with this impl, all infallible conversions will now have a fallible counterpart, where the Err
type of the fallible version is !
.
Just to be clear, I want these four impls
From
implies Into
(for stability)TryFrom
implies TryInto
or TryInto
implies TryFrom
(because they are semantically equivalent)From
implies TryFrom
Into
implies TryInto
I don't care if they are done directly or indirectly.
Or, we could make TryInto
an alias:
trait TryInto<T> = where T: TryFrom<Self>;
I have no idea if this would actually work, but it seems simple enough.
Where would the into
method be defined?
@clarcharr Like @SimonSapin said, where would the into method be defined. We don't have trait aliases. That would need to be another RFC. And we would need to block this one on that RFC, which is undesirable.
@KrishnaSannasi It's currently impossible to have all four of From => Into
, From => TryFrom
, TryFrom => TryInto
, and Into => TryInto
, because everything that implements From
would have two competing impl's for TryInto
(one from From => Into => TryInto
, and another from From => TryFrom => TryInto
).
Since From => Into
and TryFrom => TryInto
are both critical, Into => TryInto
has to be sacrificed. Or at least that is my understanding.
Yeah, I didn't think the TryInto
alias out properly so just ignore me ><
I do agree with what @scottjmaddox says though.
@scottjmaddox
Just to clear up a possible misunderstand, I am removing From
auto impls TryFrom
and replacing it with Into
auto impls TryFrom
.
It's currently impossible to have all four of From => Into, From => TryFrom, TryFrom => TryInto, and Into => TryInto
This is just false, simply look at the implementation proposed.
This one:
From
-> Into
-> TryFrom
-> TryInto
From
auto-impls Into
Into
auto-impls TryFrom
TryFrom
auto-impls TryInto
By extension, due to transitive auto-impls we get
From
implies TryFrom
(because From
auto impls Into
and Into
auto impls TryFrom
)
Into
implies TryInto
(because Into
auto impls TryFrom
and TryFrom
auto impls TryInto
)
each with 1 level of indirection (I think this is fine)
So all of my conditions have been met.
We could have all of
Impl | levels of indirection
-----------------------|-------------------
From
implies Into
| No indirection
TryFrom
implies TryInto
| No indirection
From
implies TryFrom
| 1 level of indirection
Into
implies TryInto
| 1 level of indirection
From
-> Into
-> TryInto
-> TryFrom
Would also work, but I would like to maintain consistency and the original version (seen above) is more consistent than this version
Note on terminology: (when I use them)
->
means auto impl
auto impl refers the the actual impl that is typed in.
implies means if I have this impl I will also get that impl
Also note that I have made a pull request and all tests have passed, meaning there isn't a hole in my logic, this is possible. We just need to discuss if this behaviour is wanted. I think it is, because it will preserve consistency (which is very important).
@KrishnaSannasi Ahhh, now I see your point. Yes, that makes sense and I now see how your proposed change solves the problem and provides the desired behavior. Thanks for the thorough explanation!
Edit: okay, wait, though... I still don't understand why the current blanket impl's are not sufficient. There's presumably a case where you would be able to implement Into
but not From
? And yet it's possible to auto-derive TryFrom
?
Edit 2: Okay, I just went back and read your explanation about how the orphan rule can prevent implementations of From
. It all makes sense now. I totally support this change. It's still a bit frustrating that the orphan rule has so many unintended consequences, but we're not going to fix that here, so it makes sense to make the best of things.
Would someone from @rust-lang/libs be willing to start FCP on this now that https://github.com/rust-lang/rust/issues/49593 has been fixed and never_type
is a candidate for stabilization once again?
This feature has already gone through FCP for stabilization. (Proposal, completion.) I think it is not necessary to go through another 10 days comment period.
After a stabilization PR for the never type has landed, we can make a (new) stabilization PR for TryFrom
/TryInto
and use rfcbot there to make sure team members see it.
Would it be possible to change the existing empty enum types like FromStringError
to be aliases to !
alongside the stabilisation?
@clarcharr Yes. Because of trait impl coherence, we only get to do this for one type. Luckily, we only have std::string::ParseError
. (And this is exactly why we used it instead of adding a new type for impl FromString for PathBuf
in https://github.com/rust-lang/rust/pull/55148.)
However this is unrelated to TryFrom
/TryInto
. This a topic for https://github.com/rust-lang/rust/issues/49691 / https://github.com/rust-lang/rust/issues/57012, as it needs to happen in the same release cycle as stabilization of !
.
@SimonSapin Would it be possible to merge my pull request (#56796) before this stabilizes?
Sure, Iâve added it to the issue description so we donât lose track.
Thanks!
Could this be integrated into stable? It's a dependency for argdata for CloudABI.
@mcandre Yes. This is currently waiting on https://github.com/rust-lang/rust/issues/57012, which was recently unblocked. Iâm hopeful that TryFrom
will make it in Rust 1.33 or 1.34.
I'm sick of waiting for this and I have free time (finally). If there's any code or documentation type stuff that I could do to push this forward, I volunteer to help.
@icefoxen I guess right now the best you can do is to provide feedback (and changes) about the docs of these traits. Right now I find them a bit lacking compared to the From
and Into
traits.
Working on documentation. Minor road-bump: I want to assert_eq!(some_value, std::num::TryFromIntError(()));
. However, since TryFromIntError
has no constructor and no public fields, I can't make an instance of it. Any advice on how to proceed? For now I'm just doing assert!(some_value_result.is_err());
.
Sorry if this is the wrong place, but looks like there is a mistake in the docs for TryFromIntError - it lists impl Debug for TryFromIntError
, when actually there is no code for it. The compiler generates an error currently when trying to unwrap a result from a TryFrom usage.
Is there meant to be a fmt::Debug
impl on this?
@marco9999
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct TryFromIntError(());
It's got a #[derive(Debug)]
attribute.
Hmm yea there is... is something not working properly?
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 You're probably missing a generic constraint. TryFromIntError
is only used by some types, but your T could be anything:
fn foo<T: TryInto<u8>>(x: T) -> u8
where
<T as TryInto<u8>>::Error: Debug,
{
x.try_into().unwrap()
}
Anyway, this is a bit out of topic, sorry everyone. IRC might be a better place to ask these questions.
I want to
assert_eq!(some_value, std::num::TryFromIntError(()));
@icefoxen There's no useful value associated with TryFromIntError
so such an assertion doesn't seem to have much value. If you have a Result<_, TryFromIntError>
and it's an Err
, there's no other value it could be.
assert!(some_value_result.is_err());
This seems reasonable to me.
Cross-referencing: https://github.com/rust-lang/rust/pull/58302
Thanks @glaebhoerl.
Due to a blocking bug being fixed (https://github.com/rust-lang/rust/issues/49593) I was hoping that the never type could be stabilized SoonÂź https://github.com/rust-lang/rust/issues/57012 and unblock this. However a new issue (https://github.com/rust-lang/rust/issues/57012#issuecomment-460740678) has come up, and we also donât have consensus on another one (https://github.com/rust-lang/rust/issues/57012#issuecomment-449098855).
So in a libs meeting last week I brought up again the idea, I believe first proposed by @scottmcm in https://github.com/rust-lang/rust/issues/33417#issuecomment-299124605, to stabilize TryFrom
and TryInto
without the never type, and instead have an empty enum that could later be made an alias to !
.
Last time we discussed this (https://github.com/rust-lang/rust/issues/33417#issuecomment-423069246), we couldnât remember why we hadnât done this the previous time.
Last week @dtolnay reminded us of the issue: before !
becomes a full type, it can already be used in place of the return type of a function to indicates that it never returns. This includes function pointer types. So, assuming https://github.com/rust-lang/rust/pull/58302 lands in this cycle, code like this will be valid in Rust 1.34.0:
use std::convert::Infallible;
trait MyTrait {}
impl MyTrait for fn() -> ! {}
impl MyTrait for fn() -> Infallible {}
Because fn() -> !
and fn() -> Infallible
are two different (pointer) types, the two impls do not overlap. But if we replace the empty enum with a pub type Infallible = !;
type alias (when !
becomes a full type), then the two impls will start to overlap and code like the above will break.
So changing the enum to an alias would be a breaking change. On principle we wouldnât allow it in the standard library, but in this case we felt that:
Based on this discussion, I submitted https://github.com/rust-lang/rust/pull/58302 which is now in Final Comment Period.
@kennytm Isn't it possible to refer to !
in stable already? For instance, consider the following:
trait MyTrait {
type Output;
}
impl<T> MyTrait for fn() -> T {
type Output = T;
}
type Void = <fn() -> ! as MyTrait>::Output;
After doing this, Void
refers to !
type.
That looks like a bug, which means stability guarantees don't extend to it. Using the never type (!
) as a type in any capacity is still unstable, at least until #57012 is merged.
How can I help with documentation? :-)
Oh I thought https://github.com/rust-lang/rust/pull/58015 had landed, but it hasnât yet⊠Letâs discuss it there.
Could TryFrom
trait have a method to check if argument could be converted without consuming it?
fn check(value: &T) -> bool
One way to work with non-consuming impossible conversion could be to return the consumed non-convertible value along with the associated error.
Oops this should have been closed by https://github.com/rust-lang/rust/pull/58302. Closing now.
@o01eg The typical way to do non-consuming conversion is to implement e.g. TryFrom<&'_ Foo>
instead of TryFrom<Foo>
.
Wait...this shouldn't actually close until landing on stable Thursday, right?
No, we close tracking issues when the PR stabilizing the feature lands on master.
No, we usually close tracking issue when the stabilization or removal lands in the master
branch. After that there is nothing left to track. (Unless a newly-reported bug comes up, but we handle that separately.)
Tracking issues are closed by the PR that stabilizes them. Depending on the release cycle, this could be up to 12 weeks before stable is released.
Got it. Thanks for the clarification, everyone! :)
@gregdegruy update your version of Rust to 1.34 or higher.
Most helpful comment
I'd like
!
to become enough of a vocabulary type so thatResult<_, !>
would be intuitively read as "infallible result", or "result that's (literally) never Err." Using an alias (never mind a separate type!) seems to me a bit redundant and I can see it causing at least to myself a momentary pause when reading type signaturesâ"wait, how was this different from!
again?" YMMV, of course.