-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
SemVer: Add section on RPIT capturing #14849
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,6 +81,7 @@ considered incompatible. | |
* [Minor: generalizing a type to use generics (with identical types)](#generic-generalize-identical) | ||
* [Major: generalizing a type to use generics (with possibly different types)](#generic-generalize-different) | ||
* [Minor: changing a generic type to a more generic type](#generic-more-generic) | ||
* [Major: capturing more generic parameters in RPIT](#generic-rpit-capture) | ||
* Functions | ||
* [Major: adding/removing function parameters](#fn-change-arity) | ||
* [Possibly-breaking: introducing a new function type parameter](#fn-generic-new) | ||
|
@@ -1616,6 +1617,47 @@ fn main() { | |
} | ||
``` | ||
|
||
### Major: capturing more generic parameters in RPIT {#generic-rpit-capture} | ||
|
||
It is a breaking change to capture additional generic parameters in an [RPIT] (return-position impl trait). | ||
|
||
```rust,ignore | ||
// MAJOR CHANGE | ||
|
||
/////////////////////////////////////////////////////////// | ||
// Before | ||
pub fn f<'a, 'b>(x: &'a str, y: &'b str) -> impl Iterator<Item = char> + use<'a> { | ||
x.chars() | ||
} | ||
|
||
/////////////////////////////////////////////////////////// | ||
// After | ||
pub fn f<'a, 'b>(x: &'a str, y: &'b str) -> impl Iterator<Item = char> + use<'a, 'b> { | ||
x.chars().chain(y.chars()) | ||
} | ||
|
||
/////////////////////////////////////////////////////////// | ||
// Example usage that will break. | ||
fn main() { | ||
let a = String::new(); | ||
let b = String::new(); | ||
let iter = updated_crate::f(&a, &b); | ||
drop(b); // Error: cannot move out of `b` because it is borrowed | ||
} | ||
``` | ||
|
||
Adding generic parameters to an RPIT places additional constraints on how the resulting type may be used. | ||
|
||
Note that there are implicit captures when the `use<>` syntax is not specified. In Rust 2021 and earlier editions, the lifetime parameters are only captured if they appear syntactically within a bound in the RPIT type signature. Starting in Rust 2024, all lifetime parameters are unconditionally captured. This means that starting in Rust 2024, the default is maximally compatible, requiring you to be explicit when you want to capture less, which is a SemVer commitment. | ||
|
||
See the [edition guide][rpit-capture-guide] and the [reference][rpit-reference] for more information on RPIT capturing. | ||
|
||
It is a minor change to capture fewer generic parameters in an RPIT. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this might not be true for functions in non-sealed traits. If the trait's function definition changes the RPIT captures, I expect implementations of the trait have to be updated to match. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm… sounds very likely. Could you give an example of it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah it seems But here's proof from a related thread: rust-lang/rust#132795 It shows this example: trait TypeParam<T> {
fn test() -> impl Sized;
}
// Indirectly capturing a lifetime param through a type param substitution.
impl<'a> TypeParam<&'a ()> for i32 {
fn test() -> impl Sized + use<> {}
//~^ WARN impl trait in impl method captures fewer lifetimes than in trait
} and states that the impl is a valid refinement of the trait. But if it's a refinement, that means not all implementations of the trait have to adhere to it — it's a stricter interface than what the trait requires. Therefore, applying this to our question at hand about removing generics from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So to summarize, the statement is accurate as written so long as I think the accurate statement here is "major change in RPIT in traits unless the trait is sealed, minor change otherwise." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not quite following here. Since RPITIT is not stabilized, I would prefer to not refer to it or document its behavior. When it is stabilized, we can update the documentation to say that it is a major change to change which generics are captured in RPITIT unless the trait is sealed. (Depending on if the design of RPITIT changes.) Does that make sense? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, sorry. My statement was not intended for inclusion in this document. It was merely intended as a summary of my understanding of Rust's present and plausible future rules here. If you're sure we can remember to follow up on this section when I just wanted to raise this for your consideration — it's your call to make and I'm happy with whatever you think is best. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure how to make sure to remember updating the doc, but at least now we have three people aware of it. I think it's good to move forward. Thanks folks for the discussion! |
||
|
||
[RPIT]: ../../reference/types/impl-trait.md#abstract-return-types | ||
[rpit-capture-guide]: ../../edition-guide/rust-2024/rpit-lifetime-capture.html | ||
[rpit-reference]: ../../reference/types/impl-trait.md#capturing | ||
|
||
### Major: adding/removing function parameters {#fn-change-arity} | ||
|
||
Changing the arity of a function is a breaking change. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A thought: would it be worth adding an example, or even just mentioning in a sentence or two that
use<>
syntax can also be used to capture type parameters, not just lifetimes? That wasn't immediately obvious to me when I first heard about this new language feature.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I added a note here. I don't think type and const generics have any impact here because you cannot change them. I generally would like to avoid using this space for teaching how the language works, and that's why there are links to the reference and edition guide which do elaborate on how it works.