Storing data borrowed out of MutexGuard

There are types FooWrapper and BarWrapper wrapping around remote types Foo and Bar. Foo has a method bar that creates a new instance of Bar which borrows from itself. The FooWrapper does the same for BarWrapper. The implementation is straightforward so far:

struct FooWrapper {
    foo: Foo,
}

impl FooWrapper {
    fn new() -> Self {
        Self {
            foo: Foo::new(),
        }
    }

    fn bar(&self) -> BarWrapper {
        BarWrapper {
            bar: self.foo.bar(),
        }
    }
}

struct BarWrapper<'a> {
    bar: Bar<'a>,
}

struct Foo {
    value: u32,
}

impl Foo {
    fn new() -> Self {
        Self {
            value: 0,
        }
    }

    fn bar(&self) -> Bar {
        Bar {
            value: &self.value,
        }
    }
}

struct Bar<'a> {
    value: &'a u32,
}

fn main() {
    let foo = FooWrapper::new();
    let bar = foo.bar();
    println!("{}", bar.bar.value)
}

However, it gets more complicated if FooWrapper and BarWrapper are using a std::sync::Mutex to be able to be shared between threads. Here's FooWrapperMutex and BarWrapperMutex:

use std::sync::Mutex;

struct FooWrapperMutex {
    foo: Mutex<Foo>,
}

impl FooWrapperMutex {
    fn new() -> Self {
        Self {
            foo: Mutex::new(Foo::new()),
        }
    }

    fn bar(&self) -> BarWrapperMutex {
        let foo = self.foo.lock().unwrap();
        BarWrapperMutex {
            bar: foo.bar(),
        }
    }
}

struct BarWrapperMutex<'a> {
    bar: Bar<'a>,
}

This won't work, because the instance of BarWrapperMutex created in bar would outlive the acquired std::sync::MutexGuard. Storing the MutexGuard in BarWrapperMutex won't work either, because self-referential structs are not allowed.

How can I solve this properly?

The parking-lot crate provides mutex guard mapping for this use-case.

2 Likes

If you don't want to (or can't) use parking-lot, you can wrap the guard:

impl FooWrapperMutex {
    fn new() -> Self {
        Self {
            foo: Mutex::new(Foo::new()),
        }
    }

    fn lock(&self) -> FooGuard {
        FooGuard(self.foo.lock().unwrap())
    }
}

struct FooGuard<'a>(MutexGuard<'a, Foo>);

impl<'a> FooGuard<'a> {
    fn bar(&self) -> BarWrapper {
        BarWrapper { bar: self.0.bar() }
    }
}

fn main() {
    let foo = FooWrapperMutex::new();
    let lock = foo.lock();
    let bar = lock.bar();
    println!("{}", bar.bar.value)
}

Another alternative is to keep the mutex separate from the wrappers. This simplifies the wrappers and gives the user the choice of:

  • Which type of mutex to use
  • How much the mutex protects. e.g. maybe the user needs to keep thread-shared state consistent with Foo, so needs to keep that state under the same mutex.
fn main() {
    let foo = Mutex::new(FooWrapper::new());
    let lock = foo.lock().unwrap();
    let bar = lock.bar();
    println!("{}", bar.bar.value)
}

Thanks for your replies.

Mutex guard mapping seems ideal, but for now I'd like to stay within std if possible.

Making a wrapper for the guard works in this case and makes sense. However, I have a more specific case: FooWrapperMutex and BarWrapperMutex are specific implementations of traits AWrapper and BWrapper that just so happen to need locking because of their usage of Foo and Bar:

use std::sync::Mutex;

trait AWrapper {
    type BWrapper<'a> where Self: 'a;

    fn b(&self) -> Self::BWrapper<'_>;
}

trait BWrapper {
    fn value(&self) -> u32;
}

struct FooWrapperMutex {
    foo: Mutex<Foo>,
}

impl FooWrapperMutex {
    fn new() -> Self {
        Self {
            foo: Mutex::new(Foo::new()),
        }
    }
}

impl AWrapper for FooWrapperMutex {
    type BWrapper<'a> = BarWrapperMutex<'a>;

