Why does the compiler need to be specified the type for my tuple?

fn main() {
    let rule = |i: (_, _), divisor, text| {
        if i.0 % divisor == 0 {
            (i.0, i.1 + text)
        } else {
            i
        }
    };
    
    (0..101)
        .enumerate()
        .map(|i| (i.0, String::new()))
        .map(|i| rule(i, 2, "Ping"))
        .map(|i| rule(i, 3, "Fizz"))
        .map(|i| rule(i, 5, "Buzz"))
        .map(|i| rule(i, 7, "Bazz"))
        .map(|i| rule(i, 11, "Whirl"))
        .map(|i| {
            if i.1.is_empty() {
                format!("{}", i.0)
            } else {
                i.1
            }
        })
        .for_each(|i| println!("{}", i));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=05e99de1df603ee45c1d916d9a2d5c03
In line 2, note that I need to tell the compiler that i is a tuple. Why is this the case?

Based on the body alone, it could be a tuple with 2 elements, sure. But also one with 3, or 4, or...

If you say i: (_, _, _) instead, you'll see that the closure itself is accepted but calling it with a tuple of size 2 is not, for example.

Edit: No, that's not it, the if/else should be enough to constrain tuple length.

Interesting, I don't know anything about compiler design, but I thought the compiler would generate functions for tuples of each sizes that were passed to that closure.

Actually I was too hasty in my answer. The if branch returns a 2-tuple and the else branch returns i, so inference is failing in some other way. I'm not actually sure why it fails to infer the parameters if you don't specify a 2-tuple, but succeeds when you specify a 2-tuple but not the types within. I could speculate, but it would just be speculation. I now agree with you that it should be able to handle this.

Rust will do this for generic functions, but tuples with the same prefix don't fall into this category. There's no trait for something like "tuple where field 0 is T and field 1 is U".

1 Like

Note that you can use patterns in closure arguments, which may be more readable than tuple indexing:

    let rule = |(n, buffer), divisor, text| {
        if n % divisor == 0 {
            (n, buffer + text)
        } else {
            (n, buffer)
        }
    };
4 Likes
fn main() {
    (|i| i.0)((0i32, 0i32));
}
fn main() {(|a| a[0])([0]);}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.