cripplejayb
cripplejayb

Reputation: 123

How can I denote a field that can be either Rc<T> or Weak<T>

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?

Upvotes: 3

Views: 167

Answers (3)

Michael Anderson
Michael Anderson

Reputation: 73490

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

Upvotes: 2

user2722968
user2722968

Reputation: 16475

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.

Upvotes: 2

Peter Hall
Peter Hall

Reputation: 58735

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.

Upvotes: 2

Related Questions