Llogiq on stuff

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