Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

One thing that recently surprised me is that Rust lacks default values and named arguments for functions.

Some consider it a counterpattern or bad memory to be able to not fill out all parameters or reference them by name. However some interfaces easily require like 50 different function parameters that cannot be removed in a simple way and they all make sense in different configurations. Without default values and named parameters you're lost there. I don't get this design decision on Rust side at all.



Coming from Python I agree that the lack of keyword/default arguments is initially conspicuous, though in practice it seems that library authors seem content to implement builder patterns to work around their absence. While ideally I do think that the builder pattern is less elegant than supporting keyword arguments, in practice it does avoid the unfortunate pattern I've seen in some Python APIs to just cry #yolo and cram a hundred parameters into a single function call. I've never seen a good interface that requires "like 50 different function parameters"; call me zealous, but if I were to design my own programming language, I'd make it a hard compiler error to have more than four parameters to a function. :P


> I'd make it a hard compiler error to have more than four parameters to a function

There's already such a language, it's called Haskell and it only allows one parameter per function.


Haskell has currying, which is cheating. :P


Saying library authors are content with their absence is probably a bit too strong. There's not really another option, and I doubt many folks would choose not to author libraries for this reason alone. I personally have found their absence annoying when writing Rust libraries.

I'd probably be in favor of a hard cap on # of function arguments. :)


But there is another option: a configuration struct, potentially combined with an implementation of Default. Indeed, part of the reason this conversation has gone on so long is that there's one camp who just wants to emulate keyword arguments via additional sugar for structs and Default.

