From eaa41de2a5e2fd002cc8fdd433b2ad160adac64c Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 6 May 2024 12:20:30 +0200 Subject: [PATCH 01/17] RFC to extend format_args implicit arguments to allow field access --- text/0000-format-args-implicit-dot.md | 190 ++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 text/0000-format-args-implicit-dot.md diff --git a/text/0000-format-args-implicit-dot.md b/text/0000-format-args-implicit-dot.md new file mode 100644 index 00000000000..7f911c530b6 --- /dev/null +++ b/text/0000-format-args-implicit-dot.md @@ -0,0 +1,190 @@ +- Feature Name: `format_args_implicit_dot` +- Start Date: 2023-10-01 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#00000](https://github.com/rust-lang/rust/issues/00000) + +# Summary +[summary]: #summary + +This RFC extends the "implicit named arguments" mechanism to allow accessing +field names with `var.field` syntax: `format!("{self.x} {var.another_field}")`. + +# Motivation +[motivation]: #motivation + +[RFC 2795](https://github.com/rust-lang/rfcs/pull/2795) added "implicit named +arguments" to `std::format_args!` (and other macros based on it such as +`format!` and `println!` and `panic!`), allowing the format string to reference +variables in scope using identifiers. For instance, `println!("Hello {name}")` +is now equivalent to `println!("Hello {name}", name=name)`. + +The original implicit named arguments mechanism only permitted single +identifiers, to avoid the complexity of embedding arbitrary expressions into +format strings. The implicit named arguments mechanism is widely used, and one +of the most common requests and most common reasons people cannot use that +syntax is when they need to access a struct field. Adding struct field syntax +does not conflict with any other format syntax, and unlike allowing *arbitrary* +expressions, allowing struct field syntax does not substantially increase +complexity or decrease readability. + +This proposal has the same advantages as the original implicit named arguments +proposal: making more formatting expressions easy to read from left-to-right +without having to jump back and forth between the format string and the +arguments. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +With this proposal accepted, the following (currently invalid) macro +invocation: + +```rust +format_args!("hello {person.name}") +``` + +would become a valid macro invocation, and would be equivalent to a shorthand +for the already valid: + +```rust +format_args!("hello {name}", name=person.name) +``` + +The identifier at the beginning of the chain (`person` in this case) must be an +identifier which existed in the scope in which the macro is invoked, and must +have a field of the appropriate name (`name` in this case). + +This syntax works for fields within fields as well: + +```rust +format_args!("{obj.field.nested_field.another_field}") +``` + +As a result of this change, downstream macros based on `format_args!` would +also be able to accept implicit named arguments in the same way. This would +provide ergonomic benefit to many macros across the ecosystem, including: + + - `format!` + - `print!` and `println!` + - `eprint!` and `eprintln!` + - `write!` and `writeln!` + - `panic!`, `unreachable!`, `unimplemented!`, and `todo!` + - `assert!`, `assert_eq!`, and similar + - macros in the `log` and `tracing` crates + +(This is not an exhaustive list of the many macros this would affect.) + +## Additional formatting parameters + +As a result of this RFC, formatting parameters can also use implicit named +argument capture: + + println!("{self.value:self.width$.self.precision$}"); + +This is slightly complex to read, but unambiguous thanks to the `$`s. + +## Compatibility + +This syntax is not currently accepted, and results in a compiler error. Thus, +adding this syntax should not cause any breaking changes in any existing Rust +code. + +## No field access from named arguments + +This syntax only permits referencing fields from identifiers in scope. It does +not permit referencing fields from named arguments passed into the macro. For +instance, the following syntax is not valid, and results in an error: + +```rust +println!("{x.field}", x=expr()); // Error +``` + +If there is an ambiguity between an identifier in scope and an identifier used +for a named argument, the compiler emits an error. + +```rust +let x = SomeStruct::new(); +println!("{x.field}", x=expr()); // Error +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +The implementation captures the first identifier in the chain using the same +mechanism as implicit format arguments, and then uses normal field accesses to +obtain the value, just as if the field were accessed within a named argument. +Thus, the following two expressions are semantically equivalent: + +```rust +format_args!("{name.field1.field2}") + +format_args!("{unique_identifier}", unique_identifier=name.field1.field2) +``` + +Any `Deref` operations associated with the `.` in each format argument are +evaluated from left-to-right as they appear in the format string, at the point +where the format string argument is evaluated, before the positional or named +arguments are evaluated. (In general, `Deref` operations should be idempotent, +so the evaluation order should not matter.) + +If the identifier at the start of the chain does not exist in the scope, the +usual error E0425 would be emitted by the compiler, with the span of that +identifier: + +``` +error[E0425]: cannot find value `person` in this scope + --> src/main.rs:X:Y + | +X | format_args!("hello {person.name}"); + | ^^^^^^ not found in this scope +``` + +If one of the field references refers to a field not contained in the +structure, the usual error E0609 would be emitted by the compiler, with the +span of the field identifier: + +``` +error[E0609]: no field `name` on type `person` + --> src/main.rs:X:Y + | +5 | format_args!("hello {person.name}"); + | ^^^^ unknown field +``` + +# Drawbacks +[drawbacks]: #drawbacks + +This adds incremental additional complexity to format strings. + +Having `x.y` available may make people assume other types of expressions work +as well. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +The null alternative is to avoid adding this syntax, and let users continue to +pass named arguments or bind local temporary names rather than performing +inline field accesses within format strings. This would continue to be +inconvenient but functional. + +This functionality could theoretically be implemented in a third-party crate, +but would then not be automatically and consistently available within all of +Rust's formatting macros, including those in the standard library and those +throughout the ecosystem. + +We could omit support for other formatting parameters (width, precision). +However, this would introduce an inconsistency that people have to remember; +people would *expect* this to work. + +# Prior art +[prior-art]: #prior-art + +Rust's existing implicit format arguments serve as prior art, and discussion +around that proposal considered the possibility of future (cautious) extension +to additional types of expressions. + +The equivalent mechanisms in some other programming languages (e.g. Python +f-strings, Javascript backticks, C#, and various other languages) allow +arbitrary expressions. This RFC does *not* propose adding arbitrary +expressions, nor should this RFC serve as precedent for arbitrary expressions, +but nonetheless these other languages provide precedent for permitting more +than just single identifiers. From ad43d2a4e480a2b65f77d03c84b214a0325d681a Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 6 May 2024 12:23:32 +0200 Subject: [PATCH 02/17] RFC 3626 --- ...at-args-implicit-dot.md => 3626-format-args-implicit-dot.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-format-args-implicit-dot.md => 3626-format-args-implicit-dot.md} (98%) diff --git a/text/0000-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md similarity index 98% rename from text/0000-format-args-implicit-dot.md rename to text/3626-format-args-implicit-dot.md index 7f911c530b6..0cf14e95612 100644 --- a/text/0000-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -1,6 +1,6 @@ - Feature Name: `format_args_implicit_dot` - Start Date: 2023-10-01 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3626](https://github.com/rust-lang/rfcs/pull/3626) - Rust Issue: [rust-lang/rust#00000](https://github.com/rust-lang/rust/issues/00000) # Summary From 6bbf9bca1d2cddd9432597a18d44133ea6822c74 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 6 May 2024 13:04:48 +0200 Subject: [PATCH 03/17] Clarify the example desugaring --- text/3626-format-args-implicit-dot.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 0cf14e95612..952169f0c74 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -46,7 +46,7 @@ would become a valid macro invocation, and would be equivalent to a shorthand for the already valid: ```rust -format_args!("hello {name}", name=person.name) +format_args!("hello {unique_ident}", unique_ident=person.name) ``` The identifier at the beginning of the chain (`person` in this case) must be an From a84773be180430810bc2f45769a2fdcbf185ec98 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 6 May 2024 14:40:20 +0200 Subject: [PATCH 04/17] Fix formatting --- text/3626-format-args-implicit-dot.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 952169f0c74..62fc058697d 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -78,7 +78,9 @@ provide ergonomic benefit to many macros across the ecosystem, including: As a result of this RFC, formatting parameters can also use implicit named argument capture: - println!("{self.value:self.width$.self.precision$}"); +```rust +println!("{self.value:self.width$.self.precision$}"); +``` This is slightly complex to read, but unambiguous thanks to the `$`s. From de4deb1d72e9623fb429895340a48bb57add5620 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 6 May 2024 14:40:27 +0200 Subject: [PATCH 05/17] Allow `.await` --- text/3626-format-args-implicit-dot.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 62fc058697d..823e7518212 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -84,6 +84,14 @@ println!("{self.value:self.width$.self.precision$}"); This is slightly complex to read, but unambiguous thanks to the `$`s. +## `await` + +Formatting can use `.await`, as well: + +```rust +println!("{future1.await} {future2.await}"); +``` + ## Compatibility This syntax is not currently accepted, and results in a compiler error. Thus, @@ -122,11 +130,10 @@ format_args!("{name.field1.field2}") format_args!("{unique_identifier}", unique_identifier=name.field1.field2) ``` -Any `Deref` operations associated with the `.` in each format argument are -evaluated from left-to-right as they appear in the format string, at the point -where the format string argument is evaluated, before the positional or named -arguments are evaluated. (In general, `Deref` operations should be idempotent, -so the evaluation order should not matter.) +Any `Deref` operations or `.await` operations associated with the `.` in each +format argument are evaluated from left-to-right as they appear in the format +string, at the point where the format string argument is evaluated, before the +positional or named arguments are evaluated. If the identifier at the start of the chain does not exist in the scope, the usual error E0425 would be emitted by the compiler, with the span of that @@ -177,6 +184,9 @@ We could omit support for other formatting parameters (width, precision). However, this would introduce an inconsistency that people have to remember; people would *expect* this to work. +We could omit support for `.await`. However, to users this may seem like an +arbitrary restriction. + # Prior art [prior-art]: #prior-art From 09626b5d99140b56ae24e68a8dc14c23edad4275 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Mon, 6 May 2024 17:10:20 +0200 Subject: [PATCH 06/17] Evaluation happens exactly once --- text/3626-format-args-implicit-dot.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 823e7518212..c62125745b2 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -131,9 +131,9 @@ format_args!("{unique_identifier}", unique_identifier=name.field1.field2) ``` Any `Deref` operations or `.await` operations associated with the `.` in each -format argument are evaluated from left-to-right as they appear in the format -string, at the point where the format string argument is evaluated, before the -positional or named arguments are evaluated. +format argument are evaluated exactly once, from left-to-right as they appear +in the format string, at the point where the format string argument is +evaluated, before the positional or named arguments are evaluated. If the identifier at the start of the chain does not exist in the scope, the usual error E0425 would be emitted by the compiler, with the span of that From 2e3d1b8a6c75bcd2a1e03bc9de5d03f4125dcefd Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 7 May 2024 10:01:41 +0200 Subject: [PATCH 07/17] No deduplication --- text/3626-format-args-implicit-dot.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index c62125745b2..8d6e2c6104d 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -133,7 +133,9 @@ format_args!("{unique_identifier}", unique_identifier=name.field1.field2) Any `Deref` operations or `.await` operations associated with the `.` in each format argument are evaluated exactly once, from left-to-right as they appear in the format string, at the point where the format string argument is -evaluated, before the positional or named arguments are evaluated. +evaluated, before the positional or named arguments are evaluated. No +deduplication occurs: if `name.field` or `name.await` is mentioned multiple +times, it will be evaluated multiple times. If the identifier at the start of the chain does not exist in the scope, the usual error E0425 would be emitted by the compiler, with the span of that From 9f2e777985ad4a52b84c285765fead19823c2861 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 7 May 2024 10:03:53 +0200 Subject: [PATCH 08/17] Mention side effects --- text/3626-format-args-implicit-dot.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 8d6e2c6104d..8705c43b951 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -169,6 +169,10 @@ This adds incremental additional complexity to format strings. Having `x.y` available may make people assume other types of expressions work as well. +This introduces an additional mechanism to allow side-effects while evaluating +a format string. However, format strings could already cause side effects while +evaluating, if a `Display` or `Debug` implementation has side effects. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From 1fdd26025cd2c0c73792f82df1b7076794be9599 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Tue, 7 May 2024 10:15:35 +0200 Subject: [PATCH 09/17] Clarify that allowing `.` is a purely syntactic choice --- text/3626-format-args-implicit-dot.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 8705c43b951..4eca900d042 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -191,7 +191,8 @@ However, this would introduce an inconsistency that people have to remember; people would *expect* this to work. We could omit support for `.await`. However, to users this may seem like an -arbitrary restriction. +arbitrary restriction. The rationale for this RFC is purely *syntactic*, on the +basis that we can allow expressions using `.` without requiring delimiters. # Prior art [prior-art]: #prior-art From bb72da96d2e6b53eb513242e513c77a1589fcea6 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 24 Aug 2025 22:03:30 -0700 Subject: [PATCH 10/17] Remove `.await` While syntactically similar, it's simultaneously more complex and less important. Remove it, in the hopes of unblocking the much more important field access. --- text/3626-format-args-implicit-dot.md | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 4eca900d042..7f3da11b037 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -84,14 +84,6 @@ println!("{self.value:self.width$.self.precision$}"); This is slightly complex to read, but unambiguous thanks to the `$`s. -## `await` - -Formatting can use `.await`, as well: - -```rust -println!("{future1.await} {future2.await}"); -``` - ## Compatibility This syntax is not currently accepted, and results in a compiler error. Thus, @@ -130,12 +122,11 @@ format_args!("{name.field1.field2}") format_args!("{unique_identifier}", unique_identifier=name.field1.field2) ``` -Any `Deref` operations or `.await` operations associated with the `.` in each -format argument are evaluated exactly once, from left-to-right as they appear -in the format string, at the point where the format string argument is -evaluated, before the positional or named arguments are evaluated. No -deduplication occurs: if `name.field` or `name.await` is mentioned multiple -times, it will be evaluated multiple times. +Any `Deref` operations associated with the `.` in each format argument are +evaluated exactly once, from left-to-right as they appear in the format string, +at the point where the format string argument is evaluated, before the +positional or named arguments are evaluated. No deduplication occurs: if +`name.field` is mentioned multiple times, it will be evaluated multiple times. If the identifier at the start of the chain does not exist in the scope, the usual error E0425 would be emitted by the compiler, with the span of that @@ -190,9 +181,9 @@ We could omit support for other formatting parameters (width, precision). However, this would introduce an inconsistency that people have to remember; people would *expect* this to work. -We could omit support for `.await`. However, to users this may seem like an -arbitrary restriction. The rationale for this RFC is purely *syntactic*, on the -basis that we can allow expressions using `.` without requiring delimiters. +We could include support for `.await`. To users, the ability to perform field +accesses but not `.await` may seem like an arbitrary restriction, since the two +both use `.` syntactically. # Prior art [prior-art]: #prior-art From c22262dd766f30d6081cb3a75ad931f119cc5c10 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Sun, 24 Aug 2025 22:10:29 -0700 Subject: [PATCH 11/17] Add discussion of arbitrary expressions --- text/3626-format-args-implicit-dot.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 7f3da11b037..b3e39233ea9 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -185,6 +185,18 @@ We could include support for `.await`. To users, the ability to perform field accesses but not `.await` may seem like an arbitrary restriction, since the two both use `.` syntactically. +We could (in addition to this, or instead of this) add a syntax that allows +arbitrary expressions, or a large subset of arbitrary expressions; this would +likely require some way to make them syntactically unambiguous, such as the use +of parentheses. This would have the downside of allowing substantial additional +visual complexity (e.g. string constants with `"..."` in an expression in a +format string). The rationale for allowing field accesses, in particular, +*without* parentheses, is that they are already syntactically unambiguous +without requiring any additional delimiters, and given that, the absence of +additional delimiters makes them *more* readable rather than less. For example, +`format!("{self.field}")` is entirely readable, and is not made more readable +by changing it to (for instance) `format!("{(self.field)}")`. + # Prior art [prior-art]: #prior-art @@ -197,4 +209,5 @@ f-strings, Javascript backticks, C#, and various other languages) allow arbitrary expressions. This RFC does *not* propose adding arbitrary expressions, nor should this RFC serve as precedent for arbitrary expressions, but nonetheless these other languages provide precedent for permitting more -than just single identifiers. +than just single identifiers. See the discussion in "Rationale and +alternatives" for further exploration of this. From 7e525fd0354c424ab592ba855d81f4012872ba8d Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Wed, 5 Nov 2025 11:17:21 -0800 Subject: [PATCH 12/17] Add more information about `Deref` --- text/3626-format-args-implicit-dot.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index b3e39233ea9..45fa243d4c8 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -177,6 +177,16 @@ but would then not be automatically and consistently available within all of Rust's formatting macros, including those in the standard library and those throughout the ecosystem. +We could omit support for `Deref`; however, this would be inconsistent with +what's possible with `a.b` expressions in the arguments of a format macro. +People will expect to be able to move an `a.b` from the arguments to the format +string, and this should not depend on the type of `a`. + +We could attempt to unify references to the same structure, or the same field, +and call `Deref` only once. However, this would be inconsistent with normal +Rust expressions. We could consider such an optimization in the future if we +add a `DerefPure` marker. + We could omit support for other formatting parameters (width, precision). However, this would introduce an inconsistency that people have to remember; people would *expect* this to work. From aa251f9a166cd2aadd19bdbdf415381daf80f684 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 13 Nov 2025 14:25:32 -0800 Subject: [PATCH 13/17] Implicit arguments use raw identifiers and may conflict with keywords --- text/3626-format-args-implicit-dot.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 45fa243d4c8..867a33c71b8 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -152,6 +152,17 @@ error[E0609]: no field `name` on type `person` | ^^^^ unknown field ``` +The field references, like the initial identifier, are resolved as though +written using raw identifiers; thus, they may conflict with Rust keywords. +(This is for consistency with existing non-field arguments, and may change in a +future edition of Rust.) Thus, the following two expressions are semantically +equivalent: + +``` +format_args!("{type.field} {while.for}"); +format_args!("{uniq1} {uniq2}", uniq1=r#type.field, uniq2=r#while.r#for); +``` + # Drawbacks [drawbacks]: #drawbacks @@ -195,6 +206,14 @@ We could include support for `.await`. To users, the ability to perform field accesses but not `.await` may seem like an arbitrary restriction, since the two both use `.` syntactically. +Rather than implicitly using raw identifiers (and thus allowing fields whose +names conflict with Rust keywords), we could instead require the use of `r#` +explicitly, or disallow names that conflict with keywords. However, this would +be inconsistent with existing non-field names in format strings; +`format!("{type}")` works today, so `format!("{type.for}")` should be +consistent with that. Note, though, that in being consistent with current +behavior, we prevent supporting `.await` unless we change this. + We could (in addition to this, or instead of this) add a syntax that allows arbitrary expressions, or a large subset of arbitrary expressions; this would likely require some way to make them syntactically unambiguous, such as the use @@ -221,3 +240,10 @@ expressions, nor should this RFC serve as precedent for arbitrary expressions, but nonetheless these other languages provide precedent for permitting more than just single identifiers. See the discussion in "Rationale and alternatives" for further exploration of this. + +# Future possibilities +[future possibilities]: #future-possibilities + +In a future edition, we could stop treating `"{type}"` as though written with a +raw keyword, and instead require `"{r#type}"`, or disallow it entirely. This +would then unblock the ability to write `"{x.await}"` or similar. From 928b3a87a2ebe1a63b50cc080bdf8a3812b016a6 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 13 Nov 2025 14:36:25 -0800 Subject: [PATCH 14/17] Allow field references from named arguments --- text/3626-format-args-implicit-dot.md | 29 +++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 867a33c71b8..469ee18208f 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -50,7 +50,8 @@ format_args!("hello {unique_ident}", unique_ident=person.name) ``` The identifier at the beginning of the chain (`person` in this case) must be an -identifier which existed in the scope in which the macro is invoked, and must +identifier which existed in the scope in which the macro is invoked or an +identifier introduced as a named argument of the formatting macro, and must have a field of the appropriate name (`name` in this case). This syntax works for fields within fields as well: @@ -90,23 +91,21 @@ This syntax is not currently accepted, and results in a compiler error. Thus, adding this syntax should not cause any breaking changes in any existing Rust code. -## No field access from named arguments +## Field access from named arguments -This syntax only permits referencing fields from identifiers in scope. It does -not permit referencing fields from named arguments passed into the macro. For -instance, the following syntax is not valid, and results in an error: +This syntax allows referencing fields from identifiers in scope, or from named +arguments passed into the macro. For instance, all of the following work: ```rust -println!("{x.field}", x=expr()); // Error +let x = SomeStruct::new(); +println!("{x.field}"); +println!("{y.field}", y = x); +println!("{z.field}", z = SomeStruct::new()); ``` If there is an ambiguity between an identifier in scope and an identifier used -for a named argument, the compiler emits an error. - -```rust -let x = SomeStruct::new(); -println!("{x.field}", x=expr()); // Error -``` +for a named argument, the named argument takes precedence, just as it does for +implicit named arguments without fields. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -128,9 +127,9 @@ at the point where the format string argument is evaluated, before the positional or named arguments are evaluated. No deduplication occurs: if `name.field` is mentioned multiple times, it will be evaluated multiple times. -If the identifier at the start of the chain does not exist in the scope, the -usual error E0425 would be emitted by the compiler, with the span of that -identifier: +If the identifier at the start of the chain does not exist in the scope or as a +named argument, the usual error E0425 would be emitted by the compiler, with +the span of that identifier: ``` error[E0425]: cannot find value `person` in this scope From 208a842f882120c98ef09809387066f73e2d1cb9 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 13 Nov 2025 17:49:46 -0800 Subject: [PATCH 15/17] Deduplicate identical field expressions, for now, for consistency with implicit named arguments without fields We may wish to change this, but until we do, let's be consistent. --- text/3626-format-args-implicit-dot.md | 39 ++++++++++++++++++++------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index 469ee18208f..d7cb6bec21b 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -121,11 +121,25 @@ format_args!("{name.field1.field2}") format_args!("{unique_identifier}", unique_identifier=name.field1.field2) ``` -Any `Deref` operations associated with the `.` in each format argument are -evaluated exactly once, from left-to-right as they appear in the format string, -at the point where the format string argument is evaluated, before the -positional or named arguments are evaluated. No deduplication occurs: if -`name.field` is mentioned multiple times, it will be evaluated multiple times. +The field access expressions are deduplicated textually, and each unique +expression (including any `Deref` operations in it) is evaluated exactly once, +from left-to-right as it first appears, after all explicit arguments to the +formatting macro have been evaluated. Thus, the following expressions are +semantically equivalent: + +```rust +format_args!("{name.a.b} {name.c.d} {name.a.b}") + +format_args!("{unique1} {unique2} {unique1}", unique1=name.a.b, unique2=name.c.d) +``` + +Evaluating the implicit named arguments with fields last is consistent with +current handling of implicit named arguments without fields, which are +evaluated after all explicit arguments. + +Deduplicating identical field access expressions is consistent with non-field +implicit named arguments; however, we may wish to change this in a future +edition, to more closely match how function calls handle their arguments. If the identifier at the start of the chain does not exist in the scope or as a named argument, the usual error E0425 would be emitted by the compiler, with @@ -192,10 +206,12 @@ what's possible with `a.b` expressions in the arguments of a format macro. People will expect to be able to move an `a.b` from the arguments to the format string, and this should not depend on the type of `a`. -We could attempt to unify references to the same structure, or the same field, -and call `Deref` only once. However, this would be inconsistent with normal -Rust expressions. We could consider such an optimization in the future if we -add a `DerefPure` marker. +Rather than unifying references to the same field, we could evaluate every +field expression left-to-right, after all explicit fields. This would be more +consistent with normal expressions (e.g. function calls), but would be +inconsistent with existing support for implicit named arguments without fields. +We should consider changing the behavior for implicit named arguments without +fields, via an edition. We could omit support for other formatting parameters (width, precision). However, this would introduce an inconsistency that people have to remember; @@ -246,3 +262,8 @@ alternatives" for further exploration of this. In a future edition, we could stop treating `"{type}"` as though written with a raw keyword, and instead require `"{r#type}"`, or disallow it entirely. This would then unblock the ability to write `"{x.await}"` or similar. + +In a future edition, we could stop deduplicating `"{x.field} {x.field}"`, and +instead desugar to a distinct evaluation for each field access expression. This +would more closely match how function calls handle their arguments (e.g. +`func(x.field, x.field)`). From 68fd601c1c9f311449f3e181e8d37ac7a248bd13 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 13 Nov 2025 17:52:00 -0800 Subject: [PATCH 16/17] Change all "via an edition" to allow "or via a careful crater run" --- text/3626-format-args-implicit-dot.md | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index d7cb6bec21b..e930453eb19 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -139,7 +139,8 @@ evaluated after all explicit arguments. Deduplicating identical field access expressions is consistent with non-field implicit named arguments; however, we may wish to change this in a future -edition, to more closely match how function calls handle their arguments. +edition or via a careful crater run, to more closely match how function calls +handle their arguments. If the identifier at the start of the chain does not exist in the scope or as a named argument, the usual error E0425 would be emitted by the compiler, with @@ -168,8 +169,8 @@ error[E0609]: no field `name` on type `person` The field references, like the initial identifier, are resolved as though written using raw identifiers; thus, they may conflict with Rust keywords. (This is for consistency with existing non-field arguments, and may change in a -future edition of Rust.) Thus, the following two expressions are semantically -equivalent: +future edition of Rust or via a careful crater run.) Thus, the following two +expressions are semantically equivalent: ``` format_args!("{type.field} {while.for}"); @@ -211,7 +212,7 @@ field expression left-to-right, after all explicit fields. This would be more consistent with normal expressions (e.g. function calls), but would be inconsistent with existing support for implicit named arguments without fields. We should consider changing the behavior for implicit named arguments without -fields, via an edition. +fields, via an edition or a careful crater run. We could omit support for other formatting parameters (width, precision). However, this would introduce an inconsistency that people have to remember; @@ -259,11 +260,12 @@ alternatives" for further exploration of this. # Future possibilities [future possibilities]: #future-possibilities -In a future edition, we could stop treating `"{type}"` as though written with a -raw keyword, and instead require `"{r#type}"`, or disallow it entirely. This -would then unblock the ability to write `"{x.await}"` or similar. +In a future edition or via a careful crater run, we could stop treating +`"{type}"` as though written with a raw keyword, and instead require +`"{r#type}"`, or disallow it entirely. This would then unblock the ability to +write `"{x.await}"` or similar. -In a future edition, we could stop deduplicating `"{x.field} {x.field}"`, and -instead desugar to a distinct evaluation for each field access expression. This -would more closely match how function calls handle their arguments (e.g. -`func(x.field, x.field)`). +In a future edition or via a careful crater run, we could stop deduplicating +`"{x.field} {x.field}"`, and instead desugar to a distinct evaluation for each +field access expression. This would more closely match how function calls +handle their arguments (e.g. `func(x.field, x.field)`). From b29c22b5334826e4f281371e846b286c2b8c0f01 Mon Sep 17 00:00:00 2001 From: Josh Triplett Date: Thu, 13 Nov 2025 18:04:59 -0800 Subject: [PATCH 17/17] Add ordering and deduplication as an unresolved question --- text/3626-format-args-implicit-dot.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/text/3626-format-args-implicit-dot.md b/text/3626-format-args-implicit-dot.md index e930453eb19..798a8cc0d1a 100644 --- a/text/3626-format-args-implicit-dot.md +++ b/text/3626-format-args-implicit-dot.md @@ -257,6 +257,13 @@ but nonetheless these other languages provide precedent for permitting more than just single identifiers. See the discussion in "Rationale and alternatives" for further exploration of this. +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +Can we successfully change the ordering and deduplication issue identified in +https://github.com/rust-lang/rust/issues/145739#issuecomment-3530192688 ? If we +can, we should do so, before stabilizing this. + # Future possibilities [future possibilities]: #future-possibilities