I have a type with a string value that is computed in lazy fashion; but once it is computed, it never changes. I've got an implementation, but I'm wondering whether there's a more idiomatic (or simply more clever) way of doing it.
Either the string exists when the Value is created, or it's computed and saved by a method the first time it's wanted. Clients need to be able to retrieve the string_rep on demand.
I'm not entirely happy with this implementation; it seems like I've got a whole lot of allocation going on, and it seems overly complicated. In particular, I've got that second Rc in Option<Rc<String>>. That's because I don't want the use of interior mutability to appear in the API (semantically, the Value is immutable). Rather than letting a stray Ref escape, the retrieval method calls RefCell::borrow, clones the Rc<String>, and returns it.
What I'm wanting is something where I can still compute and return the string_rep in lazy fashion; but I can provide a method like this:
impl Value {
// I.e., the lifetime of the result is the same as the lifetime of the `Value`.
pub fn as_string(&self) -> &str { ... }
}
Is there a canonical way to do this that doesn't involve RefCell? Is there a simple unsafe solution?
This worked very well. I started using the ::once_cell crate, and then converted over to using UnsafeCell<Option<String>> directly. The UnsafeCell is created with either Some, or None; and it is queried in exactly one method which returns it if Some and sets and returns the string if None.
this is indeed sound, good job; but it relies on Valuenot being Sync (else there could be a data race if two threads attempted to initialize it). I recommend you mention this fact somewhere in the NOTE:s, to "prevent" someone from adding an unsound unsafe impl Sync for Value {} later on.
Or even better, you could go and add an assert_not_impl!(Value, Sync) to explicitely make compilation fail if someone were to add such impl.
if slot.is_some() {
return slot.as_ref().expect("string rep");
}
you can avoid this usage of .expect:
if let &Some(ref inner) = slot {
return &**inner;
}
or if you prefer to let Rust do stuff under the hood, you can just do:
Thank you very much for taking the time to give me some style notes! Writing idiomatic Rust takes time to learn, and I'm still very much learning.
Regarding Sync, I'll see about adding the assert_not_impl. In fact, Value is part of a much larger system that isn't Sync either; the usual thing in multi-threaded TCL programming is to keep each TCL Interp in its own thread and send TCL commands back and forth. I suppose it would be possible to make the Interp Sync if I worked at it....