Should `From` be implemented for references

#1

Say I have two datatypes, A and B, which can be converted from one into another. It also so happens that they don’t share internal representation at all, so that, if I implement conversion manually, I’d go with

impl A {
    // Note the reference
    fn to_b(&self) -> B { ... }
}

Should I implement From<A> for B, or for<'a> From<&'a A> for B or both?

#2

Probably it is a matter of usage, but that’s my personal opinion:

  • Implement From<A> for B because, in any case, it should be possible to consume the original object. In the end, the user is unaware of the internal representation, and the opportunity to consume should be given.
  • Implement for<'a> From<&'a A> for B if A is not Copy-able. Since the original object cannot be cheaply copied, the user would like to create B without having to do something let b: B = a.clone().into(). If the internal representation is different, this is also important to avoid useless copies around.

As said, this is merely my opinion, and probably macros helps quite a lot to avoid boilerplate code.

2 Likes
#3

I’d tend to follow the standard library on this. For instance, String implements From<&str>, and PathBuf implements a rather complicated-looking but convenient From which uses AsRef to simultaneously handle a lot of possible inputs. In general, there are an awful lot of From implementations, and that is great. So in your case, from a reference sounds great.

#4

Note that standard library does not have a similar &OwnedA -> OwnedB conversion. In all of the case where stdlib’s From is parametrized by lifetimes, its either that lhs is naturally parametrized by a lifetime (like From<Cow<'a, Path>> for PathBuf), or rhs has a lifetime (like From<&'a PathBuf> for Cow<'a, Path>).

The impl<'a, T: ?Sized + AsRef<OsStr>> From<&'a T> for PathBuf impl is the first-case, due to T: ?Sized.

In general, there are an awful lot of From implementations, and that is great.

More doesn’t mean better. In general, if From<A> for B reuses all allocations, adding From<&A> for B would be a bad idea, because it might lead to unnecessary copies. Similarly, if you don’t reuse any allocations, From<A> for B seems less than ideal, because it forces a needless .clone .

1 Like