Arguing about arguments
Arguing about arguments
Arguing about arguments Sep 21, 2026 Rust 关于参数的争论 2026年9月21日 Rust
Here’s some Rust code: 这里有一些 Rust 代码:
// define a function
fn foo(x: i32, y: i32) -> i32 {
// body elided
}
// call it
let z = foo(5, 6);
In programming language jargon, we call x and y parameters and 5 and 6 arguments. Rust does not have many fancy features related to parameters and arguments. But other languages do. For example, here’s a function in Ruby, another language I’ve written a ton of:
在编程语言的术语中,我们将 x 和 y 称为“形参”(parameters),而将 5 和 6 称为“实参”(arguments)。Rust 在参数相关的特性上并没有太多花哨的功能,但其他语言则不然。例如,这是 Ruby 中的一个函数,这也是我经常使用的一种语言:
# its definition in Rails
def redirect_to(options = {}, response_options = {})
# body elided
end
# you can call it in all of these ways:
# pass a string with a URL
redirect_to "http://www.rubyonrails.org"
# pass an instance of a model to go to it, if post.id = 5 then this might go to
# "/posts/5"
redirect_to @post
# invoke an action directly, might be equivalent to the above in a Posts controller
redirect_to action: "show", id: 5
# redirect to a model's URL, passing a flash message
redirect_to post_url(@post), alert: "Watch it, mister!"
# do the same but also set a specific HTTP code
redirect_to post_url(@post), status: :found
# do both
redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
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. You pretty much have to hope that someone has written good documentation, and while Rails’s documentation is pretty good, not every library is going to have it.
我曾经热爱 Ruby,现在我热爱 Rust。老实说,redirect_to 及其同类函数是我如此反对 Rust 引入这类花哨特性的重要原因:它们确实非常简洁,在某些人眼中也很美观,但这也使得人们极难弄清楚一个函数到底能做什么。你几乎只能寄希望于有人写了好的文档,虽然 Rails 的文档相当不错,但并非每个库都能做到这一点。
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. So, what exactly are all of these features? 明确一点,这里混合了多种特性:灵活的参数类型、选项哈希(options hashes)、默认值以及类似关键字的语法。正是这种 API 风格塑造了我的怀疑态度,而不仅仅是参数标签本身。那么,这些特性究竟是什么呢?
Named parameters
命名参数
The simplest one is named parameters. As you might guess, this means that we can explicitly use parameter names at the call site: 最简单的是命名参数。正如你所猜想的,这意味着我们可以在调用处显式地使用参数名称:
// without named parameters
let z = foo(5, 6);
// if Rust had them
let z = foo(x: 5, y: 6);
Named parameters are nice because they can give you more information at the call site, but also they can be more flexible. Some languages also let you supply named arguments in a different order: 命名参数很好,因为它们可以在调用处提供更多信息,而且它们也更灵活。有些语言还允许你以不同的顺序提供命名参数:
// since we have names, we can write this:
let z = foo(x: 5, y: 6);
// but we can also write this:
let z = foo(y: 6, x: 5);
The downside of named parameters is that they can get quite verbose: 命名参数的缺点是它们可能会变得非常冗长:
# FastAPI in Python, UserOut class not shown
@app.get(
"/me",
response_model=UserOut,
status_code=200,
tags=["Users"],
summary="Get current user profile"
)
def get_current_user():
I tried to not strawman this by showing an extreme example, but get can take 23 different keyword arguments, and so this could get quite, quite long. Named arguments can be good when passing expressions, so we can understand something more at the call site. In the above, status_code=200 is much nicer than a bare 200, if you know HTTP well it’s kind of obvious what it is, but the additional clarity is nice. 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.
我尽量避免通过展示极端例子来歪曲事实,但 get 可以接受 23 个不同的关键字参数,因此代码可能会变得非常非常长。当传递表达式时,命名参数很有用,因为我们可以在调用处理解更多信息。在上面的例子中,status_code=200 比单纯的 200 要好得多;如果你熟悉 HTTP,这显而易见,但额外的清晰度确实很有帮助。不过,将真正复杂的例子拆解成变量再转发给函数是很常见的做法,这样反而会变得冗长且多余。
As an example from inside FastAPI where get is invoked directly with all of those options:
以下是 FastAPI 内部直接调用 get 并传入所有选项的示例:
self.router.get(
path,
response_model=response_model,
status_code=status_code,
tags=tags,
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
responses=responses,
deprecated=deprecated,
operation_id=operation_id,
response_model_include=response_model_include,
response_model_exclude=response_model_exclude,
response_model_by_alias=response_model_by_alias,
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
)
We actually snuck in another two features into this example: how did we only pass five arguments into a function that has 23 parameters? 我们实际上在这个例子中悄悄引入了另外两个特性:我们是如何只向一个拥有 23 个参数的函数传递了五个参数的呢?
Optional and/or Default arguments
可选参数和/或默认参数
As the name implies, optional arguments is a feature where you can choose to not pass in an argument, and default arguments lets you set a default when an argument is not passed. To go back to that example: 顾名思义,可选参数是一种你可以选择不传递某个参数的特性,而默认参数则允许你在未传递参数时设置一个默认值。回到那个例子:
@app.get(
"/me",
response_model=UserOut,
status_code=200,
tags=["Users"],
summary="Get current user profile"
)
def get_current_user():
We only passed in five of the 22 possible arguments. If we had to pass all 22 every time, that would be extremely verbose. So by declaring defaults for some parameters, we can leave them off of the call site, and not pass arguments for them, and get the defaults instead. We saw this in the Ruby: 我们只传递了 22 个可能参数中的 5 个。如果我们每次都必须传递全部 22 个参数,那将极其冗长。因此,通过为某些参数声明默认值,我们可以在调用处省略它们,无需传递参数,从而获得默认值。我们在 Ruby 中也看到了这一点:
# its definition in Rails
def redirect_to(options = {}, response_options = {})
The = {} are defaults for options and response_options: they’re an empty hash map. You’ll often find optional and default arguments come together: after all, if you don’t pass an argument in, what should the value of that parameter be? If your language has some sort of null value, that can be one solution, and it can feel like optional arguments without default arguments. But you can truly get one without the other with our next feature, function overloading:
= {} 是 options 和 response_options 的默认值:它们是一个空的哈希映射。你经常会发现可选参数和默认参数是结合在一起使用的:毕竟,如果你没有传入参数,该参数的值应该是什么呢?如果你的语言有某种空值(null),那可能是一种解决方案,这感觉就像是没有默认值的可选参数。但通过我们的下一个特性——函数重载,你确实可以实现二者分离:
Function Overloading
函数重载
Languages that support function overloading allow you to define multiple functions with the same name, but different signatures. Which function gets invoked depends on which arguments you pass. This lets us truly have optional arguments without default arguments. In Java: 支持函数重载的语言允许你定义多个名称相同但签名不同的函数。调用哪个函数取决于你传递了哪些参数。这使我们能够真正实现没有默认值的可选参数。在 Java 中:
// Version 1: Requires both arguments
void connect(String url, int timeout) { ... }
// Version 2: Timeout is optional to the caller, and handled internally
void connect(String url) { ... }