Is it possible to get an &str out of a RefCell<string>

89 views Asked by At

Trying to un-XY this problem as much as possible

I have a struct like this

struct Thing {
     text:String
     other : Other
}  

and a method signature like this

impl Thing {
   fn get_text(&self) -> &str{

   }
}

Note that get_text is immutable. ( I cant make it mutable. I am am dropping a replacement implementation of a componenet into a large project, I cannot change the signatures)

Other has a method get_text too. It returns &[String]. I need to convert that slice into a '\n' delimited String and return a &str pointing at it.

This works in a mutable environment;

fn get_text(&mut self) -> {
    self.msg = self.other.get_text().join("\n");
    &self.msg 
}

But it has to be immutable.

So I tried to use RefCell.

struct Thing {
     text:RefCell<String>,
     other : Other
}  

pub fn get_text(&self) -> Ref<String> {
    let text = self.other.get_text().join("/n");
    self.text.replace(text);
    self.text.borrow()
}

I can almost get it to work. Depending on how the caller uses that return value, sometimes the DeRef trait kicks in and sometimes not. But I also get complicated borrow errors in the calling code eg (this is caller's code, self.input is an instance of Thing)

fn foo(&mut self) {
    let msg = self.input.get_text();
    let signed_msg = self.bar(&msg);
    if let std::result::Result::Ok(signed_msg) = signed_msg {
        self.input.set_text(signed_msg);
    }
}

produces this

error[E0502]: cannot borrow `self.input` as mutable because it is also borrowed as immutable
   --> src\components\commit.rs:352:4
    |
349 |         let msg = self.input.get_text();
    |                   ---------- immutable borrow occurs here
...
352 |             self.input.set_text(signed_msg);
    |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
353 |         }
354 |     }
    |     - immutable borrow might be used here, when `msg` is dropped and runs the destructor for type `Ref<'_, std::string::String>`

note that this

let signed_msg = self.bar(&msg);
                          ^

is an edit I already did just to see what I can get working. I really would prefer not to make any changes to the calling code

I feel that I should be able to return a &str somehow, rather than a Ref. The lifetime of self.msg is guaranteed to be OK, I mean in a mutable env I could simply return &self.msg. My caller is not expecting anything mutable to be returned, they are just reading, but I dont see a RefCell method that returns a &T. Maybe I should use something else.

EDIT

I found that I can make this work

struct Thing {
     text:OnceCell<String>,
     other : Other
}  


pub fn get_text(&self) -> &str {
    let text = self.other.get_text().join("/n");
    self.msg.get_or_init(|| text).as_str()
}

But That only works if get_text is only called once (which I cannot be sure of). Feel I am getting very close

EDIT 2

Since I own all the code paths that lead to other updating its internal state I can call msg.take in those paths (they are all &mut self functions). So any update erases the msg:OnceCell and the next call to get_text will rebuild it. Perfect

0

There are 0 answers