Arguing about arguments

a month ago by codeinabox to c/rust

syklemil 9 points a month ago

I'm used to named arguments from other languages, and that makes me fairly partial to them. Having to tool around with builders feels a lot more complex and clunky.

That said, I do wonder at how many of the usecases aren't workarounds for long argument lists that are either stringly typed, or something similar. As in, with a signature of (i32,i32,i32,i32) -> whatever, named arguments seem like a way of getting the compiler to catch errors in confusing {x,y}_{position,length}; but if the signature were newtypes like Rect(Origin(i32,i32), Size(i32, i32)) -> whatever, then the felt need for named arguments drops considerably.

path: 0 25953036, hotness: undefined, score: 9, children: 5
anton 7 points a month ago

With newtypes you can also emulate named args with a single struct. This also allows them to be passed on together.

struct RectArgs{
    x:i32,
    y:32,
    w:32,
    h:32,
}
impl Default for RectArgs{...}

Then the call site looks like this:

rect(RectArgs{
    x:0,
    y:0,
    w:30,
    h:20,
}
// with default arguments
rect(RectArgs{
    w:30,
    h:20,
    .. RectArgs::default ()
}
path: 0 25953036 25953978, hotness: undefined, score: 7, children: 3
syklemil 1 point a month ago

Yeah, though in those cases you might get an extra question about why rect is a function and not a method, e.g.

Rect {
     w: 30,
     h: 20,
     .. RectArgs::default()
}.do_the_thing()
path: 0 25953036 25953978 25954156, hotness: undefined, score: 1, children: 2
anton 1 point a month ago

I assumed the rect is a method on something like a graphics context or physics engine, but yeah.

path: 0 25953036 25953978 25954156 25954389, hotness: undefined, score: 1, children: 1
syklemil 1 point a month ago

Yeah, decent assumption, I'll tone down my comment a bit.

path: 0 25953036 25953978 25954156 25954389 25954694, hotness: undefined, score: 1, children: 0
deadcream 1 point a month ago

Newtypes have their uses, but as a replacement for named arguments they are an overkill in many cases. They solve different problems.

Newtypes are beneficial when some domain-specific value with an invariant is used extensively throughout the codebase. You would use them to store and pass around that value without fear that something goes wrong.

However if we start to create a type for each parameter of every function (which may not be related at all), it would only result in a lot of unnecessary boilerplate. Named arguments is a simple and elegant solution for calling a function in a more explicit way, nothing more.

path: 0 25953036 25961944, hotness: undefined, score: 1, children: 0
BB_C 6 points a month ago

Who is even the target audience of this post? Beginners (which included all of us once) ask for all sorts of things. But these features didn't happen in Rust for a reason.

Named parameters are not needed in the age of LSP hints, where this exists as an editor feature (including (neo)vim).

But if you really want named parameters AND optionals/defaults, rust always allowed the args struct pattern:

struct Args<'a> {
  a: &'a str,
  b: u64,
  c: Option<u64>,
}

impl<'a> Default for Args<'a> {
  fn default() -> Self {
    Self {
       a: "hi", // default
       b: 3, // default
       c: None, // optional default
    }
  }
}

takes_args(Args{a: "not hi", ..Args::default()})

The builder pattern is more about valid initialization, but does interact with the arg struct pattern when you have non-default (forced) arguments, although even then, you can not use it by having:

struct Args<'a> {
  forced_arg1: &'a str,
  forced_arg2: u8,
  defaults: DefaultArgs, // implements Default
}

So mentioning the builder pattern is out of place.

Overloading is not used because we have traits (and the sound subset of specialization for when that's needed).

So all in all, those "features" didn't make it to Rust not because of the backlog, or for simplicity's sake, but because idiomatic Rust (and even modern tooling in general, a la LSP) have alternatives that give you what you want and more.

Everything mentioned above should be known by anyone who knew Rust for more than a month.

path: 0 25954666, hotness: undefined, score: 6, children: 0
Valmond 3 points a month ago

No.

path: 0 25952504, hotness: undefined, score: 3, children: 7
Varesti 1 point a month ago

YES!

path: 0 25952504 25952631, hotness: undefined, score: 1, children: 6
Valmond 5 points a month ago

Is this the 5 minute argument?

path: 0 25952504 25952631 25953621, hotness: undefined, score: 5, children: 3
Varesti 5 points a month ago

Well it WAS the 5 second argument.

path: 0 25952504 25952631 25953621 25953840, hotness: undefined, score: 5, children: 2
sukhmel 2 points a month ago

Is it when you pick up an argument someone dropped?

path: 0 25952504 25952631 25953621 25953840 25953872, hotness: undefined, score: 2, children: 1
sukhmel 2 points a month ago

I don't know, it looks like the amount of issues is way higher than improvement in comfort. Beside, they mentioned a lot of corner cases that would definitely show up and become a problem

I've been using C++ for quite some time, and it also had some talks about having named parameters, but it only helps in some places and hinders in most, imo. I feel like in Rust you may use a struct as parameter containing all of function parameteres where you really want the names

path: 0 25952504 25952631 25952933, hotness: undefined, score: 2, children: 0
Zykino 2 points a month ago

Maybe

path: 0 25952504 25952631 25952876, hotness: undefined, score: 2, children: 0
tatterdemalion 2 points a month ago

I'd love to see a language with structural types where functions are always unary and tuples don't exist. Every function call would look like either f () (no args), f 1 (one arg) or f (foo: 1, bar: 2) (multiple args, names mandatory).

I started building a language like this but I haven't worked on it in a while.

path: 0 25967762, hotness: undefined, score: 2, children: 0
somegeek -2 points a month ago

This greatly depends on language design and implementation.

For example, Clojure has all of these and much more, and it's still much simpler and hassle free than rust. The functions are just functions, but the forms and lists that are very powerful.

functions are just

(defn foo [a b] (print "do thing"))

(foo 1 2)

With the power of destructuring that maps provide us, we can have named arguments:

(defn foo [a & {:keys [b c]}] (print a b c))

(foo 1 {:b 2, :c 3})

and for multi-arity functions, there's no special syntax. It's still just plain functions and forms

(def foo
  ([a] (print "I only have one" a)
  ([a b] (print "I have two" a b)))

It also has more advanced ways of polymorphysm with methods and protocols.

It depends on the design of the language.

path: 0 25953678, hotness: undefined, score: -2, children: 0
rust
rust

@programming.dev

login for more options
8329
1423
314

Welcome to the Rust community! This is a place to discuss about the Rust programming language.

Wormhole

!performance@programming.dev

Credits
  • The icon is a modified version of the official rust logo (changing the colors to a gradient and black background)

go to feed...