Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TryFrom and TryInto traits #1542

Merged
merged 1 commit into from
May 4, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions text/0000-try-from.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
- Feature Name: try_from
- Start Date: 2016-03-10
- RFC PR:
- Rust Issue:

# Summary
[summary]: #summary

The standard library provides the `From` and `Into` traits as standard ways to
convert between types. However, these traits only support *infallable*
conversions. This RFC proposes the addition of `TryFrom` and `TryInto` traits
to support these use cases in a standard way.

# Motivation
[motivation]: #motivation

Fallible conversions are fairly common, and a collection of ad-hoc traits has
arisen to support them, both [within the standard library][from-str] and [in
third party crates][into-connect-params]. A standardized set of traits
following the pattern set by `From` and `Into` will ease these APIs by
providing a standardized interface as we expand the set of fallible
conversions.

One specific avenue of expansion that has been frequently requested is fallible
integer conversion traits. Conversions between integer types may currently be
performed with the `as` operator, which will silently truncate the value if it
is out of bounds of the target type. Code which needs to down-cast values must
manually check that the cast will succeed, which is both tedious and error
prone. A fallible conversion trait reduces code like this:

```rust
let value: isize = ...;

let value: u32 = if value < 0 || value > u32::max_value() as isize {
return Err(BogusCast);
} else {
value as u32
};
```

to simply:

```rust
let value: isize = ...;
let value: u32 = try!(value.try_into());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is pretty untypical.
These overflows, like arithmetic overflows, are very rarely handled, so most of the time people will have to deal with

let value: u32 = value.try_into().unwrap();

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not all that uncommon in my experience:
https://github.com/sfackler/rust-postgres/blob/master/src/types/mod.rs#L700
https://github.com/sfackler/rust-postgres-large-object/blob/master/src/lib.rs#L220

But in any case, what you do with the Err once you get it doesn't really have much to do with this RFC - it's focused on how you get that Err.

```

# Detailed design
[design]: #detailed-design

Two traits will be added to the `core::convert` module:

```rust
pub trait TryFrom<T>: Sized {
type Err;

fn try_from(t: T) -> Result<Self, Self::Err>;
}

pub trait TryInto<T>: Sized {
type Err;

fn try_into(self) -> Result<T, Self::Err>;
}
```

In a fashion similar to `From` and `Into`, a blanket implementation of `TryInto`
is provided for all `TryFrom` implementations:

```rust
impl<T, U> TryInto<U> for T where U: TryFrom<T> {
type Error = U::Err;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Err, not Error.


fn try_into(self) -> Result<U, Self::Err> {
U::try_from(self)
}
}
```

In addition, implementations of `TryFrom` will be provided to convert between
*all combinations* of integer types:

```rust
#[derive(Debug)]
pub struct TryFromIntError(());

impl fmt::Display for TryFromIntError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.write_str(self.description())
}
}

impl Error for TryFromIntError {
fn description(&self) -> &str {
"out of range integral type conversion attempted"
}
}

impl TryFrom<usize> for u8 {
type Err = TryFromIntError;

fn try_from(t: usize) -> Result<u8, TryFromIntError> {
// ...
}
}

// ...
```

This notably includes implementations that are actually infallible, including
implementations between a type and itself. A common use case for these kinds
of conversions is when interacting with a C API and converting, for example,
from a `u64` to a `libc::c_long`. `c_long` may be `u32` on some platforms but
`u64` on others, so having an `impl TryFrom<u64> for u64` ensures that
conversions using these traits will compile on all architectures. Similarly, a
conversion from `usize` to `u32` may or may not be fallible depending on the
target architecture.

The standard library provides a reflexive implementation of the `From` trait
for all types: `impl<T> From<T> for T`. We could similarly provide a "lifting"
implementation of `TryFrom`:

```rust
impl<T, U: From<T>> TryFrom<T> for U {
type Err = Void;

fn try_from(t: T) -> Result<U, Void> {
Ok(U::from(t))
}
}
```

However, this implementation would directly conflict with our goal of having
uniform `TryFrom` implementations between all combinations of integer types. In
addition, it's not clear what value such an implementation would actually
provide, so this RFC does *not* propose its addition.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a very important implementation to add.

I write code like this all the time:

fn foo<T: Into<Bar>>(bar: T) -> … {
    let bar = bar.into();
}

which then can be used with anything that implements From. I would certainly like to be able to do

fn foo_2<T: TryInto<Bar>>(bar: T) -> … {
    let bar = bar.try_into().unwrap();
}

and be able to pass in anything implementing plain old From as well.

The coherence problem could’ve been a reason for not considering this at the time of writing, but since specialisation has been implemented, perhaps it can be made to work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not believe this can be done in the current implementation of specialization.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, IIRC this requires the "lattice rule". (I expect something with that level of expressiveness to be available eventually, but it will be a while.)


# Drawbacks
[drawbacks]: #drawbacks

It is unclear if existing fallible conversion traits can backwards-compatibly
be subsumed into `TryFrom` and `TryInto`, which may result in an awkward mix of
ad-hoc traits in addition to `TryFrom` and `TryInto`.

# Alternatives
[alternatives]: #alternatives

We could avoid general traits and continue making distinct conversion traits for
each use case.

# Unresolved questions
[unresolved]: #unresolved-questions

Are `TryFrom` and `TryInto` the right names? There is some precedent for the
`try_` prefix: `TcpStream::try_clone`, `Mutex::try_lock`, etc.

What should be done about `FromStr`, `ToSocketAddrs`, and other ad-hoc fallible
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dislike the tendency of replacing concrete methods with meaningless generic implementations.
I'd never want parse(&str) -> Result<i32, Err> to become try_into(&str) -> Result<i32, Err>.
From/Into are already overused in this sense, impl From<Ipv4Addr> for u32 goddamit.

Is anyone going to write generic code which would be applicable to both FromStr and ToSocketAddrs? I suppose no. It means than TryFrom/TryInto in this case is a dangerous over-generalization.

I'm not against TryFrom/TryInto in principle, but I'm pretty sure they will be heavily misused.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t know what should be concluded from this, but note that ToSocketAddrs has an associated type for the Ok case.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petrochenkov Why do you feel that impl FromStr for Foo is an intrinsically more meaningful implementation than impl<'a str> TryFrom<&'a str> for Foo? They seem totally isomorphic to me.'

It's also worth noting that the fate of parse is basically unrelated to the fate of FromStr.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's also worth noting that the fate of parse is basically unrelated to the fate of FromStr.

parse itself is not affected, it'll just get a generic duplicate try_into if TryFrom<str> is implemented for the same types as FromStr and TryInto gets the blanket impl based on TryFrom.
And I'm saying that this duplicate will almost never be used, because parse is preferable in concrete code and probably never used in code generic enough to not be able to use parse.

Why do you feel that impl FromStr for Foo is an intrinsically more meaningful implementation than impl<'a str> TryFrom<&'a str> for Foo?

I don't feel that way with respect to FromStr in particular, FromStr is already meaningless enough :)

conversion traits? An upgrade path may exist in the future with specialization,
but it is probably too early to say definitively.

Should `TryFrom` and `TryInto` be added to the prelude? This would be the first
prelude addition since the 1.0 release.

[from-str]: https://doc.rust-lang.org/1.7.0/std/str/trait.FromStr.html
[into-connect-params]: http://sfackler.github.io/rust-postgres/doc/v0.11.4/postgres/trait.IntoConnectParams.html