The ambiguity of "the type of this value must be known in this context" error

Good error messages are crucial to Rust's usability. Therefore, I'd like to highlight an instance of an error message which I found most unhelpful, so that we may improve Rust.

Here is the function:

// Gets the ASCII character represented by a string of binary digits.
//
// `s` must contain exactly 8 binary digits, such as "01001111".
fn bin_to_char(s: &str) -> char {
    s.char_indices()
        .map(|(pos, c)| c.to_digit(2).unwrap() as u8 * (1 << (7 - pos)))
        .sum()
        .into()
}

and this is the output of the compiler:

rustc 1.14.0-nightly (5665bdf3e 2016-11-02)
error: the type of this value must be known in this context
  --> <anon>:11:5
   |
11 |     s.char_indices()
   |     ^

error: aborting due to previous error

Here's the Playground link if you want to have a try at fixing this function.

The error message points at s, as if the compiler can't figure out what type s has. But s is the function's parameter with type specified to be &str, so this isn't logical. It seems the compiler needs to blame a piece of code, but can't locate exactly where the problem is, so it just picks something.

I'm interested in hearing about similar issues others have had. I'm also curious what causes the current behavior relating to both why this code doesn't compile, and why the error message is so vague.

Related Github issues:
Misplaced "type of this value must be known ..." error
The compiler could have been more precise about missing type annotation.

1 Like

IIRC, when switching to the new error format, it was decided to not highlight whole multiline blocks but just point at the beginning. If you changed the parameter name from s to stuff, the error would still point at the first character:

error: the type of this value must be known in this context
  --> <anon>:11:5
   |
11 |     stuff.char_indices()
   |     ^

By the way, trough trial and error (and a bit of guessing), I managed to fix you function by specifying what type sum should return (using .sum::<u8>()) :slight_smile:

I think it would be better if a horizontal line like "-->" were used, so it wouldn't look like it's the variable that is being pointed at if the variable is a single character.

But the point I'm trying to make is that the error is unhelpful, and it would be nice not to have to use trial and error, and guessing to find out what's wrong.

I also don't understand why we must specify that sum use u8, as the result of the .map() expression is an iterator over u8s. Isn't u8 the only possible type that can go here?

2 Likes

This is a common question, and the surprising answer is, no.

Like Iterator::collect, it is generic over its output type.

use std::iter::Sum;

#[derive(Clone,Copy,Debug)]
struct Imposter(u8);

impl Sum<u8> for Imposter {
    fn sum<I>(iter: I) -> Imposter where I: Iterator<Item=u8> {
        let mut acc = Imposter(0u8);
        for x in iter {
            acc = Imposter(acc.0 + x);
        }
        acc
    }
}

fn main() {
    let a = vec![1u8; 10];
    println!("{:?}", a.iter().cloned().sum::<u8>());       // 10
    println!("{:?}", a.iter().cloned().sum::<Imposter>()); // Imposter(10)
}

Suppose there was also an impl for Imposter<u8>: Into<char> which, say, always returned 'A'. Then certainly, bytes.sum().into::<char>() would be ambiguous!

In all fairness, it does seem to me the existing implementations of Sum for standard library types could, in theory, uniquely determine the output type--but only if Sum were somehow closed to new implementations.


As to why there's this funny Sum trait that is generic over its output, well here's a short history lesson: Iterator::sum used to be a little different:

fn sum<S = Self::Item>(self) -> S 
where S: Add<Self::Item, Output=S> + Zero
{
    self.fold(Zero::zero(), |s, e| s + e)
}

It still required just as many type annotations as it does today; but with this definition, the motivation for making the function generic over the return type might be plainer to see.

However, Zero and One were never stabilized and left to the num crate instead. Eventually the traits left in std were ripped out and replaced with the backwards-compatible Sum trait.

2 Likes

I like the idea of using an arrow to make it clearer that the mark is indicating the start of a larger expression that produces a value with an unknown type. If highlighting an arbitrarily long, multi-line expression would be considered too verbose, that is. Otherwise, just showing the start and end of the expression in question is much clearer.

Thanks for the explanation. Something that was confusing me was the A = Self bit in the Sum trait definition:

pub trait Sum<A = Self> {
    fn sum<I>(iter: I) -> Self
        where I: Iterator<Item=A>;
}

I was reading this as A must equal Self, but I've learned now that this just means A defaults to Self if unspecified.