Skip to content

Commit 46efff3

Browse files
committed
Extend discussion around interpolation and prior art
The RFC itself is not intended to be a way to sneak interpolation into the formatting macros; the RFC author believes that they do not need full interpolation support, although would not rule it out if it was deemed desirable. This update to the RFC text clarifies the distinction between implicit named arguments and interpolation. It also adds a note on prior art that Field Init Shorthand is an existing precedent where language ergonomics have introduced a special case for single identifiers.
1 parent d1fb492 commit 46efff3

File tree

1 file changed

+106
-32
lines changed

1 file changed

+106
-32
lines changed

text/0000-format-args-implicit-identifiers.md

Lines changed: 106 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -137,21 +137,104 @@ Implicit named arguments seek to combine the brevity of positional arguments wit
137137

138138
format_args!("The {species}'s name is {name}")
139139

140-
## Alternatives
140+
## Alternative Implementations and Syntax
141141

142142
Users who wish to use implicit named arguments could make use of a third-party crate, for example the existing [fstrings crate](https://crates.io/crates/fstrings), which was built during early discussion about this proposal. This RFC accepts that deferring to a third-party crate is a reasonable option. It would however miss out on the opportunity to provide a small and straightforward ergnomic boost to many macros which are core to the rust language as well as the ecosytem which is derived from these standard library macros.
143143

144144
For similar reasons this RFC would argue that introducing a new alternative macro to `format_args!` in the standard library would not be a good outcome compared to adding to the existing macro.
145145

146146
An alternative syntax for implicit named arguments is welcomed by this RFC if it can be argued why it is preferable to the RFC's proposed form. The RFC author argues the chosen syntax is the most suitable, because it matches the existing syntax for named arguments.
147147

148+
## Alternative Solution - Interpolation
149+
[interpolation]: #interpolation
150+
151+
Some may argue that if it becomes possible to write identifiers into format strings and have them passed as implicit named arguments to the macro, why not make it possible to do similar with expressions. For example, these macro invocations seem innocent enough, reasonably readable, and are supported in Python 3 and Javascript's string formatting mechanisms:
152+
153+
println!("hello {get_person()}"); // function call
154+
println!("hello {self.person}"); // field access
155+
156+
The RFC author anticipates in particular that field access may be requested by many as part of this RFC. After careful consideration this RFC does not propose to go further than the single identifier special case, proposed above as implicit named arguments.
157+
158+
If any expressions beyond identifiers become accepted in format strings, then the RFC author expects that users will inevitably ask "why is *my* particular expression not accepted?". This could lead to feature creep, and before long perhaps the following might become valid Rust:
159+
160+
println!("hello { if self.foo { &self.person } else { &self.other_person } }");
161+
162+
This no longer seems easily readable to the RFC author.
163+
164+
### Proposed Interpolation Syntax
165+
166+
Early review of this RFC raised an observation that the endpoint of such feature creep would be that eventually Rust would embrace interpolation of any expressions inside these macros.
167+
168+
To keep interpolation isolated from named and positional arguments, as well as for readability and (possibly) to reduce parsing complexity, curly-plus-bracket syntax was proposed for interpolation:
169+
170+
println!("hello {(get_person())}");
171+
println!("hello {(self.person)}");
172+
173+
Indeed the RFC's perverse example reads slightly easier with this syntax:
174+
175+
println!("hello {( if self.foo { &self.person } else { &self.other_person } )}");
176+
177+
Because the interpolation syntax `{(expr)}` is orthogonal to positional `{}` and named `{ident}` argument syntax, and is a superset of the functionality which would be offered by implict named arguments, the argument was made that we should make the leap directly to interpolation without introducing implicit named arguments so as to avoid complicating the existing cases.
178+
179+
### Argument Against Interpolation
180+
181+
It should first be noted that the interpolation in other languages is often a language feature; if they have string formatting functions they typically do not enjoy syntax-level support. Instead other language formatting functions often behave similarly to Rust's positional and/or named arguments to formatting macros.
182+
183+
For example, Python 3's `.format()` method is on the surface extremely similar to Rust's formatting macros:
184+
185+
"hello {}".format(person)
186+
"hello {person}".format(person=person)
187+
188+
However, Python 3 cannot improve the ergonomics of these functions in the same way that this RFC proposes to use implicit named arguments. This is for technical reasons: Python simply does not have a language mechanism which could be used to add implicit named arguments to the `.format()` method. As a result, offering improved ergonomics in Python would neccesitate the introduction of a language-level interpolation syntax.
189+
190+
(Note, the closest Python 3's `.format()` can get to implicit named arguments is this:
191+
192+
"hello {person}".format(**locals())
193+
194+
but as noted in [PEP 498](https://www.python.org/dev/peps/pep-0498/#no-use-of-globals-or-locals), the Python language designers had reasons why they wanted to avoid this pattern becoming commonplace in Python code.)
195+
196+
Rust's macros are not constrained by the same technical limitations, being free to introduce syntax as long as it is supported by the macro system and hygeiene. The macros can therefore enjoy carefully-designed ergonomic improvements without needing to reach for large extensions such as interpolation.
197+
198+
The RFC author would argue that if named arguments (implicit or regular) become popular as a result of implementation of this RFC, then the following interpolation-free invocations would be easy to read and good style:
199+
200+
// Just use named arguments in simple cases
201+
println!("hello {person}", person=get_person());
202+
println!("hello {person}", person=self.person);
203+
204+
// For longwinded expressions, create identifiers to pass implicitly
205+
// so as to keep the macro invocation concise.
206+
let person = if self.foo { &self.person } else { &self.other_person };
207+
println!("hello {person}");
208+
209+
Similar to how implicit named arguments can be offered by third-party crates, interpolation macros already exist in the [ifmt crate](https://crates.io/crates/ifmt).
210+
211+
### Interpolation Summary
212+
213+
The overall argument is not to deny that the standard library macros in question would not become more expressive if they were to gain fully interpolation.
214+
215+
However, the RFC author argues that adding interpolation to these macros is less neccessary to improve ergonomics when comparing against other languages which chose to introduce language-level interpolation support. Introduction of implicit named arguments will cater for many of the common instances where interpolation would have been desired. The existing positional and named arguments can accept arbitrary expressions, and are not so unergonomic that they feel overly cumbersome when the expression in question is also nontrivial.
216+
148217

149218
# Prior art
150219
[prior-art]: #prior-art
151220

152-
A number of languages support string-interpolation functionality similar to what Rust's formatting macros offer. The RFC author's influence comes primarily from Python 3's "f-strings" and Javscript's backticks.
221+
## Field Init Shorthand
222+
223+
Rust already has another case in the language where the single identifier case is special-cased:
224+
225+
struct Foo { bar: u8 }
226+
let bar = 1u8;
227+
228+
let foo = Foo { bar: bar };
229+
let foo = Foo { bar }; // This shorthand only accepts single identifiers
230+
231+
This syntax is widely used and clear to read. It's [introduced in the Rust Book as one of the first topics in the section on structs](https://doc.rust-lang.org/book/ch05-01-defining-structs.html#using-the-field-init-shorthand-when-variables-and-fields-have-the-same-name). This sets a precedent that the Rust language is prepared to accept special treatment for single identifiers when it keeps syntax concise and clear.
232+
233+
## Other languages
153234

154-
For a comparison of the three languages, the following code would be the equivalent way to produce a new string combining a `greeting` and a `person`:
235+
A number of languages support string-interpolation functionality with similar syntax to what Rust's formatting macros. The RFC author's influence comes primarily from Python 3's "f-strings" and Javscript's backticks.
236+
237+
The following code would be the equivalent way to produce a new string combining a `greeting` and a `person` in a variety of languages:
155238

156239
// Rust
157240
format!("{} {}", greeting, person) // positional form,
@@ -163,17 +246,30 @@ For a comparison of the three languages, the following code would be the equival
163246
// Javascript
164247
`${greeting} ${person}`
165248

166-
It is the RFC author's experience that Python and Javascript's functionality read easily from left-to-right and it is clear where each variable is being substituted into the format string.
249+
// C# / VB
250+
$"{greeting} {person}"
251+
252+
// Swift
253+
"\(greeting) \(person)"
254+
255+
// Ruby
256+
"#{greeting} #{person}"
257+
258+
// Scala / PHP
259+
s"$greeting $person"
260+
261+
It is the RFC author's experience that these interpolating mechanisms read easily from left-to-right and it is clear where each variable is being substituted into the format string.
167262

168-
In the Rust forms illustrated above, the positional form suffers the drawback of not reading strictly from left to right; the reader of the code must refer back-and-forth between the format string and the argument list to determine where each variable will be subsituted. The named form avoids this drawback at the cost of much longer code.
263+
In the Rust formatting macros as illustrated above, the positional form suffers the drawback of not reading strictly from left to right; the reader of the code must refer back-and-forth between the format string and the argument list to determine where each variable will be subsituted. The named form avoids this drawback at the cost of much longer code.
169264

170265
Implementing implicit named arguments in the fashion suggested in this RFC would eliminate the drawbacks of each of the Rust forms and permit new syntax much closer to the other languages:
171266

172267
// Rust - implicit named arguments
173268
format!("{greeting} {person}")
174269

175-
It should be noted, however, that both Python 3's f-strings and Javascript's backticks accept a wide variety of expressions beyond the simple identifier case that this RFC is focussed on. The RFC author argues that supporting expressions inside format strings is unneccessary in Rust, however does not rule out that this is possible as a future extension. Please see the discusison in the [future possibilities](#future-possibilities) section at the end of this RFC.
270+
It should be noted, however, that other languages' string interpolation mechanisms allow substitution of a wide variety of expressions beyond the simple identifier case that this RFC is focussed on.
176271

272+
Please see the discusison on [interpolation](#interpolation) as an alternative to this RFC.
177273

178274
# Unresolved questions
179275
[unresolved-questions]: #unresolved-questions
@@ -232,32 +328,10 @@ It is not clear how significant a change this might require to `format_args!`'s
232328
# Future possibilities
233329
[future-possibilities]: #future-possibilities
234330

235-
Some may argue that if it becomes possible to write identifiers into format strings and have them passed as implicit named arguments to the macro, why not make it possible to do similar with expressions. For example, these macro invocations seem innocent enough, reasonably readable, and are supported in Python 3 and Javascript's string formatting mechanisms:
236-
237-
println!("hello {get_person()}"); // function call
238-
println!("hello {self.person}"); // field access
331+
The main alternative raised by this RFC is interpolation, which is a superset of the functionality offered by implicit named arguments.
239332

240-
The RFC author anticipates in particular that field access may be requested by many as part of this RFC. After careful consideration this RFC does not propose to go further than the single identifier expression, described above as implicit named arguments.
241-
242-
If any expressions beyond identifiers become accepted in format strings, then the RFC author expects that users will inevitably ask "why is *my* particular expression not accepted?". This could lead to feature creep, and before long perhaps the following might become valid Rust:
243-
244-
println!("hello { if self.foo { &self.person } else { &self.other_person } });
245-
246-
This no longer seems easily readable to the RFC author.
247-
248-
Instead, the RFC author would argue that if named arguments (implicit or regular) become popular as a result of implementation of this RFC, then the following invocations would be easy to read and good style:
249-
250-
// Just use named arguments in simple cases
251-
println!("hello {person}", person=get_person());
252-
println!("hello {person}", person=self.person);
253-
254-
// For longwinded expressions, create identifiers to pass implicitly
255-
// so as to keep the macro invocation concise.
256-
let person = if self.foo { &self.person } else { &self.other_person };
257-
println!("hello {person}");
258-
259-
(It should be noted that Python 3's f-strings and Javascript's backticks are not able to accept arguments other than the format string itself, and so they cannot enjoy the benefits of Rust's existing mechanism of named arguments.)
260-
261-
The RFC author would be prepared to extend the RFC if discussion about certain simple expressions raises a strong desire for these to also become acceptable in format strings. But he cautions that it may be difficult to agree where to draw the line, and so proposes the sole addition of implicit named arguments as a fine ergonomic improvement for now which doesn't rule out further extensions in the future.
333+
Accepting the addition of implicit named arguments now is not incompatible with adding interpolation at a later date.
262334

263335
In particular the RFC author expects that more than once in the future he'll be frustrated that formatting macro invocations which involve field access will require significantly more typing than invocations receiving implicit named arguments!
336+
337+
However, for reasons discussed above, interpolation is not the objective of this RFC.

0 commit comments

Comments
 (0)