Question about Trait methods vs Struct methods


#1

So, I’m trying to organize my code and am not sure what the implications are for keeping what I have already vs refactoring.

I have a struct

#[derive(Debug, Deserialize)]
pub struct Xml {
    ...
}

and i have a trait on Vec

pub trait NeoVec {
    fn into_xml(&self) -> Result<Xml>;
}

impl NeoVec for Vec<u8> {
    fn into_xml(&self) -> Result<Xml> {
        // serde kicks butt
        let xml: Xml = deserialize( self.as_slice() )
            .unwrap();
        Ok(xml)
    }
}

I did this so i can do this:

// Type is ellided in actual code
let string_in_the_xml: String = returns_a_result_vec(&resolve_url)
                                    .unwrap()
                                    .into_xml()
                                    .unwrap()
                                    .something;

So, it works and thinking about it, it would be just as easy to add a method for Xml like from_vec

But that would read like

impl Xml {
    pub fn from_vec( Vec<u8> ) -> Result<Self> {
        ...
    }
}

// Type is ellided in actual code
let string_in_the_xml: String = 
    Xml::from_vec(returns_a_result_vec(&resolve_url).unwrap()).unwrap().something;

Which is kind of hard to read. Couple this with .chain_err(|| format!("something {}", &something)? instead of .unwrap() and it’s even worse.

Is there a reason to use Struct methods vs Trait methods? This is a binary and not a crate so, the trait is really about just extending the type for readability and not about offering the trait for others to use this very narrowly-focused function ( and I wanted to see what it looked like haha ).


#2

Do note that Result types have a .map() function for Ok variants.

returns_a_result_vec(&resolve_url) // => Result<Vec<_>, ErrorA>
    .map(Xml::from_vec)            // => Result<Result<Xml, ErrorB>, ErrorA>
    .unwrap()                      // => Result<Xml, _> or panic
    .unwrap()                      // => Xml or panic
    .something();

Updated:

Actually, from_vec returns a Result of it’s own. In that case, let’s use and_then.

returns_a_result_vec(&resolve_url) // => Result<Vec<_>, ErrorA>
    .and_then(Xml::from_vec)       // => Result<Xml, ErrorB>
    .unwrap()                      // => Xml or panic
    .something();

The above might have some issues with ErrorA and ErrorB being different types, but since you’re using error-chain, it’s easy to clean up.

error_chain! {
    foreign_links {
        MyErrorA(ErrorA);
        MyErrorB(ErrorB);
    }
}

returns_a_result_vec(&resolve_url) // => Result<Vec<_>, ErrorA>
    .map_err(Error::from)          // Result<Vec<_>, Error> -- MyErrorA
    .and_then(|x| {
        Xml::from_vec(x).map_err(Error::from)
    })                             // => Result<Xml, Error> -- MyErrorA or MyErrorB
    .unwrap()                      // => Xml or panic
    .something();

#3

Will other types implement the NeoVec trait? Will you ever use the trait as a bound on some type parameter?

If only one type ever implements a trait those behaviors would in my opinion make more sense associated with the type they produce.

If you are using nightly I think TryFrom would be best here.


#4

So, there are multiple ways to do it, and there are opinions on which is preferred based on personal taste ( I’m may use that map_err version when I refactor to make my own error types ) but, is there a hard and fast rule on a best practice?

Is there benefit derived from using a struct method? It seems like you both prefer it.
Would a more complex case reveal a flaw in using ad-hoc trait methods?

If I implemented TryFrom, what is gained over my own ad-hoc trait? Is it just standardization for others to read my code?


#5

Is there a benefit derived from using a struct method?

I’ll go out on a limb and say you really have to compare the machine code to see any difference. So long as you’re not using trait objects, struct methods and trait methods should compile to the same machine code in a release target.

It really does come down to personal taste, especially since your project isn’t a library. I think traits would be a bit more verbose than the struct method, if that cost matters to you.

Only [marginal] downside to traits I can think of is you have to import the trait into files that invoke a trait method; e.g. use NeoVec;, whereas struct method only has to be pub.