Turbofish operator

In this function that computes the average of a slice of i32 values I show two ways to compute the sum.
Is there any reason to prefer approach 1 over 2?
Maybe the main reason to use turbofish is in the middle of a chained expression rather than at the end.

Why is the type i32 lost by the iterator? It's not clear to me why I can't just use let sum = numbers.iter().sum();. Why doesn't it know that the type of sum is i32 based on the type of numbers?

fn average_i32(numbers: &[i32]) -> f32 {
    // Approach #1
    let sum = numbers.iter().sum::<i32>();

    // Approach #2
    let sum: i32 = numbers.iter().sum();

    sum as f32 / numbers.len() as f32
}

Normally the return value is known because the Add trait defines the output type as an associated type, i.e. i32 implements the trait Add<i32> (the <i32> here being the right-hand-side of the sum) with associated type Output = i32. Since i32 can only implement the trait Add<i32> once, there is only one possible output type for this addition.

However, when using Iterator::sum, the output type is determined through the Sum trait, and this trait does not specify the output type as an associated type. Instead you implement Sum<i32> on the output type, and there is nothing stopping multiple types from implementing the trait Sum<i32>.

This ambiguity is why you need to tell it what output type you want.

7 Likes

It is a personal preference. No specifying the type to variables makes code consistent.

sum isn't a function, it is a generic function. It it the compilers job to make a function from its definition when used. Typically the inputs give all the information and so enough is known to infer everything. The language allows some outputs(returns) to be not confined exclusively by input so in such cases the type must come from elsewhere.

2 Likes

You said "The language allows some outputs(returns) to be not confined exclusively by input so in such cases the type must come from elsewhere." Is the reason that is the case for the Sum trait that the result might overflow the type of the inputs? For example, when summing a bunch of i8 values, the result may not fit in an i8.

Summing into some other type is certainly a use-case of the multiple allowed output types, though the standard library doesn't provide the impls to sum into larger types for the ordinary integer types.

1 Like

(p.s. Turbofish is a qualifier rather than operator. Both are sigils when just a bunch of symbols.)

Overflow is a property (capability / some better word goes here) of values, the type is retained.
Your allowed to make operators of multiple types. Showing an example is likely quickest;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Dri {
    neg: u32,
    pos: u32,
}

impl std::ops::Add<&i32> for Dri {
    type Output = Self;

    fn add(self, rhs: &i32) -> Self {
        Self {
            neg: self.neg + if *rhs < 0 { rhs.abs() as u32 } else { 0 },
            pos: self.pos + if *rhs > 0 { rhs.abs() as u32 } else { 0 },
        }
    }
}

impl<'a> std::iter::Sum<&'a i32> for Dri {
    fn sum<I>(iter: I) -> Self
    where
        I: Iterator<Item = &'a i32>,
    {
        let mut a = Dri { neg: 0, pos: 0 };
        for i in iter {
            a = a + i;
        }
        a
    }
}

/// I have no idea what this value is computing
fn posi(numbers: &[i32]) -> f32 {
    let sum = numbers.iter().sum::<Dri>();
    sum.pos as f32 / numbers.len() as f32
}
1 Like

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.