cpichard
cpichard

Reputation: 11

From trait called inside a generic function

I am using the From trait to convert an i32 to a structure of my own. I use this conversion in a generic function do_stuff that doesn't compile:

use std::convert::*;

struct StoredValue {
    val: i32,
}

impl From<i32> for StoredValue {
    fn from(value: i32) -> StoredValue {
        return StoredValue {val: value};
    }
}

/// Generic function that stores a value and do stuff
fn do_stuff<T>(value: T) -> i32 where T: From<T> {
    let result = StoredValue::from(value);
    // .... do stuff and
    return 0;
}

fn main () {
    let result = do_stuff(0); // call with explicit type
}

and the compilation error:

main.rs:15:18: 15:35 error: the trait `core::convert::From<T>` is not implemented for the type `StoredValue` [E0277]
main.rs:15     let result = StoredValue::from(value);

Does it make sense to implement a generic version of From<T> for StoredValue?

Upvotes: 1

Views: 184

Answers (2)

starblue
starblue

Reputation: 56772

To make do_stuff() work, it must be possible to convert type T into StoredValue. So its declaration should be

fn do_stuff<T>(value: T) -> i32 where StoredValue: From<T> {

Edit: I agree with Shepmaster that that should better be

fn do_stuff<T>(value: T) -> i32 where T: Into<StoredValue> {
    let result = value.into();
    // ...

Since there is a generic implementation that turns T: From<U> into U: Into<T>, this allows to use both kinds of conversions, those implementing From and those implementing Into. With my first version only conversions implementing From would work.

Upvotes: 1

kbknapp
kbknapp

Reputation: 302

Your generic function is saying, "I accept any type that implements being created from itself." Which isn't what you want.

There's a few things you could be wanting to say:

"I accept any type that can be converted into an i32 so that I can create a StoredValue." This works because you know StoredValue implements From<i32>.

fn do_stuff<T>(value: T) -> i32 where T: Into<i32> {
    let result = StoredValue::from(value.into());
    // ...
}

Or, "I accept any type that can be converted into a StoredValue." There is a handy trait that goes along with the From<T> trait, and it's called Into<T>.

fn do_stuff<T>(value: T) -> i32 where T: Into<StoredValue> {
    let result = value.into();
    // ...
}

The way to remember how/when to use these two traits that go hand in hand is this:

  • Use Into<T> when you know what you want the end result to be, i.e from ?->T
  • Use From<T> when you know what you have to start with, but not the end result, i.e. T->?

The reason these two traits can go hand in hand together, is if you have a T that implements Into<U>, and you have V that implements From<U> you can get from a T->U->V.

The Rust std lib has such a conversion already baked in that says, "Any type T that implements From<U>, than U implements Into<T>."

Because of this, when you implemented From<i32> for StoredValue you can assume there is a Into<StoredValue> for i32.

Upvotes: 2

Related Questions