    fn b(&self) -> BarWrapperMutex {
        let foo = self.foo.lock().unwrap();
        BarWrapperMutex {
            bar: foo.bar(),
        }
    }
}

struct BarWrapperMutex<'a> {
    bar: Bar<'a>,
}

impl BWrapper for BarWrapperMutex<'_> {
    fn value(&self) -> u32 {
        *self.bar.0
    }
}

struct Foo(u32);

impl Foo {
    fn new() -> Self {
        Self(0)
    }

    fn bar(&self) -> Bar {
        Bar(&self.0)
    }
}

struct Bar<'a>(&'a u32);

fn main() {
    let a = FooWrapperMutex::new();
    let b = a.b();
    println!("{}", b.value())
}

Because of this, I can't make locking a part of the API.

Sorry I didn't mention that, I just thought it's not important for a minimal example. Sorry for the naming too, it starts getting more confusing now because I didn't think there will be a need to expand on it...

Same thing goes for a separate mutex - it makes sense in this case, but does not work for the more specific one.

I'm going to make a guess on your ultimate goal: create a set of traits which mirror object relationships whether they use locking or not. If this is accurate, then you probably need to model guards in your traits.

If your goal is different, create set of objects which implement locking without exposing that locking to the traits, then you might be stuck.

use std::sync::{Mutex, MutexGuard};

trait AWrapper {
    type AWrapperGuard<'a>: AWrapperGuard
    where
        Self: 'a;

    fn lock(&self) -> Self::AWrapperGuard<'_>;
}

trait AWrapperGuard {
    type BWrapper<'a>: BWrapper
    where
        Self: 'a;

    fn b(&self) -> Self::BWrapper<'_>;
}

trait BWrapper {
    fn value(&self) -> u32;
}

struct FooWrapperMutex {
    foo: Mutex<Foo>,
}

impl FooWrapperMutex {
    fn new() -> Self {
        Self {
            foo: Mutex::new(Foo::new()),
        }
    }
}

impl AWrapper for FooWrapperMutex {
    type AWrapperGuard<'a> = FooWrapperMutexGuard<'a>;

    fn lock(&self) -> Self::AWrapperGuard<'_> {
        FooWrapperMutexGuard(self.foo.lock().unwrap())
    }
}

struct FooWrapperMutexGuard<'a>(MutexGuard<'a, Foo>);

impl<'a> AWrapperGuard for FooWrapperMutexGuard<'a> {
    type BWrapper<'b> = BarWrapperMutex<'b> where Self:'b;

    fn b(&self) -> Self::BWrapper<'_> {
        BarWrapperMutex { bar: self.0.bar() }
    }
}

struct BarWrapperMutex<'a> {
    bar: Bar<'a>,
}

impl<'a> BWrapper for BarWrapperMutex<'a> {
    fn value(&self) -> u32 {
        *self.bar.0
    }
}

struct Foo(u32);

impl Foo {
    fn new() -> Self {
        Self(0)
    }

    fn bar(&self) -> Bar {
        Bar(&self.0)
    }
}

impl AWrapper for Foo {
    type AWrapperGuard<'a> = FooGuard<'a>;

    fn lock(&self) -> Self::AWrapperGuard<'_> {
        FooGuard(self)
    }
}

struct FooGuard<'a>(&'a Foo);

impl<'a> AWrapperGuard for FooGuard<'a> {
    type BWrapper<'b> = Bar<'b> where Self:'b;

    fn b(&self) -> Self::BWrapper<'_> {
        self.0.bar()
    }
}

struct Bar<'a>(&'a u32);

impl<'a> BWrapper for Bar<'a> {
    fn value(&self) -> u32 {
        *self.0
    }
}

fn test<'a, T: AWrapper>(a: &'a T) {
    let guard = a.lock();
    let b = guard.b();
    println!("{}", b.value())
}

fn main() {
    test(&Foo::new());
    test(&FooWrapperMutex::new());
}

Your guess is correct, I'm not simply implementing some traits, I am designing them, and they are supposed to work for both cases, locking or not. I'll mark this as solved, thanks for your help.

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.