Trait impls from where clauses (ParamEnv) take precedence over freestanding trait impls

f3f062a
Opened by Hanna Kruppe at 2024-12-21 05:03:17

I stumbled upon this trying to write a function that generically takes a range (or something else with which str can be indexed), and slices a str with that range. I got that to work, this bug comes into play when the str is produced by slicing another string with a RangeFrom or another concrete type (in the same function).

Here's a simpler (no lifetimes, no associated types) and self-contained example (playpen):

trait Frobnicate<I> {
    fn frob(&self, i: I) -> Self;
}

struct Thing;

struct IA;
struct IB;

impl Frobnicate<IA> for Thing {
    fn frob(&self, _i: IA) -> Thing { Thing }
}

impl Frobnicate<IB> for Thing {
    fn frob(&self, _i: IB) -> Thing { Thing }
}

fn delegate_frob<I>(t: Thing, i: I) -> Thing
        where Thing : Frobnicate<I> {
    let t2: Thing = t.frob(IA);
    t2.frob(i)
}

fn main() {}

This seems innocent enough. There's a impl Frobnicate<IA> for Thing, so the .frob(IA) call should work, and it's known to return Thing again, so via the where clause the .frob(i) call is good as well. However, I get this error message:

<anon>:20:28: 20:30 error: mismatched types:
 expected `I`,
    found `IA`
(expected type parameter,
    found struct `IA`) [E0308]
<anon>:20     let t2: Thing = t.frob(IA);
                                     ^~

It appears that the where clause makes the compiler forget that there are other impls. Adding a Thing : Frobnicate<IA> bound only makes the compiler (rightly) complain that that's not a bound at all, since it doesn't involve any type parameters. UFCS makes it compile, though:

    let t2: Thing = <Thing as Frobnicate<IA>>::frob(&t, IA);

Edit: Besides playpen, I can also reproduce this locally:

rustc 1.0.0-beta (9854143cb 2015-04-02) (built 2015-04-02)
binary: rustc
commit-hash: 9854143cba679834bc4ef932858cd5303f015a0e
commit-date: 2015-04-02
build-date: 2015-04-02
host: x86_64-pc-windows-gnu
release: 1.0.0-beta

But I already noticed it a couple of weeks ago, so it can't be a recent regression.

  1. @nikomatsakis Is this not a bug? It seems to be the feature that was discussed in #33108 of lifting trait methods to inherent methods.

    bluss at 2016-05-06 19:25:52

  2. I would say this is... a grey area. Perhaps a known shortcoming would be the best description. The trait selection algorithm definitely prefers where clauses to impls when there is inference involved -- this often is crucial to making progress, but here creates a problem. You could probably workaround it by adding some (redundant) where-clauses like where Thing : Frobnicate<IA>, but that shouldn't be necessary. I'm not sure though of the best way to improve the trait selection algorithm to circumvent this problem.

    Niko Matsakis at 2016-05-09 09:15:03

  3. Here's a simple example of what I think is the same bug:

    fn f<V>(_: V) -> String where String: From<V> { String::from("hello") }
    

    This is definitely a problem.

    Robert O'Callahan at 2016-12-06 22:58:48

  4. The example in the issue description now compiles. Closing.

    Mark Rousskov at 2017-05-04 18:01:27

  5. Not again, I tested in the wrong directory. Re-opening....

    Mark Rousskov at 2017-05-04 18:02:53

  6. "Me too" — play exampleforum.

    Diggory Hardy at 2018-02-08 23:07:06

  7. Small example to illustrate the problem (I do velieve it caused by the same bug):

    fn f<V>(_: V) -> String
    where
        String: From<V>,
    {
        From::from("Hello")   // works
        String::from("Hello") // doesn't work
    }
    

    playground

    Psilon at 2019-10-24 22:03:15

  8. One thing I noted while investigating a related issue also applies to @Pzixel's example above, where adding another trait bound can fix the problem in strange ways. The type parameter of the generic trait in the extra bound can be another generic type parameter or an HRTB with a concrete type, but not another concrete type. Also in this specific case with String and From it cannot even be the expected concrete type for some reason. And I can't reproduce the behavior with either a local trait or a local type

    Here is more (confusing) food for thought as of 1.55.0-nightly (2021-07-12 955b9c0d4cd9176b53f5) : playground link

    fn ok_with_another_bound_generic<V, W>() -> String
    where
        String: From<V> + From<W>,
    {
        String::from("✅")
    }
    
    fn nok_with_another_bound_concrete<V>() -> String
    where
        String: From<V> + From<char>,
    {
        String::from("❌")
    }
    
    fn ok_with_another_bound_concrete_hrtb<V>() -> String
    where
        String: From<V> + for<'a> From<&'a String>,
    {
        String::from("✅")
    }
    
    fn nok_with_another_bound_concrete_expected_type<V>() -> String
    where
        String: From<V> + From<&'static str>,
    {
        String::from("❌")
    }
    
    fn ok_with_another_bound_concrete_expected_type_local_trait<V>() -> String
    where
        String: MyFrom<V> + MyFrom<&'static str>,
    {
        String::my_from("✅")
    }
    
    fn ok_with_another_bound_concrete_expected_type_local_type<V>() -> MyString
    where
        MyString: From<V> + From<&'static str>,
    {
        MyString::from("✅")
    }
    
    trait MyFrom<T> { fn my_from(_: T) -> Self; }
    impl<T> MyFrom<T> for T { fn my_from(x: T) -> Self { x } }
    impl MyFrom<&str> for String { fn my_from(c: &str) -> Self { c.into() } }
    
    struct MyString;
    impl From<&str> for MyString { fn from(_: &str) -> Self { MyString } }
    

    Guillaume Depardon at 2021-07-13 11:53:40

  9. I think I ran into the same problem with some code that I reduced to:

    Playground

    use std::ops::Mul;
    
    #[derive(Debug)]
    struct UsizeWrapper(usize);
    
    impl Mul<UsizeWrapper> for usize {
        type Output = UsizeWrapper;
    
        fn mul(self, rhs: UsizeWrapper) -> Self::Output {
            UsizeWrapper(self * rhs.0)
        }
    }
    
    fn mul_6<T>(v: T) -> T
    where
        usize: Mul<T, Output = T>,
    {
        // Doesn't work:
        2usize * 3usize * v
        // Works:
        //6usize * v
    }
    
    fn main() {
        println!("{:?}", mul_6(UsizeWrapper(10usize)));
    }
    
    <hr>

    Error:

    error[E0308]: mismatched types
      --> src/main.rs:19:14
       |
    14 | fn mul_6<T>(v: T) -> T
       |          - expected this type parameter
    ...
    19 |     2usize * 3usize * v
       |              ^^^^^^ expected type parameter `T`, found `usize`
       |
       = note: expected type parameter `T`
                            found type `usize`
    
    error[E0369]: cannot multiply `T` by `T`
      --> src/main.rs:19:21
       |
    19 |     2usize * 3usize * v
       |     --------------- ^ - T
       |     |
       |     T
       |
    help: consider further restricting type parameter `T`
       |
    16 |     usize: Mul<T, Output = T>, T: std::ops::Mul<Output = T>
       |                              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    Some errors have detailed explanations: E0308, E0369.
    For more information about an error, try `rustc --explain E0308`.
    

    Asger Hautop Drewsen at 2024-07-17 14:48:18