Implementing foreign traits on foreign objects


#1

Hi,

I’m quite new to rust, but I can see this being a huge limitation, although I do understand the concerns as well.
What is the current opinion on partly lifting this limitation?

I’m thinking something along the lines of limiting the implementation to the crate where the implementation lives only, or enabling it for binary crates only?

Those solutions seem like a fairly good middle ground without giving place for ambiguity, since binary crates cannot be imported.


#2

If I understand correctly, the argument against this is that it makes it a breaking change for a crate to implement a trait in the crate for a new type - a user might have already defined the trait for that type.


#3

Wrapping has zero cost. What problem do you see that can’t be solved this way?


#4

True, but that means I’d have to wrap each and every value manually that is coming from somewhere else, such as an external crate.


#5

If there would be a mechanism such as scala’s implicit classes, I would be fine with that solution, but there is none… Is there?..


#6

An argument could be made that this is a good thing, as it isolates you from your dependencies to some degree. That isn’t always a good thing, but if you find yourself carriaging around a lot of types from external libraries, your code may be too tightly coupled to them.


#7

I find that to be a gross generalization. It may be fitting (probably) for huge software projects, but in small projects, where agility is more important, isolating dependencies becomes very counterproductive.

Also this potentially introduces a lot of boilerplate, which is a very very bad thing in any case.

Newtypes are a good pattern when the thing that the newtype expresses are different than the basetype, or introduces details above it (such as the Millis in the example), but I find it hard to justify for extending an existing type with new functionality.

Implicit classes in scala solve this issue quite nicely, although I agree that with great power comes great responsibility, but hey, that’s also what a systems programming language should be all about, should it not?


#9

It was a generalization, this is true.

I can see how implicit classes make things easier, though I think Rust tends to avoid implicit construction/conversion and would be unlikely to adopt something like this.

One thing that could possibly reduce friction with your newtypes is to impl From/Into between them and the original type. That way instead of manually wrapping them every time, you can just call into() in some cases.

You can also make some of your own functions generic over this, as in:

fn do_something<T: Into<MyNewType>>(t: T) {
    let t = t.into();
    // Use the trait implemented on your newtype
    ...
}

That way you can potentially move the burden of wrapping things into the function where you need them to be wrapped, and the rest of the time, you don’t have to worry about it.

Again, this obviously isn’t always appropriate, but depending on what you’re trying to do, it could be helpful.