Problem accessing a parent node within a closure

Hi everyone,

I've a code like below, where there are getter closures for accessing a value from a (context) structure.
The structure can contain a smart pointer to a parent struct and I'm having a trouble
finding a way to access the parent's value passing a child struct as a reference to a closure.

Any ideas what kind of a solution would work here?

Thanks a lot for any suggestions! :slight_smile:

use std::sync::Arc;
use parking_lot::{Mutex, MutexGuard, MappedMutexGuard};
use std::borrow::Cow;

#[derive(Debug)]
pub struct Ctx(pub Arc<Mutex<CtxInner>>);

impl Ctx {
    pub fn new() -> Self {
        let parent_ctx = Ctx(Arc::new(Mutex::new(CtxInner {
            parent: None,
            val: "Parent".into()
        })));
        Ctx(Arc::new(Mutex::new(CtxInner {
            parent: Some(parent_ctx),
            val: "Child".into()
        })))
    }

    pub fn inner_ctx(&self) -> CtxRef<'_> {
        let guard = self.0.lock();
        MutexGuard::map(guard, |ctx| ctx)
    }
}

type CtxRef<'c> = MappedMutexGuard<'c, CtxInner>;

#[derive(Debug)]
pub struct CtxInner {
    pub parent: Option<Ctx>,
    pub val: String
}

impl CtxInner {
    pub fn parent_inner_ctx(&self) -> Option<MappedMutexGuard<'_, CtxInner>> {
        if let Some(parent_ctx) = &self.parent {
            let guard = parent_ctx.0.lock();
            Some(MutexGuard::map(guard, |ctx| ctx))
        } else {
            None
        }
    }
}

pub enum CtxValueRef<'c> {
    Str(&'c str)
}

impl<'c> CtxValueRef<'c> {
    pub fn as_str(&self) -> Cow<'_, str> {
        match self {
            CtxValueRef::Str(s) => Cow::Borrowed(s),
        }
    }
}

#[derive(Clone)]
pub struct Getter(Arc<dyn for<'c> Fn(&'c CtxRef) -> Option<CtxValueRef<'c>> + 'static + Send + Sync>);

impl Getter
{
    pub fn new(closure: impl for<'c> Fn(&'c CtxRef) -> Option<CtxValueRef<'c>> + 'static + Send + Sync) -> Self
    {
        Self(Arc::new(closure))
    }

    pub fn execute<'c>(&self, ctx: &'c CtxRef) -> Option<CtxValueRef<'c>> {
        self.0(ctx)
    }
}

fn main() {
    let child_ctx = Ctx::new();

    let get_value = Getter::new(|ctx| -> Option<CtxValueRef> {
        Some(CtxValueRef::Str(ctx.val.as_str()))
    });
    let access_parent = Getter::new(move |ctx| -> Option<CtxValueRef> {
        get_value.execute(&ctx.parent_inner_ctx()?)
    });

    let child_ctx_ref = child_ctx.inner_ctx();
    let parent_name = access_parent.execute(&child_ctx_ref);
    if let Some(name) = parent_name {
        println!("{:?}", name.as_str());
    } else {
        println!("<no value>");
    }
}

The problem with the code above is

error[E0515]: cannot return value referencing temporary value
  --> src/main.rs:79:9
   |
79 |         get_value.execute(&ctx.parent_inner_ctx()?)
   |         ^^^^^^^^^^^^^^^^^^^-----------------------^
   |         |                  |
   |         |                  temporary value created here
   |         returns a value referencing data owned by the current function

For more information about this error, try `rustc --explain E0515`.

If you fix the immediate error, you get a more pertinent one:

     let access_parent = Getter::new(move |ctx| -> Option<CtxValueRef> {
-        get_value.execute(&ctx.parent_inner_ctx()?)
+        let pic = ctx.parent_inner_ctx()?; // a MutexGuard of some sort
+        get_value.execute(&pic) // a bare reference of some sort
     });

:arrow_right:

error[E0515]: cannot return value referencing local variable `pic`
  --> src/main.rs:80:9
   |
80 |         get_value.execute(&pic) // a bare reference of some sort
   |         ^^^^^^^^^^^^^^^^^^----^
   |         |                 |
   |         |                 `pic` is borrowed here
   |         returns a value referencing data owned by the current function

More specifically, you can't have a bare reference to the inside of the Mutex that outlives your MutexGuard. If you need to return a reference in some form, you'll need to return some type of guard, which the caller could use to get a reference. (But perhaps a larger rethink is in order; you don't want to lock your mutex for long periods generally.)

1 Like