66# Summary
77[ summary ] : #summary
88
9- Improves the clarity, ergonomics, and learnability around explicit lifetimes, so that instead of writing
9+ Eliminate the need for separately binding lifetime parameters in ` fn `
10+ definitions and ` impl ` headers, so that instead of writing:
1011
1112``` rust
1213fn two_args <'b >(arg1 : & Foo , arg2 : & 'b Bar ) -> & 'b Baz
13- fn two_lifetimes <'a , 'b : 'a >(arg1 : & 'a Foo , arg2 : & 'b Bar ) -> & 'a Quux <'b >
14+ fn two_lifetimes <'a , 'b >(arg1 : & 'a Foo , arg2 : & 'b Bar ) -> & 'a Quux <'b >
15+
16+ fn nested_lifetime <'inner >(arg : && 'inner Foo ) -> & 'inner Bar
17+ fn outer_lifetime <'outer >(arg : & 'outer & Foo ) -> & 'outer Bar
1418```
1519
1620you can write :
1721
1822```rust
19- fn two_args (arg1 : & Foo , arg2 : & Bar ) -> & 'arg2 Baz
20- fn two_lifetimes (arg1 : & Foo , arg2 : & Bar ) -> & 'arg1 Quux <'arg2 >
21- ```
22-
23- More generally , this RFC completely removes the need for listing lifetime parameters , instead binding them " in-place" (but with absolute clarity about * when * this binding is happening ):
23+ fn two_args (arg1 : & Foo , arg2 : & 'b Bar ) -> & 'b Baz
24+ fn two_lifetimes (arg1 : & 'a Foo , arg2 : & 'b Bar ) -> & 'a Quux <'b >
2425
25- ```rust
26- fn named_lifetime (arg : & 'inner Foo ) -> & 'inner Bar
2726fn nested_lifetime (arg : && 'inner Foo ) -> & 'inner Bar
2827fn outer_lifetime (arg : & 'outer & Foo ) -> & 'outer Bar
2928```
3029
31- It also proposes linting against leaving off lifetime parameters in structs (like `Ref ` or `Iter `), instead nudging people to use explicit lifetimes in this case (but leveraging the other improvements to make it ergonomic to do so ).
30+ It also proposes linting against leaving off lifetime parameters in structs
31+ (like `Ref ` or `Iter `), instead nudging people to use explicit lifetimes in this
32+ case (but leveraging the other improvements to make it ergonomic to do so ).
3233
3334The changes , in summary , are :
3435
3536- A signature is taken to bind any lifetimes it mentions that are not already bound .
36- - If an argument has a single elided lifetime , that lifetime is bound to the name of the argument .
37- - You can write `'_ ` to explicitly elide a lifetime .
38- - It is deprecated to :
39- - Bind lifetimes within the generics list `<>` for `impl `s and `fn `s .
40- - Implicitly elide lifetimes for non `& ` types .
41- - The deprecations become errors at the next [epoch ](https : // github.com/rust-lang/rfcs/pull/2052).
37+ - A style lint checks that lifetimes bound in `impl ` headers are capitalized , to
38+ avoid confusion with lifetimes bound within functions . (There are some
39+ additional , less important lints proposed as well . )
40+ - You can write `'_ ` to explicitly elide a lifetime , and it is deprecated to
41+ entirely leave off lifetime arguments for non - `& ` types
4242
43- * * This RFC does not introduce any breaking changes ** , but does deprecate some existing forms in favor of improved styles of expression .
43+ * * This RFC does not introduce any breaking changes ** , but does deprecate some
44+ existing forms in favor of improved styles of expression .
4445
4546# Motivation
4647[motivation ]: #motivation
@@ -61,15 +62,6 @@ requires changing three parts of the signature:
6162fn two_args <'a , 'b : 'a >(arg1 : & 'a Foo , arg2 : & 'b Bar ) -> & 'a Baz <'b >
6263```
6364
64- In much idiomatic Rust code , these lifetime parameters are given meaningless
65- names like `'a `, because they 're serving merely to tie pieces of the signature
66- together . This habit indicates a kind of design smell : we 're forcing programmers
67- to conjure up and name a parameter whose identity doesn 't matter to them .
68-
69- Moreover , when reading a signature involving lifetime parameters , you need to
70- scan the whole thing , keeping `'a ` and `'b ` in your head , to understand the
71- pattern of borrowing at play .
72-
7365These concerns are just a papercut for advanced Rust users , but they also
7466present a cliff in the learning curve , one affecting the most novel and
7567difficult to learn part of Rust . In particular , when first explaining borrowing ,
@@ -88,7 +80,7 @@ the next section, I'll show how this RFC provides a gentler learning curve
8880around lifetimes and disambiguation .
8981
9082Another point of confusion for newcomers and old hands alike is the fact that
91- you can leave lifetimes off when using types :
83+ you can leave off lifetime parameters for types :
9284
9385```rust
9486struct Iter <'a > { ... }
@@ -100,9 +92,10 @@ impl SomeType {
10092
10193As detailed in the [ ergonomics initiative blog post] , this bit of lifetime
10294elision is considered a mistake: it makes it difficult to see at a glance that
103- borrowing is occurring, especially if you're unfamiliar with the types involved.
104- This RFC proposes some steps to rectify this situation without regressing
105- ergonomics significantly.
95+ borrowing is occurring, especially if you're unfamiliar with the types
96+ involved. (The ` & ` types, by contrast, are universally known to involve
97+ borrowing.) This RFC proposes some steps to rectify this situation without
98+ regressing ergonomics significantly.
10699
107100[ ergonomics initiative blog post ] : https://blog.rust-lang.org/2017/03/02/lang-ergonomics.html
108101
@@ -217,15 +210,6 @@ which is a way of asking the compiler to determine their "intersection"
217210returned ` Item ` borrow is valid for that period (which means it may incorporate
218211data from both of the input borrows).
219212
220- In addition, when an argument has only one lifetime you could be referencing,
221- like ` &Data ` , you can refer to that lifetime by the argument's name:
222-
223- ``` rust
224- fn select (data : & Data , params : & Params ) -> & 'data Item ;
225- ```
226-
227- Otherwise, you are not allowed to use an argument's name as a lifetime.
228-
229213## ` struct ` s and lifetimes
230214
231215Sometimes you need to build data types that contain borrowed data. Since those
@@ -269,22 +253,19 @@ might not otherwise be clear.
269253## ` impl ` blocks and lifetimes
270254
271255When writing an ` impl ` block for a structure that takes a lifetime parameter,
272- you can give that parameter a name:
256+ you can give that parameter a name, which by convention is capitalized (to
257+ clearly distinguish it from lifetimes introduced at the ` fn ` level):
273258
274259``` rust
275- impl <T > VecIter <'vec , T > { ... }
260+ impl <T > VecIter <'Vec , T > { ... }
276261```
277262
278263This name can then be referred to in the body:
279264
280265``` rust
281- impl <T > VecIter <'vec , T > {
282- fn foo (& self ) -> & 'vec T { ... }
283- fn bar (& self , arg : & Bar ) -> & 'arg Bar { ... }
284-
285- // these two are the same:
286- fn baz (& self ) -> & T { ... }
287- fn baz (& self ) -> & 'self T { ... }
266+ impl <T > VecIter <'Vec , T > {
267+ fn foo (& self ) -> & 'Vec T { ... }
268+ fn bar (& self , arg : & 'a Bar ) -> & 'a Bar { ... }
288269}
289270```
290271
@@ -297,17 +278,15 @@ impl<T> VecIter<'_, T> { ... }
297278# Reference-level explanation
298279[ reference-level-explanation ] : #reference-level-explanation
299280
300- ** Note: these changes are designed to * not* require a new epoch** . They
301- introduce several deprecations, which are essentially style lints. The next
302- epoch should turn these deprecations into errors.
281+ ** Note: these changes are designed to * not* require a new epoch** . They do
282+ expand our naming style lint, however.
303283
304284## Lifetimes in ` impl ` headers
305285
306- When writing an ` impl ` header, it is deprecated to bind a lifetime parameter
307- within the generics specification (e.g. ` impl<'a> ` ). Instead the ` impl ` header
308- can mention lifetimes without adding them as generics. Any lifetimes that are
309- not already in scope (which, today, means any lifetime whatsoever) is treated as
310- being bound as a parameter of the ` impl ` .
286+ When writing an ` impl ` header, you can mention lifetimes without binding them in
287+ the generics list. Any lifetimes that are not already in scope (which, today,
288+ means any lifetime whatsoever) is treated as being bound as a parameter of the
289+ ` impl ` .
311290
312291Thus, where today you would write:
313292
@@ -319,54 +298,51 @@ impl<'a, 'b> SomeTrait<'a> for SomeType<'a, 'b> { ... }
319298tomorrow you would write:
320299
321300``` rust
322- impl Iterator for MyIter <'a > { ... }
323- impl SomeTrait <'a > for SomeType <'a , 'b > { ... }
301+ impl Iterator for MyIter <'A > { ... }
302+ impl SomeTrait <'A > for SomeType <'A , 'B > { ... }
324303```
325304
326- ## Lifetimes in ` fn ` signatures
327-
328- When writing a ` fn ` declaration, it is deprecated to bind a lifetime parameter
329- within the generics specification (e.g. ` fn foo<'a>(arg: &'a str) ` ).
305+ This change goes hand-in-hand with a convention that lifetimes introduced in
306+ ` impl ` headers (and perhaps someday, modules) are capitalized; this convention
307+ will be enforced through the existing naming style lints.
330308
331- Instead:
309+ ## Lifetimes in ` fn ` signatures
332310
333- - If a lifetime appears that is not already in scope, it is taken to be a new
334- binding, treated as a parameter to the function.
335- - If an argument has exactly one elided lifetime, you can refer to that lifetime
336- by the argument's name preceded by ` ' ` . Otherwise, it is not permitted to use
337- that lifetime name (unless it is already in scope, which generates a warning).
338- - As with today's elision rules, lifetimes that appear * only* within ` Fn ` -style
339- bounds or trait object types are bound in higher-rank form (i.e., as if you'd
340- written them using a ` for<'a> ` ).
311+ When writing a ` fn ` declaration, if a lifetime appears that is not already in
312+ scope, it is taken to be a new binding, i.e. treated as a parameter to the
313+ function. ** This rule applies regardless of where the lifetime
314+ appears** . However, elision for higher-ranked types continues to work as today.
341315
342316Thus, where today you would write:
343317
344318``` rust
345- fn elided (& self ) -> & str ;
319+ fn elided (& self ) -> & str
346320fn two_args <'b >(arg1 : & Foo , arg2 : & 'b Bar ) -> & 'b Baz
347321fn two_lifetimes <'a , 'b : 'a >(arg1 : & 'a Foo , arg2 : & 'b Bar ) -> & 'a Quux <'b >
348322
349323impl <'a > MyStruct <'a > {
350- fn foo (& self ) -> & 'a str ;
351- fn bar <'b >(& self , arg : & 'b str ) -> & 'b str ;
324+ fn foo (& self ) -> & 'a str
325+ fn bar <'b >(& self , arg : & 'b str ) -> & 'b str
352326}
353327
328+ fn take_fn_simple (f : fn (& Foo ) -> & Bar )
354329fn take_fn <'a >(x : & 'a u32 , y : for <'b > fn (& 'a u32 , & 'b u32 , & 'b u32 ))
355330```
356331
357332tomorrow you would write :
358333
359334```rust
360- fn elided (& self ) -> & str ;
335+ fn elided (& self ) -> & str
361336fn two_args (arg1 : & Foo , arg2 : & Bar ) -> & 'arg2 Baz
362337fn two_lifetimes (arg1 : & Foo , arg2 : & Bar ) -> & 'arg1 Quux <'arg2 >
363338
364339impl MyStruct <'A > {
365- fn foo (& self ) -> & 'A str ;
366- fn bar (& self , arg : & 'b str ) -> & 'b str ;
340+ fn foo (& self ) -> & 'A str
341+ fn bar (& self , arg : & 'b str ) -> & 'b str
367342}
368343
369- fn take_fn (x : & u32 , y : fn (& 'x u32 , & 'b u32 , & 'b u32 ));
344+ fn take_fn_simple (f : fn (& Foo ) -> & Bar )
345+ fn take_fn (x : & 'a u32 , y : for <'b > fn (& 'a u32 , & 'b u32 , & 'b u32 ))
370346```
371347
372348## The wildcard lifetime
@@ -391,13 +367,26 @@ fn foo(&self) -> Ref<'_, SomeType>
391367fn iter (& self ) -> Iter <'_ , T >
392368```
393369
370+ ## Additional lints
371+
372+ Beyond the change to the style lint for capitalizing `impl ` header lifetimes ,
373+ two more lints are provided :
374+
375+ - One deny - by - default lint against `fn ` definitions in which a lifetime occurs
376+ exactly once . Such lifetimes can always be replaced by `'_ ` (or for `& `,
377+ elided altogether ), and giving an explicit name is confusing at best , and
378+ indicates a typo at worst .
379+
380+ - An expansion of Clippy 's lints so that they warn when a signature contains
381+ other unnecessary elements , e . g. when it could be using elision or could leave
382+ off lifetimes from its generics list .
383+
394384# Drawbacks
395385[drawbacks ]: #drawbacks
396386
397- The deprecations here involve some amount of churn (in the form of deleting
398- lifetimes from `<>` blocks ) if not ignored . Users exercise a lot of control over
399- when they address that churn and can do so incrementally , and we can likely
400- provide an automated tool for switching to the new style .
387+ The style lint for `impl ` headers could introduce some amount of churn . This
388+ could be mitigated by only applying that lint for lifetimes not bound in the
389+ generics list .
401390
402391The fact that lifetime parameters are not bound in an out - of - band way is
403392somewhat unusual and might be confusing --- but then , so are lifetime parameters!
@@ -412,7 +401,7 @@ Cases where you could write `fn foo<'a, 'b: 'a>(...)` now need the `'b: 'a` to
412401be given in a `where ` clause , which might be slightly more verbose . These are
413402relatively rare , though , due to our type well - formedness rule .
414403
415- Otherwise , it 's a bit hard to see drawbacks here : nothings is made more explicit
404+ Otherwise , it 's a bit hard to see drawbacks here : nothings is made less explicit
416405or harder to determine , since the binding structure continues to be completely
417406unambiguous ; ergonomics and , arguably , learnability both improve . And
418407signatures become less noisy and easier to read .
@@ -434,23 +423,48 @@ parameters is buying us very little today:
434423While this might change if we ever allow modules to be parameterized by
435424lifetimes , it won 't change in any essential way : the point is that there are
436425generally going to be * very * few in - scope lifetimes when writing a function
437- signature . We can likely use conventions or some other mechanism to help
438- distinguish between the `impl ` header and `fn ` bindings, if needed .
426+ signature . So the premise is that we can use naming conventions to distinguish
427+ between the `impl ` header (or eventual module headers) and `fn ` bindings.
439428
440- This RFC proposes to impose a strict distinction between lifetimes introduced in
441- `impl ` headers and `fn ` signatures. We could instead impose a distinction
442- through a convention, such as capitalization, and enforce the convention via a
443- lint. Alternatively , we could instead distinguish it purely at
444- the use - site, for example by writing `outer('a)` or some such to refer to the
445- `impl ` block bindings.
429+ Alternatively , we could instead distinguish these cases at the use - site, for
430+ example by writing `outer('a)` or some such to refer to the `impl ` block
431+ bindings.
446432
447- ## Alternatives
433+ ## Possible extension or alternative: "backreferences"
434+
435+ A different approach would be refering to elided lifetimes through their
436+ parameter name, like so:
437+
438+ ```rust
439+ fn scramble(& self , arg: & Foo ) -> & 'self Bar
440+ ```
441+
442+ The idea is that each parameter that involves a single, elided lifetime will be
443+ understood to * bind* a lifetime using that parameter's name.
444+
445+ Earlier iterations of this RFC combined these "backreferences" with the rest of
446+ the proposal, but this was deemed too confusing and error- prone, and in
447+ particular harmed readability by requiring you to scan both lifetime mentions
448+ * and* parameter names.
448449
449450We could consider * only* allowing "backreferences" (i. e. references to argument
450- names), and otherwise keeping binding as - is. However , that would forgo the
451- benefits of eliminating out- of- band binding, which would still be needed in some
452- cases. Conversely , we could drop "backreferences", but that would reduce the win
453- for a lot of common cases.
451+ names), and otherwise keeping binding as - is. However , this has a few downsides:
452+
453+ - It doesn't help with `impl ` headers
454+ - It doesn't entirely eliminate the need for lifetimes in generics lists for
455+ `fn ` definitions, meaning that there's still * another* step of learning to
456+ reach fully expressive lifetimes.
457+ - As @ rpjohnst [argued](https: // github.com/rust-lang/rfcs/pull/2115#issuecomment-324147717),
458+ backreferences can end up reinforcing an importantly- wrong mental model, namely
459+ that you're borrowing from an argument, rather than from its (already- borrowed)
460+ contents. By contrast, requiring you to write the lifetime reinforces the opposite
461+ idea: that borrowing has already occurred, and that what you're tying together is
462+ that existing lifetime.
463+ - On a similar note, using backreferences to tie multiple arguments together is
464+ often nonsensical, since there's no sense in which one argument is the "primary
465+ definer" of the lifetime.
466+
467+ ## Alternatives
454468
455469We could consider using this as an opportunity to eliminate `'` altogether, by
456470tying these improvements to a new way of providing lifetimes, e. g. `& ref (x) T `.
@@ -470,7 +484,5 @@ lifetimes from an `impl` header.
470484# Unresolved questions
471485[unresolved]: #unresolved- questions
472486
473- - Should we go further and eliminate the need for `for <'a>` notation as well?
474-
475- - Should we introduce a style lint for imposing a convention distinguishing
476- between `impl ` and `fn ` lifetimes?
487+ - Should we introduce higher- ranked bounds automatically when using named
488+ lifetimes in e. g. an embedded `fn ` type ?
0 commit comments