Hello, most interest for unleakable and undroppable types is for borrowing guards, that are preventing use of some resource until guard is dropped/consumed, not to actually "run some code", either in Drop
impl or consumer of linear type.
Example: spawn_unchecked can become safe function if it would join
in Drop
of JoinHanlde
.
My point is that only functions of like T -> ()
can be a threat, when T
is not in the output of the signature.
Here is extension of classic code for safe 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());
}
struct Foo<'a>(&'a mut i32);
impl Drop for Foo<'_> {
fn drop(&mut self) {
unreachable!()
}
}
fn main() {
let mut value = 32;
forget(Foo(&mut value));
println!("{}", value);
}
My observation is that problem is not only in Rc
, but in a signarute of forget
itself - it takes T
by value and "removes" the borrow. It expresses that after function with signature T -> ()
any borrows is removed.
If function has T -> T
like signature, then borrow checker will extend lifetime further, preventing any unsound usage. As T
is the same type, then lifetimes are also the same. If constructors of T
are private, then library author gains more control, as described later.
In the following example we are actually forgetting T
, but this is sound, because borrow checker will stop us from unsound activity (accessing value
in that case, or some protected data that are worked with in other thread/c code):
fn sound_forget<T>(val: T) -> T {
forget(val);
std::process::abort()
}
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());
}
struct Foo<'a>(&'a mut i32);
impl Drop for Foo<'_> {
fn drop(&mut self) {
unreachable!()
}
}
fn main() {
let mut value = 32;
let foo = sound_forget(Foo(&mut value));
println!("{}", value);
forget(foo);
}
So, maybe we can make some progress by disallowing use of unleakable types in a signatures that "remove" them, except drop
itself, for example implicit one? This is not a full solution of course, but it can help in creating sound signatures...
The only thing that breaks it, as far as I can see, is unwinding - one can have a function with signature T -> T
, panic, then unwind to the point where no T
is present and violate soundness. But it is probably possible for compiler to support in only in unwind = abort
mode - a lot of people do not need unwinding anyway.
What do you think about it?