Can I require mutability when calling a function?

I am working on a library that operates like a state machine for a microcontroller. I have something similar to the following code:

struct Pin<MODE> {
   mode: PhantomData<MODE>
}

impl Pin<mode::Disabled> {
    fn enable(mut self) -> Pin<mode::Enabled> {
        // inline assembly to enable pin, does not use any variables from the program
    }
}

This works, but when compiling I get warning: variable does not need to be mutable for that function.

If I add #[allow(unused_mut)] to the function, I can still use this with an immutable variable. If I call it with a mutable variable, I get the same warning but on the variable.

From Rust's perspective I am not changing anything with self, but I want mutability to be required as this is altering the state of the hardware. Is there a way I can require mutability to call this function?

Edit: modified example to show state machine usage

You probably want &mut self. The method you've declared takes ownership of the Pin struct which means the variable won't be accessible after you call the method.

I simplified my problem too much, I'll add more back in...

Added back in more info to show the problem.

Well the old variable will be unusable still in this version, assuming the type isn't Copy. What would the advantage be of requiring the variable to be mutable?

1 Like

There are other functions that this pin can do, such as writing a voltage high/low (mutable only) or reading a voltage (mutable or immutable). It would be nice to take advantage of Rust's mut guarantees for this.

Right, but when you use those other functions the mutability of the returned value is what matters, not the original value's mutability.

Playground

use std::marker::PhantomData;


mod mode {
    #[derive(Debug)]
    pub struct Enabled;
    #[derive(Debug)]
    pub struct Disabled;
}

#[derive(Debug)]
struct Pin<MODE> {
    mode: PhantomData<MODE>,
}

impl Pin<mode::Disabled> {
    fn new() -> Self {
        Self { mode: PhantomData }
    }

    fn enable(self) -> Pin<mode::Enabled> {
        // inline assembly to enable pin, does not use any variables from the program
        Pin { mode: PhantomData }
    }
}

impl Pin<mode::Enabled> {
    fn read(&self) -> bool {
        true
    }

    fn write(&mut self, _: bool) {}
}

fn main() {
    let disabled = Pin::new();
    let enabled = disabled.enable(); 
    // disabled is now unusable, we have to use enabled.

    enabled.write(true); // error[E0596]: cannot borrow `enabled` as mutable, as it is not declared as mutable

    println!("{disabled:?}") // error[E0382]: borrow of moved value: `disabled`
}

When you take the disabled Pin by value you're guaranteeing that the old value can never be used again. So whether or not it's mutable is irrelevant. In this example we get an error on calling write because it isn't mutable, and we get an error for trying to use disabled at all. The mutability of disabled is completely irrelevant.

2 Likes

I see what you mean. I guess it feels weird to me to have an immutable variable that can directly change hardware. I do see that it doesn't really matter in this scenario, but I would still like to know if there is a way to require a mutable parameter.

I'm not aware of any tricks that would make that required. Methods taking &mut self require it, but that isn't compatible with needing to change the type parameter like you're doing there.

Sorry, I made two posts here, and I didn't read the thread well before. My apologies.

My third attempt, sorry.

First of all, consuming self and returning a type with a type parameter changed is also used by the state builder pattern. Thus making Pin::enable consume Self can be justified if you want some compile-time safety.


Writing

    fn enable(mut self) -> Pin<mode::Enabled> {

instead of

    fn enable(self) -> Pin<mode::Enabled> {

has the only difference that within the function body, you may obtain a &mut reference to self, e.g. to call methods which take &mut self, or to assign the local self to a new value (of the same type).

Whether you write mut self or self solely depends on what you do in the implementation of the method. It has no effect on the outside interface.

Note that in @semicoleon's example, the disabled variable stores a Pin<mode::Disabled>. This variable is never mutated. Its value is converted into a value of a different type, namely Pin<mode::Enabled>. When you consume a value, you do not mutate it. See also the following example:

fn main() {
    let a = String::from("Hello"); // not `mut`
    let b = String::from(" World");
    let x = [a, b].concat(); // we consume `a` and `b` to create a new `String`
    println!("{x}");

    let mut c = String::from("Hello"); // `mut`
    c.push_str(" World");
    println!("{c}");
}

(Playground)

(Some more examples in this other Playground.)

So in this code:

fn main() {
    let disabled = Pin::new();
    let mut enabled = disabled.enable();

    enabled.write(true);
}

the variable disabled will not be mutated. It will be consumed to create a new enabled value (of a different type).


Additional note, this is a bit unrelated to what you have asked:

You might even consider to remove the mut for the enabled, and to make write work on &self instead of &mut self (depending on your needs). This is because mut in Rust isn't only linked to mutability but also to exclusive access. A mutable reference is also always an exclusive one. This means you cannot access the hardware pin from two threads, for example, or from within multiple event or interrupt handlers.

This is why it often can make sense to make state-altering methods working on &self. This concept is called Interior Mutability. If a type implements the Sync marker trait, it guarantees that it's okay to send shared references to different threads. This way, you can modify the state from several threads in a synchronized fashion.

For types which do not implement Sync or which require &mut self on their methods, you can wrap the type in a Mutex.

But if your struct already performs atomic operations on the hardware, it might be a disadvantageous to demand a &mut self just for the sake of semantics. That is because if you later want to use the pin synchronized from several places, you'll have to wrap it again in a Mutex (or RefCell in the single-threaded cases).

The problem here is that mut really has two meanings, which are partly overlapping:

  • exclusive access
  • mutability / changing state

It's used for both purposes in Rust.

2 Likes

That makes sense. Thank you both very much, I'm still getting a grasp of the language.

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.