How to store a reference without having to deal with lifetimes? - rust

As suggested by the dynamic_reload crate's example, I collected Symbols instead of extracting them every time, but Symbol requires a lifetime. Using a lifetime changes method signatures and breaks the compatibility with method DynamicReload::update.
Is it a valid workaround to use std::mem::transmute to change Symbol's lifetime to 'static?
extern crate dynamic_reload;
use dynamic_reload::{DynamicReload, Lib, Symbol, Search, PlatformName, UpdateState};
use std::sync::Arc;
use std::time::Duration;
use std::thread;
use std::mem::transmute;
struct Plugins {
plugins: Vec<(Arc<Lib>, Arc<Symbol<'static, extern "C" fn() -> i32>>)>,
}
impl Plugins {
fn add_plugin(&mut self, plugin: &Arc<Lib>) {
match unsafe { plugin.lib.get(b"shared_fun\0") } {
Ok(temp) => {
let f: Symbol<extern "C" fn() -> i32> = temp;
self.plugins.push((plugin.clone(), Arc::new(unsafe { transmute(f) })));
},
Err(e) => println!("Failed to load symbol: {:?}", e),
}
}
fn unload_plugins(&mut self, lib: &Arc<Lib>) {
for i in (0..self.plugins.len()).rev() {
if &self.plugins[i].0 == lib {
self.plugins.swap_remove(i);
}
}
}
fn reload_plugin(&mut self, lib: &Arc<Lib>) {
Self::add_plugin(self, lib);
}
// called when a lib needs to be reloaded.
fn reload_callback(&mut self, state: UpdateState, lib: Option<&Arc<Lib>>) {
match state {
UpdateState::Before => Self::unload_plugins(self, lib.unwrap()),
UpdateState::After => Self::reload_plugin(self, lib.unwrap()),
UpdateState::ReloadFailed(_) => println!("Failed to reload"),
}
}
}
fn main() {
let mut plugs = Plugins { plugins: Vec::new() };
// Setup the reload handler. A temporary directory will be created inside the target/debug
// where plugins will be loaded from. That is because on some OS:es loading a shared lib
// will lock the file so we can't overwrite it so this works around that issue.
let mut reload_handler = DynamicReload::new(Some(vec!["target/debug"]),
Some("target/debug"),
Search::Default);
// test_shared is generated in build.rs
match reload_handler.add_library("test_shared", PlatformName::Yes) {
Ok(lib) => plugs.add_plugin(&lib),
Err(e) => {
println!("Unable to load dynamic lib, err {:?}", e);
return;
}
}
//
// While this is running (printing a number) change return value in file src/test_shared.rs
// build the project with cargo build and notice that this code will now return the new value
//
loop {
reload_handler.update(Plugins::reload_callback, &mut plugs);
if plugs.plugins.len() > 0 {
let fun = &plugs.plugins[0].1;
println!("Value {}", fun());
}
// Wait for 0.5 sec
thread::sleep(Duration::from_millis(500));
}
}
I still have to keep Arc<Lib> inside the vector because Symbol doesn't implement PartialEq.

How to store a reference without having to deal with lifetimes?
The answer in 98% of the cases is: you don't. Lifetimes are one of the biggest reasons to use Rust. Lifetimes enforce, at compile time, that your references will always refer to something that is valid. If you wish to "ignore" lifetimes, then perhaps Rust may not the best language to realize a particular design. You may need to pick a different language or design.
Is it a valid workaround to use std::mem::transmute to change Symbol's lifetime to 'static?
transmute is The Big Hammer, suitable for all sorts of good and bad ideas and implementations. I would encourage never using it directly, but instead wrapping it in a layer of abstraction that somehow helps you enforce the appropriate restrictions that make that particular transmute correct.
If you choose to use transmute, you are assuming the full responsibility that the compiler previously had. It will be up to you to ensure that the reference is always valid, otherwise you are invoking undefined behavior and your program is allowed to do any number of Very Bad things.
For your specific case, you may be able to use the Rental crate to keep around "the library" and "references into the library" in a single struct that hides the lifetimes of the Symbols. In fact, Rental uses libloading as the motivating example and libloading powers dynamic_reload. See
Why can't I store a value and a reference to that value in the same struct? for more details and pitfalls.
I'm not optimistic that this will work because DynamicReload::update requires a &mut self. During that method call, it could easily invalidate all of the existing references.
See also:
Why can't I store a value and a reference to that value in the same struct?
How can I avoid a ripple effect from changing a concrete struct to generic?

Related

Sharing values between functions in rust

I'm trying to learn Rust, but have hit a wall, tying to do something I expected to be relatively straightforward. I'm trying to write a simple blink example for the ESP32c3 MCU. I got a basic example working, but started running into compilation errors when trying to expand/generalize the example.
My project consists of a cargo workspace with two crates - entrypoint and blink.
I was able to get the following basic version working without issues:
// entrypoint/src/main.rs
use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use blink::blink;
fn main() {
// Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
// or else some patches to the runtime implemented by esp-idf-sys might not link properly.
esp_idf_sys::link_patches();
println!("Hello, world!");
blink();
}
// blink/src/lib.rs
use std::thread;
use std::time::Duration;
use esp_idf_hal::prelude::Peripherals;
use esp_idf_hal::gpio;
pub fn blink() {
let peripherals = Peripherals::take().unwrap();
let mut led = gpio::PinDriver::output(peripherals.pins.gpio8).unwrap();
for _ in 0..20 {
led.set_high().unwrap();
thread::sleep(Duration::from_secs(1));
led.set_low().unwrap();
thread::sleep(Duration::from_secs(1));
}
}
Then I wanted to improve error handling (stop using unwrap() inside the blink crate) and make the blink() function reusable (the Peripherals::take() call panics, if it's executed more than once).
I came up with the following changes to improve error handling. This version also worked fine, I'm only including it to get feedback on how idiomatic my approach is / what would you do differently? I'm guessing it would be better practice to make a custom error type or is it acceptable/common place to return a string slice as an error even in production code?
pub fn blink(count: i32) -> Result<(), &'static str> {
let peripherals = Peripherals::take().ok_or("Failed to take peripherals")?;
let mut led = gpio::PinDriver::output(peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
for _ in 0..count {
led.set_high().map_err(|_: EspError| "Failed to set pin high")?;
thread::sleep(Duration::from_secs(1));
led.set_low().map_err(|_: EspError| "Failed to set pin low")?;
thread::sleep(Duration::from_secs(1));
}
Ok(())
}
Next, I attempted to make the blink() function reusable by separating the Peripherals::take() call from the rest of the blink() function, so it could be called only once at boot. I know I could make the call in my entrypoint and pass the peripherals as an argument to blink(), but I wanted to keep the blink crate responsible for making the Peripherals::take() call. This is where I started running into issues.
Attempt nr. 1: My first approach was trying to use a global Peripherals variable. I quickly found out that won't work unless I wrap the global variable with the thread_local macro or wrap operations on the global variable into an unsafe block which I wanted to avoid. I tried a number of things, but couldn't get my code to compile when using thread_local.
Both with and without RefCell (I found articles suggesting to use RefCell, but after trying it and reading the docs, I didn't see a good reason to use it for my use-case), thread_local seems to wrap my global variable into a LocalKey. I'm not sure how to use the LocalKey, besides the with() function - I'd like to avoid using with(), if possible, since I need to move my code into a closure, making it harder to read. I'm also not sure how to keep the for loop outside of the closure and only initialize the led variable from inside the closure - usually I'd move the variable declaration out of the closure, initialized to null, but null doesn't seem to be a concept which exists within Rust as far as I can tell.
thread_local! {
static PERIPHERALS: Option<Peripherals> = Peripherals::take();
}
pub fn blink(count: i32) -> Result<(), &'static str> {
PERIPHERALS.with(| p | {
let peripherals = match p {
Some(peripherals) => peripherals,
None => return Err("Failed to take peripherals")
};
let mut led = gpio::PinDriver::output(peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
for _ in 0..count {
led.set_high().map_err(|_: EspError| "Failed to set pin high")?;
thread::sleep(Duration::from_secs(1));
led.set_low().map_err(|_: EspError| "Failed to set pin low")?;
thread::sleep(Duration::from_secs(1));
}
Ok(())
})
}
The above code resulted in the following compiler error:
error[E0507]: cannot move out of `peripherals.pins.gpio8` which is behind a shared reference
--> blink/src/lib.rs:19:47
|
19 | let mut led = gpio::PinDriver::output(peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
| ^^^^^^^^^^^^^^^^^^^^^^ move occurs because `peripherals.pins.gpio8` has type `Gpio8`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0507`.
error: could not compile `blink` due to previous error
The same error occurs, if I try to dereference peripherals variable first:
...
let mut led = gpio::PinDriver::output((*peripherals).pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
...
Attempt nr. 2: As my next approach, I tried to write a struct with a couple functions which would act as a class. Unfortunately I ran into the exact same compiler error.
// blink/src/lib.rs
use std::thread;
use std::time::Duration;
use anyhow::Result;
use esp_idf_hal::prelude::Peripherals;
use esp_idf_hal::gpio;
use esp_idf_sys::EspError;
pub struct Blink {
peripherals: Peripherals,
}
impl Blink {
pub fn new() -> Result<Blink, &'static str> {
match Peripherals::take() {
Some(peripherals) => Ok(Blink{ peripherals }),
None => return Err("Failed to take peripherals")
}
}
pub fn blink(&self, count: i32) -> Result<(), &'static str> {
let mut led = gpio::PinDriver::output(self.peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
for _ in 0..count {
led.set_high().map_err(|_: EspError| "Failed to set pin high")?;
thread::sleep(Duration::from_secs(1));
led.set_low().map_err(|_: EspError| "Failed to set pin low")?;
thread::sleep(Duration::from_secs(1));
}
Ok(())
}
}
// entrypoint/src/main.rs
use std::thread;
use std::time::Duration;
use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
use blink::Blink;
fn main() {
// Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
// or else some patches to the runtime implemented by esp-idf-sys might not link properly.
esp_idf_sys::link_patches();
println!("Hello, world!");
let blink = Blink::new()?;
loop {
blink.blink(2).unwrap();
thread::sleep(Duration::from_secs(5));
}
}
error[E0507]: cannot move out of `self.peripherals.pins.gpio8` which is behind a shared reference
--> blink/src/lib.rs:23:47
|
23 | let mut led = gpio::PinDriver::output(self.peripherals.pins.gpio8).map_err(|_: EspError| "Failed to set pin to output")?;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ move occurs because `self.peripherals.pins.gpio8` has type `Gpio8`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0507`.
error: could not compile `blink` due to previous error
I don't have a good enough understanding of how borrowing, references, and/or variable moving/copying works in Rust just yet to be able to solve this. It seems to be drastically different from other (more traditional) languages I'm familiar with (C, C++, Java, JS/TS, Python, Dart).
Once again, I'd also really appreciate any best practice recommendations/corrections, if you find anything out of the ordinary in my code above.
The basic gist of the error can be reproduced with a simpler example:
struct Foo {
bar: String
}
impl Foo {
fn baz(&self) {
let s = self.bar;
}
}
What's happening is:
self has type &Foo, because the parameter is declared with &self, which is a shorthand for self: &Self
self.bar has type String, because bar is declared to be a String
this leads to an issue, we're trying to *move s out of self, but we only have a reference to self, not owned access to self
You'll need to find a way to make that work without owned access. Disclaimer, I've not used this crate before (and the setup instructions are a bit crazy), but here's what I could piece together from the docs for PinDriver::output
PinDriver::output takes an impl Peripheral, i.e. any type that implements the trait Peripheral
looking at the docs for Peripheral, we can see a list of structs that implement it.
Gpio8 does implement it, but this is no good, since we'd run into the "cannot move out of shared reference" issue again
in most Rust code, you might want to .clone() the gpio pin, however, there's no clone method (because it represents a unique handle to a physical resource, unless you've got some board that can grow new pins :p)
however, right at the bottom, there's this impl:
impl<T: DerefMut> Peripheral for T
where
T::Target: Peripheral { ... }
i.e. any type which implements DerefMut<Target = T> implements Periperal, as long as T also implements Peripheral
this means you can use &mut Gpio8. This makes sense, since you're mutating the state of the light, so you need a mutable handle to it. If you change blink() to take a &mut self, you should be able to write PinDriver::output(&mut self.peripherals.pins.gpio8)
FWIW, embedded Rust often uses clever type system tricks to verify more behavior at compile time (either to save precious CPU cycles or for better reliability). If you're new to Rust, it can be quite confusing, since it's arguably more "advanced" than most CLI apps, for example

Transferring ownership between enum variants [duplicate]

I'm tring to replace a value in a mutable borrow; moving part of it into the new value:
enum Foo<T> {
Bar(T),
Baz(T),
}
impl<T> Foo<T> {
fn switch(&mut self) {
*self = match self {
&mut Foo::Bar(val) => Foo::Baz(val),
&mut Foo::Baz(val) => Foo::Bar(val),
}
}
}
The code above doesn't work, and understandibly so, moving the value out of self breaks the integrity of it. But since that value is dropped immediately afterwards, I (if not the compiler) could guarantee it's safety.
Is there some way to achieve this? I feel like this is a job for unsafe code, but I'm not sure how that would work.
mem:uninitialized has been deprecated since Rust 1.39, replaced by MaybeUninit.
However, uninitialized data is not required here. Instead, you can use ptr::read to get the data referred to by self.
At this point, tmp has ownership of the data in the enum, but if we were to drop self, that data would attempt to be read by the destructor, causing memory unsafety.
We then perform our transformation and put the value back, restoring the safety of the type.
use std::ptr;
enum Foo<T> {
Bar(T),
Baz(T),
}
impl<T> Foo<T> {
fn switch(&mut self) {
// I copied this code from Stack Overflow without reading
// the surrounding text that explains why this is safe.
unsafe {
let tmp = ptr::read(self);
// Must not panic before we get to `ptr::write`
let new = match tmp {
Foo::Bar(val) => Foo::Baz(val),
Foo::Baz(val) => Foo::Bar(val),
};
ptr::write(self, new);
}
}
}
More advanced versions of this code would prevent a panic from bubbling out of this code and instead cause the program to abort.
See also:
replace_with, a crate that wraps this logic up.
take_mut, a crate that wraps this logic up.
Change enum variant while moving the field to the new variant
How can I swap in a new value for a field in a mutable reference to a structure?
The code above doesn't work, and understandibly so, moving the value
out of self breaks the integrity of it.
This is not exactly what happens here. For example, same thing with self would work nicely:
impl<T> Foo<T> {
fn switch(self) {
self = match self {
Foo::Bar(val) => Foo::Baz(val),
Foo::Baz(val) => Foo::Bar(val),
}
}
}
Rust is absolutely fine with partial and total moves. The problem here is that you do not own the value you're trying to move - you only have a mutable borrowed reference. You cannot move out of any reference, including mutable ones.
This is in fact one of the frequently requested features - a special kind of reference which would allow moving out of it. It would allow several kinds of useful patterns. You can find more here and here.
In the meantime for some cases you can use std::mem::replace and std::mem::swap. These functions allow you to "take" a value out of mutable reference, provided you give something in exchange.
Okay, I figured out how to do it with a bit of unsafeness and std::mem.
I replace self with an uninitialized temporary value. Since I now "own" what used to be self, I can safely move the value out of it and replace it:
use std::mem;
enum Foo<T> {
Bar(T),
Baz(T),
}
impl<T> Foo<T> {
fn switch(&mut self) {
// This is safe since we will overwrite it without ever reading it.
let tmp = mem::replace(self, unsafe { mem::uninitialized() });
// We absolutely must **never** panic while the uninitialized value is around!
let new = match tmp {
Foo::Bar(val) => Foo::Baz(val),
Foo::Baz(val) => Foo::Bar(val),
};
let uninitialized = mem::replace(self, new);
mem::forget(uninitialized);
}
}
fn main() {}

having two struct reference each other - rust

I'm quite new to Rust programming, and I'm trying to convert a code that I had in js to Rust.
A plain concept of it is as below:
fn main() {
let mut ds=DataSource::new();
let mut pp =Processor::new(&mut ds);
}
struct DataSource {
st2r: Option<&Processor>,
}
struct Processor {
st1r: &DataSource,
}
impl DataSource {
pub fn new() -> Self {
DataSource {
st2r: None,
}
}
}
impl Processor {
pub fn new(ds: &mut DataSource) -> Self {
let pp = Processor {
st1r: ds,
};
ds.st2r = Some(&pp);
pp
}
}
As you can see I have two main modules in my system that are inter-connected to each other and I need a reference of each in another.
Well, this code would complain about lifetimes and such stuff, of course 😑. So I started throwing lifetime specifiers around like a madman and even after all that, it still complains that in "Processor::new" I can't return something that has been borrowed. Legit. But I can't find any solution around it! No matter how I try to handle the referencing of each other, it ends with this borrowing error.
So, can anyone point out a solution for this situation? Is my app's structure not valid in Rust and I should do it in another way? or there's a trick to this that my inexperienced mind can't find?
Thanks.
What you're trying to do can't be expressed with references and lifetimes because:
The DataSource must live longer than the Processor so that pp.st1r is guaranteed to be valid,
and the Processor must live longer than the DataSource so that ds.st2r is guaranteed to be valid. You might think that since ds.st2r is an Option and since the None variant doesn't contain a reference this allows a DataSource with a None value in st2r to outlive any Processors, but unfortunately the compiler can't know at compile-time whether st2r contains Some value, and therefore must assume it does.
Your problem is compounded by the fact that you need a mutable reference to the DataSource so that you can set its st2r field at a time when you also have an immutable outstanding reference inside the Processor, which Rust won't allow.
You can make your code work by switching to dynamic lifetime and mutability tracking using Rc (for dynamic lifetime tracking) and RefCell (for dynamic mutability tracking):
use std::cell::RefCell;
use std::rc::{ Rc, Weak };
fn main() {
let ds = Rc::new (RefCell::new (DataSource::new()));
let pp = Processor::new (Rc::clone (&ds));
}
struct DataSource {
st2r: Weak<Processor>,
}
struct Processor {
st1r: Rc<RefCell<DataSource>>,
}
impl DataSource {
pub fn new() -> Self {
DataSource {
st2r: Weak::new(),
}
}
}
impl Processor {
pub fn new(ds: Rc::<RefCell::<DataSource>>) -> Rc<Self> {
let pp = Rc::new (Processor {
st1r: ds,
});
pp.st1r.borrow_mut().st2r = Rc::downgrade (&pp);
pp
}
}
Playground
Note that I've replaced your Option<&Processor> with a Weak<Processor>. It would be possible to use an Option<Rc<Processor>> but this would risk leaking memory if you dropped all references to DataSource without setting st2r to None first. The Weak<Processor> behaves more or less like an Option<Rc<Processor>> that is set to None automatically when all other references are dropped, ensuring that memory will be freed properly.

What are the use cases of the newly proposed Pin type?

There is a new Pin type in unstable Rust and the RFC is already merged. It is said to be kind of a game changer when it comes to passing references, but I am not sure how and when one should use it.
Can anyone explain it in layman's terms?
What is pinning ?
In programming, pinning X means instructing X not to move.
For example:
Pinning a thread to a CPU core, to ensure it always executes on the same CPU,
Pinning an object in memory, to prevent a Garbage Collector to move it (in C# for example).
What is the Pin type about?
The Pin type's purpose is to pin an object in memory.
It enables taking the address of an object and having a guarantee that this address will remain valid for as long as the instance of Pin is alive.
What are the usecases?
The primary usecase, for which it was developed, is supporting Generators.
The idea of generators is to write a simple function, with yield, and have the compiler automatically translate this function into a state machine. The state that the generator carries around is the "stack" variables that need to be preserved from one invocation to another.
The key difficulty of Generators that Pin is designed to fix is that Generators may end up storing a reference to one of their own data members (after all, you can create references to stack values) or a reference to an object ultimately owned by their own data members (for example, a &T obtained from a Box<T>).
This is a subcase of self-referential structs, which until now required custom libraries (and lots of unsafe). The problem of self-referential structs is that if the struct move, the reference it contains still points to the old memory.
Pin apparently solves this years-old issue of Rust. As a library type. It creates the extra guarantee that as long as Pin exist the pinned value cannot be moved.
The usage, therefore, is to first create the struct you need, return it/move it at will, and then when you are satisfied with its place in memory initialize the pinned references.
One of the possible uses of the Pin type is self-referencing objects; an article by ralfj provides an example of a SelfReferential struct which would be very complicated without it:
use std::ptr;
use std::pin::Pin;
use std::marker::PhantomPinned;
struct SelfReferential {
data: i32,
self_ref: *const i32,
_pin: PhantomPinned,
}
impl SelfReferential {
fn new() -> SelfReferential {
SelfReferential { data: 42, self_ref: ptr::null(), _pin: PhantomPinned }
}
fn init(self: Pin<&mut Self>) {
let this : &mut Self = unsafe { self.get_unchecked_mut() };
// Set up self_ref to point to this.data.
this.self_ref = &mut this.data as *const i32;
}
fn read_ref(self: Pin<&Self>) -> Option<i32> {
let this : &Self= self.get_ref();
// Dereference self_ref if it is non-NULL.
if this.self_ref == ptr::null() {
None
} else {
Some(unsafe { *this.self_ref })
}
}
}
fn main() {
let mut data: Pin<Box<SelfReferential>> = Box::new(SelfReferential::new()).into();
data.as_mut().init();
println!("{:?}", data.as_ref().read_ref()); // prints Some(42)
}

How does interior mutability work for caching behavior?

I'm trying to create a struct that takes a Path and, on demand, loads the image from the path specified. Here's what I have so far:
extern crate image;
use std::cell::{RefCell};
use std::path::{Path};
use image::{DynamicImage};
pub struct ImageCell<'a> {
image: RefCell<Option<DynamicImage>>,
image_path: &'a Path,
}
impl<'a> ImageCell<'a> {
pub fn new<P: AsRef<Path>>(image_path: &'a P) -> ImageCell<'a>{
ImageCell { image: RefCell::new(None), image_path: image_path.as_ref() }
}
//copied from https://doc.rust-lang.org/nightly/std/cell/index.html#implementation-details-of-logically-immutable-methods
pub fn get_image(&self) -> &DynamicImage {
{
let mut cache = self.image.borrow_mut();
if cache.is_some() {
return cache.as_ref().unwrap(); //Error here
}
let image = image::open(self.image_path).unwrap();
*cache = Some(image);
}
self.get_image()
}
}
This fails to compile:
src/image_generation.rs:34:24: 34:29 error: `cache` does not live long enough
src/image_generation.rs:34 return cache.as_ref().unwrap();
^~~~~
src/image_generation.rs:30:46: 42:6 note: reference must be valid for the anonymous lifetime #1 defined on the block at 30:45...
src/image_generation.rs:30 pub fn get_image(&self) -> &DynamicImage {
src/image_generation.rs:31 {
src/image_generation.rs:32 let mut cache = self.image.borrow_mut();
src/image_generation.rs:33 if cache.is_some() {
src/image_generation.rs:34 return cache.as_ref().unwrap();
src/image_generation.rs:35 }
...
src/image_generation.rs:32:53: 39:10 note: ...but borrowed value is only valid for the block suffix following statement 0 at 32:52
src/image_generation.rs:32 let mut cache = self.image.borrow_mut();
src/image_generation.rs:33 if cache.is_some() {
src/image_generation.rs:34 return cache.as_ref().unwrap();
src/image_generation.rs:35 }
src/image_generation.rs:36
src/image_generation.rs:37 let image = image::open(self.image_path).unwrap();
...
I think I understand why because the lifetime of cache is tied to borrow_mut().
Is there anyway to structure the code so that this works?
I'm not totally convinced you need interior mutability here. However, I do think the solution you've proposed is generally useful, so I'll elaborate on one way to achieve it.
The problem with your current code is that RefCell provides dynamic borrowing semantics. In other words, borrowing the contents of a RefCell is opaque to Rust's borrow checker. The problem is, when you try to return a &DynamicImage while it still lives inside the RefCell, it is impossible for the RefCell to track its borrowing status. If a RefCell allowed that to happen, then other code could overwrite the contents of the RefCell while there was a loan out of &DynamicImage. Whoops! Memory safety violation.
For this reason, borrowing a value out of a RefCell is tied to the lifetime of the guard you get back when you call borrow_mut(). In this case, the lifetime of the guard is the stack frame of get_image, which no longer exists after the function returns. Therefore, you cannot borrow the contents of a RefCell like you're doing.
An alternative approach (while maintaining the requirement of interior mutability) is to move values in and out of the RefCell. This enables you to retain cache semantics.
The basic idea is to return a guard that contains the dynamic image along with a pointer back to the cell it originated from. Once you're done with the dynamic image, the guard will be dropped and we can add the image back to the cell's cache.
To maintain ergonomics, we impl Deref on the guard so that you can mostly pretend like it is a DynamicImage. Here's the code with some comments and a few other things cleaned up:
use std::cell::RefCell;
use std::io;
use std::mem;
use std::ops::Deref;
use std::path::{Path, PathBuf};
struct ImageCell {
image: RefCell<Option<DynamicImage>>,
// Suffer the one time allocation into a `PathBuf` to avoid dealing
// with the lifetime.
image_path: PathBuf,
}
impl ImageCell {
fn new<P: Into<PathBuf>>(image_path: P) -> ImageCell {
ImageCell {
image: RefCell::new(None),
image_path: image_path.into(),
}
}
fn get_image(&self) -> io::Result<DynamicImageGuard> {
// `take` transfers ownership out from the `Option` inside the
// `RefCell`. If there was no value there, then generate an image
// and return it. Otherwise, move the value out of the `RefCell`
// and return it.
let image = match self.image.borrow_mut().take() {
None => {
println!("Opening new image: {:?}", self.image_path);
try!(DynamicImage::open(&self.image_path))
}
Some(img) => {
println!("Retrieving image from cache: {:?}", self.image_path);
img
}
};
// The guard provides the `DynamicImage` and a pointer back to
// `ImageCell`. When it's dropped, the `DynamicImage` is added
// back to the cache automatically.
Ok(DynamicImageGuard { image_cell: self, image: image })
}
}
struct DynamicImageGuard<'a> {
image_cell: &'a ImageCell,
image: DynamicImage,
}
impl<'a> Drop for DynamicImageGuard<'a> {
fn drop(&mut self) {
// When a `DynamicImageGuard` goes out of scope, this method is
// called. We move the `DynamicImage` out of its current location
// and put it back into the `RefCell` cache.
println!("Adding image to cache: {:?}", self.image_cell.image_path);
let image = mem::replace(&mut self.image, DynamicImage::empty());
*self.image_cell.image.borrow_mut() = Some(image);
}
}
impl<'a> Deref for DynamicImageGuard<'a> {
type Target = DynamicImage;
fn deref(&self) -> &DynamicImage {
// This increases the ergnomics of a `DynamicImageGuard`. Because
// of this impl, most uses of `DynamicImageGuard` can be as if
// it were just a `&DynamicImage`.
&self.image
}
}
// A dummy image type.
struct DynamicImage {
data: Vec<u8>,
}
// Dummy image methods.
impl DynamicImage {
fn open<P: AsRef<Path>>(_p: P) -> io::Result<DynamicImage> {
// Open image on file system here.
Ok(DynamicImage { data: vec![] })
}
fn empty() -> DynamicImage {
DynamicImage { data: vec![] }
}
}
fn main() {
let cell = ImageCell::new("foo");
{
let img = cell.get_image().unwrap(); // opens new image
println!("image data: {:?}", img.data);
} // adds image to cache (on drop of `img`)
let img = cell.get_image().unwrap(); // retrieves image from cache
println!("image data: {:?}", img.data);
} // adds image back to cache (on drop of `img`)
There is a really important caveat to note here: This only has one cache location, which means if you call get_image a second time before the first guard has been dropped, then a new image will be generated from scratch since the cell will be empty. This semantic is hard to change (in safe code) because you've committed to a solution that uses interior mutability. Generally speaking, the whole point of interior mutability is to mutate something without the caller being able to observe it. Indeed, that should be the case here, assuming that opening an image always returns precisely the same data.
This approach can be generalized to be thread safe (by using Mutex for interior mutability instead of RefCell) and possibly more performant by choosing a different caching strategy depending on your use case. For example, the regex crate uses a simple memory pool to cache compiled regex state. Since this caching should be opaque to callers, it is implemented with interior mutability using precisely the same mechanism outlined here.

Resources