And why or why not?
eg
fn foo() -> impl Trait {
Implementor
}
to
fn foo() -> Implementor {
Implementor
}
And why or why not?
eg
fn foo() -> impl Trait {
Implementor
}
to
fn foo() -> Implementor {
Implementor
}
In general, I would think that's not a breaking change. This is because you shouldn't be able to do anything with an impl Trait
value other than what Trait
lets you do. By going to a concrete type, you would be allowing callers to do more with the return value (by virtue of knowing it's full type), not less, so I wouldn't expect existing code to break.
An exception to this could be method resolution: if the concrete type had an inherent method with the same name as a method in Trait
, and a different signature, that could break existing code. Or worse: the same signature but different behaviour. Then, the code compiles, but no longer does what you expect. I've had that happen to me, and it was absolutely no fun whatsoever to debug. I genuinely thought I was going insane before I worked that one out...
I'm not sure about this. Wouldn't that imply that adding a method to any type is a breaking change?
trait FooTrait {
fn foo(&self);
}
impl<T> FooTrait for T {
fn foo(&self) {
eprintln!("foo");
}
}
fn main() {
let lib = library::LibraryType;
lib.foo();
}
In this situation, adding a foo
method to LibraryType
would cause lib.foo()
to call that method instead of the trait's implementation.
It's a minor breaking change, not semver breaking. (Most changes can cause some sort of minor breakage.)
I haven't thought of a way to make going from RPIT to concrete semver breaking. There are other various related surprises from giving the caller more information. But it's typically analogous to a type implementing more traits or becoming covariant or so on.