Cant impl From<T> and TryFrom<T> for T2

Note: I don't need help fixing anything. I am just curious about why an Infallible fallible function is automatically implemented, and how that is more useful than being able to implement both TryFrom and From for the same types?

This is a strange case that i ran into today.
I was trying to do something like this:

struct One(String);
struct Two(u32);
struct TwoFromOneError;

impl TryFrom<One> for Two {
    type Error = TwoFromOneError;

    fn try_from(value: One) -> Result<Self, Self::Error> {
        if let Ok(thing) = value.0.parse::<u32>() {
            Ok(Two(thing))
        }else{
            Err(TwoFromOneError)
        }
    }
}

impl From<One> for Two {
    fn from(value: One) -> Self {
        if let Ok(thing) = value.0.parse::<u32>() {
            Two(thing)
        }else{
            Two(69)
        }
    }
}

but with much more going on. using a default for the 1 possible error is the main point of this example. I believe this doesn't compile because:

  • From<One> for Two auto implements Into<Two> for One.
  • Which auto implements TryFrom<One> for Two with Infallible as the error.
  • Which conflicts with my Implementation for TryFrom<One> for Two.

I guess I'm just confused why this limitation exists?
Is it really useful to have a Infallible TryFrom?
Shouldn't i be able to overwrite the blanket impl?

The premise of the conversion traits (From, Into, TryFrom, TryInto) is that there's one canonical way to convert from one type to another, and each trait in the collection offers a less strict way (i.e. more types are capable) of performing that conversion. Nowadays, Into and TryInto are basically equivalent to From and TryFrom, but the relationship still exists between From/Into and TryFrom/TryInto.

Functions that take TryFrom assume that they can also take From implementers through the automatic TryFrom impl.

Consider implementing Default to create Two(69) and calling try_from(_).unwrap_or_default(). Otherwise, maybe use a different trait or different type.

Again not looking for help, never was a real problem, but your advice would not have worked. my actual code is for creating a configuration from quite a few values from 2 other structs, which normally i would want to know the error because the program should stop running. but i am making i quick configuration option which just uses a default when creating the configuration for testing purposes. my example is just a minimal code example to show case my issue. my main point is that this limits what you are capable of for the convenience of being able to make an assumption. I more looking for other people opinions on why you should or should not be able to implement both From and TryFrom individually for the same types.

edit: wait... Functions that take TryFrom cant assume From is also implemented. Only From implementers can assume TryFrom is implemented right?

You can read the discussion in the tracking issue.

In brief for this to be useful

fn example<T: TryInto<Something>>(t: T) -> Result<(), T::Error> {
    let t = t.try_into()?;
    Ok(())
}

You need at least TryInto<T> for T.[1] FFI with platform dependent integer sizes was one an explicit case considered, where you want to accept any integer that might work.

(Note that just having impl TryFrom<T> for T in the standard library would still conflict with impl TryFrom<U> for MyType where MyType: From<U> in downstream libraries.)

The thought was that if a blanket implementation wasn't included, everyone else should probably write their own type-specific versions, but probably wouldn't until it came in a feature request or such (like how every type probably should implement AsRef<Self> but most don't).

Was it the right choice? I'm not sure. The fallout is roughly that no one else gets to have a generic TryFrom implementation.


  1. The alternative is having to wrap a value in something fallible just to be able to pass it in when you already have a T. ↩ī¸Ž

2 Likes

Even if you could implement both, I think you shouldn't because consumers of the trait assume TryFrom and From are equivalent when both are implemented. For example, they may change an API to take TryFrom instead of From and assume anything that previously worked will not return errors.

It's also generally bad to do something for testing convenience that leaks into the API.

Functions that take TryFrom assume the From impl, if it exists, is reachable through TryFrom::try_from.

There could also be a function taking From and passing it to another function that takes TryFrom under the assumption that this ensures the other function will not fail.

This is only relevant to API/ecosystem concerns, though. If these types are kept internal, then it doesn't really matter if they use TryFrom/From weirdly as long as it isn't forgotten.

I did not consider FFI. I may be missing something here, but why would you want to use an "Infallible" try_from if a truly Infallible from exists?

In the generic case when you accept the fallible version, you don't know when or if the infallible version exists. Your only choice is to call try_from.

In the FFI case, whether c_char has a fallible or infallible conversion from u8 (say) varies by platform.

3 Likes

I still think they should have implemented TryFrom manually. but the FFI combined with cross-platform stuff does help me understand why they might have made the decision.

Sorry drewtato but i disagree with your explanation, i am not saying you are wrong but I personally believe (this is my opinion) assumptions are the easiest way to make mistakes, which is something i have been teaching in my high school programming class for 10 years now. I came to this conclusion because many of my students have made many mistakes because of assumptions.

Edit: i have also made plenty of mistakes due to assumptions.

I'm not saying you should make assumptions, I'm saying you should write code that works the same whether other people make assumptions or not.