3 ms·
What is wrong with the following solution? Any trait implementations where the type and trait are not local are: * Private, and cannot be exported from a crat
by logophobia 2y ago
What is wrong with the following solution?
Any trait implementations where the type and trait are not local are:
* Private, and cannot be exported from a crate
* The local trait implementation always overrides any external implementation
That would solve part of the problem right? Only crate libraries that want to offer trait implementations for external traits/types are not possible, but that might be a good thing.
The solution proposed by the author with implicits is quite complex, I can see why it wasn't chosen.
- yccs27 2y agoI think Rust assumes that trait implementations are the same across the whole program. This avoids problems e.g. with inlining code or passing data structures between crates. I don't believe this is absolutely necessary though.
- withoutboats3 2y agoImplementations are not exported or public at all: they are used in functions and those functions are exported. For correctness, you want those implementations to be resolved consistently (this is what coherence is). This post gives the example of unioning two sets: you need to know that they're ordered the same way for your algorithm to work. So the problem isn't that the implementation is public, it's that its used somewhere by a function which is public (or called, transitively, by a public function). For a library, code which is not being used by a public function is dead code, so any impl that is actually used is inherently public. You might say, okay, well can binaries define orphan impls? The problem here is that we like backward compatibility: when a new impl is added to your dependency, possibly in a point release, it could conflict with your orphan and break you. You could allow users, probably with some ceremony, to opt into orphan impls in binaries, with the caveat that they are accepting that updating any of their dependencies could cause a compilation failure. But that's it: if you allow this in libraries, downstream users could start seeing unsolvable, unpredictable compilation failures as point releases of their dependencies introduce conflicts with orphan impls in other dependencies.
- pornel 2y agoHow about downgrading duplicate implementation in the binary to a warning? SQL has CREATE TABLE IF NOT EXISTS. Rust could have `impl Trait if not already implemented`.
- hdevalence 2y agoThis is a bad solution because now method resolution is suddenly unpredictable and can change out from under you based on changes to remote crates
- pornel 2y agoOf course it can change, that's what removal of coherence does. It seems to me to be a logical impossibility to allow orphan implementations, and allow crate updates, and not have trait implementations changing at the same time. It's a pick-two situation.
- withoutboats3 2y agoYour conclusion is correct. I'm very happy with the two that Rust picked and tired of people pretending that there will be a magical pick three option if we just keep talking about it.
- Ygg2 2y agoThe problem is people want to write glue code that adds foreign traits to types they don't own. For example they need to implement diesel trait on a type from crate they don't own (e.g. matrix) Is it possible to square that circle? Perhaps not through traits, but something else?
- withoutboats3 2y agoBetter newtypes are the answer. Consider Java for example. In Java, interfaces are even more restrictive than traits: only the package which defines the class can implement them for that class, not even the package which defines the interface. But this is fine, because if you want to implement an interface for a foreign class, you create a new class which inherits from it, and it can be used like an instance of the foreign class except it also implements this interface. In Rust, to the extent this is possible with the new type pattern it’s a lot of cruft. Making this more ergonomic would ease the burden of the orphan rule without giving up on the benefits the orphan rule provides.
- pornel 2y agoThis doesn't solve the hashtable problem: Crate A implements Hash(vA) for T Crate B implements Hash(vB) for T Crate C has a global HashSet<T> Crates A and B can both put their T instance in the C::HashSet. They can do it in their private code. Their Hash overrides any external implementation. The trait is used, but not exported. C::HashSet now has an inconsistent state. Boom!
- haileys 2y agoThe problem is that, when you're implementing a foreign trait for a foreign type, you usually want that impl to then be visible to a foreign crate. Not just the local crate. If it were good enough to only have that impl be visible to the local crate, then you could side step this whole problem by defining a local trait, which you can then impl for any type you like. So maybe we relax the rules a bit such that only the local crate, and crates that it calls, can see the impl. But then what if a third, unrelated crate, depends on that same foreign crate your local crate depends on? We'd need to keep some sort of stack tracking which impls are visible at any given time to make sure that such foreign crate can only see our impl when that foreign crate is used from our local crate. Hmmm... this is starting to look a lot like dynamic scoping.
- atq2119 2y agoHow about explicitly declaring orphan trait implementations as public (or not) and explicitly importing them (or not). Trait implementations are resolved at the point where a concrete type becomes generic, and the set of available traits can depend on that context. This isn't exactly trivial, but it avoids coherence problems.