-
Notifications
You must be signed in to change notification settings - Fork 13.1k
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
RefCell::borrow does not pass borrow-check without a seemingly no-op let binding #23338
Comments
cc @pnkfelix |
@SimonSapin has also mentioned that there are some cases (e.g., in our "script" crate) where even this transformation does not work. |
I got stuck at some point but @jdm managed to find work arounds servo/servo@0c12dd9 |
cc #22321 |
Standalone test case: use std::cell::RefCell;
fn foo(x: RefCell<String>) -> String {
x.borrow().clone()
}
fn main() { } |
leaving nominated tag to talk again next week, assigning to self to investigate. |
Another work-around worth noting (if only because it provides a hint at the problem here): fn foo(x: RefCell<String>) -> String {
let t = x.borrow().clone();
t // this works!
} I think this is an artifact of our temporary r-value rules, in particular the one that says that all temporaries for the tail expression in a block are assigned the lifetime of the parent of the block (that is, they can outlive the block), and thus the |
(Having said that, I would like to see if we can still fix this in some way. But I no longer see this as a potential smoking gun pointing at some fundamental flaw in the system. So I've removed the I-nominated tag.) More details for those interested: the thing that comes up is this:
We could hack in special support for this case (i.e. some hack in terms of how
use std::cell::RefCell;
fn foo(x: RefCell<String>) -> String {
let result = {
let t = x;
t.borrow().clone()
};
result
}
fn main() { } (but maybe that is acceptable...) |
Actually, according to the current dynamic semantics, fn parameters already are torn down after the fn body is torn down. So it should be entirely sound (perhaps even a legitimate bug fix, not sure) to make the code-extents reflect the fact that the temporaries from the tail expression for the fn-body are destroyed before the parameters are dropped (i.e., that the parameters strictly outlive all of the r-value temporaries of the fn body). Here is some demo code illustrating this (note also that the behavior differs if one passes struct D(&'static str, u32);
impl Drop for D {
fn drop(&mut self) {
println!("Dropping D({}, {})", self.0, self.1);
}
}
impl D {
fn incr(&self, name: &'static str) -> D {
D(name, self.1 + 1)
}
}
#[cfg(not(nested))]
fn foo(a1: D, b1: D) -> (&'static str, D) {
let _b2 = b1.incr("b");
let _a2 = a1.incr("a");
let b3 = b1.incr("temp1").incr("b");
println!("made b2 a2 and b3");
("foo_direct", b3.incr("temp2").incr("b"))
}
#[cfg(nested)]
fn foo(a1: D, b1: D) -> (&'static str, D) {
let _b2 = b1.incr("b");
{
let _a2 = a1.incr("a");
let b3 = b1.incr("temp1").incr("b");
println!("made b2 a2 and b3");
("foo_nested", b3.incr("temp2").incr("b"))
}
}
fn main() {
let (name, result) = foo(D("param_a", 1), D("param_b", 1));
println!("called {}, got result D({}, {})",
name, result.0, result.1);
} (The reason that its interesting that the behavior differs with and without fn function(args ...) -> result { stmts ...; expr } to fn function(args ...) -> result { { stmts ...; expr } } is not semantics preserving; it changes the time at which the temporaries within |
Okay, I'm pretty happy with the solution given in #24021. |
Encode more precise scoping rules for function params Function params outlive everything in the body (incl temporaries). Thus if we assign them their own `CodeExtent`, the region inference can properly show that it is sound to have temporaries with destructors that reference the parameters (because such temporaries will be dropped before the parameters are dropped). Fix #23338
@pnkfelix this still is present w.r.t expression blocks, though, which are pretty useful when attempting to control the length of borrows. Using workaround in #23338 (comment) for now but it would be great for this to work nicely at some point, it led me to think I was going crazy 😄 |
When upgrading Rust in Servo, many usages of
RefCell::borrow
that were previously fine now cause borrow-check errors like this:This error message makes no sense to me, the two blocks it talks about are the same.
This can be worked around by binding the result of
.borrow()
withlet
before using it, enough though such a change looks like a no-op:The text was updated successfully, but these errors were encountered: