2
votes

I'd like to have a field in struct like this:

struct Foo<T> {
    bar: Smart<T>
}

where bar could be either Rc<T or Weak<T>, depending on the "ownership relationships" between different instances of Foo. Is there any idiomatic way in Rust how to do this, other than to create a custom enum?

3
I don't think that such a type exist, but I'm not sure that it is a good idea: weak vs rc allows to model an ownership relation. For example, a parent owns a child (Rc<Child>) while the child points to its owner: Weak<Parent>. Being unable to distinguish this fundamental difference can be confusing. - Boiethios

3 Answers

2
votes

Is there any idiomatic way in Rust how to do this, other than to create a custom enum?

Just like most other "either this or that" choices in Rust, an enum is the idiomatic way.

2
votes

An Peter's answer suggested, there is is no such abstraction in the stdlib. You can define a small enum to handle both cases:

use std::rc::{Rc, Weak};

enum MaybeStrong<T> {
    Strong(Rc<T>),
    Weak(Weak<T>),
}

impl<T> MaybeStrong<T> {
    fn get(&self) -> Option<Rc<T>> {
        match self {
            MaybeStrong::Strong(t) => Some(Rc::clone(t)),
            MaybeStrong::Weak(w) => w.upgrade(),
        }
    }
}

struct Foo<T> {
    bar: MaybeStrong<T>
}

impl<T> Foo<T> {
    fn from_weak(inner: Weak<T>) -> Self {
        Self { bar: MaybeStrong::Weak(inner) }
    }

    fn from_strong(inner: Rc<T>) -> Self {
        Self { bar: MaybeStrong::Strong(inner) }
    }

    fn say(&self) where T: std::fmt::Debug {
        println!("{:?}", self.bar.get())
    }
}

fn main() {
    let inner = Rc::new("foo!");
    Foo::from_weak(Rc::downgrade(&inner)).say();
    Foo::from_strong(inner).say();
}

self.bar() will always return a Some if it was created from a strong pointer and return None in case it's a Weak and it's dangling. Notice that due to the fact that get() needs to create an owned Rc first, the method can't return a &T (including Option<&T>) because that &T could be dangling. This also means that all users of bar() will own one strong count on the inner value while processing, making it safe to use in any case.

2
votes

This kind of construct is often called "Either" and there's a crate that looks like it can address some of the usual use-cases: https://docs.rs/either/1.5.3/either/

Then you could write

struct Foo<T> {
    bar: Either<Weak<T>, Rc<T>>
}

Then an example function to get an Option<Rc<T>> might be:

impl <T> Foo<T> {
  fn get_rc(self) -> Option<Rc<T>> {
     self.bar
       .map_left( |weak| weak.upgrade() )
       .map_right( |v| Some(v) )
       .into_inner()
  }
}

Then it can be used like this:

fn main() {
    let x = Rc::new(1);
    let f_direct = Foo{ bar:Either::Right(x.clone()) };
    println!("f_direct.get_rc() = {:?}", f_direct.get_rc());
    let f_weak = Foo{ bar:Either::Left(Rc::downgrade(&x)) };
    println!("f_weak.get_rc() = {:?}", f_weak.get_rc());
}

Link to complete example in the playground:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c20faaa46277550e16a3d3b24f3d1750