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