Help needed: self referential struct

I want to create a self referential struct. I came up with following toy program. Is this correct and safe? If not, could you please explain why and how to fix it? Thanks

use std::pin::Pin;

#[derive(Debug)]
struct A {
    s: String,
}

#[derive(Debug)]
struct B<'a> {
    w: Vec<&'a str>,
}

impl B<'_> {
    fn new(s: &str) -> B {
        B {
            w: s.split_whitespace().collect(),
        }
    }

    fn leak(self) -> usize {
        Box::into_raw(Box::new(self)) as _
    }
}

struct C {
    a: A,
    b: usize,
}

impl C {
    fn new(s: String) -> Pin<Box<Self>> {
        let a = A { s };
        let mut c = Box::new(C { a, b: 0 });
        let b = B::new(&c.a.s).leak();
        c.b = b;
        Pin::new(c)
    }

    fn a(&self) -> &A {
        &self.a
    }

    fn b(self: Pin<&Self>) -> Option<&B> {
        if self.b == 0 {
            None
        } else {
            Some(unsafe { &*(self.b as *const B) })
        }
    }

    fn update(self: Pin<&mut Self>, s: String) {
        unsafe { drop_raw(self.b) };
        let me = self.get_mut();
        me.a.s = s;
        me.b = B::new(&me.a.s).leak();
    }
}

impl Drop for C {
    fn drop(&mut self) {
        unsafe { drop_raw(self.b) }
    }
}

unsafe fn drop_raw(p: usize) {
    if p != 0 {
        Box::from_raw(p as *mut B);
    }
}

fn main() {
    let mut c = C::new(String::from("Hello world"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    c.as_mut().update(String::from("foo bar"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());
}

C should be marked Unpin !Unpin otherwise Pin does nothing. The rest looks fine. One thing though, don't use usize for type-erased poinnters, use *mut ()/*const (). This way your code is more clear.

edit: Should be !Unpin, not Unpin

I don't have the whole Pin concept internalized fully yet but I checked if the type C was Unpin using following fn and it is.

fn check_unpin<T: Unpin>(_: T) {}

fn main() {
    let mut c = C::new(String::from("Hello world"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    c.as_mut().update(String::from("foo bar"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    check_unpin(c);
}

Did you mean !Unpin?

Thanks for the advise regarding explicit *{mut|const} (). Updated:

use std::pin::Pin;
use std::ptr;

#[derive(Debug)]
struct A {
    s: String,
}

#[derive(Debug)]
struct B<'a> {
    w: Vec<&'a str>,
}

impl B<'_> {
    fn new(s: &str) -> B {
        B {
            w: s.split_whitespace().collect(),
        }
    }

    fn leak(self) -> *mut () {
        Box::into_raw(Box::new(self)) as _
    }
}

struct C {
    a: A,
    b: *mut (),
}

impl C {
    fn new(s: String) -> Pin<Box<Self>> {
        let a = A { s };
        let mut c = Box::new(C {
            a,
            b: ptr::null_mut(),
        });
        let b = B::new(&c.a.s).leak();
        c.b = b;
        Pin::new(c)
    }

    fn a(&self) -> &A {
        &self.a
    }

    fn b<'a>(self: Pin<&'a Self>) -> Option<&'a B> {
        if self.b.is_null() {
            None
        } else {
            Some(unsafe { &*(self.b as *const B) })
        }
    }

    fn update(self: Pin<&mut Self>, s: String) {
        unsafe { drop_raw(self.b) };
        let me = self.get_mut();
        me.a.s = s;
        me.b = B::new(&me.a.s).leak();
    }
}

impl Drop for C {
    fn drop(&mut self) {
        unsafe { drop_raw(self.b) }
    }
}

unsafe fn drop_raw(p: *mut ()) {
    if !p.is_null() {
        Box::from_raw(p as *mut B);
    }
}

fn check_unpin<T: Unpin>(_: T) {}

fn main() {
    let mut c = C::new(String::from("Hello world"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    c.as_mut().update(String::from("foo bar"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    check_unpin(c);
}

Yes, sorry. I meant !Unpin. You can do this by making a field with the type std::marker::PhantomPinned.

The reason being, I can do this if your type is Unpin

fn main() {
    let mut c1 = C::new(String::from("Hello world"));
    let mut c2 = C::new(String::from("Hello world"));
    
    std::mem::swap::<C>(&mut c1, &mut c2);
    
    drop(c2);
    let c = c1;

    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    c.as_mut().update(String::from("foo bar"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    check_unpin(c);
}

And now we have a problem. Because you have a double free, using only the safe interface you provided.

I updated it:

use std::marker::PhantomPinned;
use std::pin::Pin;
use std::ptr;

#[derive(Debug)]
struct A {
    s: String,
}

#[derive(Debug)]
struct B<'a> {
    w: Vec<&'a str>,
}

impl B<'_> {
    fn new(s: &str) -> B {
        B {
            w: s.split_whitespace().collect(),
        }
    }

    fn leak(self) -> *mut () {
        Box::into_raw(Box::new(self)) as _
    }
}

struct C {
    a: A,
    b: *mut (),
    _marker: PhantomPinned,
}

impl C {
    fn new(s: String) -> Pin<Box<Self>> {
        let a = A { s };
        let mut c = Box::pin(C {
            a,
            b: ptr::null_mut(),
            _marker: PhantomPinned,
        });
        let c_mut = unsafe { c.as_mut().get_unchecked_mut() };
        let b = B::new(&c_mut.a.s).leak();
        c_mut.b = b;
        c
    }

    fn a(&self) -> &A {
        &self.a
    }

    fn b<'a>(self: Pin<&'a Self>) -> Option<&'a B> {
        if self.b.is_null() {
            None
        } else {
            Some(unsafe { &*(self.b as *const B) })
        }
    }

    fn update(self: Pin<&mut Self>, s: String) {
        unsafe { drop_raw(self.b) };
        let me = unsafe { self.get_unchecked_mut() };
        me.a.s = s;
        me.b = B::new(&me.a.s).leak();
    }
}

impl Drop for C {
    fn drop(&mut self) {
        unsafe { drop_raw(self.b) }
    }
}

unsafe fn drop_raw(p: *mut ()) {
    if !p.is_null() {
        Box::from_raw(p as *mut B);
    }
}

fn main() {
    let mut c = C::new(String::from("Hello world"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());

    c.as_mut().update(String::from("foo bar"));
    println!("{:?}", c.as_ref().a());
    println!("{:?}", c.as_ref().b());
}

Is this correct?

Looks good to me

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.