Why is there no `mut T` like there is `&mut T`?

⚓ Rust    📅 2025-12-23    👤 surdeus    👁️ 1      

surdeus

First, I started with the question: Why is mut necessary for owned types? Through some reading, trying to understand that question, I found myself more confused by a seeming inconsistency/inadequacy of Rust's design choices when it comes to mut for owned types.

fn func1(mut x: String) { x.push_str("push to x") }

Why is, in this situation, the mut required on the x param?

Without it, E0596 occurs; cannot borrow x as mutable, as it is not declared as mutable. Okay, sure, that makes enough sense.

fn func2(mut y: String) { func1(y) }

Now, I go and write func2 and because I call func1, which mutates x, I assume I need to specify that y is mutable. Wrong! The compiler informs me that variable does not need to be mutable. So, I am allowed to rewrite func2 without the mut.

fn func2(y: String) { func1(y) }

No more warnings, all works.. but why? In func2, if I try to directly call the push_str(&str) method, I would have to bind y to a mutable local variable, either through the mut declaration shorthand or explicitly with let mut mut_y = y.

I did some readings in the Rust documentation, as well as some forum posts (linked below). From that, I got a better understanding of the mut x: T in the parameter of a function syntax and how the mut on the left side of the : does not affect the type the function consumes.

But it still seems to me that there is an inconsistency here: if a function is always allowed to mutate the value of a local variable by merely moving it within the same scope, why require the move to a mut variable at all? Why not just let an owned value be mutated by the owner scope? I'm having trouble seeing the benefit of the explicit use of mut if any type T can be expanded to be mutable at any time by a simple move, succinctly demonstrated but not explained as to why in Rust By Example.

With references, there seems to be more clear consistency: &T can never become &mut T. This makes a lot of sense; if the owner gave you an immutable reference, you shouldn't be able to expand your access to the value to mutate the referred value.

And with owned types T, as the owner, you do always have the ability to gain mutability with let mut mut_x: T = x, so what is the added benefit of having to explicitly do so? Why not, instead, always allow mutability of owned values? I get that it's nice to know that your value won't be mutated, but if you move it to some function, you don't know that it won't be mutated. And further, you can always just rebind it and change the value in your own scope, making it unclear from the initial declaration if the value ever changes.

// without the mut, you know that 
// x will never change (supposedly)
let x = String::from("foo");

/* some code */

// but x not changing doesn't mean the 
// value that x represents can't change
// because you can just move it to a 
// mutable variable and now it can be changed
let mut mut_x = x;

// you could even do `let mut x = x;`
// to make it extra confusing to read
// and determine what can and can't
// mutate throughout the function

What benefit do you really get from knowing that x won't be changed without knowing whether the value behind x will change? If owned types are always subject to mutating, why bother with ever requiring the mut?

Also, this could go the other direction: why can you rebind variables at any time to make them mutable? Why isn't the mutability of owned types a part of the type the way mutability of references is a part of the type. i.e. why not have let x: mut String = mut String::new() be the way mutable values are declared? And then String would be a type mismatch with mut String the same way &String is a type mismatch with &mut String.

Personally, I think this is the correct direction: you should have to be explicit about whether something will mutate. And I think the mutability being a part of the type would make sense as a way to truly provide this for owned types. As an example, Kotlin has mutability explicitly defined for both the variable and the type.

var list: List<Int> = listOf(0, 1, 2)
list.add(3) // fails, type List is immutable
list = listOf(0, 1, 2, 3) // succeeds, vars can be reassigned

val mutableList: MutableList<Int> = mutableListOf(0, 1, 2)
mutableList = mutableListOf(0, 1, 2, 3) // fails, val cannot be reassigned
mutableList.add(3) // succeeds, type MutableList is mutable

I don't think Kotlin's solution here is a perfect fit for Rust; I merely use it to illustrate how mutability could be a property of a type, while separately being a property of the binding. As far as I understand, Rust already provides this for reference types. Why not owned?


21 posts - 7 participants

Read full topic

🏷️ Rust_feed