Why can't we map tuples on arguments in Rust?

Basically: Why can't we do

fn foo(i: i32, j: i32) -> i32 {
    i + j
}

foo((1, 2)) // -> 3

in Rust? More details why this would be nice are written down in this blog post.

I'm not sure whether this is technically possible and whether there are things I did not think of before writing this down. I would love to get this into an RFC, if you people here think it would be worth it. Let me know what you think and/or where I'm wrong with what I've written down!

:slight_smile:

Not sure if you’re aware but you can get kinda close with patterns:

fn foo((i, j): (i32, i32)) -> i32 {
    i + j
}

foo((1, 2));

Yes, I just saw that option in a stackoverflow post. The thing is that this is not nice from an API-design standpoint. If you occasionally have to call your API functions outside of a chain (like the ones I have in my blogpost), you have to write foo((1, 2)) ... which is not nice. And if there is no technical point why this cannot be implemented, we get awesome API-design possibilities "for free".

You can, in fact, do something very close in nightly today:

#![feature(fn_traits)]

fn my_function(_: u32, _: u32) {}

fn main() {
    let tuple = (1, 2);
    my_function.call(tuple);
}

The "it's the same syntax to call with tuple or not" is a bit scary, though, once type inference gets involved, so I think I prefer the explicit way--perhaps a function adapter that takes something with multiple arguments and returns a function that takes a tuple.

2 Likes

Personally I’d be against this type of thing for Rust :slight_smile:. There’s no real need to be clever in places like this and as you say, there are ways to stay explicit but convenience things up a bit.

2 Likes

Having this tuple application done automatically and silently is a bad idea. Implicit things that make a language act like that usually bite your ass later. And this pain has happened in other languages.

On the other hand Python uses an application (apply in Lisp lingo) syntax that is both simple, short and explicit:

def foo(a, b):
    print(a)

ab = (1, 2)
foo(*ab)

You can't use a star like that in Rust, but other short syntaxes could be invented.

In D language there are both struct-like library-defined tuples as in Rust, and built-in "type tuples" that can contain argument packs like that. They have a differerent ABI, the type tuples are designed to have the same memory layout as function calls, so they are efficient if you want to apply a tuple as discussed here. In Rust I think such efficiency is missing (if the function is not inlined).

3 Likes

Here's some starters:

How do you plan to deal with 1-tuples?

let x = (3,);
f(x); // does this call f(3) or f((3,))?
      // does it depend on f?  What if f is generic?

What is a tuple?

In your example, you have a function and_then_chain turn a T into a (T, U), then into a (T, U, V).

But what if T is statically known to be (A, B)? Is it impossible to make this produce ((A, B), U, V)? If so, it reminds me of perl's eternally-flat array data type...

4 Likes

Thank you all for your replies. I see now that it is not that simple.
I learned something today, so I'd say: success! :smile:

1 Like

This gave me scary flashbacks to my days writing Perl. :confounded: