You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
mem::forget is unsafe, but can be written in entirely safe code #24456
There were other ways to forget even before Rc, at least in some cases. For example, if T:Send holds, you could send the value to a thread that runs an infinite loop, or which is deadlocked on a port.
It is true that one cannot assume that a destructor will run, and hence that forget is not itself unsafe (rather, it is unsafe to write a dtor that must run). This is what happened with std::thread::JoinGuard (and scoped) are unsound because of reference cycles #24292. You can sidestep this by writing destructors that must run but never giving up ownership of the value with the destructor. This often involves some closures.
It'd be nice if we could use RAII as we initially wanted to. So I suspect we may in the future work on a solution for identifying types whose destructors must run and preventing them from being placed into Rc. (Some kind of trait, most likely.)
If we did step 2, then we would probably want forget to continue to "forget" the values in question -- which would mean that forget would become unsafe again. So that argues in favor of leaving it as unsafe for now -- we can always make it safe later if we so choose.
@nikomatsakis My concern is that (3) couldn't be done backwards-compatibly, at least not without making it unsafe to create a new type that leaks like Rc does. Doing it now would subsume all four points (except point [1], but as I mentioned earlier Rust is a long way off from providing guarantees of totality or anything crazy like that, so it will be sufficient in practice).
If you have T: 'static, then you can move it into a thread-local Vec<Box<SomeTrait>> (with an impl<T:?Sized> SomeTrait for T {}) or something and lose it, so forget is only "interesting" on lifetime-bounded traits, where it allows the lifetime to end without the destructor running.
A safe mem::forget has the advantage that it makes it easier to write the counterexample proving thread::JoinGuard unsafe. Safe mem::forget makes it more likely that people will know that destructors are not realiable, so they can avoid repeating this mistake.
we can always make it safe later if we so choose.
Making an unsafe function safe is a (minor) breaking change (fn(T) is not a subtype of unsafe fn(T)).
I commented on #24292, but the gist is that there are multiple ways to leak memory today (e.g. #14875 and #16135), so a targeted solution at Rc may not cover all use cases. Although as I mention in #24292 these other bugs can also be considered separate bugs on their own which need to be fixed regardless (but sometimes is quite difficult to do so).
By the way, is it okay to leak references through Rc-powered forget?
fn forget<T>(val: T) {
use std::cell::RefCell;
use std::rc::Rc;
struct Foo<T>(T, RefCell<Option<Rc<Foo<T>>>>);
let x = Rc::new(Foo(val, RefCell::new(None)));
*x.1.borrow_mut() = Some(x.clone());
}
fn main() {
{
let mut a = 10;
forget(&mut a); // Leak the reference to `a`
let b = &mut a; // Now we have two mut references to `a`, one here and one somewhere
}
// The dangling reference to `a` still lives somewhere. Can it be made accessible without `unsafe`?
}
@reem the idea was to funnel conversation over to the RFC. A single comment in the middle of a thread won't do that.
(To be clear: this was was the outcome of the team triage, since the issue had been nominated; it was was a group decision. My initial instinct had been to let it sit for a week with the nomination, but was persuaded by arguments to close in favor of the RFC.)
Activity
bstrie commentedon Apr 15, 2015
Nominating for 1.0 because some men just want to watch the world burn.
theemathas commentedon Apr 15, 2015
This is one of the root causes of #24292
elinorbgr commentedon Apr 15, 2015
Hmm... I was trying to understand this example, but it doesn't compile:
My first guess was to wrap
x.clone()intoSome(x.clone())so that the types matches, but it doesn't compile either:retep998 commentedon Apr 15, 2015
Here is a working example:
nikomatsakis commentedon Apr 15, 2015
My current thinking:
T:Sendholds, you could send the value to a thread that runs an infinite loop, or which is deadlocked on a port.forgetis not itself unsafe (rather, it is unsafe to write a dtor that must run). This is what happened with std::thread::JoinGuard (and scoped) are unsound because of reference cycles #24292. You can sidestep this by writing destructors that must run but never giving up ownership of the value with the destructor. This often involves some closures.Rc. (Some kind of trait, most likely.)forgetto continue to "forget" the values in question -- which would mean thatforgetwould become unsafe again. So that argues in favor of leaving it asunsafefor now -- we can always make it safe later if we so choose.Kimundi commentedon Apr 15, 2015
@nikomatsakis Did you mean "4. If we did step 3, [...]" rather than "4. If we did step 2, [...]"?
pythonesque commentedon Apr 15, 2015
@nikomatsakis My concern is that (3) couldn't be done backwards-compatibly, at least not without making it unsafe to create a new type that leaks like
Rcdoes. Doing it now would subsume all four points (except point [1], but as I mentioned earlier Rust is a long way off from providing guarantees of totality or anything crazy like that, so it will be sufficient in practice).arielb1 commentedon Apr 15, 2015
@nikomatsakis
If you have
T: 'static, then you can move it into a thread-localVec<Box<SomeTrait>>(with animpl<T:?Sized> SomeTrait for T {}) or something and lose it, soforgetis only "interesting" on lifetime-bounded traits, where it allows the lifetime to end without the destructor running.dgrunwald commentedon Apr 15, 2015
A safe
mem::forgethas the advantage that it makes it easier to write the counterexample provingthread::JoinGuardunsafe. Safemem::forgetmakes it more likely that people will know that destructors are not realiable, so they can avoid repeating this mistake.Making an unsafe function safe is a (minor) breaking change (
fn(T)is not a subtype ofunsafe fn(T)).We might want two functions:
alexcrichton commentedon Apr 15, 2015
I commented on #24292, but the gist is that there are multiple ways to leak memory today (e.g. #14875 and #16135), so a targeted solution at
Rcmay not cover all use cases. Although as I mention in #24292 these other bugs can also be considered separate bugs on their own which need to be fixed regardless (but sometimes is quite difficult to do so).alexcrichton commentedon Apr 16, 2015
I have created an RFC for marking
forgetas a safe function.petrochenkov commentedon Apr 16, 2015
By the way, is it okay to leak references through
Rc-powered forget?pnkfelix commentedon Apr 16, 2015
closing this issue to encourage all further discussion of this matter to happen on the RFC thread, see rust-lang/rfcs#1066
reem commentedon Apr 16, 2015
@pnkfelix seems weird to close the issue if it's not fixed, how about just leaving the comment?
pnkfelix commentedon Apr 17, 2015
@reem the idea was to funnel conversation over to the RFC. A single comment in the middle of a thread won't do that.
(To be clear: this was was the outcome of the team triage, since the issue had been nominated; it was was a group decision. My initial instinct had been to let it sit for a week with the nomination, but was persuaded by arguments to close in favor of the RFC.)