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?