How to replicate C++ enable_shared_from_this behavior

While looking to translate some code from C++ to Rust I found that the use of enable_shared_from_this was omnipresent in the code.
In it there is a collection of objects that implement a given interface (that in itself extends enable_shared_from_this). Then those objects can get shared pretty much everywhere, including different threads. While trying to mimic this behavior I have reached a standing point and cannot seem to solve it.

Here is what I have so far (does not compile). I cannot seem to figure out what should be the signature of create_new and the BehaviorStorage::storage.

use std::sync::Arc;

// Cannot do 'trait Behavior: Clone'
trait Behavior {
    fn talk(&self) -> String;
}

// Structs need to clone themselves
struct Happy {}
// Structs need to clone themselves
struct Sad {}

impl Behavior for Arc<Happy> {
    fn talk(&self) -> String {
        let myself = self.clone();
        String::from("I am Happy")
    }
}

impl Behavior for Arc<Sad> {
    fn talk(&self) -> String {
        let myself = self.clone();
        String::from("I am Sad")
    }
}

struct BehaviorStorage<T: Behavior + Clone> {
    // Should store different types of behaviors and not lose the clone ability
    storage: Vec<T>,
}

impl<T> BehaviorStorage<T> where T: Behavior + Clone {
    fn create_new(&mut self, happy: bool) -> T {
        let behavior = if happy {
            Arc::new(Happy {})
        }
        else {
            Arc::new(Sad {})
        };
        behavior.talk();
        self.storage.push(Box::new(behavior.clone()));
        behavior
    }
}

fn main() {
    let my_storage = BehaviorStorage {
        storage: Vec::new(),
    };
    let behavior = my_storage.create_new(true);
    let behavior_clone = behavior.clone();
    // Send clone somewhere else, possibly another thread
}

(Playground)

Another solution I had in mind was using something like

#[derive(Clone)]
enum Behavior {
   Happy(Arc<Happy>),
   Sad(Arc<Sad>),
}

which can be stored simply as Vec<Behavior> and then implement the talk() method for the enum that calls the respective talk() method. But before I start going down that road is there anything I am missing here?
What would be the best approach here?

How many behaviors do you have? If it is a finite count, then you could do something like Arc<Behavior>

enum Behavior {
    Happy(Happy),
    Sad(Sad),
    // .. and more
}

If you have an unbounded number of behaviors, you can do something like

trait Behavior {
    // .. methods
}

struct Happy { ... }
impl Behavior for Happy { ... }

// more behavior impls

Then use dynamic dispatch Arc<dyn Behavior + Send + Sync> or Rc<dyn Behavior> for single-threaded applications.

struct BehaviorStorage {
    // Should store different types of behaviors and not lose the clone ability
    storage: Vec<Arc<dyn Behavior + Send + Sync>>,
}

impl BehaviorStorage {
    fn add_behavior<B: Behavior + Send + Sync>(&mut self, behavior: B) -> Arc<B> {
        let behavior = Arc::new(behavior);
        self.storage.push(behavior as _);
        behavior
    }

    fn create_new(&mut self, happy: bool) -> Arc<dyn Behavior + Send + Sync> {
        if happy {
                self.add_behavior(Happy { .. }) as _
        } else {
                self.add_behavior(Sad { .. }) as _
        }
    }
}
2 Likes

Using either of those approaches does not seem to replicate what is needed as I would lose the ability to do inner cloning like this:

impl Behavior for Arc<Happy> {
    fn talk(&self) -> String {
        let myself = self.clone();  // Cannot do it unless self implements Clone
        String::from("I am Happy")
    }
}

Problem with the dynamic dispatch is that I obviously cannot have any kind of trait bounds with Clone such as

// Not valid
trait Behavior: Clone {...}

or

// Not valid
struct BehaviorStorage {
    storage: Vec<dyn Behavior + Clone>,
}

If I do this

struct BehaviorStorage<B: Behavior + Clone> {
    storage: Vec<B>,
}

fn create_new<B: Behavior + Clone>(&mut self, happy: bool) -> B { ... }

I can still retain the self.clone() but then I'm no longer able to store different kinds of Behavior inside the Vec.

It seems that the enum with inner Arc might be the only solution?

Actually, because Arc is a valid "receiver" type, you can do

trait Behavior {
    fn talk(self: Arc<Self>);
}

struct Happy;

impl Behavior for Happy {
    fn talk(self: Arc<Self>) {
        let _ = self.clone();
        println!("Happy");
    }
}

fn main() {
    let happy = Arc::new(Happy);
    (happy.clone()).talk();
    let erased: Arc<dyn Behavior> = happy;
    erased.talk();
}

Unfortunately, this requires "+1 passing" everywhere (roughly, taking std::shared_ptr) rather than "+0 passing" where ref counts aren't immediately incremented.

Of course, you can always just do the (C++17 and up) shared_from_this pattern manually: store a sync::Weak<Self> member and upgrade that for shared_from_this.

(Note that it is not sound to go from &T to Arc<T>, ever even if you know T is allocated behind an Arc. This is because Arc is an internal mutability type (Arc::get_mut), and if you're derived from &T you still only have &T permissions (i.e. no mutation). My rc-borrow crate provides a type that provides a sound "+0" type.)

† disclaimer: no warranty, yada yada, and the current version actually doesn't do the sound thing, because it only does the sound thing when Arc::as_raw exists, a function that doesn't even exist on nightly yet. For stable it currently prefers the "actually +0" code, but now that std has fixed its own misuse of references for into_raw, I need to fix rc-borrow to do a sound "+1, -1" rather than an unsound "+0".

3 Likes

I did not know it could be a "receiver" type like that, neat!
Since in the use case I have most of the methods would end up internally doing the "+1 passing" you are mentioning it does not make much of a difference to me. So I will mark it as the solution.

I am however curious how would I go about making the shared_from_this pattern manually?
Tried this, and while it seems to work (I'm not very proficient in unsafe code so not sure), the ability to do the shared_from_self() is lost once the struct gets put inside a data structure.

trait Behavior {}

struct Happy {
    me: Weak<Happy>
}
impl Behavior for Happy {}

impl Happy {
    fn new() -> Arc<Happy> {
        let mut happy = Arc::new(Happy {me: Weak::new()});
        let happy_weak = Arc::downgrade(&happy);
        unsafe {
            let mut happy_mut = Arc::get_mut_unchecked(&mut happy);
            happy_mut.me = happy_weak;
        }
        happy
-    }

    fn shared_from_self(self: &Arc<Happy>) -> Arc<Happy> {
        self.me.clone().upgrade().unwrap()
    }
}

struct BehaviorStorage {
    storage: Vec<Arc<dyn Behavior>>,
}

fn main() {
    let mut my_storage = BehaviorStorage {
        storage: Vec::new(),
    };
    let behavior: Arc<Happy> = Happy::new();
    my_storage.storage.push(behavior.shared_from_self());
    let some_behavior: &Arc<dyn Behavior> = my_storage.storage.first().unwrap();
    some_behavior.shared_from_self(); // Lost my Happy type so cannot clone now
}

It does not seem possible to have the shared_from_self method in a trait, as the signature would have to be something like

trait SharedFromSelf {
   fn shared_from_self(&self) -> Arc<Self>;
}

which is not valid. Is there another approach possible?

EDIT: I'm a dumbass, after I take it out of the Vec I can just use normal clone instead of shared_from_self. Also apologies for the numerous edits.

Anyway, the new method seems safe, or am I missing something and will get horrible demons later?

That unsafe code is fine. Only because you are creating the Arc there and know there are no aliasing Arc, and the one Weak that you have is not dereferenced while you use the reference obtained from get_mut_unchecked.

But note: if you already have a reference to an Arc, you could just clone it. (It looks like you noticed this)

Finally, in BehaviorStorage, store Vec<Arc<dyn Behavior + Send + Sync>> otherwise you won't be able to use this across multiple threars. If you don't need multithreading, use Rc instead of Arc

1 Like

I'll be keeping an eye on get_mut_unchecked stability then as that means getting rid of the "+1 passing" here mentioned.

Yeah, I noticed it right after posting :sweat_smile:
I did not include the Send + Sync here, but I am aware I need those.

Thank you for the help

trait ArcAsRaw {
    type T;
    fn as_raw (this: &'_ Self) -> *const Self::T
    ;
}
impl<T> ArcAsRaw for Arc<T> {
    type T;
    fn as_raw (this: &'_ Arc<T>) -> *const T
    {
        Arc::into_raw(unsafe { <*const Arc<T>>::read(this) })
    }
}
2 Likes

You can also use self: &Arc<Self> to avoid incrementing ref count.

Using self: &Arc<Self> on a trait method is not allowed, which was why self: Arc<Self> has to be used.
It can certainly be done for struct methods though.

Actually, you can take self: &Arc<Self> in a trait method, it's just not Object Safe, which means that you cannot create a trait object if a method takes self by &Arc<Self>.

You can put the shared_from_self behavior on Behavior as well. I think this here is about as close as you're going to get to C++ enable_shared_from_this.

use std::sync::{Arc, Weak};

trait Behavior {
    fn shared_from_self(&self) -> Option<Arc<dyn Behavior + Send + Sync>>;
}

struct Happy {
    me: Weak<Happy>,
}

impl Behavior for Happy {
    fn shared_from_self(&self) -> Option<Arc<dyn Behavior + Send + Sync>> {
        self.shared_from_self()
            .map(|x| -> Arc<dyn Behavior + Send + Sync> { x })
    }
}

impl Happy {
    fn shared_from_self(&self) -> Option<Arc<Happy>> {
        self.me.clone().upgrade()
    }
    
    fn make_shared(self) -> Arc<Happy> {
        let mut this = Arc::new(self);
        let weak = Arc::downgrade(&this);
        unsafe {
            let mut this = Arc::get_mut_unchecked(&mut this);
            this.me = weak;
        }
        this
    }
}
2 Likes

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