From a90c2242ba0390e811a0751f67051e31d04aec0b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 23 Sep 2026 02:07:34 +0200 Subject: [PATCH 1/8] New blog post: Have Named Parameters At Home Signed-off-by: Matthias --- .../blog/named-parameters-at-home/index.md | 931 ++++++++++++++++++ 1 file changed, 931 insertions(+) create mode 100644 content/blog/named-parameters-at-home/index.md diff --git a/content/blog/named-parameters-at-home/index.md b/content/blog/named-parameters-at-home/index.md new file mode 100644 index 00000000..a628082f --- /dev/null +++ b/content/blog/named-parameters-at-home/index.md @@ -0,0 +1,931 @@ ++++ +title = "We Have Named Parameters At Home" +date = 2026-09-23 +draft = false +template = "article.html" +[extra] +series = "Idiomatic Rust" ++++ + +Steve Klabnik recently wrote [“Arguing about arguments”](https://steveklabnik.com/writing/arguing-about-arguments/), about named parameters, optional arguments, default arguments, function overloading, and why most of that design space has historically made him nervous in Rust. It's a wonderful read, and I recommend it. + +I agree with Steve. In fact, I think I agree slightly more strongly than Steve does. :) + +Steve has warmed to named parameters. I’m still happy with Rust’s existing ways to get similar results, not just for named parameters, but also defaults, function overloading, and, regrettably or otherwise, variadic arguments. + +None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. That’s the point. They tend to give you maybe 80% of the ergonomics while preserving the property Steve cares about: there is considerably less magic in figuring out what a call means. + +The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it with an ordinary piece of the type system instead. I think that’s usually a pretty good trade. + +## Named parameters at home + +Steve uses this example from the `image` crate: + +```rust +pub fn crop_imm( + image: &I, + x: u32, + y: u32, + width: u32, + height: u32, +) -> SubImage<&I> { + // ... +} + +let cropped = image::imageops::crop_imm(&img, 10, 20, 200, 100); +``` + +The problem is obvious. Four consecutive `u32`s are not an API you can reliably read without knowing the signature. + +With hypothetical named arguments: + +```rust +let cropped = image::imageops::crop_imm( + image: &img, + x: 10, + y: 20, + width: 200, + height: 100, +); +``` + +Yep. Better. But stable Rust has another syntax which looks suspiciously familiar: + +```rust +struct Crop { + x: u32, + y: u32, + width: u32, + height: u32, +} + +fn crop_imm( + image: &I, + crop: Crop, +) -> SubImage<&I> { + // ... +} + +let cropped = image::imageops::crop_imm( + &img, + Crop { + x: 10, + y: 20, + width: 200, + height: 100, + }, +); +``` + +That is named arguments with one extra type name. + +We also get arbitrary field order: + +```rust +Crop { + width: 200, + height: 100, + x: 10, + y: 20, +} +``` + +We get typo checking, autocomplete, and documentation attached to each field. We can put invariants on the type and pass the arguments around as a value. + +And, importantly, the names belong to the **type**, rather than becoming part of every function’s calling convention. + +That last property neatly dodges several problems Steve raises about real named parameters. Consider function pointers: + +```rust +fn resize(width: u32, height: u32) {} +fn offset(dx: u32, dy: u32) {} + +let f: fn(u32, u32) = + if resizing { resize } else { offset }; +``` + +What would the parameter names of `f` be? + +With an argument struct, the question simply doesn’t arise: + +```rust +struct Size { + width: u32, + height: u32, +} + +fn resize(size: Size) {} + +let f: fn(Size) = resize; +``` + +If names are semantically important, give the names a type. If they aren’t, don’t. + +There is another cute advantage here. Steve brings up evaluation order: + +```rust +consume(length: data.len(), data: data); +``` + +Should this evaluate in call-site order or declaration order? Rust already answered this question for structs: + +```rust +let args = Args { + length: data.len(), + data, +}; +``` + +Expressions are evaluated where you wrote them. No new rule required. + +This feels extremely Rusty to me: rather than teaching function calls a second field-like syntax with subtly different semantics, use the field syntax that already exists. + +### The lightweight version + +Of course, declaring a bespoke argument type for every two-argument function would be ridiculous. I would not write: + +```rust +struct PushArgs { + value: T, +} + +vec.push(PushArgs { value: 42 }); +``` + +The trick is to notice that named parameters are most useful exactly where an argument bundle becomes conceptually meaningful. + +These are bad: + +```rust +draw(x1, y1, x2, y2, width, opacity); +connect(host, port, timeout, retries, tls); +``` + +And these are often better APIs *anyway*: + +```rust +draw(Line { + start: Point { x: x1, y: y1 }, + end: Point { x: x2, y: y2 }, + width, + opacity, +}); + +connect(ConnectionOptions { + host, + port, + timeout, + retries, + tls, +}); +``` + +The “named argument” transformation forced us to discover some actual domain concepts. I’ll take that. + +## Optional arguments at home + +An optional argument is, at some level, an argument which may or may not exist. Rust has a type for that. + +```rust +fn connect(url: &str, timeout: Option) { + // ... +} + +connect("https://example.com", None); + +connect( + "https://example.com", + Some(Duration::from_secs(5)), +); +``` + +This is not as pleasant as: + +```text +connect("https://example.com") +connect("https://example.com", timeout: 5s) +``` + +But it has a useful property: the optionality appears in the function’s type. There isn’t a hidden second calling convention for `connect`. There is one function: + +```rust +fn(&str, Option) +``` + +and every caller supplies both arguments. + +This is obviously not what you want once you have six optional parameters: + +```rust +request( + url, + None, + None, + Some(timeout), + None, + None, + None, +); +``` + +Nobody wants this. But that is the point where the arguments have stopped being a parameter list and started being configuration. So: + +```rust +struct RequestOptions { + timeout: Option, + proxy: Option, + redirect: Option, + // ... +} + +request( + url, + RequestOptions { + timeout: Some(Duration::from_secs(5)), + proxy: None, + redirect: None, + }, +); +``` + +We have traded optional arguments for ordinary data. And that gives the API something nice: there is no special distinction between “arguments supplied syntactically to this invocation” and “options I calculated somewhere else.” + +```rust +let options = RequestOptions { + timeout: config.request_timeout, + proxy: detect_proxy(), + redirect: None, +}; + +request(url, options); +``` + +It composes because it is just a value. + +## Default arguments at home + +Now the obvious objection: writing all those `None`s is terrible. Correct. So don’t. Rust has `Default` and struct update syntax: + +```rust +#[derive(Default)] +struct RequestOptions { + timeout: Option, + proxy: Option, + follow_redirects: bool, +} + +request( + url, + RequestOptions { + timeout: Some(Duration::from_secs(5)), + ..Default::default() + }, +); +``` + +That is getting awfully close to: + +```text +request(url, timeout: 5s) +``` + +The syntactic tax is: + +```rust +RequestOptions { + ... + ..Default::default() +} +``` + +That is not nothing. But look at what we *didn’t* have to add: rules for which parameters may be omitted, how positional and named arguments interact, or whether you can omit something in the middle. + +There’s no special syntax for declaring parameter defaults, no question about whether default expressions run at declaration time or invocation time, and no special representation in `fn` types. + +`Default` is just a trait. Struct update is just struct update. Function calls remain function calls. + +And defaults are now usable independently of the function: + +```rust +let defaults = RequestOptions::default(); +``` + +That is frequently useful in its own right. For library APIs, I often like being slightly more deliberate: + +```rust +struct RequestOptions { + timeout: Duration, + follow_redirects: bool, +} + +impl Default for RequestOptions { + fn default() -> Self { + Self { + timeout: Duration::from_secs(30), + follow_redirects: true, + } + } +} +``` + +Then: + +```rust +request( + url, + RequestOptions { + timeout: Duration::from_secs(5), + ..Default::default() + }, +); +``` + +This is default arguments at home, and I think it gets most of the important bit right. + +## The builder pattern is the deluxe version + +Sometimes even the options struct is too noisy, especially when construction requires validation or conversion. + +Then yes, there is the builder: + +```rust +let request = Request::builder(url) + .timeout(Duration::from_secs(5)) + .follow_redirects(false) + .build()?; +``` + +Steve is right that builders should not automatically be celebrated. They can become their own tiny programming language. But a small builder has a very useful property: each “argument” is an ordinary method call. That means we can do things like: + +```rust +let mut request = Request::builder(url); + +if let Some(timeout) = config.timeout { + request = request.timeout(timeout); +} + +let request = request.build()?; +``` + +Doing that with language-level keyword arguments generally requires constructing a map, splatting something, or adding some other mechanism. + +In Rust, the boring desugaring is sitting right there. It’s methods. The price is verbosity, but the semantic budget stays tiny. + +## Function overloading at home + +Steve’s Java example is roughly: + +```java +void connect(String url, int timeout) { ... } +void connect(String url) { ... } +``` + +Rust doesn’t let you define both: + +```rust +fn connect(url: &str) {} +fn connect(url: &str, timeout: Duration) {} +``` + +I am very happy about this. But there are several different things people mean when they say they want overloading, and Rust already covers most of them separately. + +### “I want one convenience form and one configurable form” + +Give them different names: + +```rust +fn connect(url: &str) { + connect_with_timeout(url, DEFAULT_TIMEOUT) +} + +fn connect_with_timeout(url: &str, timeout: Duration) { + // ... +} +``` + +`Vec::new()` and `Vec::with_capacity()` are the canonical version of this. + +This costs the library author one additional name and saves every reader from doing overload resolution in their head. Seems like a bargain. + +### “I want several input types” + +Use a trait. The standard library does this constantly with traits like `Into`, `AsRef`, and `Borrow`. For example: + +```rust +fn greet(name: impl AsRef) { + println!("Hello, {}", name.as_ref()); +} + +greet("Ferris"); +greet(String::from("Ferris")); +``` + +This gives us a useful part of overload-like behavior: different source types accepted by one API. + +For owned conversion: + +```rust +fn set_name(name: impl Into) { + let name = name.into(); + // ... +} +``` + +Again, one function. One parameter list. Ordinary trait dispatch. And unlike unrestricted overloading, the relationship between accepted types is explicit: they must satisfy the bound. + +### “Different types should do genuinely different things” + +That’s a trait too: + +```rust +trait Render { + fn render(self, out: &mut Output); +} + +impl Render for &str { + fn render(self, out: &mut Output) { + // ... + } +} + +impl Render for Image { + fn render(self, out: &mut Output) { + // ... + } +} + +fn render(value: impl Render, out: &mut Output) { + value.render(out); +} +``` + +We got ad-hoc polymorphism. We just put it in the trait system instead of in name resolution. That is a theme. + +## Flexible argument types at home + +Steve’s Ruby example has this delightful/terrifying quality: + +```ruby +redirect_to "http://www.rubyonrails.org" +redirect_to @post +redirect_to action: "show", id: 5 +``` + +These calls look like they’re invoking one conceptual operation, but they mean wildly different things. + +Rust can model that directly: + +```rust +enum Redirect { + Url(Url), + Post(Post), + Action { + action: String, + id: u64, + }, +} + +fn redirect_to(target: Redirect) { + // ... +} +``` + +Then: + +```rust +redirect_to(Redirect::Url(url)); + +redirect_to(Redirect::Post(post)); + +redirect_to(Redirect::Action { + action: "show".into(), + id: 5, +}); +``` + +More verbose? Absolutely. But I can ask my editor, “what can I redirect to?” and the answer is the variants of `Redirect`. That’s a much stronger property than “read the docs and discover the accepted shapes of this hash.” + +If we really care about smoothing the edges, add conversions: + +```rust +impl From for Redirect { + fn from(url: Url) -> Self { + Self::Url(url) + } +} + +impl From for Redirect { + fn from(post: Post) -> Self { + Self::Post(post) + } +} + +fn redirect_to(target: impl Into) { + let target = target.into(); + // ... +} +``` + +Now: + +```rust +redirect_to(url); +redirect_to(post); +``` + +And for the structurally interesting case: + +```rust +redirect_to(Redirect::Action { + action: "show".into(), + id: 5, +}); +``` + +That is pretty good! Importantly, the weirdness has a name. It is called `Redirect`. + +## Options hashes at home + +An options hash is basically a dynamically typed anonymous struct. So the extremely boring Rust translation is: use a statically typed named struct. + +Ruby: + +```ruby +redirect_to post_url(@post), + status: 301, + flash: { updated_post_id: @post.id } +``` + +Rust: + +```rust +redirect_to( + post_url(&post), + RedirectOptions { + status: StatusCode::MOVED_PERMANENTLY, + flash: Some(Flash { + updated_post_id: Some(post.id), + ..Default::default() + }), + ..Default::default() + }, +); +``` + +Yes, the Rust version is considerably louder. It is also difficult to misspell `status`, impossible to pass a string where a status code belongs, and straightforward to enumerate every supported option. + +I don’t think Rust should optimize for winning this particular code-golf competition. + +There is an interesting middle ground, though. If a set of options is common enough to deserve convenient syntax, it is probably common enough to deserve a type. That type then becomes part of your API vocabulary: + +```rust +fn redirect_to(target: Url, options: RedirectOptions) +``` + +instead of: + +```text +redirect_to(thing, bag_of_stuff) +``` + +That feels like exactly the kind of pressure Rust generally likes to apply. + +## Variadic arguments at home + +Rust does not have general-purpose variadic Rust functions. But once again, it already has several ways of expressing the underlying use cases. + +If all arguments have the same type, take a slice: + +```rust +fn sum(values: &[i32]) -> i32 { + values.iter().sum() +} + +sum(&[1, 2, 3, 4]); +``` + +Or accept an iterator: + +```rust +fn sum(values: impl IntoIterator) -> i32 { + values.into_iter().sum() +} + +sum([1, 2, 3, 4]); +sum(vec![1, 2, 3, 4]); +``` + +That is arguably *more* composable than: + +```text +sum(1, 2, 3, 4) +``` + +because the caller can naturally pass an existing collection. + +If the arguments are heterogeneous and syntax really matters, we have macros. + +You might have encountered one: + +```rust +println!("{} + {} = {}", 1, 2, 3); +``` + +Or: + +```rust +vec![1, 2, 3, 4] +``` + +`macro_rules!` is stable Rust. + +I would not reach for a macro just to fake variadic functions. But when an API genuinely wants syntax that the function-call grammar does not provide, Rust already has an explicit mechanism for saying: + +> This is syntax, not an ordinary function. + +I like that honesty. A macro invocation looks like a macro invocation. It doesn’t silently make functions more complicated. + +## Keyword-looking syntax at home + +There is one tiny feature hidden in all of these examples that I think deserves more credit: field-init shorthand. + +Rust lets you turn this: + +```rust +let options = RequestOptions { + timeout: timeout, + proxy: proxy, + retries: retries, +}; +``` + +into this: + +```rust +let options = RequestOptions { + timeout, + proxy, + retries, +}; +``` + +This directly addresses one of Steve’s complaints about keyword arguments: + +```python +response_model=response_model, +status_code=status_code, +tags=tags, +dependencies=dependencies, +``` + +Rust’s answer is effectively: + +```rust +Options { + response_model, + status_code, + tags, + dependencies, +} +``` + +That is arguably *better* than keyword arguments for the forwarding case. + +The labels are still present. The duplication disappears. And no new function-call feature was required. + +This is one of my favorite small pieces of Rust syntax because it is exactly the kind of ergonomic feature I want: local sugar over an already-simple semantic model. + +## And all of these compose + +Here is where I think the “at home” versions become more compelling than they initially appear. + +Suppose we want an HTTP request API with: + +* a required URL, +* several accepted URL-like input types, +* named options, +* defaults, +* optional timeout, +* configurable redirects, +* and a variable number of headers. + +We could imagine a language feature pile that lets us write: + +```text +request( + "/hello", + timeout: 5s, + redirects: false, + headers: [ + ("Accept", "application/json"), + ("X-Foo", "bar"), + ], +) +``` + +Stable Rust can instead say: + +```rust +#[derive(Default)] +struct RequestOptions { + timeout: Option, + redirects: bool, + headers: Vec
, +} + +fn request( + url: impl Into, + options: RequestOptions, +) -> Result { + // ... +} + +request( + "/hello", + RequestOptions { + timeout: Some(Duration::from_secs(5)), + redirects: false, + headers: vec![ + Header::new("Accept", "application/json"), + Header::new("X-Foo", "bar"), + ], + }, +)?; +``` + +Maybe you prefer a builder: + +```rust +Request::new("/hello") + .timeout(Duration::from_secs(5)) + .redirects(false) + .header("Accept", "application/json") + .header("X-Foo", "bar") + .send()?; +``` + +Neither one is as concise as the imaginary version. + +But look at the mechanisms involved: + +* structs, +* enums, +* `Option`, +* `Default`, +* struct update syntax, +* field-init shorthand, +* traits, +* generics, +* iterators, +* methods, +* macros when you genuinely need custom syntax. + +Those mechanisms are all useful far beyond argument passing. Rust doesn’t need a separate version of each concept specifically for functions. + +## The missing 20% is doing work + +This is the part that makes me agree with Steve’s larger point. The obvious response to everything above is: + +> Come on. These aren't actually named/default/overloaded/variadic arguments. They're workarounds. + +Correct. That is why I called this “named parameters at home,” not “Rust secretly has named parameters.” The distinction matters. + +Actual named arguments might let me turn: + +```rust +crop_imm(&img, 10, 20, 200, 100); +``` + +into: + +```rust +crop_imm( + image: &img, + x: 10, + y: 20, + width: 200, + height: 100, +); +``` + +without defining anything else. + +That is undeniably nicer at the call site. + +Actual default parameters might let me write: + +```rust +request(url, timeout: timeout); +``` + +instead of introducing `RequestOptions`. + +Actual overloading might let two functions share the same name instead of forcing me to invent `with_timeout`. + +That last 20% is real. + +But the friction in the Rust versions often pushes APIs toward things that turn out to be useful anyway. + +Four coordinates become a `Rect`. Seven configuration parameters become `RequestOptions`. A grab bag of dynamically accepted values becomes an enum. + +A family of related operations becomes a trait. A repeated sequence becomes an iterator. A genuinely unusual call syntax becomes visibly macro-shaped syntax. + +The workaround frequently turns accidental API structure into explicit type structure. That seems valuable. + +## Types are Rust’s options hashes + +There is a broader design instinct here that I’ve come to appreciate. + +Dynamic languages often accumulate power by allowing a function call to describe increasingly elaborate things: + +```text +foo(x) +foo(x, y) +foo(x, timeout: 3) +foo(path: x, timeout: 3) +foo(x, **options) +foo(*args, **options) +``` + +Rust tends to move that complexity one level outward: + +```rust +foo(FooOptions { ... }) +``` + +and then lets the type system do the work. + +The API becomes slightly more ceremonial. But the *function call* remains incredibly boring: you name a function, pass one value for each parameter, and the arguments evaluate in order. The types have to match. Done. + +Like Steve, I think there is substantial value in preserving that boringness, especially in Rust, where there are already enough places for sophisticated language semantics to live. + +## What about agents? + +Steve’s new openness to named parameters comes partly from coding agents. The argument makes sense: if a machine is doing the typing, verbosity becomes cheaper, while redundant labels may make a call easier to understand locally. + +I agree with the premise. I’m less sure it changes the conclusion. + +An agent looking at: + +```rust +crop_imm( + &img, + Crop { + x: 10, + y: 20, + width: 200, + height: 100, + }, +); +``` + +gets essentially the same local information. + +Arguably it gets more: `Crop` gives the bundle a semantic identity which the function parameter list alone does not. + +Similarly: + +```rust +request( + url, + RequestOptions { + timeout, + ..Default::default() + }, +); +``` + +says something useful to both humans and agents. + +`timeout` is not merely an optional syntactic argument to this particular invocation. It is one field in the `RequestOptions` concept. + +And if agents really do make typing cost increasingly irrelevant, then the principal downside of these slightly-more-verbose Rust idioms gets cheaper too. + +The robots can type `RequestOptions` for me. Great. + +## Keep the functions boring + +I’m not philosophically opposed to Rust ever gaining named parameters. + +There may be a proposal that finds a tiny, coherent design which handles patterns, function pointers, traits, evaluation order, compatibility, and all the other sharp edges Steve describes. Language design is full of features that initially look impossible and eventually acquire a satisfying formulation. + +But I don’t feel much urgency. + +Stable Rust already gives me: + +* named arguments: **struct fields** +* optional arguments: **`Option`** +* default arguments: **`Default` + struct update syntax** +* overloads for convenience: **differently named functions** +* overloads across input types: **traits / `Into` / `AsRef`** +* heterogeneous alternatives: **enums** +* options hashes: **options structs** +* repeated homogeneous arguments: **slices and iterators** +* truly syntax-shaped variable arguments: **macros** +* `foo=foo` forwarding noise: **field-init shorthand** + +None is a perfect substitute. Collectively, though, they cover a remarkable amount of the territory. And they do it by reusing features Rust already needs rather than teaching function calls how to become a little programming language of their own. + +Maybe Rust eventually gets named parameters. Until then, we have named parameters at home. + +I kind of like them. From 6905d10bd52461c5f5a18f88554735b58a03a1c3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 23 Sep 2026 02:09:18 +0200 Subject: [PATCH 2/8] Clarify Rust API alternatives to named parameters Signed-off-by: Matthias --- content/blog/named-parameters-at-home/index.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/content/blog/named-parameters-at-home/index.md b/content/blog/named-parameters-at-home/index.md index a628082f..95a1b12c 100644 --- a/content/blog/named-parameters-at-home/index.md +++ b/content/blog/named-parameters-at-home/index.md @@ -11,7 +11,7 @@ Steve Klabnik recently wrote [“Arguing about arguments”](https://steveklabni I agree with Steve. In fact, I think I agree slightly more strongly than Steve does. :) -Steve has warmed to named parameters. I’m still happy with Rust’s existing ways to get similar results, not just for named parameters, but also defaults, function overloading, and, regrettably or otherwise, variadic arguments. +Steve has warmed to named parameters. I think we can get most of what we want without adding language features, by leaning into what Rust already provides. That goes for named parameters, defaults, function overloading, and, regrettably or otherwise, variadic arguments. None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. That’s the point. They tend to give you maybe 80% of the ergonomics while preserving the property Steve cares about: there is considerably less magic in figuring out what a call means. @@ -433,7 +433,7 @@ fn set_name(name: impl Into) { Again, one function. One parameter list. Ordinary trait dispatch. And unlike unrestricted overloading, the relationship between accepted types is explicit: they must satisfy the bound. -### “Different types should do genuinely different things” +### “Different types need different behavior” That’s a trait too: @@ -503,7 +503,7 @@ redirect_to(Redirect::Action { }); ``` -More verbose? Absolutely. But I can ask my editor, “what can I redirect to?” and the answer is the variants of `Redirect`. That’s a much stronger property than “read the docs and discover the accepted shapes of this hash.” +More verbose? Absolutely. But I can ask my editor, “what can I redirect to?” and the answer is the variants of `Redirect`. That’s a much stronger property than “read the docs and discover which keys and values this hash accepts.” If we really care about smoothing the edges, add conversions: @@ -639,7 +639,7 @@ vec![1, 2, 3, 4] `macro_rules!` is stable Rust. -I would not reach for a macro just to fake variadic functions. But when an API genuinely wants syntax that the function-call grammar does not provide, Rust already has an explicit mechanism for saying: +I would not reach for a macro just to fake variadic functions. But when an API needs syntax that the function-call grammar does not provide, Rust already has an explicit mechanism for saying: > This is syntax, not an ordinary function. @@ -778,7 +778,7 @@ But look at the mechanisms involved: * generics, * iterators, * methods, -* macros when you genuinely need custom syntax. +* macros when you need custom syntax. Those mechanisms are all useful far beyond argument passing. Rust doesn’t need a separate version of each concept specifically for functions. @@ -828,7 +828,7 @@ But the friction in the Rust versions often pushes APIs toward things that turn Four coordinates become a `Rect`. Seven configuration parameters become `RequestOptions`. A grab bag of dynamically accepted values becomes an enum. -A family of related operations becomes a trait. A repeated sequence becomes an iterator. A genuinely unusual call syntax becomes visibly macro-shaped syntax. +A family of related operations becomes a trait. A repeated sequence becomes an iterator. Custom call syntax becomes an explicit macro invocation. The workaround frequently turns accidental API structure into explicit type structure. That seems valuable. @@ -921,7 +921,7 @@ Stable Rust already gives me: * heterogeneous alternatives: **enums** * options hashes: **options structs** * repeated homogeneous arguments: **slices and iterators** -* truly syntax-shaped variable arguments: **macros** +* variable arguments with custom syntax: **macros** * `foo=foo` forwarding noise: **field-init shorthand** None is a perfect substitute. Collectively, though, they cover a remarkable amount of the territory. And they do it by reusing features Rust already needs rather than teaching function calls how to become a little programming language of their own. From 7c5a4f92ae368389fa2163593734a7a759671c82 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 23 Sep 2026 02:11:48 +0200 Subject: [PATCH 3/8] Refine named parameters introduction prose Signed-off-by: Matthias --- content/blog/named-parameters-at-home/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/blog/named-parameters-at-home/index.md b/content/blog/named-parameters-at-home/index.md index 95a1b12c..1327c704 100644 --- a/content/blog/named-parameters-at-home/index.md +++ b/content/blog/named-parameters-at-home/index.md @@ -11,9 +11,9 @@ Steve Klabnik recently wrote [“Arguing about arguments”](https://steveklabni I agree with Steve. In fact, I think I agree slightly more strongly than Steve does. :) -Steve has warmed to named parameters. I think we can get most of what we want without adding language features, by leaning into what Rust already provides. That goes for named parameters, defaults, function overloading, and, regrettably or otherwise, variadic arguments. +Steve has warmed to named parameters, but I think we can get most of what we want without adding language features. Instead, we can lean into what Rust already provides. That goes for named parameters, defaults, function overloading, and, regrettably or otherwise, variadic arguments. -None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. That’s the point. They tend to give you maybe 80% of the ergonomics while preserving the property Steve cares about: there is considerably less magic in figuring out what a call means. +None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. But that’s the point. They tend to give you maybe 80% of the ergonomics while preserving the property Steve cares about: there is considerably less magic in figuring out what a call means. The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it with an ordinary piece of the type system instead. I think that’s usually a pretty good trade. From 4758e47c99d056124a73e1c42529e1d7539de55a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 23 Sep 2026 11:40:03 +0200 Subject: [PATCH 4/8] Refine named parameters article introduction --- content/blog/named-parameters-at-home/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/blog/named-parameters-at-home/index.md b/content/blog/named-parameters-at-home/index.md index 1327c704..92d4326f 100644 --- a/content/blog/named-parameters-at-home/index.md +++ b/content/blog/named-parameters-at-home/index.md @@ -13,9 +13,9 @@ I agree with Steve. In fact, I think I agree slightly more strongly than Steve d Steve has warmed to named parameters, but I think we can get most of what we want without adding language features. Instead, we can lean into what Rust already provides. That goes for named parameters, defaults, function overloading, and, regrettably or otherwise, variadic arguments. -None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. But that’s the point. They tend to give you maybe 80% of the ergonomics while preserving the property Steve cares about: there is considerably less magic in figuring out what a call means. +None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. But that’s the point. You get most of the ergonomics without adding magic to function calls. -The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it with an ordinary piece of the type system instead. I think that’s usually a pretty good trade. +The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it with a **familiar piece of the type system** instead. I think that’s often the better trade. ## Named parameters at home From b4a5ba232bd65c508cfd9ad1a253b115aaf6bec3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 23 Sep 2026 17:02:26 +0200 Subject: [PATCH 5/8] Rename post to We Have Named Arguments At Home --- .../index.md | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) rename content/blog/{named-parameters-at-home => named-arguments-at-home}/index.md (93%) diff --git a/content/blog/named-parameters-at-home/index.md b/content/blog/named-arguments-at-home/index.md similarity index 93% rename from content/blog/named-parameters-at-home/index.md rename to content/blog/named-arguments-at-home/index.md index 92d4326f..7e95e20f 100644 --- a/content/blog/named-parameters-at-home/index.md +++ b/content/blog/named-arguments-at-home/index.md @@ -1,5 +1,5 @@ +++ -title = "We Have Named Parameters At Home" +title = "We Have Named Arguments At Home" date = 2026-09-23 draft = false template = "article.html" @@ -7,17 +7,17 @@ template = "article.html" series = "Idiomatic Rust" +++ -Steve Klabnik recently wrote [“Arguing about arguments”](https://steveklabnik.com/writing/arguing-about-arguments/), about named parameters, optional arguments, default arguments, function overloading, and why most of that design space has historically made him nervous in Rust. It's a wonderful read, and I recommend it. +Steve Klabnik recently wrote [“Arguing about arguments”](https://steveklabnik.com/writing/arguing-about-arguments/), about named arguments, optional arguments, default arguments, function overloading, and why most of that design space has historically made him nervous in Rust. It's a wonderful read, and I recommend it. I agree with Steve. In fact, I think I agree slightly more strongly than Steve does. :) -Steve has warmed to named parameters, but I think we can get most of what we want without adding language features. Instead, we can lean into what Rust already provides. That goes for named parameters, defaults, function overloading, and, regrettably or otherwise, variadic arguments. +Steve has warmed to named arguments, but I think we can get most of what we want without adding language features. Instead, we can lean into what Rust already provides. That goes for named arguments, defaults, function overloading, and, regrettably or otherwise, variadic arguments. None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. But that’s the point. You get most of the ergonomics without adding magic to function calls. The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it with a **familiar piece of the type system** instead. I think that’s often the better trade. -## Named parameters at home +## Named arguments at home Steve uses this example from the `image` crate: @@ -94,7 +94,7 @@ We get typo checking, autocomplete, and documentation attached to each field. We And, importantly, the names belong to the **type**, rather than becoming part of every function’s calling convention. -That last property neatly dodges several problems Steve raises about real named parameters. Consider function pointers: +That last property neatly dodges several problems Steve raises about real named arguments. Consider function pointers: ```rust fn resize(width: u32, height: u32) {} @@ -152,7 +152,7 @@ struct PushArgs { vec.push(PushArgs { value: 42 }); ``` -The trick is to notice that named parameters are most useful exactly where an argument bundle becomes conceptually meaningful. +The trick is to notice that named arguments are most useful exactly where an argument bundle becomes conceptually meaningful. These are bad: @@ -214,7 +214,7 @@ fn(&str, Option) and every caller supplies both arguments. -This is obviously not what you want once you have six optional parameters: +This is obviously not what you want once you have six optional arguments: ```rust request( @@ -298,7 +298,7 @@ RequestOptions { } ``` -That is not nothing. But look at what we *didn’t* have to add: rules for which parameters may be omitted, how positional and named arguments interact, or whether you can omit something in the middle. +That is not nothing. But look at what we *didn’t* have to add: rules for which arguments may be omitted, how positional and named arguments interact, or whether you can omit something in the middle. There’s no special syntax for declaring parameter defaults, no question about whether default expressions run at declaration time or invocation time, and no special representation in `fn` types. @@ -788,7 +788,7 @@ This is the part that makes me agree with Steve’s larger point. The obvious re > Come on. These aren't actually named/default/overloaded/variadic arguments. They're workarounds. -Correct. That is why I called this “named parameters at home,” not “Rust secretly has named parameters.” The distinction matters. +Correct. That is why I called this “named arguments at home,” not “Rust secretly has named arguments.” The distinction matters. Actual named arguments might let me turn: @@ -812,7 +812,7 @@ without defining anything else. That is undeniably nicer at the call site. -Actual default parameters might let me write: +Actual default arguments might let me write: ```rust request(url, timeout: timeout); @@ -861,7 +861,7 @@ Like Steve, I think there is substantial value in preserving that boringness, es ## What about agents? -Steve’s new openness to named parameters comes partly from coding agents. The argument makes sense: if a machine is doing the typing, verbosity becomes cheaper, while redundant labels may make a call easier to understand locally. +Steve’s new openness to named arguments comes partly from coding agents. The argument makes sense: if a machine is doing the typing, verbosity becomes cheaper, while redundant labels may make a call easier to understand locally. I agree with the premise. I’m less sure it changes the conclusion. @@ -905,7 +905,7 @@ The robots can type `RequestOptions` for me. Great. ## Keep the functions boring -I’m not philosophically opposed to Rust ever gaining named parameters. +I’m not philosophically opposed to Rust ever gaining named arguments. There may be a proposal that finds a tiny, coherent design which handles patterns, function pointers, traits, evaluation order, compatibility, and all the other sharp edges Steve describes. Language design is full of features that initially look impossible and eventually acquire a satisfying formulation. @@ -926,6 +926,6 @@ Stable Rust already gives me: None is a perfect substitute. Collectively, though, they cover a remarkable amount of the territory. And they do it by reusing features Rust already needs rather than teaching function calls how to become a little programming language of their own. -Maybe Rust eventually gets named parameters. Until then, we have named parameters at home. +Maybe Rust eventually gets named arguments. Until then, we have named arguments at home. I kind of like them. From 70c88bd003c5797276e2aff432359460221d102a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Sep 2026 15:55:40 +0200 Subject: [PATCH 6/8] Revise named arguments article Signed-off-by: Matthias --- content/blog/named-arguments-at-home/index.md | 419 +++++++++--------- 1 file changed, 198 insertions(+), 221 deletions(-) diff --git a/content/blog/named-arguments-at-home/index.md b/content/blog/named-arguments-at-home/index.md index 7e95e20f..1bc78885 100644 --- a/content/blog/named-arguments-at-home/index.md +++ b/content/blog/named-arguments-at-home/index.md @@ -1,25 +1,31 @@ +++ title = "We Have Named Arguments At Home" -date = 2026-09-23 +date = 2026-09-24 draft = false template = "article.html" [extra] series = "Idiomatic Rust" +resources = [ + "[Steve Klabnik on our 'Rust in Production' podcast](/podcast/s03e03-oxide/)", +] +++ -Steve Klabnik recently wrote [“Arguing about arguments”](https://steveklabnik.com/writing/arguing-about-arguments/), about named arguments, optional arguments, default arguments, function overloading, and why most of that design space has historically made him nervous in Rust. It's a wonderful read, and I recommend it. +Steve Klabnik recently [wrote about](https://steveklabnik.com/writing/arguing-about-arguments/) named arguments, optional arguments, default arguments, function overloading, and why most of that design space has historically made him nervous in Rust. -I agree with Steve. In fact, I think I agree slightly more strongly than Steve does. :) +I agree with Steve. +In fact, I think I agree slightly more strongly than Steve does. :) -Steve has warmed to named arguments, but I think we can get most of what we want without adding language features. Instead, we can lean into what Rust already provides. That goes for named arguments, defaults, function overloading, and, regrettably or otherwise, variadic arguments. +I actually think we can get most of what we want without adding any new language features. +Instead, we can lean into what Rust already provides. -None of these substitutes is *quite* the feature you get in Python, Ruby, C++, or Kotlin. But that’s the point. You get most of the ergonomics without adding magic to function calls. +None of these substitutes what you get in Python, Ruby, C++, or Kotlin, but that's sort of the point. +Instead, you can get 80% of the ergonomics without adding any magic to function calls at all. -The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it with a **familiar piece of the type system** instead. I think that’s often the better trade. +The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it as a normal part of its type system, elegantly sidestepping the mentioned design problems. ## Named arguments at home -Steve uses this example from the `image` crate: +Let's revisit Steve's example from the `image` crate: ```rust pub fn crop_imm( @@ -35,9 +41,9 @@ pub fn crop_imm( let cropped = image::imageops::crop_imm(&img, 10, 20, 200, 100); ``` -The problem is obvious. Four consecutive `u32`s are not an API you can reliably read without knowing the signature. +The obvious problem is that four consecutive `u32`s are not a great API that you can reliably use without reading the docs. -With hypothetical named arguments: +Let's assume for a moment that we had named arguments: ```rust let cropped = image::imageops::crop_imm( @@ -49,7 +55,7 @@ let cropped = image::imageops::crop_imm( ); ``` -Yep. Better. But stable Rust has another syntax which looks suspiciously familiar: +That's clearly better, but stable Rust has another syntax in its place: structs. ```rust struct Crop { @@ -77,9 +83,8 @@ let cropped = image::imageops::crop_imm( ); ``` -That is named arguments with one extra type name. - -We also get arbitrary field order: +A struct is a named argument with one extra type name. +On top of that, we also get arbitrary field order: ```rust Crop { @@ -90,23 +95,23 @@ Crop { } ``` -We get typo checking, autocomplete, and documentation attached to each field. We can put invariants on the type and pass the arguments around as a value. +We also get typo checking, autocomplete, and per-field documentation for free! +And we can put invariants on the type and pass the arguments around as values. -And, importantly, the names belong to the **type**, rather than becoming part of every function’s calling convention. +And, perhaps most importantly, **the names belong to the type**, rather than becoming part of every function's calling convention. -That last property neatly dodges several problems Steve raises about real named arguments. Consider function pointers: +That last property neatly avoids several problems with actual named arguments. +Consider function pointers: ```rust fn resize(width: u32, height: u32) {} fn offset(dx: u32, dy: u32) {} -let f: fn(u32, u32) = - if resizing { resize } else { offset }; +let f: fn(u32, u32) = if resizing { resize } else { offset }; ``` What would the parameter names of `f` be? - -With an argument struct, the question simply doesn’t arise: +With an argument struct, the question simply wouldn't come up: ```rust struct Size { @@ -116,18 +121,24 @@ struct Size { fn resize(size: Size) {} +// No more arguing about arguments let f: fn(Size) = resize; ``` -If names are semantically important, give the names a type. If they aren’t, don’t. +If names are semantically important, give the names a type. +If they aren't, then... don't. -There is another cute advantage here. Steve brings up evaluation order: +There is another delightful benefit here. +Steve brings up evaluation order: ```rust consume(length: data.len(), data: data); ``` -Should this evaluate in call-site order or declaration order? Rust already answered this question for structs: +I.e., should arguments be evaluated in the order they appear at the call site, or in the order the parameters appear in the declaration? +Here, that means computing `data.len()` before moving data or after? + +Rust already answered this question for structs: ```rust let args = Args { @@ -136,13 +147,15 @@ let args = Args { }; ``` -Expressions are evaluated where you wrote them. No new rule required. +Expressions are evaluated where you wrote them. +No new rules required. -This feels extremely Rusty to me: rather than teaching function calls a second field-like syntax with subtly different semantics, use the field syntax that already exists. +This feels extremely idiomatic to me: rather than teaching function calls a second field-like syntax with subtly different semantics, just use the field syntax that already exists. ### The lightweight version -Of course, declaring a bespoke argument type for every two-argument function would be ridiculous. I would not write: +Of course, declaring a bespoke argument type for every two-argument function would be ridiculous. +I would not write: ```rust struct PushArgs { @@ -152,7 +165,9 @@ struct PushArgs { vec.push(PushArgs { value: 42 }); ``` -The trick is to notice that named arguments are most useful exactly where an argument bundle becomes conceptually meaningful. +That would be silly. +The trick is to notice that named arguments are most useful exactly where an argument bundle becomes conceptually meaningful, +which is the same point in time where you reach for a struct anyway. These are bad: @@ -180,11 +195,13 @@ connect(ConnectionOptions { }); ``` -The “named argument” transformation forced us to discover some actual domain concepts. I’ll take that. +The design pressure forced us to uncover missing domain concepts. +That's just great language design. ## Optional arguments at home -An optional argument is, at some level, an argument which may or may not exist. Rust has a type for that. +An optional argument is, to some extent, an argument which may or may not exist. +Rust has a type for that. ```rust fn connect(url: &str, timeout: Option) { @@ -206,7 +223,9 @@ connect("https://example.com") connect("https://example.com", timeout: 5s) ``` -But it has a useful property: the optionality appears in the function’s type. There isn’t a hidden second calling convention for `connect`. There is one function: +But it has a useful property: the optionality appears in the function's type. +There isn't a hidden second calling convention for `connect`. +There is but one function: ```rust fn(&str, Option) @@ -228,7 +247,9 @@ request( ); ``` -Nobody wants this. But that is the point where the arguments have stopped being a parameter list and started being configuration. So: +I've personally been found guilty of this pattern in the past. +The problem is that the arguments have stopped being a parameter list and started being configuration. +So: ```rust struct RequestOptions { @@ -248,7 +269,8 @@ request( ); ``` -We have traded optional arguments for ordinary data. And that gives the API something nice: there is no special distinction between “arguments supplied syntactically to this invocation” and “options I calculated somewhere else.” +Instead of optional arguments we deal with data. +And that adds a nice property: there is no special distinction between "arguments supplied syntactically to this invocation" and "options I calculated elsewhere." ```rust let options = RequestOptions { @@ -260,11 +282,16 @@ let options = RequestOptions { request(url, options); ``` -It composes because it is just a value. +It composes nicely because it's just a value. ## Default arguments at home -Now the obvious objection: writing all those `None`s is terrible. Correct. So don’t. Rust has `Default` and struct update syntax: +Now the obvious objection: writing all those `None`s is terrible. + +Correct. +So don't. + +That's why we have `Default` and struct update syntax: ```rust #[derive(Default)] @@ -289,7 +316,7 @@ That is getting awfully close to: request(url, timeout: 5s) ``` -The syntactic tax is: +with one minor wrinkle: ```rust RequestOptions { @@ -298,19 +325,21 @@ RequestOptions { } ``` -That is not nothing. But look at what we *didn’t* have to add: rules for which arguments may be omitted, how positional and named arguments interact, or whether you can omit something in the middle. +That is not nothing. +But look at what we *didn't* have to add: rules for which arguments may be omitted, how positional and named arguments interact, or whether you can omit something in the middle. -There’s no special syntax for declaring parameter defaults, no question about whether default expressions run at declaration time or invocation time, and no special representation in `fn` types. +There's no special syntax for declaring parameter defaults, no question about whether default expressions run at declaration time or invocation time, and no special representation in `fn` types. -`Default` is just a trait. Struct update is just struct update. Function calls remain function calls. +`Default` is just a trait and function calls stay uneventful. -And defaults are now usable independently of the function: +Defaults are now usable independently of the function: ```rust let defaults = RequestOptions::default(); ``` -That is frequently useful in its own right. For library APIs, I often like being slightly more deliberate: +That is frequently useful in its own right. +For library APIs, I often like being slightly more explicit: ```rust struct RequestOptions { @@ -340,13 +369,13 @@ request( ); ``` -This is default arguments at home, and I think it gets most of the important bit right. +I think this gets most of the important bits right. ## The builder pattern is the deluxe version -Sometimes even the options struct is too noisy, especially when construction requires validation or conversion. +Sometimes even the options struct is too noisy, often when construction requires validation or conversion. -Then yes, there is the builder: +Then, yes, there is the builder: ```rust let request = Request::builder(url) @@ -355,7 +384,10 @@ let request = Request::builder(url) .build()?; ``` -Steve is right that builders should not automatically be celebrated. They can become their own tiny programming language. But a small builder has a very useful property: each “argument” is an ordinary method call. That means we can do things like: +Steve is right that builders should not be the default. +They can become their own tiny programming language. +But a small builder has a very useful property: each "argument" is an ordinary method call. +That means we can do things like: ```rust let mut request = Request::builder(url); @@ -367,29 +399,31 @@ if let Some(timeout) = config.timeout { let request = request.build()?; ``` -Doing that with language-level keyword arguments generally requires constructing a map, splatting something, or adding some other mechanism. +Doing that with language-level keyword arguments generally requires constructing a map, splatting things, or some other mechanism. -In Rust, the boring desugaring is sitting right there. It’s methods. The price is verbosity, but the semantic budget stays tiny. +In Rust, it's method calls. +I personally find this very pleasing to read. ## Function overloading at home -Steve’s Java example is roughly: +In Java, you can write: ```java void connect(String url, int timeout) { ... } void connect(String url) { ... } ``` -Rust doesn’t let you define both: +Rust doesn't let you define both: ```rust fn connect(url: &str) {} fn connect(url: &str, timeout: Duration) {} ``` -I am very happy about this. But there are several different things people mean when they say they want overloading, and Rust already covers most of them separately. +I am very happy about this. +But there are several different things people mean when they say they want overloading, and Rust already covers most of them separately. -### “I want one convenience form and one configurable form” +### "I want one convenience form and one configurable form" Give them different names: @@ -403,13 +437,16 @@ fn connect_with_timeout(url: &str, timeout: Duration) { } ``` -`Vec::new()` and `Vec::with_capacity()` are the canonical version of this. +The standard library does this often. +See [`Vec::new()`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.new) and [`Vec::with_capacity()`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.with_capacity), for example. -This costs the library author one additional name and saves every reader from doing overload resolution in their head. Seems like a bargain. +This costs the library author one additional name (often just `with_...`) and saves every user from doing overload resolution in their head. -### “I want several input types” +### "I want several input types" -Use a trait. The standard library does this constantly with traits like `Into`, `AsRef`, and `Borrow`. For example: +Use a trait. +The standard library does this all the time with traits like `Into`, `AsRef`, and `Borrow`. +For example: ```rust fn greet(name: impl AsRef) { @@ -420,7 +457,7 @@ greet("Ferris"); greet(String::from("Ferris")); ``` -This gives us a useful part of overload-like behavior: different source types accepted by one API. +This gives us another useful part of overload-like behavior: one API can accept different input types. For owned conversion: @@ -431,11 +468,12 @@ fn set_name(name: impl Into) { } ``` -Again, one function. One parameter list. Ordinary trait dispatch. And unlike unrestricted overloading, the relationship between accepted types is explicit: they must satisfy the bound. +That's just a single function one parameter list, and a trait dispatch. +And unlike unrestricted overloading, the relationship between accepted types is explicit: they work as long as they satisfy the bound. -### “Different types need different behavior” +### "Different types need different behavior" -That’s a trait too: +That's a trait, too: ```rust trait Render { @@ -459,11 +497,11 @@ fn render(value: impl Render, out: &mut Output) { } ``` -We got ad-hoc polymorphism. We just put it in the trait system instead of in name resolution. That is a theme. +That's polymorphism, we just put it in the trait system instead of in name resolution. ## Flexible argument types at home -Steve’s Ruby example has this delightful/terrifying quality: +Steve's Ruby example has this equally lovely and terrifying quality: ```ruby redirect_to "http://www.rubyonrails.org" @@ -471,9 +509,9 @@ redirect_to @post redirect_to action: "show", id: 5 ``` -These calls look like they’re invoking one conceptual operation, but they mean wildly different things. +These calls look like they're invoking one conceptual operation, but they mean wildly different things. -Rust can model that directly: +In Rust, we can model that directly: ```rust enum Redirect { @@ -503,9 +541,11 @@ redirect_to(Redirect::Action { }); ``` -More verbose? Absolutely. But I can ask my editor, “what can I redirect to?” and the answer is the variants of `Redirect`. That’s a much stronger property than “read the docs and discover which keys and values this hash accepts.” +That's more verbose, but in a good way. +And I can ask, "hey editor, what can I redirect to?" and the editor replies that it is the variants of `Redirect`. +That's more helpful than "read the docs and discover which keys and values this hash accepts." -If we really care about smoothing the edges, add conversions: +If we really cared about smoothing down the edges, we could add `From` conversions: ```rust impl From for Redirect { @@ -542,11 +582,13 @@ redirect_to(Redirect::Action { }); ``` -That is pretty good! Importantly, the weirdness has a name. It is called `Redirect`. +Remember that there is no runtime cost and fully typesafe. +Not bad for a compiled language. ## Options hashes at home -An options hash is basically a dynamically typed anonymous struct. So the extremely boring Rust translation is: use a statically typed named struct. +An "options hash" is basically a dynamically typed anonymous struct. +So the extremely boring Rust translation is: use a statically typed, named struct. Ruby: @@ -572,27 +614,21 @@ redirect_to( ); ``` -Yes, the Rust version is considerably louder. It is also difficult to misspell `status`, impossible to pass a string where a status code belongs, and straightforward to enumerate every supported option. - -I don’t think Rust should optimize for winning this particular code-golf competition. +Yes, the Rust version is more noisy, but it also detects when we misspell `status`, it's impossible to pass a string where the status code goes, and straightforward to list every supported option. -There is an interesting middle ground, though. If a set of options is common enough to deserve convenient syntax, it is probably common enough to deserve a type. That type then becomes part of your API vocabulary: +I don't think Rust should optimize for making the syntax as dense as possible. +Instead, if a set of options is common enough to deserve convenient syntax, it is probably common enough to justify a type. ```rust fn redirect_to(target: Url, options: RedirectOptions) ``` -instead of: - -```text -redirect_to(thing, bag_of_stuff) -``` - -That feels like exactly the kind of pressure Rust generally likes to apply. +Now, options have a name and the fields can be documented in one place. ## Variadic arguments at home -Rust does not have general-purpose variadic Rust functions. But once again, it already has several ways of expressing the underlying use cases. +Rust does not have general-purpose variadic Rust functions. +But once again, it already has several ways of expressing the same concept. If all arguments have the same type, take a slice: @@ -615,39 +651,21 @@ sum([1, 2, 3, 4]); sum(vec![1, 2, 3, 4]); ``` -That is arguably *more* composable than: +That is arguably more composable than: ```text sum(1, 2, 3, 4) ``` because the caller can naturally pass an existing collection. +(All typesafe, of course, and with zero indirection at runtime.) -If the arguments are heterogeneous and syntax really matters, we have macros. - -You might have encountered one: - -```rust -println!("{} + {} = {}", 1, 2, 3); -``` - -Or: - -```rust -vec![1, 2, 3, 4] -``` - -`macro_rules!` is stable Rust. - -I would not reach for a macro just to fake variadic functions. But when an API needs syntax that the function-call grammar does not provide, Rust already has an explicit mechanism for saying: - -> This is syntax, not an ordinary function. - -I like that honesty. A macro invocation looks like a macro invocation. It doesn’t silently make functions more complicated. +If the arguments are heterogeneous the last resort is to write a custom macro. +To be clear, I would not use macros just to fake variadic functions, but I do like how macros can be used in stable Rust, and how the exclamation mark stands out from normal function calls. ## Keyword-looking syntax at home -There is one tiny feature hidden in all of these examples that I think deserves more credit: field-init shorthand. +There is one tiny affordance in all of these examples that I think deserves more credit: field-init shorthand. Rust lets you turn this: @@ -669,7 +687,7 @@ let options = RequestOptions { }; ``` -This directly addresses one of Steve’s complaints about keyword arguments: +This directly addresses one of Steve's complaints about keyword arguments: ```python response_model=response_model, @@ -678,7 +696,7 @@ tags=tags, dependencies=dependencies, ``` -Rust’s answer is effectively: +Rust's answer is effectively: ```rust Options { @@ -689,17 +707,17 @@ Options { } ``` -That is arguably *better* than keyword arguments for the forwarding case. +In my opinion, that's even better than keyword arguments. +That's because the labels are still present, the duplication disappears, and nothing needs to change in how we call functions. -The labels are still present. The duplication disappears. And no new function-call feature was required. +This is one of my favorite small pieces of Rust syntax because it's cuts down on boilerplate with little fanfare, and it works in a lot of places, not just function calls. -This is one of my favorite small pieces of Rust syntax because it is exactly the kind of ergonomic feature I want: local sugar over an already-simple semantic model. +## Composition is a superpower -## And all of these compose +The thing I like the most about Rust is how each concept nicely interacts with the others. +That is not an easy task and Rust deserves a lot of credit for that. -Here is where I think the “at home” versions become more compelling than they initially appear. - -Suppose we want an HTTP request API with: +For example, suppose we want a complex HTTP request API with: * a required URL, * several accepted URL-like input types, @@ -709,9 +727,9 @@ Suppose we want an HTTP request API with: * configurable redirects, * and a variable number of headers. -We could imagine a language feature pile that lets us write: +We could imagine a pile of language features that lets us write: -```text +```ruby request( "/hello", timeout: 5s, @@ -723,7 +741,24 @@ request( ) ``` -Stable Rust can instead say: +Now wouldn't that be nice? +However, we can already do this in stable Rust with a combination of already existing, composable features: + +```rust +request( + "/hello", + RequestOptions { + timeout: Some(Duration::from_secs(5)), + redirects: false, + headers: vec![ + Header::new("Accept", "application/json"), + Header::new("X-Foo", "bar"), + ], + }, +)?; +``` + +And all we had to do is write the code we'd likely write anyway: ```rust #[derive(Default)] @@ -739,21 +774,9 @@ fn request( ) -> Result { // ... } - -request( - "/hello", - RequestOptions { - timeout: Some(Duration::from_secs(5)), - redirects: false, - headers: vec![ - Header::new("Accept", "application/json"), - Header::new("X-Foo", "bar"), - ], - }, -)?; ``` -Maybe you prefer a builder: +Or maybe you prefer a builder? ```rust Request::new("/hello") @@ -764,79 +787,54 @@ Request::new("/hello") .send()?; ``` -Neither one is as concise as the imaginary version. - -But look at the mechanisms involved: +We combined standard Rust concepts: structs, enums, `Option`, `Default`, struct update syntax, field-init shorthand, traits, generics, iterators, and methods. +Those mechanisms are all useful far beyond argument passing. -* structs, -* enums, -* `Option`, -* `Default`, -* struct update syntax, -* field-init shorthand, -* traits, -* generics, -* iterators, -* methods, -* macros when you need custom syntax. +Basic Rust syntax is all the machinery required to build ergonomic APIs. -Those mechanisms are all useful far beyond argument passing. Rust doesn’t need a separate version of each concept specifically for functions. +## Friction produces better APIs -## The missing 20% is doing work +The obvious response to everything above is: -This is the part that makes me agree with Steve’s larger point. The obvious response to everything above is: +> Come on. +> These aren't actually named/default/overloaded/variadic arguments. +> They're workarounds. -> Come on. These aren't actually named/default/overloaded/variadic arguments. They're workarounds. +Correct. -Correct. That is why I called this “named arguments at home,” not “Rust secretly has named arguments.” The distinction matters. +- Actual named arguments might let me turn `crop_imm(&img, 10, 20, 200, 100)` into: + ```rust + crop_imm( + image: &img, + x: 10, + y: 20, + width: 200, + height: 100, + ); + ``` + That surely is nicer at the call site. +- Actual default arguments might let me write `request(url, timeout: timeout);` instead of introducing `RequestOptions`. +- Actual overloading might let two functions share the same name instead of forcing me to invent `with_timeout`. -Actual named arguments might let me turn: +All of the above might be useful, though localized, syntax improvements. +But the hidden tax is that the language becomes more complex, for arguably little gain. -```rust -crop_imm(&img, 10, 20, 200, 100); -``` +Friction in APIs often pushes us toward solutions that turn out to be useful beyond the original problem: -into: +- These four coordinates become a `Rect`. +- Seven random config parameters become `RequestOptions`. +- A bunch of dynamically accepted values turn into an enum. +- A family of related operations becomes a trait. -```rust -crop_imm( - image: &img, - x: 10, - y: 20, - width: 200, - height: 100, -); -``` +In a sense, the concrete issue points to a broader design problem and resolving it opens up completely new ways to solve similar problems. +That's great systems design. -without defining anything else. +## Rust's Answer To Almost Everything Is: Better Types -That is undeniably nicer at the call site. +I think there's a broader design principle behind all of this. -Actual default arguments might let me write: - -```rust -request(url, timeout: timeout); -``` - -instead of introducing `RequestOptions`. - -Actual overloading might let two functions share the same name instead of forcing me to invent `with_timeout`. - -That last 20% is real. - -But the friction in the Rust versions often pushes APIs toward things that turn out to be useful anyway. - -Four coordinates become a `Rect`. Seven configuration parameters become `RequestOptions`. A grab bag of dynamically accepted values becomes an enum. - -A family of related operations becomes a trait. A repeated sequence becomes an iterator. Custom call syntax becomes an explicit macro invocation. - -The workaround frequently turns accidental API structure into explicit type structure. That seems valuable. - -## Types are Rust’s options hashes - -There is a broader design instinct here that I’ve come to appreciate. - -Dynamic languages often accumulate power by allowing a function call to describe increasingly elaborate things: +A common design philosophy in dynamic languages is to make familiar constructs more powerful by overloading them with additional semantics. +After all, that is one affordance which dynamic typing allows: the ability to decide the meaning of an object at runtime. ```text foo(x) @@ -847,23 +845,18 @@ foo(x, **options) foo(*args, **options) ``` -Rust tends to move that complexity one level outward: +Rust, however, tends to move complexity *outward* and let the type system do all the work. ```rust foo(FooOptions { ... }) ``` -and then lets the type system do the work. - -The API becomes slightly more ceremonial. But the *function call* remains incredibly boring: you name a function, pass one value for each parameter, and the arguments evaluate in order. The types have to match. Done. - -Like Steve, I think there is substantial value in preserving that boringness, especially in Rust, where there are already enough places for sophisticated language semantics to live. - ## What about agents? -Steve’s new openness to named arguments comes partly from coding agents. The argument makes sense: if a machine is doing the typing, verbosity becomes cheaper, while redundant labels may make a call easier to understand locally. +One might ask: "In the age of agentic development, doesn't verbosity become cheaper while redundant labels may make a call easier to understand locally?" -I agree with the premise. I’m less sure it changes the conclusion. +I agree with the premise. +I'm less sure it changes the conclusion. An agent looking at: @@ -896,36 +889,20 @@ request( ``` says something useful to both humans and agents. - -`timeout` is not merely an optional syntactic argument to this particular invocation. It is one field in the `RequestOptions` concept. +`timeout` is not merely an optional syntactic argument to this particular invocation, it is a way to configure a request. And if agents really do make typing cost increasingly irrelevant, then the principal downside of these slightly-more-verbose Rust idioms gets cheaper too. - -The robots can type `RequestOptions` for me. Great. +The robots can type `RequestOptions` for me. ## Keep the functions boring -I’m not philosophically opposed to Rust ever gaining named arguments. - -There may be a proposal that finds a tiny, coherent design which handles patterns, function pointers, traits, evaluation order, compatibility, and all the other sharp edges Steve describes. Language design is full of features that initially look impossible and eventually acquire a satisfying formulation. - -But I don’t feel much urgency. - -Stable Rust already gives me: - -* named arguments: **struct fields** -* optional arguments: **`Option`** -* default arguments: **`Default` + struct update syntax** -* overloads for convenience: **differently named functions** -* overloads across input types: **traits / `Into` / `AsRef`** -* heterogeneous alternatives: **enums** -* options hashes: **options structs** -* repeated homogeneous arguments: **slices and iterators** -* variable arguments with custom syntax: **macros** -* `foo=foo` forwarding noise: **field-init shorthand** - -None is a perfect substitute. Collectively, though, they cover a remarkable amount of the territory. And they do it by reusing features Rust already needs rather than teaching function calls how to become a little programming language of their own. +I'm not opposed to Rust ever gaining named arguments. +There may be a proposal that finds a tiny, coherent design which handles patterns, function pointers, traits, evaluation order, compatibility, and all the other sharp edges described. +But I don't feel much urgency. -Maybe Rust eventually gets named arguments. Until then, we have named arguments at home. +Stable Rust already gives me structs for named options, `Option` and `Default` for optional values and defaults, traits and enums for varied inputs, and slices and iterators for repeated arguments. -I kind of like them. +Collectively, they cover a lot of ground. +And they do it by reusing features Rust already needs. +And I think that's a core of Rust's design philosophy: finding the smallest, composable, orthogonal set of abstractions, which, when combined, can solve many problems in elegant ways. +The whole is greater than the sum of its parts. From 57cfe022d3c833b89967cb7015a5b253d28bc06b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Sep 2026 16:12:39 +0200 Subject: [PATCH 7/8] Polish named arguments article prose Signed-off-by: Matthias --- content/blog/named-arguments-at-home/index.md | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/content/blog/named-arguments-at-home/index.md b/content/blog/named-arguments-at-home/index.md index 1bc78885..945b895f 100644 --- a/content/blog/named-arguments-at-home/index.md +++ b/content/blog/named-arguments-at-home/index.md @@ -18,7 +18,7 @@ In fact, I think I agree slightly more strongly than Steve does. :) I actually think we can get most of what we want without adding any new language features. Instead, we can lean into what Rust already provides. -None of these substitutes what you get in Python, Ruby, C++, or Kotlin, but that's sort of the point. +None of these is an exact substitute for what you get in Python, Ruby, C++, or Kotlin, but that's sort of the point. Instead, you can get 80% of the ergonomics without adding any magic to function calls at all. The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it as a normal part of its type system, elegantly sidestepping the mentioned design problems. @@ -136,7 +136,7 @@ consume(length: data.len(), data: data); ``` I.e., should arguments be evaluated in the order they appear at the call site, or in the order the parameters appear in the declaration? -Here, that means computing `data.len()` before moving data or after? +Here, does that mean computing `data.len()` before moving `data` or after? Rust already answered this question for structs: @@ -166,8 +166,7 @@ vec.push(PushArgs { value: 42 }); ``` That would be silly. -The trick is to notice that named arguments are most useful exactly where an argument bundle becomes conceptually meaningful, -which is the same point in time where you reach for a struct anyway. +The trick is to notice that named arguments are most useful exactly where an argument bundle becomes conceptually meaningful, which is the same point at which you reach for a struct anyway. These are bad: @@ -269,7 +268,7 @@ request( ); ``` -Instead of optional arguments we deal with data. +Instead of optional arguments, we deal with data. And that adds a nice property: there is no special distinction between "arguments supplied syntactically to this invocation" and "options I calculated elsewhere." ```rust @@ -330,7 +329,7 @@ But look at what we *didn't* have to add: rules for which arguments may be omitt There's no special syntax for declaring parameter defaults, no question about whether default expressions run at declaration time or invocation time, and no special representation in `fn` types. -`Default` is just a trait and function calls stay uneventful. +`Default` is just a trait, and function calls remain untouched. Defaults are now usable independently of the function: @@ -468,7 +467,7 @@ fn set_name(name: impl Into) { } ``` -That's just a single function one parameter list, and a trait dispatch. +That's just a single function with one parameter list and trait dispatch. And unlike unrestricted overloading, the relationship between accepted types is explicit: they work as long as they satisfy the bound. ### "Different types need different behavior" @@ -497,7 +496,7 @@ fn render(value: impl Render, out: &mut Output) { } ``` -That's polymorphism, we just put it in the trait system instead of in name resolution. +That's polymorphism; we just put it in the trait system instead of in name resolution. ## Flexible argument types at home @@ -542,7 +541,7 @@ redirect_to(Redirect::Action { ``` That's more verbose, but in a good way. -And I can ask, "hey editor, what can I redirect to?" and the editor replies that it is the variants of `Redirect`. +And I can ask, "Hey editor, what can I redirect to?" and the editor replies with the variants of `Redirect`. That's more helpful than "read the docs and discover which keys and values this hash accepts." If we really cared about smoothing down the edges, we could add `From` conversions: @@ -582,7 +581,7 @@ redirect_to(Redirect::Action { }); ``` -Remember that there is no runtime cost and fully typesafe. +Remember that this has no runtime cost and is fully type-safe. Not bad for a compiled language. ## Options hashes at home @@ -614,7 +613,8 @@ redirect_to( ); ``` -Yes, the Rust version is more noisy, but it also detects when we misspell `status`, it's impossible to pass a string where the status code goes, and straightforward to list every supported option. +Yes, the Rust version is noisier, but it also detects when we misspell `status`. +It's impossible to pass a string where the status code goes, and it's straightforward to list every supported option. I don't think Rust should optimize for making the syntax as dense as possible. Instead, if a set of options is common enough to deserve convenient syntax, it is probably common enough to justify a type. @@ -658,9 +658,9 @@ sum(1, 2, 3, 4) ``` because the caller can naturally pass an existing collection. -(All typesafe, of course, and with zero indirection at runtime.) +(All type-safe, of course, and with zero indirection at runtime.) -If the arguments are heterogeneous the last resort is to write a custom macro. +If the arguments are heterogeneous, the last resort is to write a custom macro. To be clear, I would not use macros just to fake variadic functions, but I do like how macros can be used in stable Rust, and how the exclamation mark stands out from normal function calls. ## Keyword-looking syntax at home @@ -710,12 +710,10 @@ Options { In my opinion, that's even better than keyword arguments. That's because the labels are still present, the duplication disappears, and nothing needs to change in how we call functions. -This is one of my favorite small pieces of Rust syntax because it's cuts down on boilerplate with little fanfare, and it works in a lot of places, not just function calls. - ## Composition is a superpower The thing I like the most about Rust is how each concept nicely interacts with the others. -That is not an easy task and Rust deserves a lot of credit for that. +That is not an easy task, and Rust deserves a lot of credit for that. For example, suppose we want a complex HTTP request API with: @@ -758,7 +756,7 @@ request( )?; ``` -And all we had to do is write the code we'd likely write anyway: +And all we had to do was write the code we'd likely write anyway: ```rust #[derive(Default)] @@ -826,7 +824,7 @@ Friction in APIs often pushes us toward solutions that turn out to be useful bey - A bunch of dynamically accepted values turn into an enum. - A family of related operations becomes a trait. -In a sense, the concrete issue points to a broader design problem and resolving it opens up completely new ways to solve similar problems. +In a sense, the concrete issue points to a broader design problem, and resolving it opens up completely new ways to solve similar problems. That's great systems design. ## Rust's Answer To Almost Everything Is: Better Types @@ -889,7 +887,7 @@ request( ``` says something useful to both humans and agents. -`timeout` is not merely an optional syntactic argument to this particular invocation, it is a way to configure a request. +`timeout` is not merely an optional syntactic argument to this particular invocation; it is a way to configure a request. And if agents really do make typing cost increasingly irrelevant, then the principal downside of these slightly-more-verbose Rust idioms gets cheaper too. The robots can type `RequestOptions` for me. @@ -904,5 +902,5 @@ Stable Rust already gives me structs for named options, `Option` and `Default` f Collectively, they cover a lot of ground. And they do it by reusing features Rust already needs. -And I think that's a core of Rust's design philosophy: finding the smallest, composable, orthogonal set of abstractions, which, when combined, can solve many problems in elegant ways. +And I think that's a core part of Rust's design philosophy: finding the smallest, composable, orthogonal set of abstractions, which, when combined, can solve many problems in elegant ways. The whole is greater than the sum of its parts. From 5a5641abdf02bb385b9849ad75ad62bdafcb57ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Sep 2026 16:20:40 +0200 Subject: [PATCH 8/8] Capitalize Blog Article Headings Consistently Signed-off-by: Matthias --- content/blog/named-arguments-at-home/index.md | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/content/blog/named-arguments-at-home/index.md b/content/blog/named-arguments-at-home/index.md index 945b895f..90c3c79d 100644 --- a/content/blog/named-arguments-at-home/index.md +++ b/content/blog/named-arguments-at-home/index.md @@ -1,5 +1,5 @@ +++ -title = "We Have Named Arguments At Home" +title = "We Have Named Arguments at Home" date = 2026-09-24 draft = false template = "article.html" @@ -23,7 +23,7 @@ Instead, you can get 80% of the ergonomics without adding any magic to function The recurring pattern is that Rust takes something another language puts into **function-call semantics** and represents it as a normal part of its type system, elegantly sidestepping the mentioned design problems. -## Named arguments at home +## Named Arguments at Home Let's revisit Steve's example from the `image` crate: @@ -152,7 +152,7 @@ No new rules required. This feels extremely idiomatic to me: rather than teaching function calls a second field-like syntax with subtly different semantics, just use the field syntax that already exists. -### The lightweight version +### Arguments Are Part of Your Domain Of course, declaring a bespoke argument type for every two-argument function would be ridiculous. I would not write: @@ -195,9 +195,8 @@ connect(ConnectionOptions { ``` The design pressure forced us to uncover missing domain concepts. -That's just great language design. -## Optional arguments at home +## Optional Arguments at Home An optional argument is, to some extent, an argument which may or may not exist. Rust has a type for that. @@ -283,7 +282,7 @@ request(url, options); It composes nicely because it's just a value. -## Default arguments at home +## Default Arguments at Home Now the obvious objection: writing all those `None`s is terrible. @@ -370,7 +369,7 @@ request( I think this gets most of the important bits right. -## The builder pattern is the deluxe version +## Builder Pattern for the Really Complex Cases Sometimes even the options struct is too noisy, often when construction requires validation or conversion. @@ -403,7 +402,7 @@ Doing that with language-level keyword arguments generally requires constructing In Rust, it's method calls. I personally find this very pleasing to read. -## Function overloading at home +## Function Overloading at Home In Java, you can write: @@ -422,7 +421,7 @@ fn connect(url: &str, timeout: Duration) {} I am very happy about this. But there are several different things people mean when they say they want overloading, and Rust already covers most of them separately. -### "I want one convenience form and one configurable form" +### "I Want One Convenience Form and One Configurable Form" Give them different names: @@ -441,7 +440,7 @@ See [`Vec::new()`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.new) This costs the library author one additional name (often just `with_...`) and saves every user from doing overload resolution in their head. -### "I want several input types" +### "I Want Several Input Types" Use a trait. The standard library does this all the time with traits like `Into`, `AsRef`, and `Borrow`. @@ -470,7 +469,7 @@ fn set_name(name: impl Into) { That's just a single function with one parameter list and trait dispatch. And unlike unrestricted overloading, the relationship between accepted types is explicit: they work as long as they satisfy the bound. -### "Different types need different behavior" +### "Different Types Need Different Behavior" That's a trait, too: @@ -498,7 +497,7 @@ fn render(value: impl Render, out: &mut Output) { That's polymorphism; we just put it in the trait system instead of in name resolution. -## Flexible argument types at home +## Flexible Argument Types at Home Steve's Ruby example has this equally lovely and terrifying quality: @@ -584,7 +583,7 @@ redirect_to(Redirect::Action { Remember that this has no runtime cost and is fully type-safe. Not bad for a compiled language. -## Options hashes at home +## Options Hashes at Home An "options hash" is basically a dynamically typed anonymous struct. So the extremely boring Rust translation is: use a statically typed, named struct. @@ -625,7 +624,7 @@ fn redirect_to(target: Url, options: RedirectOptions) Now, options have a name and the fields can be documented in one place. -## Variadic arguments at home +## Variadic Arguments at Home Rust does not have general-purpose variadic Rust functions. But once again, it already has several ways of expressing the same concept. @@ -663,7 +662,7 @@ because the caller can naturally pass an existing collection. If the arguments are heterogeneous, the last resort is to write a custom macro. To be clear, I would not use macros just to fake variadic functions, but I do like how macros can be used in stable Rust, and how the exclamation mark stands out from normal function calls. -## Keyword-looking syntax at home +## Keyword-Looking Syntax at Home There is one tiny affordance in all of these examples that I think deserves more credit: field-init shorthand. @@ -710,7 +709,7 @@ Options { In my opinion, that's even better than keyword arguments. That's because the labels are still present, the duplication disappears, and nothing needs to change in how we call functions. -## Composition is a superpower +## Composition Is a Superpower The thing I like the most about Rust is how each concept nicely interacts with the others. That is not an easy task, and Rust deserves a lot of credit for that. @@ -790,7 +789,7 @@ Those mechanisms are all useful far beyond argument passing. Basic Rust syntax is all the machinery required to build ergonomic APIs. -## Friction produces better APIs +## Friction Produces Better APIs The obvious response to everything above is: @@ -827,7 +826,7 @@ Friction in APIs often pushes us toward solutions that turn out to be useful bey In a sense, the concrete issue points to a broader design problem, and resolving it opens up completely new ways to solve similar problems. That's great systems design. -## Rust's Answer To Almost Everything Is: Better Types +## Rust's Answer to Almost Everything Is: Better Types I think there's a broader design principle behind all of this. @@ -849,7 +848,7 @@ Rust, however, tends to move complexity *outward* and let the type system do all foo(FooOptions { ... }) ``` -## What about agents? +## What about Agents? One might ask: "In the age of agentic development, doesn't verbosity become cheaper while redundant labels may make a call easier to understand locally?" @@ -892,7 +891,7 @@ says something useful to both humans and agents. And if agents really do make typing cost increasingly irrelevant, then the principal downside of these slightly-more-verbose Rust idioms gets cheaper too. The robots can type `RequestOptions` for me. -## Keep the functions boring +## Keep the Functions Boring I'm not opposed to Rust ever gaining named arguments. There may be a proposal that finds a tiny, coherent design which handles patterns, function pointers, traits, evaluation order, compatibility, and all the other sharp edges described.