(There's also another another option, which is to have a different function for each combination of parameters. This obviously doesn't scale in the large, but it's perfectly acceptable for functions that take only a single optional parameter, which IME is a plurality of APIs that want optional parameters).


While you can misuse a feature, you don't have to. My example is extreme and you probably don't run into something like that often depending on what you do. There could be other solutions. There surely are other solutions that are neither more clear nor better in any way. Sometimes you just got to do what you got to do. What surprised me about Rust is that it has all those cool features but lacks a very basic one that most programming languages have.


> a very basic one

With my language-nerd hat on...

While they may seem basic as a user, designing a language means you need to think about edge-cases. Methods in Rust have some special rules around dispatch, and in order to implement one or both of these features, all of that stuff needs to be considered and designed.

In other words, a lot of work goes into new features, even ones that are easy to use.

In Rust's case, we haven't ruled out adding these features completely, but nobody has put in that work to come up with a proposal. Part of that is that while people tend to see the lack of these features as a mild annoyance, it's not enough to prioritize over other work. We'll see how it all shakes out.


Want to echo this part:

> nobody has put in that work to come up with a proposal.

Features like this mainly need a champion who really cares about getting it into the language & can work with the language & compiler teams to complete that process.


My example of misuse is not to forswear the issue entirely, only to demonstrate that there do exist philosophical objections to keyword arguments. Trust me, I've long been an advocate of keyword/default arguments for Rust. :P The last time that anyone made a push for them was in the run-up to 1.0, when it was decided there were more pressing issues than keyword/default args (alongside a bevy of other features), and the decision to implement them was officially postponed. I might get around to submitting an RFC to revive them sometime this year, though I'm still unsure about some of the semantic details that we might want to consider.


Additional note to static_noise: would you like me to contact you if I eventually revive one of the postponed RFCs for keyword/default arguments, so that you can weigh in? Twitter, Github, etc.?


When a feature is present, without strong disincentives, it will be misused; consider for example matplotlib: many functions have over 10 possible default arguments, and discoverability is almost nil (stack exchange is the place to look for answers).


If you do have the struct implementing `Default`, as suggested from sibling comments, and it's fully public, you can also use the "functional update syntax" (where the `..` is):

    #[derive(Default)]
    struct Config {
        interesting_thing: bool,
        a: i32,
        b: i32,
        c: i32,
    }   
    
    fn main() {
        Config {
            interesting_thing: true,
            ..Config::default()
        };
    }
In addition, the builder pattern is especially useful for larger configuration objects like this.


I think the derive_builder crate is worth a mention here:

https://docs.rs/derive_builder/0.5.0/derive_builder/


The main reason I haven't used derive_builder much is that I'd much rather have a compile-time guarantee that all the field that I want are filled out than have to check for an error at runtime (or worse, just `unwrap/expect` everywhere). I'm not sure there's currently a way around this on stable, but I'd personally prefer writing boilerplate for my types than having to handle errors in places that shouldn't need them.


Maybe they'll get there with time, but with 50 different parameters (... for one function? really?) I'd say put most of them in some kind of an options object, which certainly can have defaults and also checks your values at compile-time.


Yes, the currently-considered-idiomatic solution here is the builder pattern.


Here

https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot...

I count about 40 plus more which are not listed.

You surely could first make a dictionary or structure and fill it with these arguments. But then "kwargs" are just that, a dictionary.

I find the interface of the plot function pretty straightforward. There are a lot of options but it's pretty clear what they do and when to use them. Most often you use only some of them - but often different combinations. Splitting this up into multiple helper objects that need to be constructed and filled beforehand would turn the default one-liner into a ten-liner which is not better.


> But then "kwargs" are just that, a dictionary.

Maybe Ruby on Rails is an exception, but while I used to be a fan of default/keyword arguments, especially in combination, seeing how they were used there made me very much not like them any more. It's impossible to tell what's going on.


How about how Smalltalk, Obj-C, Swift uses them?

It's very clear there...


FWIW, Obj-C doesn't have keyword arguments. It just has infix method names. But all arguments in an Obj-C method are required, and the order is significant.

Similarly, in Swift, the order of defaulted arguments is significant. So you wouldn't see anyone do something like that crazy matplotlib method in Swift, because remembering the order of all the arguments in that function is impractical.


Yeah when we do named args it will very likely be based on Swift, but a bit different due to backwards compat and the desire to integrate it into existing methods like Vec::from_raw_parts and ptr::copy.


> However some interfaces easily require like 50 different function parameters

These interfaces are poorly designed; as in natural language, you almost never need more than a subject, direct object, and one or two indirect objects, any of which may themselves be compound entities.

A call with more than about four parameters has usually decomposed at least one thing that should be composite in the argument list.


Builder pattern/defaults. I personally find it much more straightforward/organized than using default values and/or named parameters. Python loves named parameters and sometimes their over usage drives me crazy.


I agree about default values, but named parameters have other benefits beyond acting as makeshift configuration options.

1) More readable code. The classic is foo.Bar(true). It breaks the flow of reading to have to hover and see what that 'true' means. Much nicer to see foo.bar(launchMissiles: true).

2) Protects from a particular class of dumb mistakes. You have a function foo(x, y) where x and y have the same type. You refactor it so that one of the parameters isn't needed anymore. It's surprisingly easy (read: I've seen it, and I've done it), when you clean up the function calls, to accidentally delete x instead of y or vice-versa. Named parameters prevent that.


> 1) More readable code. The classic is foo.Bar(true). It breaks the flow of reading to have to hover and see what that 'true' means. Much nicer to see foo.bar(launchMissiles: true).

This is a case where I've started using Enums in Java. For example:

    foo.bar(LaunchMissiles.YES);
In Rust, you could have a macro to make defining these types easier (and have it automatically generate to_bool and from_bool methods):

    named_bool!(LaunchMissiles);
    foo.bar(LaunchMissiles::Yes);


> However some interfaces easily require like 50 different function parameters

Please tell me you're not responsible for this monstrosity https://salilab.org/modeller/9.18/manual/node315.html


Can't you just do that with passing a struct + Default[1]?

[1] https://doc.rust-lang.org/std/default/trait.Default.html


It seems like you can... but then this could be done implicitly at no loss and lead to less clutter. It's not hard to understand what default parameters or named parameters do.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: