[Solved] Mutation during immutable trait implementation

Hi,

I'm trying to implement a trait with immutable signature. In my implementation I need to to write into a file, which cause mutation and compiler error as a result. I have tried to use Box, Rc and co., but without any success. What is a Rust way to do it?

Here is the code.

use std::fs::{File, OpenOptions};
use std::io::Write;

// Existing trait from external crate which I cannot modify.
trait Foo {
    fn foo(&self, s: &str);
}

struct MyFoo {
    file: File,
}


impl MyFoo {
    pub fn new(path: &str) -> MyFoo {
        let f = OpenOptions::new()
            .create(true)
            .append(true)
            .open(path)
            .unwrap();

        MyFoo { file: f }
    }
}

impl Foo for MyFoo {
    fn foo(&self, s: &str) {
        writeln!(self.file, "{}", s); // Error happens here: "cannot mutably borrow immutable field"
    }
}

fn main() {
    let mf = MyFoo::new("/dev/null");

    mf.foo("some string");
}

Compiler error:

$ cargo build
   Compiling foo v0.1.0 (file:///home/viktor/foo)
error: cannot borrow immutable field `self.file` as mutable
  --> src/main.rs:28:18
   |
27 |     fn foo(&self, s: &str) {
   |            ----- use `&mut self` here to make mutable
28 |         writeln!(self.file, "{}", s);
   |                  ^^^^^^^^^ cannot mutably borrow immutable field

error: aborting due to previous error

error: Could not compile `foo`.

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

Hope for your help.
Thank you.

To "cheat" on mutation you need Cell/RefCell or Mutex.

2 Likes

RefCell fits perfect for me here.
Thank you, Kornel, for help and a great article!

Problem solved.

With File in particular you can make use of the fact that there's an impl<'a> Write for &'a File. This essentially means that &mut &File works (i.e. mutable ref to an immutable ref). I'm not actually sure why such an impl exists, but it's there.

But in general, what @kornel said.

1 Like

Good to know.
Thanks, Vitaly.

As I understand it, mutability is an important consideration for thread-safety and concurrency. If you want to share some data between threads, either there has to be some synchronization, or all references need to be immutable (otherwise you have data races). So for example, Cell<u8> means that the normally safe u8 is no longer safe to transfer without unsafe, because you can now break your promise that it is immutable.

So immutability is helpful in catching mistakes, but it also has implications for safety. If you can get a mutable reference to an immutable File, it probably means that concurrent writes to the file are safe, because it is synchronized by libstd.

Or I could be totally wrong :stuck_out_tongue:

1 Like

I guess you are absolutely right, at least I understand it that the same way.
I've found that Mutex can be used here instead of RefCell if we have multi-threaded environment.

No, you're right. I meant that it's odd to be able to share a file in this manner, even if threadsafe. After all, I'm not sure interleaved file output is what a user wants (in general) and this easily allows for that. I think I'd want two file types - one that truly enforces unique borrows so that some class of mistakes can be caught at compile time, and another one where you can share it across threads and arrange for synchronization/order yourself. But anyway, I digress :slight_smile:

1 Like