Description
If you add or remove a trait impl
, or anonymous impl
, or any other item in upstream code, this should have one of three effects on downstream code:
- Nothing.
- Where it previously compiled, now it fails to.
- Where it previously failed to compile, now it succeeds.
What should not happen is
4. It continues to compile, but its runtime behavior is now different.
This property is currently known to be violated by method call autoderef combined with trait impls on reference types (#11818). For instance:
mod up1 {
pub trait Stringify {
fn stringify(&self) -> ~str;
}
impl<'a, T> Stringify for &'a T {
fn stringify(&self) -> ~str { ~"Hi, I'm a borrowed reference!" }
}
}
mod up2 {
use up1::Stringify;
pub struct Foo;
#[cfg(with_foo_impl)]
impl Stringify for Foo {
fn stringify(&self) -> ~str { ~"This is Foo" }
}
}
mod down {
use up1::Stringify;
use up2::Foo;
pub fn some_string() -> ~str {
(&Foo).stringify()
}
}
fn main() {
println!("{}", down::some_string())
}
When compiled with --cfg with_foo_impl
, this code will output "This is Foo". Otherwise it will say "Hi, I'm a borrowed reference!".
Why this is important: it seriously impairs the ability to reason about interface stability and compatibility. In today's situation, upstreams cannot add or remove impl
s for their types without having to worry about introducing silent breakage somewhere downstream. If this property held, then the worst case would be that downstream would be alerted to the change by a compile failure, which is much, much preferable to silent breakage. (This is exactly the kind of thing static type systems are supposed to ensure!)
The provided example is only one instance I know of where the principle is violated, I don't know whether there might be others.