 # 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,
}

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