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
}
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?
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 knowT 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".
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
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
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
}
}