We Have Named Arguments at Home
Steve Klabnik recently discussed the concept of named arguments, optional arguments, default arguments, and function overloading in the context of the Rust programming language. While most of these design aspects have historically made him uneasy, he believes that 80% of the desired ergonomics can be achieved without introducing any new language features.
Instead, he advocates for leveraging the features already present in Rust. By treating named arguments as a normal part of the type system, Rust sidesteps the design problems typically associated with such features. The source example from the image crate demonstrates this approach, where four consecutive u32s as arguments become less reliable and more difficult to use without consulting the documentation.
Instead, using a struct to represent named arguments offers numerous advantages. Structs provide named arguments with additional type information, enabling field ordering flexibility, typo checking, autocomplete, per-field documentation, and the ability to pass arguments around as values. Furthermore, struct field names belong to the type itself rather than being distributed across multiple function calls.
This avoids several issues associated with traditional named arguments, such as ambiguity in function pointers and evaluation order. Rust already handles this aspect through the evaluation of expressions where they are written, eliminating the need for additional rules. Named arguments prove most beneficial when the argument bundle gains conceptual significance, often corresponding to the point at which a struct would be utilized.
Declaring a customized argument type for every two-argument function would be impractical, but leveraging existing structs for meaningful arguments is a more sensible approach. Optional arguments can be considered a specific case of arguments that may or may not exist. Rust already provides a type for this purpose, with the added benefit that the optionality is explicitly present in the function's type signature.
This prevents the emergence of hidden second calling conventions for functions with multiple optional arguments. Instead, the function remains singular, with the optional arguments incorporated into its type. This approach becomes more advantageous as the number of optional arguments increases. The objection raised about the verbosity of providing Nones as default values can be mitigated by utilizing Rust's Default trait and struct update syntax.
These features enable default values to be defined independently of the function declaration, avoiding the need for special syntax or rules governing argument omission and default expression evaluation. This streamlined approach keeps function calls untouched while allowing defaults to be used independently. Steve Klabnik acknowledges that builders may not be the ideal default option, as they can sometimes resemble their own mini-programming language.
However, a small builder offers a valuable property: each "argument" is represented as an ordinary method call. This allows for intuitive method-based approaches, such as using validation or conversion techniques, without the need for complex constructs like maps or splatting. The Rust programming language already covers many aspects of overloading through its trait system, including functions like Vec::new() and Vec::with_capacity().
By leveraging traits like Into, AsRef, and Borrow, the standard library provides ample opportunities for polymorphism, enabling multiple input types to be accepted within a single function parameter list. This approach offers explicit type relationships and eliminates the ambiguity associated with unrestricted overloading. In conclusion, Klabnik argues that by harnessing the existing features of Rust, such as structs, options, defaults, and traits, most of the desired functionality related to named arguments, optional arguments, default arguments, and function overloading can be achieved without introducing additional language features.
This approach not only preserves the simplicity and consistency of Rust's type system but also enhances the readability and maintainability of code by leveraging the language's natural syntax and conventions.
Written by urgent.news from Lobsters's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.