What is the Rust equivalent of C++'s shared_from_this?

Viewed 532

I have an object that I know that is inside an Arc because all the instances are always Arced. I would like to be able to pass a cloned Arc of myself in a function call. The thing I am calling will call me back later on other threads.

In C++, there is a standard mixin called enable_shared_from_this. It enables me to do exactly this

class Bus : public std::enable_shared_from_this<Bus>
{
....
   void SetupDevice(Device device,...)
   {
       device->Attach(shared_from_this());
   }
}

If this object is not under shared_ptr management (the closest C++ has to Arc) then this will fail at run time.

I cannot find an equivalent.

EDIT:

Here is an example of why its needed. I have a timerqueue library. It allows a client to request an arbitrary closure to be run at some point in the future. The code is run on a dedicated thread. To use it you must pass a closure of the function you want to be executed later.

use std::time::{Duration, Instant};
use timerqueue::*;
use parking_lot::Mutex;
use std::sync::{Arc,Weak};
use std::ops::{DerefMut};

// inline me keeper cos not on github
pub struct MeKeeper<T> {
    them: Mutex<Weak<T>>,
}

impl<T> MeKeeper<T> {
    pub fn new() -> Self {
        Self {
            them: Mutex::new(Weak::new()),
        }
    }
    pub fn save(&self, arc: &Arc<T>) {
        *self.them.lock().deref_mut() = Arc::downgrade(arc);
    }
    pub fn get(&self) -> Arc<T> {
        match self.them.lock().upgrade() {
            Some(arc) => return arc,
            None => unreachable!(),
        }
    }
}
// -----------------------------------

struct Test {
    data:String,
    me: MeKeeper<Self>,
}

impl Test {

    pub fn new() -> Arc<Test>{
        let arc = Arc::new(Self {
            me: MeKeeper::new(),
            data: "Yo".to_string()
        });
        arc.me.save(&arc);
        arc
    }
    
    fn task(&self) {
        println!("{}", self.data);
    }

    // in real use case the TQ and a ton of other status data is passed in the new call for Test
    // to keep things simple here the 'container' passes tq as an arg

    pub fn do_stuff(&self, tq: &TimerQueue) {
        // stuff includes a async task that must be done in 1 second

        //.....
        let me = self.me.get().clone();
        tq.queue(
            Box::new(move || me.task()),
            "x".to_string(),
            Instant::now() + Duration::from_millis(1000),
        );
    }


}

fn main() {
    // in real case (PDP11 emulator) there is a Bus class owning tons of objects thats
    // alive for the whole duration
    let tq = Arc::new(TimerQueue::new());
    let test = Test::new();
    test.do_stuff(&*tq);
    // just to keep everything alive while we wait
    let mut input = String::new();
    std::io::stdin().read_line(&mut input).unwrap();
}

cargo toml

[package]
name = "tqclient"
version = "0.1.0"
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
timerqueue = { git = "https://github.com/pm100/timerqueue.git" }
parking_lot = "0.11"
2 Answers

There is no way to go from a &self to the Arc that self is stored in. This is because:

  1. Rust references have additional assumptions compared to C++ references that would make such a conversion undefined behavior.
  2. Rust's implementation of Arc does not even expose the information necessary to determine whether self is stored in an Arc or not.

Luckily, there is an alternative approach. Instead of creating a &self to the value inside the Arc, and passing that to the method, pass the Arc directly to the method that needs to access it. You can do that like this:

use std::sync::Arc;

struct Shared {
    field: String,
}

impl Shared {
    fn print_field(self: Arc<Self>) {
        let clone: Arc<Shared> = self.clone();
        
        println!("{}", clone.field);
    }
}

Then the print_field function can only be called on an Shared encapsulated in an Arc.

having found that I needed this three times in recent days I decided to stop trying to come up with other designs. Maybe poor data design as far as rust is concerned but I needed it.

Works by changing the new function of the types using it to return an Arc rather than a raw self. All my objects are arced anyway, before they were arced by the caller, now its forced.

mini util library called mekeeper

use parking_lot::Mutex;
use std::sync::{Arc,Weak};
use std::ops::{DerefMut};


pub struct MeKeeper<T> {
    them: Mutex<Weak<T>>,
}

impl<T> MeKeeper<T> {
    pub fn new() -> Self {
        Self {
            them: Mutex::new(Weak::new()),
        }
    }
    pub fn save(&self, arc: &Arc<T>) {
        *self.them.lock().deref_mut() = Arc::downgrade(arc);
    }
    pub fn get(&self) -> Arc<T> {
        match self.them.lock().upgrade() {
            Some(arc) => return arc,
            None => unreachable!(),
        }
    }
}

to use it

pub struct Test {
    me: MeKeeper<Self>,
    foo:i8,
}

impl Test {
    pub fn new() -> Arc<Self> {
        let arc = Arc::new(Test {
            me: MeKeeper::new(),
            foo:42
        });
        arc.me.save(&arc);
        arc
    }
}

now when an instance of Test wants to call a function that requires it to pass in an Arc it does:

 fn nargle(){
     let me = me.get();
     Ooddle::fertang(me,42);// fertang needs an Arc<T>
 }

the weak use is what the shared_from_this does so as to prevent refcount deadlocks, I stole that idea.

The unreachable path is safe because the only place that can call MeKeeper::get is the instance of T (Test here) that owns it and that call can only happen if the T instance is alive. Hence no none return from weak::upgrade

Related