60

If I want to unpack a tuple and pass it as arguments is there a way to do this:

//Does not compile
fn main() {
    let tuple = (10, Vec::new());
    foo(tuple);
}
fn foo(a: i32, b: Vec<i32>) {
    //Does stuff.
}

Instead of having to do this:

fn main() {
    let tuple = (10, Vec::new());
    foo(tuple.0, tuple.1);
}
fn foo(a: i32, b: Vec<i32>) {
    //Does stuff.
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
HiDefender
  • 2,088
  • 2
  • 14
  • 31

4 Answers4

38

On a nightly compiler:

#![feature(fn_traits)]

fn main() {
    let tuple = (10, Vec::new());
    std::ops::Fn::call(&foo, tuple);
}
fn foo(a: i32, b: Vec<i32>) {
}

There is AFAIK no stable way to do that.

mcarton
  • 27,633
  • 5
  • 85
  • 95
36

There is a way, using the magic of pattern matching:

fn main() {
    let tuple = (10, Vec::new());
    foo(tuple);
}

fn foo((a, b): (i32, Vec<i32>)) {
    // do stuff
}

As per Rust reference:

As with let bindings, function arguments are irrefutable patterns, so any pattern that is valid in a let binding is also valid as an argument.

So you can specify an argument like:

(a, b): (i32, Vec<i32>)

just like you would in a let statement.

ljedrz
  • 20,316
  • 4
  • 69
  • 97
  • 9
    can you elaborate about this a little bit more? Isn't it then like a calling a function with a single argument which happened to be a tuple? – piotao Jul 23 '20 at 22:28
  • 3
    @piotao Yes, `foo` is a one argument function that takes a tuple. It just so happens that the tuple is immediately pattern-matched and unpacked in the function parameters. – Colonel Thirty Two Jun 24 '21 at 16:26
  • 1
    This proposal does not address the need to "unpack a tuple and pass it as argument**S**" (emphasis mine). – Joël May 24 '23 at 06:36
14
let (a, b) = (10, Vec::new());
foo(a, b);
Lee
  • 142,018
  • 20
  • 234
  • 287
  • 15
    I upvoted, but this answer would be better if it explicitly answered the question rather than just offering alternative code. – trent Oct 05 '16 at 15:57
0

This can be done in stable rust with generic functions or traits. Here's an example that uses a trait to pass 2-tuples to functions that accept two parameters.

fn main() {
    let tuple = (0, "hello");
    takes2.call(tuple);
}

fn takes2(a: u8, b: &str) {}

trait Call2<A, B, Z> {
    fn call(self, args: (A, B)) -> Z;
}

impl<F, A, B, Z> Call2<A, B, Z> for F
where
    F: FnOnce(A, B) -> Z,
{
    fn call(self, (a, b): (A, B)) -> Z {
        self(a, b)
    }
}

This is likely overkill in most cases, but if you need to do this kind of thing in a lot of places, it may be worthwhile to use this generic code.

The same idea can be extended to tuples of any size. If you really want to go nuts, you can even use a macro to define similar traits for arbitrary numbers of parameters:

extern crate paste; // FYI

fn main() {
    let pair = (0, "hello");
    let triple = (0, "hello", 1024);
    let quad = (0, "hello", 1024, 3.14);
    takes2.call(pair);
    takes3.call(triple);
    takes4.call(quad);
}

fn takes2(a: u8, b: &str) {}

fn takes3(a: u8, b: &str, c: usize) {}

fn takes4(a: u8, b: &str, c: usize, d: f64) {}

define_tuple_calls!(A, B, C, D);

macro_rules! define_tuple_calls {
    () => {};
    ($A:ident $(, $T:ident)* $(,)?) => {
        paste::paste! {
            trait [<Call $A $($T)*>]<$A, $($T,)* Z> {
                fn call(self, args: ($A, $($T,)*)) -> Z;
            }
            
            impl<F, $A, $($T,)* Z> [<Call $A $($T)*>]<$A, $($T,)* Z> for F
            where
                F: FnOnce($A, $($T,)*) -> Z,
            {
                #[allow(non_snake_case)]
                fn call(self, ($A, $($T,)*): ($A, $($T,)*)) -> Z {
                    self($A, $($T,)*)
                }
            }
        }
        define_tuple_calls!($($T,)*);
    };
}
use define_tuple_calls;

If you'd like to pass a mix of tuples and isolated arguments, as in take4.call((0, "hello"), 1024, 3.14), this starts to get pretty hairy. I can see an approach where you would turn the inputs into a nested tuple, ((0, "hello"), 1024, 3.14), and pass that into the method like take4.call(((0, "hello"), 1024, 3.14)). The call method would need to be more generic. Rather than accepting (A, B, C, D), it would accept impl FlattensTo<(A, B, C, D)>, where FlattensTo<T> is a trait that works like Into<T>, which you need to define and implement for arbitrary combinations of nested tuples or values. To implement this comprehensively without a ton of redundant code, you would likely need to write a procedural macro.

Drew Nutter
  • 955
  • 8
  • 14