How unsafe is my reference type?

OK, it took me some time, but I believe I may have something that would work and be safe without using closures (save for the visit/map method of Sc).

The solution I found involves three objects, not two like the previous solution.

The first object is Sc, and it is completely unchanged:

pub struct Sc<T>(Cell<Option<*const T>>);

The second object is our dropper, which is simpler than ever:

struct Dropper<'sc, T: 'sc> {
    sc: &'sc Sc<T>,
}

impl<'sc, T> Drop for Dropper<'sc, T> {
    fn drop(&mut self) {
        self.sc.0.set(None)
    }
}

(Dropper is not public anymore in this design)

Then, I introduce a third object that I call Locker:

pub struct Locker<'sc, 'auto, T: 'sc + 'auto> {
    sc: Option<Dropper<'sc, T>>,
    marker: Marker,
    autoref: Option<&'auto Marker>,
}

This object is self-referential, Locker::autoref will eventually reference Locker::marker.

Then, I replace the Sc::set method with the following Locker::lock method:

impl<'sc, 'auto, T> Locker<'sc, 'auto, T> {
    pub fn new() -> Self {
        Self {
            sc: None,
            marker: Marker,
            autoref: None,
        }
    }

    pub fn lock(&'auto mut self, val: &'auto T, sc: &'sc Sc<T>) {
        let ptr = val as *const T;
        sc.0.set(Some(ptr));
        self.sc = Some(Dropper { sc });
        self.autoref = Some(&self.marker);
    }
}

Because Locker becomes a self-borrowing struct after a call to Locker::lock, it is not possible to call mem::forget() on it (it is borrowed).

(for clarity, here is Sc's full impl block):


impl<T> Sc<T> {
    pub fn new() -> Self {
        Self { 0: Cell::new(None) }
    }

    unsafe fn get(&self) -> Option<&T> {
        self.0.get().map(|ptr| mem::transmute(ptr))
    }

    pub fn map<U, F: Fn(&T) -> U>(&self, f: F) -> Option<U> {
        unsafe { self.get().map(f) }
    }

    pub fn is_none(&self) -> bool {
        unsafe { self.get().is_none() }
    }
}

You can then use Locker in the following way:

fn it_works() {
    let sc = Sc::new();
    assert!(sc.is_none());
    {
        let s = String::from("foo");
        {
            let mut locker = Locker::new();
            locker.lock(&s, &sc);
            // cannot mem::forget(locker) here, because locker borrows itself
            // cannot mem::forget(s) either, because it is borrowed by locker
            assert!(!sc.is_none());
        }
        assert!(sc.is_none());
        {
            let mut locker = Locker::new();
            locker.lock(&s, &sc);
            assert!(!sc.is_none());
        }
        assert!(sc.is_none());
    }
    assert!(sc.is_none());
}

For convenience, I also provide a Wrapper type:

pub struct Wrapper<'sc, 'auto, T: 'sc + 'auto> {
    locker: Locker<'sc, 'auto, T>,
    data: T,
}

impl<'sc, 'auto, T> Wrapper<'sc, 'auto, T> {
    pub fn new(data: T) -> Self {
        Self {
            locker: Locker::new(),
            data,
        }
    }

    pub fn lock(this: &'auto mut Self, sc: &'sc Sc<T>) {
        this.locker.lock(&this.data, sc);
    }
}

// also impl Deref<Target=T> and DerefMut for Wrapper<T>

This enables the following:

#[test]
fn with_wrapper() {
    let sc = Sc::new();
    assert!(sc.is_none());
    {
        let mut s = Wrapper::new(String::from("bar"));
        {
            Wrapper::lock(&mut s, &sc);
            assert!(!sc.is_none());
        }
        // actually here we are still locked because a wrapper automatically locks for its whole lifetime
        assert!(!sc.is_none());
    }
    assert!(sc.is_none());
}

I pushed this as a branch on my repository (I only updated the Observer example, because I broke Sc with trait objects with this release).

What do you think?

2 Likes