I have a set of classes linked into an enum:
#[derive(Default)]
pub struct A {
}
#[derive(Default)]
pub struct B {
}
#[derive(Default)]
pub struct C {
}
enum Classes {
A(A),
B(B),
C(C),
}
This enum is then stored in an Rc<RefCell<Classes>>
, later if I want to get an instance of A
or return an error if the object doesn't contain A
I can do:
fn main() -> Result<(), String> {
let obj: Rc<RefCell<Classes>> = Rc::new(RefCell::new(Classes::A(A::default())));
match &*obj.borrow() {
Classes::A(_a) => println!("has A"),
_ => return Err("not A".to_string()),
}
Ok(())
}
This works but is a little awkward. What I'd like to do is implement a function like this:
fn get_a(reference: &Rc<RefCell<Classes>>) -> Option<&A> {
match &*reference.borrow() {
Classes::A(o) => return Some(o),
_ => return None,
}
}
Which would allow:
fn main() -> Result<(), String> {
let obj: Rc<RefCell<Classes>> = Rc::new(RefCell::new(Classes::A(A::default())));
let a: &A = get_a(&obj).ok_or("not A".to_string())?;
Ok(())
}
Is this possible or is there another way to achieve this?
Your approach won't work because the &A
reference would be tied to the temporary Ref<Classes>
from reference.borrow()
, which gets dropped at the end of the function.
The best solution is to use Ref::map
to create a mapped reference:
rust
use std::cell::Ref;
fn get_a(reference: &Rc<RefCell<Classes>>) -> Option<Ref<A>> {
let borrowed = reference.borrow();
if matches!(*borrowed, Classes::A(_)) {
Some(Ref::map(borrowed, |classes| match classes {
Classes::A(a) => a,
_ => unreachable!(),
}))
} else {
None
}
}
Usage:
rust
fn main() -> Result<(), String> {
let obj: Rc<RefCell<Classes>> = Rc::new(RefCell::new(Classes::A(A::default())));
let a_ref = get_a(&obj).ok_or("not A".to_string())?;
// a_ref behaves like &A and keeps the borrow alive
println!("Got A: {:?}", *a_ref);
Ok(())
}
Alternative approaches:
rust
fn with_a<R>(reference: &Rc<RefCell<Classes>>, f: impl FnOnce(&A) -> R) -> Option<R> {
match &*reference.borrow() {
Classes::A(a) => Some(f(a)),
_ => None,
}
}
// Usage:
with_a(&obj, |a| println!("Value: {:?}", a))?;
rust
impl Classes {
fn as_a(&self) -> Option<&A> {
match self { Classes::A(a) => Some(a), _ => None }
}
}
// Usage:
let a = obj.borrow().as_a().ok_or("not A")?;
The Ref::map
approach is closest to your desired API while respecting Rust's borrowing rules.