Drop wont allow me to look back into parent

Hi

I am trying to wrap a C library with Rust, so far I made a successful run, but I tried to be more idiomatic and so destroy the internal C resources upon Drop, but now it is giving me headaches.

The internal resources can be represented as a tree, we have a main resource Father that is a
factory of Child objects that in turn are factories of other objects, etc. . We obtain Father's from a singleton factory of Father's.

The catchy part is that the FFI requires a reference to the Father in order to destroy a Child

So far this has been my design

pub struct FatherFactory {
    pub(crate) inner: *mut crate::raw::FatherFactory,
}

impl FatherFactory {
    pub fn get_instance() -> Option<Self> {
        unsafe {
            let ff = crate::raw::FatherFactory_get_instance();
            if ff.is_null() {
                None
            } else {
                Some(Self { inner: ff })
            }
        }
    }
    ...
    
    pub fn create_father(
        &mut self,
        father_id: u32,
    ) -> Result<Father, ErrorCode> {
        unsafe {
            let rawfather = crate::raw::FatherFactory_create_father(
                self.inner,
                father_id,
            );

            if rawfather.is_null() {
                Err(ErrorCode::Err)
            } else {
                let father = Father::new(FatherInner {
                    raw: FatherRaw {
                        parent: self.inner,
                        me: rawfather,
                    },
                    childs: Default::default(),
                });
                Ok(father)
            }
        }
}

pub(crate) struct FatherRaw {
    pub(crate) me: *mut crate::raw::Father,
    pub(crate) parent: *mut crate::raw::FatherFactory, 
}


pub(crate) struct FatherInner {
    pub(crate) childs: [Option<Arc<RwLock<ChildInner>>>; crate::MAX_CHILDS_PER_FATHER],
    pub(crate) raw: FatherRaw,
}

pub struct Father {
    pub(crate) inner: Arc<RwLock<FatherInner>>,
}


impl Father {
    pub(crate) fn new(inner: FatherInner) -> Self {
        Self {
            inner: Arc::new(RwLock::new(inner)),
        }
    }
    ...
    
    pub fn create_child(
        &mut self,
        child_name: &str,
    ) -> Result<Child, ErrorCode> {
        unsafe {
            let rawchild = crate::raw::Father_create_child(
                self.inner.read().unwrap().raw.me,
                child_name.as_ptr() as *const _,
            );
            if rawchild.is_null() {
                Err(ErrorCode::Err)
            } else {
                let maybeidx = self
                    .inner
                    .read()
                    .unwrap()
                    .childs
                    .iter()
                    .position(|x| x.is_none());
                if let Some(idx) = maybeidx {
                    let inner = Arc::new(RwLock::new(ChildInner {
                        _super: Arc::downgrade(&self.inner),
                        raw: ChildRaw {
                            parent: self.inner.read().unwrap().raw.me,
                            me: rawchild,
                        },
                    }));
                    let child = Child {
                        inner: Arc::downgrade(&inner),
                    };

                    self.inner.write().unwrap().childs[idx] = Some(inner);

                    Ok(child)
                } else {
                    Err(ErrorCode::NoResources)
                }
            }
        }
    }
}

pub(crate) struct ChildRaw {
    pub(crate) me: *mut crate::raw::Child,
    pub(crate) parent: *mut crate::raw::Father, 
}

pub(crate) struct ChildInner {
    pub(crate) _super: Weak<RwLock<FatherInner>>,
    pub(crate) raw: ChildRaw,
}

pub struct Child {
    pub(crate) inner: Weak<RwLock<ChildInner>>,
}

When a drop of the Father happens I need to drop first the Child, hence the FatherRaw structure that allows me to defer the disposal of the father until the 1st field of FatherInner is dropped. While this code works I dont like how it looks, I dont like having two exactly same *mut crate::raw::Father (i.e. FatherRaw.me and ChildRaw.parent) I'd rather have a reference in the Child to get the raw info from the Father.

I already tried using Arc and Weak to get this info, but the Father is destroyed before the drop of the Child happens, so if I have a Weak to the Father it will fail to be upgraded.

I am not sure how to workaround this, ideally without using unsafe code / raw pointers.

Thanks

The Father should eagerly destroy the Childs in its Drop using a fn destroy(self, *mut raw::Father).

Thanks for your response and time. That wouldn't work for two reasons:

  1. I can't have a self on any Child on the Father's drop, as the drop signature is fn drop(&mut self). If it were fn drop(self) I imagine issues with double drops and stuff
  2. I forgot to add that there are certain use cases where a Child must be disposed without the Father being disposed. In this case the Weak back to the Father has a lot of sense as the Father is still alive and can provide the raw ptr information

I created some playground links for better understanding:

  1. Version that works using copies of raw pointers

  2. Version that uses references to parent and doesn't work since the parent is already gone

In 1 the ChildRaw is defined as

pub(crate) struct ChildRaw {
    pub(crate) me: *mut crate::raw::Child,
    pub(crate) parent: *mut crate::raw::Father,
}

whereas in 2 is defined as

pub(crate) struct ChildRaw {
    pub(crate) _super: Option<Weak<RwLock<ChildInner>>>,
    pub(crate) me: *mut crate::raw::Child,
}

Basically I'm saying turn everything into a case of #2.

1 Like

The approach I've taken in the past is to use reference-counted pointers and some sort of inner struct which actually owns the FFI resource and implements Drop.

Here is an example of the rough pattern:

struct Father(Rc<FatherState>);

struct FatherState {
  ptr: *mut crate::ffi::Father,
}

impl Drop for FatherState {
  fn drop(&mut self) {
    unsafe { crate::ffi::father_destroy(self.ptr); }
  }
}

Then, whenever you create a child component, store a copy of the parent's reference-counted state so you still have access to it.

impl Father {
  fn create_child(&mut self) -> Child {
    let father = Rc::clone(&self.0);
    let ptr = unsafe { crate::ffi::create_child(father.0.ptr) };
    Child { ptr, father }
  }
}

struct Child {
  ptr: *mut crate::ffi::Child, // This could be Rc<ChildState> if necessary
  father: Rc<FatherState>,
}

impl Drop for Child {
  fn drop(&mut self) {
    unsafe { crate::ffi::free_child(self.father.0.ptr, self.ptr); }
  }
}

By storing a Rc<FatherState> in every child, it means the Child's drop implementation can't accidentally use the Father after you've dropped it.

I'd also drop the RwLock and all of that. Use &self and &mut self at the very top layer (i.e. public methods on Father and Child) so the borrow checker ensures users use things correctly, but internally you should switch to raw pointers and FFI operations. Try to keep your Rust wrapper as thin as possible because it's easy to over-complicate things and blur the boundaries between safe Rust with unsafe Rust or unsafe C.

It's also possible to use a &'a Father or &'a mut Father reference instead of Rc<FatherState>, but if Child is a long-lived object this becomes a massive pain due to how viral lifetimes can be. You may be tempted to venture into self-referential struct territory (e.g. if you want to package Father up with its children so everything is easy to manage), and storing a &mut Father reference in Child means you can't do anything with father until that Child is dropped.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.