Why no warning about superfluous mut

Why does the following code not issue any warnings that the mut in foo is superfluous?

fn main() {
    let mut x = X { x: 10 };
    foo(&mut x);
}

fn foo(x: &mut X) {
    println!("{}", x.x);
}

struct X {
    x: usize,
}

playground

I would say because this allows you to later change foo's implementation if you want to. Declaring foo as fn foo(x: &mut X) means that foo can modify *x if it wants to.

I'd say it's not superfluous - that mut is part of the function signature and therefore part of your API.

In general, the compiler can't tell whether the mut in an &mut T parameter is superfluous because we doing something like let f: fn(&mut X) = foo; or passing it to a closure which expects some F: Fn(&mut X). Telling the user they should remove the mut could break code.

I guess it could be a good candidate for a "pedantic" clippy lint, though.

4 Likes

I want to point out something that trips people up sometimes; you may already know this.

The mutability of a binding is basically just a lint. It is not part of the type of the variable at all; it's not a property that values carry around with them -- it's a lint on how you can use a specific binding.

// `hw` is a `String` with a non-mutable binding
let hw = "Hello".to_string();
// `hw` is still a `String` but we've rebound it as mutable
let mut hw = hw;
// The underlying value is not immutable, as we own it
hw.push_str(", World!");
println!("{hw}");

References are a different story. &i32 and &mut i32 are two different types. You can't turn a &i32 into a &mut i32 and they behave differently in other deep ways.

I think more generally if you specify a specific type, the compiler assumes you knew what you wanted / treats it as a mandate.

6 Likes

The compiler doesn't know your intent with foo. For all it knows, there might be a reason that it only makes sense with exclusive access. Certainly as a human you can tell that it's not important in this case, but imagine if there was an AtomicUsize in there — the exclusive reference could mean that you can do a non-synchronized read since the type system says that's ok in a way that wouldn't be if it took a shared reference.

3 Likes