Steve Klabnik on Rust's resistance to fancy argument features
Programming language designer argues against adding named, optional, and default parameters to Rust, citing maintainability lessons from Ruby.
What to know
- Klabnik argues Rust should avoid adding named, optional, and default parameters despite their convenience, citing how similar features in Ruby made it harder to discover what functions could do without documentation.
- The core tradeoff: syntactic sugar can be concise and beautiful, but at the cost of explicitness and discoverability—functions with 23 optional parameters become 'god functions' that are hard to understand.
- Community commenters propose middle-ground solutions: anonymous structs with defaults, overloading by argument shape, tooling-based fill-in of labels, and using newtypes to prevent positional-argument bugs.
- Some disagree with Klabnik's blanket skepticism: several argue that modern tooling (language servers, IDE inlay hints) already solve the readability problem, and that optional arguments with Required Option types could be safely added.
The dispute Whether modern tooling adequately solves the readability problem Klabnik identifies, and thus whether Rust can safely add convenience features without the documentation burden he warns against. · positions read across 42 posts and comments
Rust should avoid fancy argument features entirely because they enable god functions and obscure APIs.
-
“I've wished for named a few times. Never needed optional, and personally hope they don't land.”
junon · Lobsters ↗
Alternative syntax like anonymous structs with defaults could give Rust the convenience without the discoverability problems.
-
“IMO the optimal way would be for Rust to have optional structural types and then an anonymous struct literal with `..` to mean filling in defaults”
mitsuhiko · Lobsters ↗
Modern tooling (language servers, IDE inlay hints) already solves the readability problem; optional arguments with required Option types could be safely added.
-
“language servers can handle labels for you. Gleam has labelled arguments, and I rarely write them. Instead I run the "fill labels" code action”
lpil · Lobsters ↗
Named arguments alone (without optional/default) would be acceptable because they improve readability without enabling god functions.
-
“I think Rust could be okay with named parameters, but not optional or default ones.”
ralfj · Lobsters ↗
“To be clear about it, there are several things bundled together here: flexible argument types, options hashes, defaults, and keyword-looking syntax. It's that whole style of API that shaped my skepticism, not argument labels by themselves.”
Steve Klabnik, Language designer · steveklabnik.com · Sep 20
Steve Klabnik Programming language designer, Rust community member
How it unfolded 3 developments, newest first · click a bar or a number to jump postscomments
-
3
Commenters highlight tooling and alternative solutions to argument confusion
Several commenters raised practical counterpoints: lpil noted that language servers like Gleam's can automatically fill labels; cceckman argued that IDE inlay hints already provide the readability benefits Klabnik seeks; pushcx suggested newtypes could prevent bugs from positional integer arguments.
“language servers can handle labels for you. Gleam has labelled arguments, and I rarely write them. Instead I run the "fill labels" code action…”
— lpil -
I wonder how much of the "improved readability" would go away if review interfaces supported LSP inlay hints as something you could toggle on. I write Rust in my day job. When writing code, or reviewing in my editor, I am rarely confused by "which argument is this" because `rust-analyzer` tells me; in fact, it uses the same `parameter: ` syntax as…
2 more of the top 3 · 38 posts in this stretch
-
> The defaults for these are different: get is checked, split_at is unchecked. `split_at` is not unchecked, and “checked” and “unchecked” aren’t opposites here, because there are two types of checked. The three types are: checked non-panicking (returns `Option`), checked panicking (returns `T` or panics), and unchecked (returns `T` and is…
-
Or newtypes. I haven't done graphics programming for a long while, but looking at this call I remember how easy it was to introduce bugs when function calls had positional arguments that were all the same integer type, even though one pair is position and another is dimension. If this wasn't in a generic graphics library but a desktop GUI library…
-
-
2
Rust community discusses alternative approaches to optional arguments
The essay sparked discussion on Lobsters with commenters proposing alternative language designs. Mitsuhiko suggested optional structural types with anonymous struct literals; ptrettner discussed function overloading by shape; and senekor proposed struct type inference at instantiation sites as middle grounds between convenience and clarity.
“IMO the optimal way would be for Rust to have optional structural types and then an anonymous struct literal with `..` to mean filling in defaults…”
— mitsuhiko -
IMO the optimal way would be for Rust to have optional structural types and then an anonymous struct literal with `..` to mean filling in defaults: ```rust let cropped = image::imageops::crop_imm({ image: &img, x: 10, y: 20, width: 200, height: 100, .. // brings in the rest }); ``` //EDIT: I know that this precise syntax is probably impossible to…
2 more of the top 3 · 4 posts in this stretch
-
function overloading has tons of variations but I want to call out one axis I'm experiment with in my own (as-of-yet private) language: "function overloading by type" vs "function overloading by shape". And the experiment is how far you can get by only overload "by shape". Which means you overload by number of args and/or by the name of the named…
-
I often think anonymous structs / records would be nice in Rust. nitpick: the last line should probably be `..Default::default()`, to stay consistent with named structs. But I think a lot of the convenience here could already be achieved if the struct's name could be inferred at instantiation site. Imagine something like this: ```rs fn main() {…
-
-
1
Klabnik argues Rust should avoid fancy argument features despite convenience
In the essay, Klabnik explains that his experience with Ruby's flexible argument syntax—particularly Rails methods like `redirect_to`—shaped his skepticism about Rust gaining similar features. He contends that while concise and beautiful, they obscure what a function can do and make good documentation essential rather than optional.
“I used to love Ruby, and now I love Rust. And to be honest, redirect_to and friends are a huge reason why I am so against Rust gaining these sorts of fancy features: they can be really concise, and beautiful to a certain kind of eye, but it also makes it really, really hard to know what all you can do with a function.”
— Steve Klabnik -
background
Klabnik publishes essay on function argument design across languages — Steve Klabnik posted an essay titled 'Arguing about Arguments' examining how different programming languages handle function parameters and arguments. The piece analyzes named parameters, optional arguments, default arguments, and function overloading, using examples from Ruby, Python (FastAPI), and Java to illustrate the tradeoffs.
What people are saying 18 voices from 1 site · best of 42 · verbatim
- Could optional arguments required to use Option<T> type prevent the god-function problem Klabnik warns about?
- Should language servers and IDE inlay hints be considered as solving the discoverability issue, making fancy argument features acceptable?
- What is the practical difference between anonymous struct syntax with defaults and named optional arguments from a maintenance perspective?
- Sep 22
-
This is fair. At the same time, every syntax was novel at one point in time, but people can and do learn it.
-
Yes, that was a conscious constraint, although there are many people that (rightly!) are annoyed by that restriction. Bevy for example would love to have arbitrary expressions be auto-inserted. I am considering restricting the default values to always be surrounded by `const { ... }` before we stabilize, as a way to keep the syntax open for…
-
> self.router.get( > path, > =response_model, > =status_code, > // ... > ) The problem with syntax like that is that there's *nothing else* in Rust that uses that syntax, so it is confusing when seen for the first time and won't be discoverable by someone extrapolating what they already know of the language's syntax and semantics.
-
Agreed, not a substitute for good code. "More readable" is relative and contextual, though; an argument name may be sometimes-useful and sometimes-noisy, depending on my state of mind when I'm reading it. As an analogy: I don't necessarily want to litter my code with type annotations that could be inferred, because sometimes it's obvious / doesn't…
-
Excellent post. Some remarks: > But it can be common to break truly complex examples down into variables that we end up forwarding to the function, and then it becomes verbose and redundant. As an example from inside FastAPI where get is invoked directly with all of those options: > > ``` > self.router.get( > path, > response_model=response_model…
-
Having done all these over the years with Python, C, Fortran, Kotlin, Swift, Elixir, I still like the Smalltalk keyword syntax best (https://book.gtoolkit.com/understanding-smalltalk-message-syntax-w9fc37am75ozp0rrdb5xftjo). It communicated the best (imo), because it weaves the parameterargument binding without the overhead of a function name as…
-
Ah, I didn't know about this feature being worked on, thanks. I like that it requires the default field values to be `const`, this means that `SomeStruct { .. }` won't execute arbitrary code, IIUC.
-
Edit: I missed that you said "anonymous". Honestly the code below doesn't bother me, even if it's more verbose. I do think that being able to omit the struct name would be fine though. Doesn't Rust have this? It's just more verbose. ```rust let cropped = crop_imm(CropImg { x: 10, y: 20, width: 200, height: 100, ..Default::default() // brings in…
-
In my opinion, the only fully orthogonal, consistent, way to implement default arguments would be to introduce an args type for every fn type, so that `fn foo(a: i32)` introduces a `struct { a: i32, b: f32}`, that could be named via `::Args`. Then the second part would be to allow implementing traits through type aliases: `impl Default for…
-
> Would you agree that if this inferred default .. works on anonymous structs, it should also work on named structs for consistency? https://doc.rust-lang.org/unstable-book/language-features/default-field-values.html Structs will be able to be constructed with `Foo { .. }` if `struct Foo { field: i32 = 42 }`. It makes sense that structural structs…
-
I think one implicit assumption is that no one will create such a monstrosity with required/positional parameters (I say, an hour or two after seeing an MR where someone added a 20th parameter to a Java method).
-
> For the simple cost of “write two different names for your functions,” you get to keep the rules very simple: there is one function definition, and you invoke it by name. Done. There's a hidden cost to these things: name confusion and discoverability. Rust has this explosion of functions that do slightly different things with also slightly…
-
> but get can take 23 different keyword arguments, and so this could get quite, quite long. I noticed that only the named argument side with 23 parameters was shown. The unnamed version might be more concise but is less understandable. You could argue that IDEs will annotate your code, at which point “concise” is no longer a virtue. > But what you…
-
> [labelled arguments do] really increase readability, no question about it. But for me, typing all of that out just isn’t really super worth the squeeze. But when Claude is gonna write it? I care a lot less. AI aside, language servers can handle labels for you. Gleam has labelled arguments, and I use them often, but I rarely write them. Instead I…
-
I see. It's not an unreasonable feature to ask for, although I don't think it's necessary. Maybe a middle ground would be to default-import `Default::default` in the prelude, such that `..default()` is enough. Would you agree that if this inferred default `..` works on anonymous structs, it should also work on named structs for consistency?
-
> I think Rust could be okay with named parameters, but not optional or default ones. Interesting position. That would certainly prevent the dreaded "god functions" like Python's process spawning, or the `get` example from the post. OTOH, omitting a sequence of pointless `None` would be very useful. Maybe optional arguments should be *required* to…
-
Agreed on +named -optional, personally. I've wished for named a few times. Never needed optional, and personally hope they don't land.
-
> nitpick: the last line should probably be ..Default::default(), to stay consistent with named structs. No, that would be intentional and have `Default::default()` be automatically added by the compiler if no filler is provided. `Default` is already in a few APIs anyways (eg: `mem::take` or `unwrap_or_default`). I don't think it would be…