0

What is the proper way to store a reference in a struct and operate on it given this example:

// Trait that cannot be changed pub trait FooTrait { pub fn open(&self, client: &SomeType); pub fn close(&self); } pub struct Foo { // HOW TO STORE IT HERE??? // client: &SomeType, } impl FooTrait for Foo { pub fn open(&self, client: &SomeType) { // HOW TO SAVE IT HERE? // NOTE that &self cannot be changed into &mut self because the trait cannot be modified // smth like self.client = client; } pub fn close(&self) { // HOW TO DELETE IT HERE? // NOTE that &self cannot be changed into &mut self because the trait cannot be modified } } 

Is there a design pattern that could fit to my snippet?

0

1 Answer 1

2

This is horribly complicated on its surface because of lifetime issues. Rust is designed to guarantee memory safety, but this pattern creates an untenable situation where the caller of FooTrait::open() needs some way to tell Rust that the client borrow will outlive *self. If it can't do that, Rust will disallow the method call. Actually making this work with references is probably not feasible, as Foo needs a lifetime parameter, but the code that creates the Foo may not know the appropriate lifetime parameter.

You can make this pattern work by combining a few things, but only if you can modify the trait. If you can't change the definition of the trait, then what you are asking is impossible.

  • You need an Option so that close can clear the value.
  • You need interior mutability (a Cell) to allow mutating self.client even if self is a shared reference.
  • You need something other than a bare reference. An owned value or a shared ownership type like Rc or Arc, for example. These types sidestep the lifetime issue entirely. You can make the code generic over Borrow<T> to support them all at once.
use std::cell::Cell; use std::borrow::Borrow; pub trait FooTrait { fn open(&self, client: impl Borrow<SomeType> + 'static); fn close(&self); } pub struct SomeType; pub struct Foo { client: Cell<Option<Box<dyn Borrow<SomeType>>>>, } impl FooTrait for Foo { fn open(&self, client: impl Borrow<SomeType> + 'static) { self.client.set(Some(Box::new(client))); } fn close(&self) { self.client.set(None); } } 
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks @cdhowie, very good explanation, unfortunately I can't change the trait as it comes from some Android generated code
@Mihai I don't think you can do what you're asking then, without the use of unsafe code that has high probability of introducing undefined behavior.
As a last resort I'm ok with unsafe as well because open will be called from only one thread
@Mihai Threads aren't relevant. The problem is that the SomeType value could be dropped before the Foo value, which creates a use-after-free bug, and therefore UB. If this is generated code you may wish to file a bug report against the project responsible for the code generator.
It seems the API designer does not want a trait implementation to cache the client. Either it is intended as a use-and-forget scenario, or the trait implementation would clone the client, or save some info extracted from the client.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.