2
\$\begingroup\$

I'm new to Rust, and I'm writing a basic command line input parser as a learning exercise. Commands entered on the command line are evaluated using the Cmd struct which contains the name of the command and a function to call when invoked. Though the code works, I find the use of the get_invocation function to be a little weird.

pub struct Cmd<T: FnMut()> {
    name: String,
    invocation: T,
}

impl<T: FnMut()> Cmd<T> {
    pub fn new(name: &str, invocation: T) -> Cmd<T> {
        Cmd {
            name: String::from(name),
            invocation,
        }
    }

    pub fn invoke(self) {
        self.get_invocation()();
    }

    fn get_invocation(self) -> T {
        self.invocation
    }
}

I am aware that I could use (self.invocation)() to directly call my struct function in invoke(), but that would involve setting the invoke self parameter to mutable, which would require me to make each instance of Cmd mutable if I wish to use the invoke() function. This is something I would like to avoid as, conceptually, nothing in the struct is being mutated. Is there a less odd / more idiomatic way to deal with stored function invocation in Rust, while still maintaining immutability? I also would appreciate any other pointers/tips on my code.

\$\endgroup\$
2
\$\begingroup\$

There are two distinct applications of mutability in Rust:

  1. Bindings (variables) can be mutable or immutable.
  2. Borrows (references) can be mutable or immutable.

Here, you're not using any borrows, so we're only concerned with bindings.

Note that values are not intrinsically mutable or immutable. Therefore, when you move a value from one binding to another, you can change the mutability. The methods invoke and get_invocation take ownership of the Cmd, therefore they can decide to declare self as mutable, even if the original value is not declared as mutable. For example, this is valid:

impl<T: FnMut()> Cmd<T> {
    pub fn invoke(mut self) {
        (self.invocation)();
    }
}

fn help() {
    println!("not implemented yet :)");
}

fn main() {
    let cmd = Cmd::new("help", help);
    cmd.invoke();
}

If you were to take self by mutable reference, rather than by value, then the original variable would indeed need to be declared as mutable. You could not invoke the function with only an immutable reference, because FnMut closures can't be invoked from an immutable borrow. If that's a problem, switch to Fn instead.

impl<T: FnMut()> Cmd<T> {
    pub fn new(name: &str, invocation: T) -> Cmd<T> {
        Cmd {
            name: String::from(name),
            invocation,
        }
    }

    pub fn invoke(&mut self) {
        (self.invocation)();
    }

    fn get_invocation(&mut self) -> &mut T {
        &mut self.invocation
    }
}

fn help() {
    println!("not implemented yet :)");
}

fn main() {
    let mut cmd = Cmd::new("help", help);
    cmd.invoke();
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.