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

Why can't we do

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

fn main() {
    println!("{}", foo((1, 2)));

in Rust?

The idea

First of all: I don't know much about what is going on in rust-compiler-land, hence I don't know whether this is technically possible or even in progress already. I'm simply writing down some thoughts here.

The idea, or rather the question, is: Why can't we map tuples on arguments in Rust? The code from above shows exactly what I mean by that: Why is the compiler not able to unpack the tuple and pass it as list of arguments to the function?

It would be really cool if it could do that. The usecase is something like this:

some_possibly_failing_fn() // -> Result<_, _>
  .and_then(|res| (res, some_other_failing_fn()?)) // Result<(_, _), _>
  .map(PartialEq::Eq) // Result<bool, _>

for example. If mapping of tuples on arguments would be possible, one could write code like this, where failing functions are chained and operators and functions can then be called on the results of the failing functions.

Currently, one has to write it like this:

some_possibly_failing_fn() // -> Result<_, _>
  .and_then(|res| (res, some_other_failing_fn()?)) // Result<(_, _), _>
  .map(|(a, b)| a == b) // Result<bool, _>

or, because it is solvable with one line less:

some_possibly_failing_fn() // -> Result<_, _>
  .and_then(|res| Ok(res == some_other_failing_fn()?)) // Result<bool, _>

You might think that the overhead for that convenience is rather low and thus it is not really beneficial to have such a functionality in the rust compiler.

That is true. But there are great possibilities of API design if this is mapping of tuples on arguments are possible. For example, one could write an extension to the Result type which extends tuples. Think of something like that:

// assuming:
// some_possibly_failing_fn() -> Result<T, _>;
// some_other_failing_fn()) -> Result<U, _>;
// even_other_failing_fn()) -> Result<W, _>;
// yet_another_failing_f()) -> Result<V, _>;
// check_results(T, U, W, V) -> Result<bool, Error>;

some_possibly_failing_fn() // -> Result<T, _>
  .and_then_chain(|| some_other_failing_fn()) // Result<(T, U), _>
  .and_then_chain(|| even_other_failing_fn()) // Result<(T, U, W), _>
  .and_then_chain(|| yet_another_failing_f()) // Result<(T, U, W, V), _>

From an API-design standpoint, this is plain awesome.

Lets compare that to what we have to write today to do this:

some_possibly_failing_fn() // -> Result<T, _>
  .and_then(|a| Ok((a, some_other_failing_fn()?)))
  .and_then_chain(|(a, b)| Ok((a, b, even_other_failing_fn()?)))
  .and_then_chain(|(a, b, c)| Ok((a, b, c, yet_another_failing_f()?)))
  .and_then(|(a, b, c, d)| check_results(a, b, c, d))

and that's really not that readable.

From a technical point of view, the compiler is able to verify the length of the tuples and thus is able to check whether the tuple actually matches the number of parameters of a function.

As said, I'm not that much into the development of the language, thus I don't know whether there are other things that prevent this, though.


After posting this as a question on users.rust-lang.org and getting some nice replies with explanations why this cannot be, I conclude:

It is not possible to do it in a way that does not yield weird edge cases, ugly implicit conversions and other weirdness we really do not want in the language.

But I learned something, and that was worth it!

tags: #tools #software #c++