Precedence of ParamEnv

Hey folks,

I was thinking a while about issue #24066 the initial post of the issue mentions the following example:

trait Trait<T> {
    fn call_me(&self, x: T) {}
}
impl<T> Trait<u32> for T {}
impl<T> Trait<i32> for T {}
fn bug<T: Trait<U>, U>(x: T) {
    x.call_me(1u32);
    //~^ ERROR mismatched types
}

My personal gut feeling tells me that it's perfectly fine how it works today, because otherwise the body could further restrict the constraints from the function signature which would probably lead to even more confusion on the call site. I think a caller should be confident that his input will be accepted if it satisfies the constraints of the function signature.

But it seems to me that most people see this behaviour as an inadequacy oder even a bug therefore I would like to ask if I'm maybe completely wrong with my assessment?

Regards
keks

This wouldn't be allowed even when the bug is fixed. The function in the example does not have any unstated requirements on T that could fail at the call site; rather, it would be using the fact that T: Trait<u32> for all T. The bug is that the bounds on the function currently hide that true fact from the body.

1 Like

Ahhh I got it thanks! But this would make a suitable blanket implementation always mandatory right?

Nothing is newly “mandatory”. Consider this modified example:

trait Trait<T> {
    fn call_me(&self, x: T) {}
}
impl<T> Trait<u32> for T {}
impl<T> Trait<i32> for T {}

fn fine<T>(x: T) {
    x.call_me(1u32);
}
fn bug<T: Trait<U>, U>(x: T) {
    x.call_me(1u32);
}

The function fine() does compile, because the implementation it uses always exists regardless of T. The function bug() does not, even though it does the same thing, because the implementation is hidden by the bound on T even though it still exists. That doesn’t make sense: bug() shouldn't be able to do less just because it has more bounds than fine(). (Consider that bug() can work around the bug by calling fine().)

2 Likes

While it can be considered a bug, the precedence of ParamEnv is also something that code can rely on.

// If the ParamEnv is not prefered, the call to `.into` is ambiguous
fn method_selection<T: Into<u64>>(x: T) -> Option<u32> {
    x.into().try_into().ok()
}

While I do think the behavior could be better, getting there will probably require some breakage along the way.

Incidentally, candidate selection is more complicated than "ParamEnv wins". Here are some breadcrumbs for anyone interested.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.