-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Inconsistent life time behavior between BorrowMut::borrow_mut() and AsMut::as_mut() #38624
Comments
There is some discussion of this issue at https://users.rust-lang.org/t/trait-objects-and-borrowmut/8520 |
Likewise, there is a related Stack Overflow question. The code there boiled down to: use std::borrow::BorrowMut;
trait Foo {}
fn repro(mut foo: Box<Foo>) {
let borrowed: &mut Foo = foo.borrow_mut();
}
fn main() {}
This isn't related to the |
@rust-lang/compiler Any insight? This looks pretty baffling to me. |
The change from stable to nightly feels like potentially a regression. I'm not sure of the cause. I'm also not 100% sure of the cause of the original error, although I agree that trait object lifetime defaults is likely part of it. (I'll note that |
Nominating for prioritization, in case we don't get to it before. |
In code, this is: fn repro<'a>(mut foo: Box<Foo + 'a>) {
let borrowed: &mut (Foo + 'a) = foo.borrow_mut();
}
fn repro2(mut foo: Box<Foo>) {
let borrowed: &mut (Foo + 'static) = foo.borrow_mut();
} |
To be clear, I'm mostly concerned about the change in nightly behavior. |
(Yes, so, per some conversation with @eddyb, and in line with the earlier comments on the thread, I think the reason an error is reported in the first place is that |
I believe there should be a coercion but it's not triggering for some reason. |
Ought it not be |
|
@nikomatsakis your analysis was very enlightening, thank you! Could I trouble you to add why let borrowed: &mut Foo = &mut *foo; |
No, because the trait appears underneath the
We do have a coercion for
This is because of the coercion that @eddyb mentioned. So, to elaborate, to try and ameliorate this problem, we added a special rule that says if you have a value of type On a related note, one of the changes that I (and others) have been contemplating is to rejigger how the type-checker works to try and have coercions trigger more often (e.g., to allow them to work also in generic cases where the types aren't known up front). But just how effective we can get it is something of an open question. |
triage: P-high |
Perform lifetime elision (more) syntactically, before type-checking. The *initial* goal of this patch was to remove the (contextual) `&RegionScope` argument passed around `rustc_typeck::astconv` and allow converting arbitrary (syntactic) `hir::Ty` to (semantic) `Ty`. I've tried to closely match the existing behavior while moving the logic to the earlier `resolve_lifetime` pass, and [the crater report](https://gist.github.com/eddyb/4ac5b8516f87c1bfa2de528ed2b7779a) suggests none of the changes broke real code, but I will try to list everything: There are few cases in lifetime elision that could trip users up due to "hidden knowledge": ```rust type StaticStr = &'static str; // hides 'static trait WithLifetime<'a> { type Output; // can hide 'a } // This worked because the type of the first argument contains // 'static, although StaticStr doesn't even have parameters. fn foo(x: StaticStr) -> &str { x } // This worked because the compiler resolved the argument type // to <T as WithLifetime<'a>>::Output which has the hidden 'a. fn bar<'a, T: WithLifetime<'a>>(_: T::Output) -> &str { "baz" } ``` In the two examples above, elision wasn't using lifetimes that were in the source, not even *needed* by paths in the source, but rather *happened* to be part of the semantic representation of the types. To me, this suggests they should have never worked through elision (and they don't with this PR). Next we have an actual rule with a strange result, that is, the return type here elides to `&'x str`: ```rust impl<'a, 'b> Trait for Foo<'a, 'b> { fn method<'x, 'y>(self: &'x Foo<'a, 'b>, _: Bar<'y>) -> &str { &self.name } } ``` All 3 of `'a`, `'b` and `'y` are being ignored, because the `&self` elision rule only cares that the first argument is "`self` by reference". Due implementation considerations (elision running before typeck), I've limited it in this PR to a reference to a primitive/`struct`/`enum`/`union`, but not other types, but I am doing another crater run to assess the impact of limiting it to literally `&self` and `self: &Self` (they're identical in HIR). It's probably ideal to keep an "implicit `Self` for `self`" type around and *only* apply the rule to `&self` itself, but that would result in more bikeshed, and #21400 suggests some people expect otherwise. Another decent option is treating `self: X, ... -> Y` like `X -> Y` (one unique lifetime in `X` used for `Y`). The remaining changes have to do with "object lifetime defaults" (see RFCs [599](https://github.com/rust-lang/rfcs/blob/master/text/0599-default-object-bound.md) and [1156](https://github.com/rust-lang/rfcs/blob/master/text/1156-adjust-default-object-bounds.md)): ```rust trait Trait {} struct Ref2<'a, 'b, T: 'a+'b>(&'a T, &'b T); // These apply specifically within a (fn) body, // which allows type and lifetime inference: fn main() { // Used to be &'a mut (Trait+'a) - where 'a is one // inference variable - &'a mut (Trait+'b) in this PR. let _: &mut Trait; // Used to be an ambiguity error, but in this PR it's // Ref2<'a, 'b, Trait+'c> (3 inference variables). let _: Ref2<Trait>; } ``` What's happening here is that inference variables are created on the fly by typeck whenever a lifetime has no resolution attached to it - while it would be possible to alter the implementation to reuse inference variables based on decisions made early by `resolve_lifetime`, not doing that is more flexible and works better - it can compile all testcases from #38624 by not ending up with `&'static mut (Trait+'static)`. The ambiguity specifically cannot be an early error, because this is only the "default" (typeck can still pick something better based on the definition of `Trait` and whether it has any lifetime bounds), and having an error at all doesn't help anyone, as we can perfectly infer an appropriate lifetime inside the `fn` body. **TODO**: write tests for the user-visible changes. cc @nikomatsakis @arielb1
Minified: pub trait BorrowMut<Borrowed> where Borrowed: ?Sized {
fn borrow_mut(&mut self) -> &mut Borrowed;
}
impl<T> BorrowMut<T> for Box<T> where T: ?Sized {
fn borrow_mut(&mut self) -> &mut T { panic!() }
}
#[cfg(not(works))]
impl<T> BorrowMut<T> for T where T: ?Sized {
fn borrow_mut(&mut self) -> &mut T { panic!() }
}
trait T {}
impl T for u8 {}
fn main() {
// D (BorrowMut and Trait Object):
let mut r: Box<T> = Box::new(23u8);
let _: &mut T = BorrowMut::borrow_mut(&mut r);
} |
Anyone want to bisect? @TimNN :) |
@brson: Sure thing :) |
The problem is that this code: use std::borrow::BorrowMut;
trait T {}
impl T for u8 {}
fn main() {
let mut r: Box<T> = Box::new(23u8);
let _: &mut T = r.borrow_mut();
} compiles on nightly but not on stable |
I believe it is due to this PR: #39066 In this case, the change is expected. |
I confirmed this by checking nightlies; it has the new behavior in 2017-02-02, the closest nightly I could find after the PR landed, but not in 2017-01-26 (before the PR). |
So essentially this issue -- while confusing -- is working as intended. Shall we close? |
#39066 does fall into the range of nightlies I bisected this to: Live Edit: Well, @nikomatsakis was a bit faster than me ;) |
My main concern was, that
I'm still a bit surprised how that difference could even occur in 1.15. Is one of those traits special-cased in the compiler? Isn't the signature the only relevant thing for the borrow checker? |
@troplin neither is special to the compiler that I know of; I don't know why |
@troplin ah well also I think @eddyb landed a PR that adjusts how the defaulting works around |
Yeah, I did mention (on IRC?) that change being a fix to the strictness identified in #38624 (comment). |
Since both changes here seem to be intentional, closing. |
The following code shows inconsistent behavior in lifetime checking:
Using the stable compiler 1.14.0, case B and D yield a lifetime error:
A nightly rustc from last week shows only an error in case D.
This is strange, as
BorrowMut::borrow_mut()
andAsMut::as_mut()
have the same type signature. So cases B and D should give the same result.Furthermore, I am not sure why case A and B (or, for that matter, case C and D) behave differently. The only difference is that they use trait objects instead of primitive types, and as far as I understand, this should have no influence on the lifetime behavior.
The text was updated successfully, but these errors were encountered: