Remix.run Logo
ivanjermakov 3 days ago

How often do you need to impl trait crate_a::A for a type crate_b::B? If it is allowed by the language, it would mean that behavior of crate_a or crate_b would change if they link one to another.

I really doubt that this is a wanted behavior. And Rust follows this logic, suggesting to use a new type to achieve the same, but not leak your impl into other crates.

jerf 3 days ago | parent | next [-]

A lot. You're not seeing it precisely because it isn't under the streetlight, because it is currently impossible: https://en.wikipedia.org/wiki/Streetlight_effect

This really amps up the composibility of a lot of systems and libraries. It would be awesome if there was a "free" way to do this. Unfortunately no one has found one yet; the posted article is a pretty good overview of that problem.

Also, bear in mind that no matter what solution you are looking at, it's generally crate_c that wants to create the implementation for crate_a's type using a trait in crate_b, so it isn't as bad as the "behavior of crate_a or crate_b would change if they link one to another", which would indeed be horrifying. crate_c has to be involved somehow in the build, without that crate_a and crate_b carry on completely normally. When it's the end-user application doing the trait, at least the end-user can manage it; the core problem arises when crate_c is third-party, and user wants to use that and also crate_d, which also implements a train from crate_b on crate_a's type. At that point you have a pretty big issue; I would characterize implicits as a way of managing the problem, but not really a "solution". It is not clear there is a "solution".

And there is clearly a problem; the Haskell community rammed into this problem decently hard a long time ago, back when they were smaller than they even are today, and certainly smaller back then than the Rust ecosystem as it stands today. The phase transition from theoretical problem to real problem happens in an ecosystem an order of magnitude or two smaller than the current Rust ecosystem, which is itself likely to grow yet by another order or two at least over its lifespan.

ivanjermakov 3 days ago | parent [-]

Love to see a problem from the frontline of computer science as an actual practical problem solution to which would greatly benefit languages' semantics.

Regarding conflicting impls in crate_c and crate_d: what is wrong with Haskell's approach of grading impls by their specialty (e.g. Show Int is more specific than Show a so prior instance would be used if matches) and only throwing an error if it's ambiguous to the compiler which instance to use?

I see that this is not sufficient, since crate_e can't do anything about conflicting impls in crate_c and crate_d. Having to explicitly import impls does not spark joy. The problem is more convoluted than I thought!

I'm very interested in this topic since I'm working on a language that features traits and thus have a chance to "fix" it.

P.S. found another good article on this topic: https://www.michaelpj.com/blog/2020/10/29/your-orphans-are-f...

jerf a day ago | parent [-]

"Regarding conflicting impls in crate_c and crate_d: what is wrong with Haskell's approach of grading impls by their specialty"

You can still get surprising errors later in crate_c and crate_d when crate_e adds something, which is suboptimal.

I'm not saying all solutions are equally good or bad, just that there isn't a known perfect solution yet. If you accept the principle that "Writing some more code into module E shouldn't cause new errors to appear in C and D", which is a reasonable thing to want in a system, then this isn't a perfect solution.

armchairhacker 3 days ago | parent | prev [-]

For example, if you want to serialize a type whose fields are all public but it doesn’t implement `serde::Serialize`. A lot of crates have an optional `serde` feature for this exact purpose, but not all.

Another use-case is if you want more abstraction than the standard library. There’s a crate named `cc-traits` that exports traits for collections like `Insert` and `Remove`, which are implemented for types on the standard library. But if you’re using a third-party collection library like `btree_vec`, its types don’t implement the `Insert` and `Remove` traits, and you could easily implement them manually except for the orphan rule.

ivanjermakov 3 days ago | parent [-]

I understand the orphan problem better now. It's not clear what compiler should do if you add two crates which have conflicting impls (where both implement the same foreign trait for the same foreign type)