Where are you From::from
And what have you turned Into::into?
(This article was written with Rust 1.4)
This time, let’s look at the From
and Into
traits (and some related ones)
and ask the question when or for what to use them. Note that there are
specializations (like IntoIterator
) and variations (like FromStr
) of those
traits; it’s good to be aware of those when writing Rust code.
From
and Into
are like mirrors of each other. From
abstracts over the
source type, whereas Into
abstracts over the target type. There’s a blanket
Into<T>
impl for each T
with a matching From<_>
implementation, so if you
are a library author, you should consider implementing From<_>
for your types
and only resort to Into<_>
implementations for types you cannot implement
From
for because of the orphan rule (which is sadly underdocumented. Error
E0117 has the goods, if you
want to read further).
But implementing From
/Into
is only half the battle. Knowing where to use
them is the other half. If you program in Rust for some time, you’ll notice
that we Rustaceans make extensive use of the type system; consequently there
are a lot of types, which means that there are a good number of places where
conversions into other types are needed.
Example time!
Let’s say we have a pretty boring function foo(bar: &Bar)
where Bar
is an
actual type (not a trait). In our case, we also have a Blob
type that contains
contains a Bar
. Also the module has a Buzz
type which can be converted to a
Bar
.
Since we expect our Blob
using clients to use the foo
function, we
implement From<&Blob> for &Bar
and while we’re at it implement
From<&Buzz> for Bar
(perhaps also Buzz
without the reference. If Buzz
is
Copy
we can omit the &Buzz
implementation and rely on deref coercion). The
code
is:
use std::convert::*;
struct Bar { num: i32 }
struct Blob { bar: Bar }
struct Buzz { num: i16 }
impl<'a> From<&'a Blob> for &'a Bar {
fn from(blob: &'a Blob) -> &'a Bar { &blob.bar }
}
impl<'a> From<&'a Buzz> for Bar {
fn from(buzz: &Buzz) -> Bar { Bar{ num: buzz.num as i32 } }
}
fn foo(bar: &Bar) {
let _num = bar.num;
/* some code here */
}
Now our foo(_)
calling clients have those two calls: foo(&blob.into())
and
foo(buzz.into())
. If we have more than two instances, those .into()
calls
become pretty repetitive. If all calls are within our own code, we may conclude
that this cost is acceptable and move on. However, for library APIs, we
probably want to do better.
Can we move the .into()
into our foo
function? Perhaps by using the Into
trait? Let’s try that. We change foo(_)
to the
following:
// foo is now generic: It takes anything that we can turn into a Bar reference
// of any lifetime (which btw. precludes us from returning the Bar reference)
fn foo<'b, B: Into<&'b Bar>>(bar: B) {
let _num = bar.into().num;
/* some code here */
}
Now we can change our foo(blob.into())
calls to foo(blob)
, which is nice.
Unfortunately for us, we get an error for the foo(buzz.into())
calls:
conv.rs:30:15: 30:21 error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]
conv.rs:30 foo(&buzz.into());
^~~~~~
conv.rs:30:15: 30:21 help: run `rustc --explain E0282` to see a detailed explanation
error: aborting due to previous error
Ok, rustc
hasn’t found the right Into
implementation, because doing that
would have required auto-referencing, which is not implemented in Rust (for
good reason, I might add). The problem here is the difference between Bar
and
&Bar
. In a more simple example, this would not even happen. Alas, reality is
scarcely that simple.
Crossroads
Now we have a few options to solve this problem. Given our Bar can be Copy
,
we can simply #[derive(Copy, Clone)]
for it and change our From<&Blob>
implementation to create a new Bar
without the reference. We also need to
change foo(_)
to take any <B: Into<Bar>>
(removing the &’b) to
get:
impl<'a> From<&'a Blob> for Bar {
fn from(blob: &'a Blob) -> Bar { blob.bar }
}
fn foo<B: Into<Bar>(bar: B) { .. }
Now we can also remove the into()
call from our blobbing foo calls and
everything’s shiny (Note that our foo will actually create a copy of the bar
contained in any blob
, but at four bytes on the stack, this comes plenty
cheap. For other types, the tradeoff may be different). From
has saved the
day, and we can foo
all blobs and buzzes to our heart’s desire. Also users of
our foo
can implement Into<Bar>
to freely use our foo(_)
with their
types. Rainbows and unicorns galore!
Another option is to go on a pasture and find a std::borrow::Cow
(remember?), which means paying a small runtime fee for
a lot of flexibility. We don’t need that here, but for more complex Bar
s, it
could be worth the price:
use std::convert::*;
use std::borrow::*;
// we still need to derive Clone here or create our own ToOwned implementation,
// though we don't actually need it here.
#[derive(Clone)]
struct Bar {
num: i32,
}
struct Blob { bar: Bar }
struct Buzz { num: i16 }
impl<'a> From<&'a Blob> for Cow<'a, Bar> {
fn from(blob: &'a Blob) -> Cow<'a, Bar> { Cow::Borrowed(&blob.borrow().bar) }
}
impl<'a, 'b> From<&'a Buzz> for Cow<'b, Bar> {
fn from(buzz: &Buzz) -> Cow<'b, Bar> { Cow::Owned(Bar{ num: buzz.num as i32 }) }
}
fn foo<'b, B: Into<Cow<'b, Bar>>>(bar: B) { .. }
Now the
code
generalizes at compile time over the type that in turn generalizes at runtime
over whether the type can turn into
a borrowed or owned Bar
. Depending on
how complex foo(_)
and how big Bar
is, the runtime cost may well be
negligible.
(As an aside, redditor doener reminds me
that it’s a good idea to use our new foo(..)
as a wrapper around the previous
foo(_: Bar)
function. This will ensure that as little code as necessary will
be cloned during monomorphisation, where the generic function is instantiated
for each type it is called with)
Finally, we could forgo Into
and create our own AsBar
trait that we
implement for both &Blob
and &Buzz
which would afford us the most
flexibility. However, this means that other crates wishing to reuse our
foo(_)
would need their types to implement our AsBar
trait, too, so we need
to make it public and document it. I’ll leave this version as an exercise to
the reader.
Wrapping up
What have we learned? Into
can make our functions a good deal more flexible
and present a more usable interface to our clients when implementing a library.
The downside is that the generics complicate the documentation, but the
improved ease of use is probably worth – especially if our function can be
reused with many types and we don’t want to have special functions for each
input type.
Reusing From
/Into
saves us some of the work of creating, documenting and
testing our own traits to do generic conversions within our methods, and they
give a much appreciated familiarity to our interfaces. Only when we need even
more flexibility should we implement our own traits to do this.
Bonus!
Redditor flying-sheep would like me
to mention that implementing From
is very useful for error handling (his
example follows):
a common wrapping Error looks something like this (if you’re lazy and don’t
impl std::error::Error
):
#[derive(Debug)]
pub enum Error { TooLong(usize), Io(io::Error) }
impl From<io::Error> for Error {
fn from(e: io::Error) -> Error { Error::Io(e) }
}
then you’re able to simply use try!(_)
to wrap another error into yours.
fn foobar() -> Result<String, Error> {
let foo = try!(some_io_operation());
match foo.len() {
0..12 => Ok(foo),
n => Err(Error::TooLong(n)),
}
}
Also redditor killercup recommended the quick-error crate, which gives us a macro to do the error wrapping dance in a hurry.
Discuss at /r/rust and/or rust-users.