May dangle (Drop) bounds

we wish we could write where Box<#[may_dangle] T>: or where Self::Kind<#[may_dangle] 'a>:

this can currently be checked by doing something along the lines of:

struct Foo<'a> {
  x: ::std::cell::Cell<&'a Box<Foo<'a>>>,
}

fn check_may_dangle() {
    fn helper(
        _f: impl ::core::ops::Fn(
            for<'a> fn([&'a (); 0]) -> Box<Foo<'a>>,
            for<'a> fn(&'a Box<Foo<'a>>)
        )
    ) {
    }
    helper(|f, g| {
        let _foo: Box<Foo<'_>> = f([]);
        g(&_foo);
    });
}

// comment/uncomment to break/unbreak (try also with #[may_dangle]!)
impl<'a> Drop for Foo<'a> {
    fn drop(&mut self) {
        todo!();
    }
}

but it can't be done generically/in the type system, which is super annoying.

ideally it'd be just, y'know, something along the lines of

struct Foo<'a> {
  x: ::std::cell::Cell<&'a Box<Foo<'a>>>,
}

fn static_assert() where for<'a> Box<Foo<#[may_dangle] 'a>>: {}

I think you’re abusing notation here, and since you don’t really explain what you’re after, it makes your post quite confusing / difficult to understand. (I think I did get what you’re after, but only with the context of previous posts of y'all on selfref, having reviewed the code myself, and with my previous experience of reviewing and designing safety checks in ouroboros around drop safety).

#[may_dangle] is (AFAIK) only a concrete syntax used for the dropck_eye_patch feature, and in that context it appears in generic parameter lists of Drop impls. It allows opt-in to behaviour w.r.t. how a type interacts with the drop check that otherwise only happens in an automatically-inferred (but safe) way for types not automatically implementing Drop. The #[may_dangle] does only give partial information even in this context though; in the case of a genetic type parameter with #[may_dangle] T in the Drop impl, AFAIK the drop check behavior will depend on the struct/enum definition, too, and this is where things like “PhantomData<T> means that dropck knows/assumes that we own (and hence we’ll drop) a value of type T” become relevant.

(To illustrate the difference [I haven’t tested this yet to double-check]: For a struct Foo<T>, (1) if T is #[may_dangle] (or there’s no Drop) and T is not owned, then the lifetime 'a in Foo<Bar<'a>> can always dangle; (2) if we have the same case as the previous, except that T is owned in the struct e.g. via PhantomData<T>, then the lifetime 'a in Foo<Bar<'a>> may dangle if and only if 'a is a #[may_dangle]-like parameter of Bar; (3) if Foo implements Drop without #[may_dangle] (or otherwise has drop-glue involving T without #[may_dangle]), then the lifetime 'a in Foo<Bar<'a>> can never dangle.)

Nonetheless, probing certain dropck-related properties of a type doesn’t seem unreasonable, it’s just that the situation is certainly somewhat complex, as demonstrated in the previous paragraph. Furthermore, assuming your use case for this is self-referencing types, and you actually want the behavior as tested in the code example you gave, then you’re not even testing for this #[may_dangle]-esque property at all, in case the type Foo<'a> is covariant in 'a. I.e. with covariance, you can borrow Foo<'a> with &'b Foo<'a>, 'a: 'b, then coerce that into &'b Foo<'b> and that’s what g receives. But in fact, that’s okay, too for self-referencing types, you’re merely testing for whether its safe to construct a &'a Foo<'a> in the first place; whether that’s possible due to variance or due to no Drop implementations (or only #[may_dangle] ones) interfering with 'a is irrelevant for this check; in fact it’s quite the elegant check in this regard. It reminds me of the discussion I had starting here identifying that either covariance or no Drop impl would be fine for safety in the context of a self-referencing-types soundness issue, and later coming to the conclusion that generating code that only compiles if the borrowck+dropck is happy with safely creating a &'a Foo<'a> is a nice solution.

1 Like

Oh neat, didn't know this was a known solution to the problem. (not like we looked at ouroboros at all until we made the announcement post...)

Yes, you're right, that makes more sense. But then, these are still tied to #[may_dangle]-ness, somehow - adding #[may_dangle] to a custom Drop impl makes stuff compile. We don't know how else to talk about this.

We don't understand why our crate is the only one with true references-to-self, whereas every other crate only does references-to-fields. It doesn't seem to break anything. However, one thing that's worth noting is that we rely on !Unpin for soundness in our crate - mostly because *Cells aren't aliasable when every &Cell is behind an &mut Cell that gets &Cell-borrowed. But that's a different issue.

on Cells
// this is currently UB:

let x = unsafecell.get(); // x is *mut T
let y = unsafecell.get_mut(); // y is &mut T, invalidates x
*y = foo;
// y is dropped, *mut T is still invalid
*x // UB, despite not having concurrent &mut T's

// and that makes us sad.

we can also make a more complete example, using selfref:

#![feature(generic_associated_types)]

use std::cell::Cell;
use std::pin::Pin;

use pin_project::pin_project;

use unsound_selfref::*;

#[pin_project]
struct Foo {
    #[pin]
    x: Holder<'static, BarK>,
}

#[derive(Copy, Clone)]
struct Bar<'a> {
    this: Option<&'a Cell<Bar<'a>>>,
}

struct BarK;

opaque! {
    impl Opaque for BarK {
        type Kind<'a> = Cell<Bar<'a>>;
    }
}

impl Foo {
    fn foo(self: Pin<&mut Self>) {
        let holder_mut = self.project().x;
        holder_mut.as_ref().operate_in(operate_in_closure::<BarK, _, _>(|bar| bar.set(Bar { this: Some(bar.get_ref()) })));
    }
    fn bar(self: Pin<&mut Self>) {
        let holder_mut = self.project().x;
        holder_mut.as_ref().operate_in(operate_in_closure::<BarK, _, _>(|bar| {
            println!("{:p}", bar.get().this.unwrap().get().this.unwrap())
        }));
    }
}

fn main() {
    let mut foo = Box::pin(Foo { x: Holder::<BarK>::new_with(new_with_closure::<BarK, _>(|_| Cell::new(Bar { this: None }))) });
    foo.as_mut().foo();
    foo.as_mut().bar();
}

So we guess maybe we'd want something more along the lines of a built-in where Self::Kind<'self>, which somehow implies building a &'self Self::Kind<'self> and checking if that's valid? But 'self is currently unspecified, and we worry if this might conflict with possible proposals for 'self. (But maybe where for<'self> Self::Kind<'self> could be a syntax that doesn't conflict with other possible proposals of 'self?)

But yeah, we just wish we could have this in the type system in some way, being able to write trait Opaque where for<'self> Self::Kind<'self>: { type Kind<'a> where Self: 'a; } and ditch the macro. We guess that's not currently possible tho.