-
Notifications
You must be signed in to change notification settings - Fork 12.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Reordering where clauses can change program behavior #41756
Comments
Is there some reason this isn't just considered a bug in inference? I'd have expected this to fail to infer |
@cramertj It's not a bug per se -- it's the direct result of intentional behavior. In particular: pub trait Foo<T> { fn foo(&self) -> T; }
impl<T> Foo<()> for T { fn foo(&self) { } }
impl Foo<bool> for bool { fn foo(&self) -> bool { *self } }
pub fn foo<T>(t: T) where T: Foo<bool> {
println!("{:?}", <T as Foo<_>>::foo(&t));
}
fn main() { foo(false); } This program prints Now the obvious question is: why did we want that trumping behavior in the first place? I don't think it's clearly documented, unfortunately, but @nikomatsakis may be able to remember. (It was a decision made in the run-up to 1.0). One easy way to tell would be to turn off the trumping behavior (very easy) and see what breaks when bootstrapping. I'm pretty confident it will fail to bootstrap and build std, but that might point us to the common place where this matters. And if it doesn't... then we should indeed crater it. @cramertj, are you interested in trying such an experiment? I can mentor on the rustc side. |
@aturon Absolutely! The trumping behavior makes sense to me when it's giving priority to a |
It seems like a 'correct implementation' of this trumping would find "two trumps" - |
@withoutboats Exactly. |
The trumping behavior works exactly as you say: it only applies when there's exactly one where clause. The thing that makes this example cause problems, nevertheless, is that we have two distinct trait bounds for the
The key is that we must separately prove @cramertj The relevant piece of code is here. You could start just by changing |
Oh and here's the code that drives the overall "impl selection" process, which in turn drives inference. You can see, in particular, that it insists on winnowing down to a single "candidate" to yield back meaningful information. But part of that winnowing is removing all impls if there are applicable where clauses. |
Winnowing can't be the cause because it runs inside a probe - the order dependence occurs at the "outermost level" of trait selection: use std::fmt::Debug;
trait Left<T> {}
trait Right<T> {}
impl<T, U: Default + Debug> Left<U> for T {}
impl<T, U: Default + Debug> Right<U> for T {}
// replacing the order of `T: Left<U>` & `T: Right<U>` changes result
fn movable<T, U>() where T: Left<U>, T: Right<U>, U: Default + Debug {
println!("{:?}", U::default())
}
fn try_it<T: Default + Debug>() where T: Left<bool>, T: Right<()> {
movable::<T, _>();
}
fn main() {
try_it::<u8>() // the type here is irrelevant
} |
So I messed around with this a bit-- I tried disabling the
@nikomatsakis addressed why the trumping logic is necessary in this comment. Later in the same comment, it's pointed out that this trumping applies even in cases with leftover inference variables, which is what's happening in the issue here. Perhaps we could check if there are remaining inference variables before applying trumping behavior? Overall, however, I'd prefer a solution that allowed us to keep the trumping behavior, but detect situations in which the the ordering of where clauses causes trumping of the same inference type to be flipped. In this case, trumping for proving Can anyone (@nikomatsakis, @aturon) comment on the performance costs of such an approach? I suppose we could always try it and see. |
The method is |
@eddyb Sorry, I should have been more clear: I'm not concerned about the performance cost of disabling trumping when deciding inference variables-- I was concerned about the performance cost of disabling trumping for inference only when trumping order would be changed by where clause ordering. For this to work, I think we'd need to compute the result of applying trumping for each where clause independently, and then check to see if starting with different where clauses resulted in conflicting decisions for inference variables. If there is a conflict, we'd report an ambiguity. |
@cramertj I'm not quite following the example error you're giving -- I'm having a hard time understanding where I should mention, btw, that I don't think this order-sensitivity is likely to pose any real practical problems; the example is very contrived. I was guided to it by thinking carefully about things in Chalk and realizing that it was possible, even though we thought the trait system avoided it. So I personally wouldn't be inclined to try to test for order-sensitivity, regardless of performance. But I would like to understand a bit better how widely trumping ends up mattering in practice. |
Forum user a-0xc-67 ran into what I assume is this issue on URLO. fn f<V>(a: f32, b: f32, v: &V) -> V where
f32: for<'a> core::ops::Mul<&'a V, Output=V>,
f32: core::ops::Mul<f32, Output=f32>
{
a * b * v
} fn not_working<V>(a: f32, b: f32, v: &V) -> V where
f32: core::ops::Mul<f32, Output=f32>,
f32: for<'a> core::ops::Mul<&'a V, Output=V>,
{
a * b * v
} (The |
Here's a contrived example:
In the order given, the output is
false
. If you swap the order as suggested in the comment, the output is()
.What's happening here is a combination of a few things. The
Join
impl creates obligations in order of its where clauses, and the solution to each obligation informs inference, which affects future obligations. At the same time, when it comes time to showT: Left<U>
, for example, it can be proved either by virtue of the blanket impl or the where clause ontry_it
. We currently give preference to the where clause, which then drives inference.The same mechanisms also lead to cases where:
These issues seem fairly fundamental to the where clause preference approach, which is something we likely cannot change backwards compatibly (and there were good reasons for it in the first place). So it's possible that we will just have to live with these consequences. But I wanted to file a bug for posterity.
The text was updated successfully, but these errors were encountered: