-
Notifications
You must be signed in to change notification settings - Fork 15
refcell_ref.md: RefCell::RefMut is fine #5
Comments
Not sure of it. It's the "disposing of example: fn foo(var: &mut u32) -> u32 {
let ptr: *mut u32 = var;
let ref_ = &mut *var;
mk_eqty!(ref_, var);
*ref_ = 2;
unsafe { *ptr }
} The |
What are you referring to here? I also don't see the relation between your example and
|
But fn foo(var: &mut u32) -> u32 {
let ptr: *mut u32 = var;
let ref_ = &mut *var;
mk_eqty!(ref_, var);
*ref_ = 2;
{ let _x = ref_; }
unsafe { *ptr }
} |
You can also have a write-sided version of this, which is more likely to be misoptimized (because abstract-interpretation optimizations move writes backward and reads forward). fn foo_will_break(var: &mut u32) -> u32 {
let ptr: *mut u32 = var;
let ref_ = &mut *var;
mk_eqty!(ref_, var);
let result = *ref_;
{ let _x = ref_; }
unsafe { *ptr = 2; }
result
} If the compiler DSEs the write to |
What I am saying in my first post is: The semantic type This is in contrast to impl<'b, T> Ref<'b, T> {
fn get_ptr(self) -> &'b T {
self.value
}
} The latter is the observation I brought up in a mail to Niko, which is the source of Unfortunately, I don't understand what this observation has to do with the code you are bringing up. There's no compiler transformations involved here, I am just looking at the flow of ownership. Here, "ownership" is a fairly wide term, it refers to anything you can "own" in Rust -- in particular, you can "own a mutable borrow of a |
The problem is that, after inlining, fn refcell_example(r: &mut RefCell<u32>) -> u32 {
let ref_ = r.borrow_mut();
let result = *ref_;
drop(ref_);
*r.borrow_mut() = 2;
return result;
} An My chain-based model allows you to "expose" a reference and remove its control over accesses, but |
I would argue there is a very fundamental difference between If we have a semantics that reasons by looking at types (e.g. to exclude aliasing), I am not surprised that doing such a type-erasing kind of inlining breaks programs. In other words, the unsafe code in |
But the example does not mess with types. It only messes with privacy. I am a little worried about introducing privacy to the aliasing rules. |
Privacy is the one mechanism in Rust which makes abstraction work. Without privacy, the entire story of "ascribing additional meaning to types" (and therefore the entire story of hiding unsafety behind an abstraction barrier) breaks down. |
Sure. But UB does not care about which additional meaning you ascribe to your types - if it did, then In your way, the aliasing rules would have to say something like "if you drop a struct containing an private |
@RalfJung I do not see the difference between The example I gave in the original Tootsie Pop model post seems to apply. Imagine that we had this function: impl<'b> RefMut<'b, u32> {
pub fn broken(self) {
let x = *self.value;
mem::drop(self.borrow);
use(x);
}
} It seems to me that, at least under an aggressive set of rules, the compiler would be within its rights to reorder those statements: impl<'b> RefMut<'b, u32> {
pub fn broken(self) {
mem::drop(self.borrow);
let x = *self.value;
use(x);
}
} After all, (And of course to make this all more deadly we can think of |
@nikomatsakis You are right. I missed the fact that it's the destructor of However, if we instead consider an alternative where |
Hmm, interesting. I'm not sure if it is true that impl<'b> RefMut<'b, u32> {
pub fn broken(self) {
let x = *self.value;
mem::drop(self); // NB, not self.borrow
use(x);
}
} |
Now the version of the code which dereferences after the Consider fn foo(v: Vec<i32>) {
let x = v[0];
mem::drop(v);
use(x);
} Sure the compiler is not allowed to move the |
@RalfJung that seems (at least potentially) different to me; the |
Consider this: struct Foo<'a>(&'a i32);
fn foo<'a>(f: Foo<'a>) {
use(f.0)
}
fn foo_prime<'a>(f: Foo<'a>) {
let x = f.0;
drop(f);
use(x);
} Basically, foo could be rewritten to foo_prime, which is okay for Foo, but not for RefMut. |
I disagree. @mystor |
@RalfJung I see your point. Very interesting! Have to think about it, but what you're saying makes sense to me. |
I just noticed though that this is not stable under inlining -- if the destructor is inlined in your Or maybe that's not a problem. After all, in the case of |
That's what I was talking about. |
(Beware of the zombies!) Now that we have done the proof, I am thinking maybe even Maybe we don't want to permit this kind of "irreversibility" when checking whether the private invariants are strong enough to justify the written type, I don't know. If we do not, then I would agree with you that |
Actually, based on @comex's comment about "Stacked Borrows", I now conclude that |
use std::cell::{RefCell, Ref};
fn break_it(rc: &RefCell<i32>, r: Ref<'_, i32>) {
// `r` has a shared reference, it is passed in as argument and hence
// a barrier is added that marks this memory as read-only for the entire
// duration of this function.
drop(r);
// *oops* here we can mutate that memory.
*rc.borrow_mut() = 2;
}
fn main() {
let rc = RefCell::new(0);
break_it(&rc, rc.borrow())
} |
Also see rust-lang/unsafe-code-guidelines#125 |
refcell_ref.md
says:However, I think that applies only to
RefCell::Ref
, not toRefCell::RefMut
. (The mistake is probably mine in the original mail to Niko.) The reason for this is that&mut T
is non-Copy
, so this actually is a reference that's valid for as long as the lifetime says. If the destructor ofRefCell::RefMut
is executed, ownership is moved fromRefMut
back to theRefCell
. In some sense, having a variable of type&'a mut T
does not actually guarantee that the borrow lives for lifetime'a
, all it really says is that if we keep hold of this variable, then the borrow lasts. But if we give up the variable, pass it to someone else, that someone may well kill the borrow earlier.In contrast to that,
&T
isCopy
, so there's no "giving up" of ownership here.(I was unsure whether an issue or the internals is the right place for this discussion. Feel free to move.)
The text was updated successfully, but these errors were encountered: