Stable compiler talks about unstable Step

When I throw this code

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
struct MyIndex(usize);

#[cfg(test)]
mod tests {
    use super::*;
    use rstest::rstest;

    fn name() {
        let (start, stop) = (6,10);
        let mapped: Vec<MyIndex> =         (start ..        stop).map(MyIndex).collect();
        let direct: Vec<MyIndex> = (MyIndex(start)..MyIndex(stop))            .collect();
    }
}

at the compiler (playground), it talks about Step

   = note: the following trait bounds were not satisfied:
           `MyIndex: Step`

which is still unstable.

Why is a stable compiler seemingly implying that I should use unstable features?

2 Likes

Well, Range<Idx> only implements Iterator if Idx implements Step.

I understand that.

What I don't understand is why a compiler version which does not allow the use of Step, talks about it. To be clear:

I'm using rust 1.84, whose documentation has the following to say about Step:

This is a nightly-only experimental API. (step_trait #42168)
  • I never asked for a nightly version of the compiler, merely stable 1.84.0.
  • If I try to use Step, the compiler generates an error.

In which case, why are the error messages relating to my code blithely talking about something that is not usable in this version of the compiler?

2 Likes

I also stumbled over this a while back and silly me trusted the compiler hint and tried to implement Step for a custom type as a solution, which obviously failed.
I agree that the stable compiler should not talk about unstable trait bounds and lead the user down a path to nowhere.

1 Like

I found a related issue that got a fix in form of an improved error message, so it might be worth filing a new issue for your scenario:

1 Like

Stable rustc gives error messages mentioning unstable traits without any regard for whether they’re stable or not. See these three:

"foo".find(123u8);
Error: u8 does not impl Pattern
error[E0277]: the trait bound `u8: Pattern` is not satisfied
    --> src/main.rs:2:16
     |
2    |     "foo".find(123u8);
     |           ---- ^^^^^ the trait `FnMut(char)` is not implemented for `u8`
     |           |
     |           required by a bound introduced by this call
     |
     = help: the following other types implement trait `Pattern`:
               &'b String
               &'b [char; N]
               &'b [char]
               &'b str
               &'c &'b str
               [char; N]
               char
     = note: required for `u8` to implement `Pattern`
note: required by a bound in `core::str::<impl str>::find`
[1u8, 2, 3].join(f64::NAN);
Error: `[u8]` does not impl `Join<_>`
error[E0599]: the method `join` exists for array `[u8; 3]`, but its trait bounds were not satisfied
 --> src/main.rs:5:17
  |
5 |     [1u8, 2, 3].join(f64::NAN);
  |                 ^^^^ method cannot be called on `[u8; 3]` due to unsatisfied trait bounds
  |
  = note: the following trait bounds were not satisfied:
          `[u8]: Join<_>`
unsafe { 2_f32.to_int_unchecked::<String>() };
Error: `f32` is not `FloatToInt<String>`
error[E0277]: the trait bound `f32: FloatToInt<String>` is not satisfied
    --> src/main.rs:3:39
     |
3    |     unsafe { 2_f32.to_int_unchecked::<String>() };
     |                                       ^^^^^^ the trait `FloatToInt<String>` is not implemented for `f32`
     |
     = help: the following other types implement trait `FloatToInt<Int>`:
               `f32` implements `FloatToInt<i128>`
               `f32` implements `FloatToInt<i16>`
               `f32` implements `FloatToInt<i32>`
               `f32` implements `FloatToInt<i64>`
               `f32` implements `FloatToInt<i8>`
               `f32` implements `FloatToInt<isize>`
               `f32` implements `FloatToInt<u128>`
               `f32` implements `FloatToInt<u16>`
             and 4 others
note: required by a bound in `core::f32::<impl f32>::to_int_unchecked`

Playground

But I don’t see a problem with these unstable traits being mentioned. You aren’t meant to implement them anyway even if they were stable. The error message gives you something to search in std docs and then click “Implementors” to find what you can use, whether it’s stable or not.

4 Likes

This is an important thing to learn with compiler errors about traits: if the compiler says Type: Trait isn’t satisfied it does not try to imply in any way (in general) that “you should implement Trait for Type”. If will tell you such things also in situations where e.g.

  • the orphan rules wouldn’t allow you to write the impl to begin with, or
  • the trait comes with restrictions that make it impossible to implement anyway (e.g. Copy is a common example where people sometimes misinterpret error messages as a hint that “you should implement Copy for Type”), or
  • the real error is that you passed a different type than you wanted in the first place, or
  • it would be undesirable to do the impl for other reasons

I do agree with @cod10129 posters that unstable traits can be actually nicer than some other errors; the documented trait impls do serve as easy-to-understand documentation of the “set of types that support the operation”. It’s much less approachable if you encounter language features where something only works for a specific set of types that you’d need to look up deep in the language reference.

I also agree with @jofas that compiler diagnostics can often be improved nonetheless. In the issue he linked, there was an active compiler suggestion to add a bound, which is bad if the trait is unstable. In your playground

error[E0599]: the method `collect` exists for struct `Range<MyIndex>`, but its trait bounds were not satisfied
  --> src/lib.rs:13:80
   |
3  | struct MyIndex(usize);
   | -------------- doesn't satisfy `MyIndex: Step`
...
13 |         let direct: Vec<MyIndex> = (MyIndex(start)..MyIndex(stop))            .collect();
   |                                                                                ^^^^^^^ method cannot be called on `Range<MyIndex>` due to unsatisfied trait bounds
   |
  ::: /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/range.rs:80:1
   |
80 | pub struct Range<Idx> {
   | --------------------- doesn't satisfy `std::ops::Range<MyIndex>: Iterator`
   |
   = note: the following trait bounds were not satisfied:
           `MyIndex: Step`
           which is required by `std::ops::Range<MyIndex>: Iterator`
           `std::ops::Range<MyIndex>: Iterator`
           which is required by `&mut std::ops::Range<MyIndex>: Iterator`
note: the trait `Step` must be implemented
  --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/range.rs:24:1
   |
24 | pub trait Step: Clone + PartialOrd + Sized {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

I find the section “the trait Step must be implemented” has suboptimal wording, because it does sound a bit too much like “you should implement the trait Step for MyIndex” (or maybe that’s even what the wording is meant to say? I’m not even sure…)

7 Likes

and also Try:

Some(()).and_then(|_| {
    ()?;
    Some(())
});

error message:

error[E0277]: the `?` operator can only be applied to values that implement `Try`
 --> src/main.rs:4:9
  |
4 |         ()?;
  |         ^^^ the `?` operator cannot be applied to type `()`
  |
  = help: the trait `Try` is not implemented for `()`

Another scenario is

  • You need to add a bound to your own generics

And in both the scenarios of "you need to add a bound" and "you need to add an implementation", it's also good to be aware that the compiler (currently) prefers to highlight a trait that would cause an existing blanket implementation to apply over the direct requirement. (This issue.)

For example:

error[E0277]: the trait bound `T: Clone` is not satisfied
   --> src/lib.rs:1:22
    |
1   | fn foo<T: ?Sized>(_: std::borrow::Cow<T>) {}
    |                      ^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `T`
    |
    = note: required for `T` to implement `ToOwned`
note: required by a bound in `Cow`
   --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/borrow.rs:181:8
    |
179 | pub enum Cow<'a, B: ?Sized + 'a>
    |          --- required by a bound in this enum
180 | where
181 |     B: ToOwned,
    |        ^^^^^^^ required by this bound in `Cow`
help: consider further restricting this bound
    |
1   | fn foo<T: ?Sized + std::clone::Clone>(_: std::borrow::Cow<T>) {}
    |                  +++++++++++++++++++

But the better (more general) fix is actually:

fn foo<T: ?Sized + ToOwned>(_: std::borrow::Cow<T>) {}
//               +++++++++

(Which is better when you need an implementation is less clear-cut, I think.)

3 Likes