-
Notifications
You must be signed in to change notification settings - Fork 13.2k
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
Show impl for Weak #14759
Show impl for Weak #14759
Conversation
Hmm I see your point. So what should it do if the upgrade returns a None? fail or empty string? |
Or it could just say None I suppose |
It definitely shouldn't fail or print an empty string. Maybe something like |
What do you think about:
|
Works for me |
I tested locally and when I used the |
What did it do instead of printing |
It printed as if it weren't an invalid reference |
That seems... wrong. Did you test with something like: let x = Rc::new(1);
let y = x.downgrade();
// x is still valid, so should print 1
println!("{}", y);
// remove the strong reference
drop(x);
// no more strong references, should print <invalid reference>
println!("{}", y); Also, this needs a test. |
(Also, I personally prefer |
I just tried it again locally with the upgrade() and it worked correctly so I must have done something wrong before. What kind of test should there be? I'm ok with |
You can use my example, except write |
FWIW, while I think this is ok for However despite that, I'm still not entirely sure it's a good idea to be hiding an |
It can. The
Why not? (That said, I'd also be happy just printing |
Well, I'm referring specifically to whether or not it extends the lifetime of the value. But you're right, theoretically the This reason is why the proposed In any case, I'm not sure if this is sufficient reason to say we shouldn't have
At least in multi-threaded code, I believe strongly that clients of println!("Twiddling the frobs with {}", foo);
match foo.upgrade() {
Some(bar) => twiddleFrobsWith(bar),
None => reportNoFrobsToTwiddle()
} In the above code, the Incidentally, this is why (I assume; I didn't write it) The issues with the above code are only a problem in concurrent code, so it doesn't actually apply to So I guess I'm now firmly on the side of saying we should reject this change. |
Pushed changing the invalid message to |
@@ -232,6 +232,16 @@ impl<T> Clone for Weak<T> { | |||
} | |||
} | |||
|
|||
impl<T: fmt::Show> fmt::Show for Weak<T> { | |||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |||
if self.strong() == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This still needs to be using .upgrade()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some people are saying use upgrade()
, some say not to. It seems simpler not to use upgrade since that will increase the strong count and then decrease it as the method ends and drop is called
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is horribly unsafe to not use upgrade()
. I have proof-of-concept code on my computer right now that will segfault if you don't use upgrade()
, without using unsafe
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kballard it would be nice to post it as a demonstration. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, here's the code I used to prove that the Show
impl could remove the last Rc
. This code as-written uses upgrade()
, but if this PR as-written lands, I can ditch the upgrade()
and start crashing:
use std::cell::RefCell;
use std::rc::Rc;
use std::fmt;
struct Foo {
inner: RefCell<Option<Rc<Foo>>>,
s: String
}
impl Foo {
fn new(s: String) -> Rc<Foo> {
let f = Rc::new(Foo { inner: RefCell::new(None), s: s });
let f_ = f.clone();
*f.inner.borrow_mut() = Some(f_);
f
}
}
impl fmt::Show for Foo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
drop(self.inner.borrow_mut().take()); // kill the Rc
// At this point, if the weak value did not use upgrade(), then the value will be
// dropped and &self will be an invalid reference. As a result, the following line
// will access deallocated memory (from the String).
write!(f, r#"Foo \{ s: {} \}"#, self.s.as_slice().escape_default())
}
}
fn main() {
let f = Foo::new("test Foo".to_string());
let f_ = f.downgrade();
drop(f);
println!("{}", f_.upgrade());
println!("{}", f_.upgrade());
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for writing it out. I don't quite understand why it wouldn't work without the upgrade()
if the current impl does the strong check, but I can take your word for it. I'm admittedly not an expert
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@forticulous because the drop happens after the strong check, i.e. the strong check starts at 1, but then goes to 0 inside the fmt
call in the else
branch.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@forticulous The current implementation only tests that it's alive before it does any work. But it then goes on to call a method on the value (value.fmt(f)
), and that method is capable of dropping the last outstanding strong reference to the value (which is precisely what my code does). When that happens, the value will immediately be dropped, even though it's in the middle of executing a method that has a valid &self
pointer. This breaks the guarantee that &
-references always refer to valid values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright I'm sold. Changes coming up
Changed var names in test |
|
This looks better. Though as described above, I'm still against making this change at all. |
I think that this change is invalid, as it could cause struct Evil {
val: RefCell<Option<Weak<Evil>>>
}
impl<T: fmt::Show> fmt::Show for Evil {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.val.borrow().fmt(f)
}
}
fn main() {
let a = Rc::new(Evil { val: RefCell::new(None) });
let b = a.clone().downgrade();
*a.val.borrow_mut() = Some(b);
println!("{}", a);
} While this code may seem odd, one of the motivations for |
Yeah i'm not so crazy about the change if it has to call |
I think |
@sfackler What use is there for implementing The only use-case I can think of is for deriving |
As discussed it doesn't seem worth it to add this |
Show impl for Weak and tweaked the Show impl for Rc for consistency