11

I have design issue that I would like solve with safe Rust that I haven't been able to find a viable solution. I can't use a RefCell because you can't get a & reference to the data, only Ref / RefMut.

Here is a simplified example with irrelevant fields / methods removed

use std::cell::RefCell;
use std::rc::Rc;

struct LibraryStruct {}
impl LibraryStruct {
    fn function(&self, _a: &TraitFromLibrary) {}
}

trait TraitFromLibrary {
    fn trait_function(&self, library_struct: LibraryStruct);
}

// I don't want to copy this, bad performance
struct A {
    // fields...
}

impl TraitFromLibrary for A {
    fn trait_function(&self, library_struct: LibraryStruct) {
        // custom A stuff
    }
}

// B manipulates A's in data
struct B {
    data: Vec<A>,
}

struct C {
    // This type doesn't have to be & for solution. C just needs immutable access
    a: Rc<RefCell<A>>,
}

impl<'a> TraitFromLibrary for C {
    fn trait_function(&self, library_struct: LibraryStruct) {
        // custom C stuff

        // Takes generic reference &, this is why Ref / RefCell doesn't work
        library_struct.function(&self.a.borrow());
    }
}

// B and C's constructed in Container and lifetime matches Container
// Container manipulates fields b and c
struct Container {
    b: B,
    c: Vec<C>,
}

fn main() {}

I would be able to solve this with Rc<RefCell<A>> but I am being restricted from the library requiring &A.

This produces the error:

error[E0277]: the trait bound `std::cell::Ref<'_, A>: TraitFromLibrary` is not satisfied
  --> src/main.rs:33:33
   |
33 |         library_struct.function(&self.a.borrow());
   |                                 ^^^^^^^^^^^^^^^^ the trait `TraitFromLibrary` is not implemented for `std::cell::Ref<'_, A>`
   |
   = note: required for the cast to the object type `TraitFromLibrary`
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
Jake
  • 245
  • 2
  • 10
  • I was trying to be vague about C's field type because it can not be `&'a A`. Struct B and C exist at the same time in Container and there would be a mutability issue as there would be an immutable reference in C and B needs mutability at times. – Jake Sep 01 '18 at 01:02
  • Ok, so ideally you want `C`'s `a` to be a `RefCell`, but that won't work with the library function? – Peter Hall Sep 01 '18 at 01:03
  • Yes because you cannot get a &A from RefCell only Ref / RefMut – Jake Sep 01 '18 at 01:06
  • I just made some changes, which I think communicates what you were getting at. And I think the answer is to just `Rc>` and then pass it to the library function as `library_struct.function(&self.a.borrow());` – Peter Hall Sep 01 '18 at 01:17
  • This works because both `Ref` implements `Deref`. If a function accepts a `&`-reference, then you can pass a reference to any type that implements `Deref`, as in [this example](https://play.rust-lang.org/?gist=3f32df819027cfe84183cbd34bf42cc7&version=stable&mode=debug&edition=2015). – Peter Hall Sep 01 '18 at 01:30

1 Answers1

8

If a function has an argument which is a of type &A, then you can call it with a reference to any type that dereferences to A, which includes things like &Ref<A>. The concept of one type dereferencing to another is captured by the Deref trait. This is also the reason why a function that accepts &str can be called with &String (String: Deref<Target = str>).

So, if you keep a as a Rc<RefCell<A>>, you can fix your code quite easily like this:

library_struct.function(&*self.a.borrow());

Note that this dereferences A and then reborrows it so that it can be coerced to a trait object.

Peter Hall
  • 53,120
  • 14
  • 139
  • 204