So after talking to @sgrif recently, I’ve been thinking about trait coherence. I still believe pretty strongly that we do not want to sacrifice coherence in general, but there are definitely some scenarios that are not easily resolved today. One thing that @sgrif raised is the following. Imagine I am defining a crate, let’s call it Ethanol, and it defines various types. I would like those types to be compatible with Serde, but I don’t want to force my users to depend on Serde. Now I am stuck, as I can’t implement the serde traits unless I depend on serde.
What people do in practice today is to use cargo features to conditionally define those traits. That’s workable, but it’s kind of annoying, and it’s stressing the preprocessor, which imo creates problems of its own. Moreover, cargo features don’t necessarily compose all that well: if I have for example one crate that needs Ethanol+Serde and one that wants Ethanol by itself, cargo won’t necessarily figure out that it should take the union of the feature flags (it’s also not obvious that this is the correct thing to do in the first place). All in all, doesn’t feel (to me) like the right solution.
What would be nice is if I could define another crate, let’s call it EthanolSerde, that took both Ethanol and Serde as dependencies and then implemented the Serde traits for the Ethanol types. Now people who want Serde support can rely on that crate instead. Moreover, since i am in control of both Ethanol and EthanolSerde, I can publish new versions in lockstep, and so we don’t really have to fear incoherence. Basically, as long as I myself don’t publish two incompatible definitions of the serde traits for my types, there will always be at most one impl to use. The problem of course is that the compiler doesn’t know that Ethanol and EthanolSerde share the same author.
The recent RFC on cargo workspaces got me thinking. One could imagine tweaking the coherence rules to require that one can only write an impl Trait for Type
if either Trait
or Type
is in your current workspace, as opposed to the current crate. Prior to publication, cargo could then even run a “whole workspace” consistency check to ensure that you don’t impl Trait for Type
in two distinct crates within the same workspace.
(Of course, the current RFC doesn’t attempt anything this ambitious. This would basically be adding workspaces as something the compiler understands more deeply, rather than a convenient shorthand for managing multiple crates.)
Thoughts?
(cc @aturon @wycats, with whom I’ve occasionally discussed this)