-
-
Notifications
You must be signed in to change notification settings - Fork 75
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Breakage related to where Self: Sized
bounds on trait associated types and methods
#786
Comments
where Self: Sized
bounds on trait associated functions (methods)where Self: Sized
bounds on trait associated types and methods
cc @ehuss this might be of interest — it appears to be a new set of SemVer hazards that might bear mentioning in the cargo reference's SemVer section. |
Thanks for the ping! Just to clarify, is this just a variant of trait-object-safety? That section explicitly says "adding an item", but could be extended to be "adding, or changing an existing item to be non-object-safe"? Also, one of the todo items in rust-lang/cargo#8736 is to clarify what changes are allowed to a trait item. The old RFC just said no "non-trivial" changes, but didn't define what "trivial" meant. I think adding or removing trait bounds would by default be non-trivial, though I can see where there could be some subtle exceptions. |
I don't believe so. Even with the In other words, the following code compiles just fine: playground pub trait Example {
fn method(&mut self) -> Option<i64> where Self: Sized;
}
fn demo(value: &mut dyn Example) {} Any other trait items that do not require
The edge cases are annoyingly difficult to pin down. I've tried and given up several times — it's a couple-week-long rabbit hole minimum, in my estimation. For example, partially-sealed traits complicate everything. Methods that cannot be called from outside the trait's crate, or methods that cannot be overridden from outside the trait's crate, both make it quite complex to determine when changes to bounds could possibly affect a downstream crate. This is one of the things I'd love to sit down and properly work out if I can find some funding to make it my full-time job for a few months. If the Rust project or Foundation might be willing to sponsor that work, I'd be happy to do it and even work out how to make the rules machine-checkable in cargo-semver-checks as well. |
As requested. These examples don't change object safety but are still breaking changes. Example 1: Removing // Now that you can omit `where Self: Sized` associated types from `dyn Trait`,
// it is a breaking change to remove `Self: Sized` from associated types of
// object-safe traits
pub trait Trait {
type Assoc where Self: Sized;
}
// If the `Self: Sized` bound on `Assoc` is removed, this must change to be
// `dyn Trait<Assoc = SomeConcreteType>`
pub fn example(_: &dyn Trait) {} Example 2: removing pub trait Trait {
fn consume(self) where Self: Sized;
fn other(&self) {}
}
// Check that it's object safe
pub fn example(_: &dyn Trait) {}
// How do you implement `Trait` for non-sized types?
impl Trait for str {
// We can't just omit the method (yet)...
// https://github.com/rust-lang/rfcs/issues/2829
// This errors because you can't pass unsized types by value...
// fn consume(self) {}
// This errors becuase trivial bounds isn't implemented yet...
// https://github.com/rust-lang/rust/issues/48214
// fn consume(self) where Self: Sized {}
// But this workaround for trivial bounds works
// (you still can't call the method)
fn consume(self) where for<'a> Self: Sized {}
}
// But if you remove `where Self: Sized` from the trait, you will break the
// implementation because it is "no longer as general" as the trait.
//
// N.b. the reference says that a "receiver type of `Self` (i.e. `self`)
// implies [`where Self: Sized`]", but as this example demonstrates, that is
// not true. There is some other special carve-out that leaves traits that
// have methods with `self` receivers object safe.
//
// https://doc.rust-lang.org/nightly/reference/items/traits.html#object-safety
//
// I.e. also comment out `impl Trait for str` and the `&dyn Trait` will still
// compile. (As far as I know, there is currently no way to implement a trait with a |
Amazing, thank you! A few follow-up questions to make sure I understand all the nuances and can implement the lints accurately:
|
|
Rust appears to allow trait items (so far, types and methods, in the future possibly also constants) to be excluded from trait objects by applying
Self: Sized
bounds like so:As far as I can tell, it is not required to repeat
where Self: Sized
on the items of trait implementations, so the breakage has to come from attempts to call or use the defined functionality, rather than being immediately caused by a definition lacking awhere Self: Sized
bound.SemVer hazards:
where Self: Sized
is generally a major breaking change for trait methods.where Self: Sized
a major breaking change for trait methods? If so, how?where Self: Sized
a major breaking change for trait associated types by itself, meaning without a simultaneouswhere Self: Sized
bound change on a trait method as well? If so, how?Adding
where Self: Sized
to a trait method is breakingplayground
Caveat: if the method is made non-callable from outside its own crate (i.e. the trait is partially-sealed as described in this post), then this is not a major breaking change since no external crate could have called the method and been broken by its signature change.
This is currently not expressible in a Trustfall query, so it cannot be linted without further work on the query schema.
The text was updated successfully, but these errors were encountered: