Struggling with understanding RUST mut vs on-mutable and ref vs deref

Is there place there is examples for every use case ? That would very usefull to learn.

Here is a problem I am running into.
I have a structure with a open file and UUID that i want to initialize once, globally and hence am using lazy_static to store it.
Having trouble using it though, method functions that use the file/uuid field to write to the file and in invoking the method

#[macro_use(lazy_static)]
extern crate lazy_static;

use std::io::prelude::*;
use std::fs::File;

pub struct Tracker {
    pub file: File,
    pub uuid: String,
}

impl Tracker {
    pub fn init() -> Tracker {
        Tracker {
            file : File::create("/tmp/file.data").unwrap(),
            uuid : "MYUUID".to_string(),
        }
    }
    
    pub fn wisk_reportop(self: &Self, value: &str) {
        write!(self.file, "{}: {}\n", self.uuid, value).unwrap();
        self.file.flush().unwrap();
    }
    
}

lazy_static! {
    pub static ref TRACKER : Tracker = Tracker::init();
}

fn main() {
    TRACKER.wisk_reportop("Hello World!");
}

with the above code I get

Compiling playground v0.0.1 (/playground)
error[E0596]: cannot borrow `self.file` as mutable, as it is behind a `&` reference
  --> src/main.rs:21:16
   |
20 |     pub fn wisk_reportop(self: &Self, value: &str) {
   |                                ----- help: consider changing this to be a mutable reference: `&mut Tracker`
21 |         write!(self.file, "{}: {}\n", self.uuid, value).unwrap();
   |                ^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable

error[E0596]: cannot borrow `self.file` as mutable, as it is behind a `&` reference
  --> src/main.rs:22:9
   |
20 |     pub fn wisk_reportop(self: &Self, value: &str) {
   |                                ----- help: consider changing this to be a mutable reference: `&mut Tracker`
21 |         write!(self.file, "{}: {}\n", self.uuid, value).unwrap();
22 |         self.file.flush().unwrap();
   |         ^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0596`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

If I use
&mut self instead, as in

pub fn wisk_reportop(&mut self, value: &str) {

I get

Compiling playground v0.0.1 (/playground)
error[E0596]: cannot borrow data in a dereference of `TRACKER` as mutable
  --> src/main.rs:32:5
   |
32 |     TRACKER.wisk_reportop("Hello World!");
   |     ^^^^^^^ cannot borrow as mutable
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `TRACKER`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0596`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

What am I doing wrong here. A fix would be greatly appreciated.
But help in explaining the 2 cases why they are wrong along with the fix would be awesome.

Thanks,
Sarvi

In the first case, you are mutating self.file through an immutable reference to self. This is not allowed.

In the second case, you need mutable access to TRACKER to call the function in a way that mutates it, but global variables are always immutable in Rust. You can wrap the global in a Mutex to avoid the issue.

lazy_static! {
    pub static ref TRACKER: Mutex<Tracker> = Mutex::new(Tracker::init());
}
fn main() {
    TRACKER.lock().unwrap().wisk_reportop("Hello World!");
}

Thanks for explanation and the solution.
I have some clarifying questions.

The program is definitely going to multi-threaded, So I get the need for Mutex/locking.

But I was hoping to do this without ay Mutex Locking to access the content of TRACKER every time I need to access one of the fields.
The assumption being, the content of Tracker once initialized, will not changing and use as readonly. defininitely the case for UUID. I am thinking file as well. But not sure if doing reads/writes/flush using the "file" handle is considered mutation/modification and requires locking.

In my case I expect either fields of Tracker, both uuid and file, to be readonly after initialization.

  1. I expect the file to be opened once, when initialized. Does writing/flush/read using "file" handle/object require exclusive/locked access across multiple threads ?
  2. uuid also is a string initialized once at creation time. Then there are many places I might read it, to prit it as
println!("Hello, world! Constructor:\n\tUUID={}", TRACKER.uuid);

I don't want to have to do as follows each time.

println!("Hello, world! Constructor:\n\tUUID={}", TRACKER.lock().unwrap().uuid);

Is there a better way of writing this code ?

For this specific case, &'_ File also impl Write so you don't need any locking here.

 pub fn wisk_reportop(self: &Self, value: &str) {
    write!(&self.file, "{}: {}\n", self.uuid, value).unwrap();
    (&self.file).flush().unwrap();
}
1 Like

If what was written by @Hyeonu works, then that's great for you.

For future reference, though, if you only want to write to a global variable once before the value is accessed as read-only across multiple threads, a RwLock sounds better than a Mutex, because it allows you to have multiple read-only references to the underlying value, at the same time. Mutex only allows a single accessor, regardless of read or write access. You can keep the RwLockReadGuard obtained from calling RwLock::read around for the rest of each thread's lifetime, which makes repeated access quite cheap. It's the best performing solution before going unsafe, AFAIK.

The reason why you cannot just mutate the global variable safely without synchronization tools before spawning other threads is, that the compiler doesn't (cannot?) keep track of how many threads have been spawned, so it always assumes, that multiple threads exist, even if that's not the case.

1 Like

@Hyeonu 's Solution Works great. Thanks.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.