Rust: Tracking issue for TryFrom/TryInto traits

Created on 5 May 2016  Â·  240Comments  Â·  Source: rust-lang/rust

Tracking issue for https://github.com/rust-lang/rfcs/pull/1542


To do:

B-unstable C-tracking-issue T-libs

Most helpful comment

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.

All 240 comments

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.

  1. Which types in std will these be implemented for?
  2. Which types should get implementations like impl TryFrom<T> for T?
  3. Which types should get implementations like 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 impl From<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.

@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 and Into to standard library types, I expect we'll do the same for TryFrom and TryInto.

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 an impl 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 on TryFrom::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<...> or TryFrom<...> 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:

  1. remove the boilerplate reimplementation, and hence remove a less familiar, and specialized trait
  2. add TryFrom<str> in the signature, which properly is in the convert module and is more readily obvious what it does
  3. will allow upstream clients to implement TryFrom<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.:

  1. I want a default version; oh let me reach for SomeType::default()
  2. I want to convert this, I wonder if SomeType::from(other) is implemented (you can just type it and see if it compiles, without reaching for documentation)
  3. I want to clone this, clone()
  4. I want to try to get this from this, and since errors are an integral part of rust, it can fail in the signature, so let me try_from - oh wait :P

All 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 FromStrfor 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 TryFrom for T where T: From for a future RFC, is this feature then ready to stabilize now? I really don't want to miss another release cycle, so I'm hoping some folks on the Rust team have the bandwidth to discuss and make a decision.

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 just T::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:

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

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.

@aturon The last discussion about it was @sfackler wondering whether impl From<T> for U should provide impl TryFrom<T> for U where TryFrom::Error = !.

@briansmith suggested that a decision on that be a separate RFC once unresolved questions around the never type are worked out.

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:

  1. TryFrom is intended for fallible conversions _only_, so doesn't have things like u8 -> u128 or usize -> usize.
  2. 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 impls of From, alas.

@glaebhoerl Yeah, you're right đŸ˜„ The motivation section of that RFC mentions aliasing impls 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 the impl<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:

  • A new type, 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)
  • Use Infallible as the Error type in the blanket impl
  • Later, consider type Infallible = !; as part of stabilizing the never type

I'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 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.

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 impls 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> and AsRef<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 Strings. 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.

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 impls 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 impls 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:

  • Move string::ParseError to convert::Infallible
  • Reexport it at its old location with pub use or pub type (does this make a difference?)
  • Use it in the TryFrom blanket impl
  • Later, in the same release as when the never type is stabilized, replace both string::ParseError and convert::Infallible with type _ = ! and use ! directly where they were used.
  • (Optional) Later, emit deprecation warnings for the old aliases

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 with type 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:

44174 Sep 29: added the Infallible type

47630 Mar 14: stabilize the never type !

49038 Mar 22: converted Infallible to the never type !

49305 Mar 27: stabilized TryFrom

49518 Mar 30: removed from prelude

50121 Apr 21: unstabilized

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

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 from Into<T> for U, but where 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).

pull request

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

56796 has been merged. So we can check that off.

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

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:

  • One has to go out of their way to build code that is broken by this change, so it seems unlikely to happen in practice
  • We’ll use Crater to get additional signal when the time come
  • If we end up judging the breakage to be significant enough, having both the never type and an empty enum with the same role is an inconsistency we can live with.

Based on this discussion, I submitted https://github.com/rust-lang/rust/pull/58302 which is now in Final Comment Period.

58015 should be ready for review/merge now.

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

Was this page helpful?
0 / 5 - 0 ratings