Rust: Binop type mismatch error could be better

Created on 23 Dec 2016  路  15Comments  路  Source: rust-lang/rust

From the new book.

let x: i8 = 5;
let y: Option<i8> = Some(5);

let sum = x + y;

If we run this code, we get an error message like this:

error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied
 -->
  |
7 | let sum = x + y;
  |           ^^^^^
  |

This error could be a lot more newbie friendly. The next section of the book even has to apologize and explain:

"Intense! What this error message is trying to say is that Rust does not understand how to add an Option and an i8, since they're different types."

cc @jonathandturner @rust-lang/docs

A-diagnostics C-enhancement T-compiler

Most helpful comment

This has now a

note: no implementation for i8 + std::option::Option<i8>

All 15 comments

On the other hand, one might argue that it's important for users to understand how to interpret this error. It seems like binops though could be special case to say "The operator + is not implemented for ...".

I also think unimplemented operators in general could be described more accessibly in the way you described @brson.

The error message you gave as an example would be optimal, I think, if it pointed out instead that it pertains to x, even though the whole context is x + y. I expect that a novice programmer will then understand the problem more readily.

I think that the general gist of the error is good, but it should add something mentioning that the reason why it needs this trait is because the function add is not implemented.

Perhaps we could explain this using the regular trait-not-found error and do something like:

"The compiler converts this operation into the form x.add(y), using the Add trait. Because this trait isn't implemented, the operation could not be performed."

That way, it tells users 1) what the compiler actually is doing with the operation and 2) doesn't add any special case for this error, just adds on information in addition to the trait error to help the user understand the conversion.

I think as a first step it would be helpful to add type annotations to x and y. For example:

error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied
 -->
  |
7 | let sum = x + y;
  |           ^   - `std::option::Option<i8>`
  |           |   
  |          `i8`

This way, people can at least recognize the Option<i8> in the error message as the type of y. I think this might be helpful in other "trait bound not satisfied" cases as well.

For operators in particular, it might be nice to add a sentence saying + becomes std::ops::Add::add. Alternatively, add an annotation to the +, but I think this will make it very noisy.

This has now a

note: no implementation for i8 + std::option::Option<i8>

The new error I think closes this, since we note that i8 + std::option::Option<i8> isn't implemented directly.

rustc 1.19.0-dev (5dfcd85fd 2017-05-19)
error[E0277]: the trait bound `i8: std::ops::Add<std::option::Option<i8>>` is not satisfied
 --> test.rs:5:11
  |
5 | let sum = x + y;
  |           ^^^^^ the trait `std::ops::Add<std::option::Option<i8>>` is not implemented for `i8`
  |
  = note: no implementation for `i8 + std::option::Option<i8>`

error: aborting due to previous error

@Mark-Simulacrum - not to be a spoil sport, but this error doesn't feel beginner friendly, yet, which was the original bug.

Specifically, a beginner friendly message probably doesn't talk about traits at all. Though we want users to understand traits as they grow in their knowledge of Rust, having to learn about them in your first few minutes of Rust is probably too much to ask.

We may want to simplify well-known traits (and their errors) to the point of being easier to read by new users. We can still leave a note to help more advanced folks, but something like this:

error[E0277]: Adding incompatible types
 -->
  |
7 | let sum = x + y;
  |           ^^^^^ can't add i8 and std::option::Option<i8>
  |
= note: trait `std::ops::Add<std::option::Option<i8>>` is not implemented for `i8`

aside: would be nice to get rid of all the std:: stuff, since well, it's std 馃槢

error[E0277]: Adding incompatible types
 -->
  |
7 | let sum = x + y;
  |           ^^^^^ can't add i8 and Option<i8>
  |
= note: trait `ops::Add<Option<i8>>` is not implemented for `i8`

(in this particular error, we can be even more specific, not mention traits, but instead point them at match statements or the like to get the i8 out of the Option)

Hm, yeah, true. Reopening. I worry about having this much of a special case, but I agree it's probably worth it.

Yeah, definitely a good thing to worry about.

Just realised something. You actually can't do what the error note says in this message, right? Because of coherence you won't be able to implement that trait, if I understand correctly.

Not sure I follow you. I don't see anything about "maybe implement this" in the error. Perhaps we could detect the impossible case and special case it, though I worry that's overly ambitious -- technically, any two types that expose some form of mutable access to internals could be "added" through newtypes.

Heh, I've been doing Rust for a year and a half, and I'm still not 100% on exactly how the newtype trick is supposed to work. ;)

For me the main point is we should be cognisant of just how much someone needs to understand to get benefit from the error message. There's a big benefit to having simpler phrases like can't add i8 and Option<i8>.

I agree with swapping the note and annotation. Perhaps an extra note could just state "the value inside an Option can't be manipulated directly; did you mean to use map or expect"

Current output:

error[E0277]: cannot add `std::option::Option<i8>` to `i8`
 --> src/main.rs:5:13
  |
5 | let sum = x + y;
  |             ^ no implementation for `i8 + std::option::Option<i8>`
  |
  = help: the trait `std::ops::Add<std::option::Option<i8>>` is not implemented for `i8`

Further suggestions about using Option::map would be interesting to add.

The reverse doesn't trigger E0277, but E0369 instead:

error[E0369]: binary operation `+` cannot be applied to type `std::option::Option<i8>`
 --> src/main.rs:6:11
  |
6 | let sum = y + x;
  |           ^^^^^
  |
  = note: an implementation of `std::ops::Add` might be missing for `std::option::Option<i8>`

I'm going to close this issue as I see very little we could improve over the following, other than maybe unifying them both:

error[E0277]: cannot add `std::option::Option<i8>` to `i8`
 --> src/main.rs:5:13
  |
5 | let sum = x + y;
  |             ^ no implementation for `i8 + std::option::Option<i8>`
  |
  = help: the trait `std::ops::Add<std::option::Option<i8>>` is not implemented for `i8`
error[E0369]: binary operation `+` cannot be applied to type `std::option::Option<i8>`
 --> src/main.rs:5:13
  |
5 | let sum = y + x;
  |           - ^ - i8
  |           |
  |           std::option::Option<i8>
  |
  = note: an implementation of `std::ops::Add` might be missing for `std::option::Option<i8>`

Filed https://github.com/rust-lang/rust/issues/60497 to track the wording change.

Was this page helpful?
0 / 5 - 0 ratings