Suppress coherence checking in binaries


#1

Currently the coherence rules prevent an external trait from being implemented by an external type. In crates intending to be used by other crates, this makes sense and prevents some very possible problems further down the line. The two problems I see these rules solving are:

  • Trait implementations getting lost in a crate’s modules while a user of the crate might not even know of it’s existence.
  • If two crates implement the same trait for the same type when neither directly ‘owns’ either, the compiler would have no way of figuring out which to use.

However, there are two situations where these are not problems:

  • If the crate is a binary rather than a library, no other crate further down the line can include it and another crate with the same implementation.
  • If the implementation could be marked as private to the crate and not used by crates using it as a dependency.

So I’m proposing a new #[suppress_coherence] that can be used to mark impl blocks as valid code in binaries, and possibly as crate-private implementations for libraries.


#2

I think you’re talking about suppressing the orphan rules, which are used to guarantee coherence, not suppressing the requirement that impls be coherent itself. That is, you don’t want to be able to provide overlapping impls, you want to be able to provide impls that the compiler won’t let you because if it did you could get overlapping impls in theory. Correct me if this is wrong.

If the crate is a binary rather than a library, no other crate further down the line can include it and another crate with the same implementation.

If the crate is a binary, crates further up the line can cause conflicts. You could upgrade a dependency, or add a new one, and in doing so break your build. Rust currently prevents that from happening.

Like every request for turning off orphan rules in some context, I think allowing users to make more complex expressions about their impls would be more fruitful.


#3

Well, if a crate produces a binary, and a crate further up the line has an impl that is overlapping one defined for the binary, wouldn’t it make sense for the local one to override the crate’s impl for any situation where the compiler finds they overlap? Unless both modules are used in the same file, there shouldn’t any overlap anyway, and if they do, then the impl defined in the crate would be used.


#4

You can’t override impls like that without potentially introducing type/memory unsafety, <T as Trait> has to always resolve to the same impl in a compilation “tree” (of crates).

This is easy to prove by changing associated types between impls, but unsafe code can also (currently) depend on <T as Trait> always resolving to the same implementation, even if there are no associated types to speak of.


#5

My expectation is that this would just fail to typecheck. Why am I wrong?


#6

You’re not wrong, that’s the obvious failure case (type unsafety, something you would always guard against), but it’s not the only one.

Consider a safe method that resets some state (e.g. sets the length of a vector to 0 or replaces an Option with None) and it gets used for turning exception unsafety during unwinding into a resource leak (aka the PPYP idiom).

If one could override such a method, they would be able to cause memory unsafety without actually writing unsafe code, by instantiating a generic function which makes use of the method in question in a safety-critical manner.

EDIT: Even worse: override something like Any::get_type_id to get transmute from safe code.