When should I implement `From` instead of writing a method that returns the type

I've just started learning Rust, and I think I'm getting the hang of it, but I'm a little confused about when to use traits vs methods, especially with respect to the From and TryFrom traits (which is the one I've dealt with the most).

If I have a struct that contains some collection of data, and I want the ability to convert the struct into a Vec<u8>, is it better to have a method on the struct that returns the Vec<u8>, or to implement From<&MyStruct> for Vec<u8>

As a very simple example, here is a simple struct with both an method, and an implementation of From

struct MyStruct {
    val1: u8,
    val2: u8,
}

impl MyStruct {
    pub fn to_bytes(&self) -> Vec<u8> {
        return vec![self.val1, self.val2];
    }
}

impl From<&MyStruct> for Vec<u8> {
    fn from(value: &MyStruct) -> Self {
        return vec![value.val1, value.val2];
    }
}

From my understanding, both options will provide me with the same result (that is my_instance.to_bytes() == Vec::from(&my_instance). Is there a reason that I would want to implement From instead of just having a method that returns the Vec?

This question extends the other direction as well, should I create constructors, or should I be implementing From<Vec<u8>> for MyStruct?

I think it's mostly down to vibes, dude.

I mean, you could argue that every function could be replaced with an appropriate From implementation. So, "is it possible" isn't a good basis.

Personally, my rules of thumb are:

  • Is this function conceptually a conversion? That is, does it take a concept represented by one type and turning it into the same concept, just represented with a different type? Implement From/Into.

  • Is it useful to provide a From/Into implementation? For example, do I expect this type to be used in contexts where that's needed (like for error types, or I expect to do frequent .map(Into::into) on iterators? Do I want to use it with generic methods that want those impls? Implement From/Into.

Also note that implementing From/Into does not preclude you from also implementing a method that does the same thing. Sometimes, that can be very handy if type inference might not be convenient. It's not uncommon to have both.

For example, if to_bytes is a common thing to want to do here, I'd definitely provide the method so that it's easy to invoke.

1 Like

I'd only write the trait implementation From<_> for Vec<_> if there was an obvious relation between your type and a Vec<_> (like you contain a Vec<_> that would be useful on its own), or if there was some other concrete reason you had to meet an Into: Vec<_> bound or the like. If the relationship isn't obvious, a well-named method can elaborate. It's also fine to have method in addition to trait implementations.

I wouldn't write From<Vec<_>> unless it was similarly obvious; most likely because I have such a Vec<_> as a field and everything else is default-able or calculatable from the Vec<_>.

In the example, though I know it's contrived,

  • From<MyStruct> for Vec<u8>
    • You don't contain a Vec<_>, so why treat that container special
    • If the fields had more realistic names, they probably wouldn't have an implicit order anyway
  • From<Vec<u8>> for MyStruct
    • You don't contain a Vec<_>, so why treat that container special
    • The Vec<_> might have less than two elements and From shouldn't panic
    • The Vec<_> might have more than two elements you would be throwing out, so it's not a good fit that way either
    • For even [u8; 2] to make sense, again the order of fields would have to be inherent for it to be a good fit
2 Likes

Sorry for taking some time to reply. Both of your answers were very useful. I ended up using a mix of both depending on what I was doing. Mostly using my own methods, as that just felt more natural to me, but I did use TryFrom in a number of places for creating new instance.

I still need to update this PR with the libs-api feedback, but here's my stab at attempting to describe when From should be used: Attempt to describe the intent behind the `From` trait further by scottmcm · Pull Request #114564 · rust-lang/rust · GitHub

For "turn it into bytes" specifically, maybe what you really want is https://serde.rs/, not From?

2 Likes