Why is the mutability of a variable not reflected in its type signature in Rust?
Asked Answered
P

3

11

As I understand, mutability is not reflected in variables type signature. For example, these two references have the same type signature &i32:

let ref_foo : &i32 = &foo;
let mut ref_bar : &i32 = &bar;

Why is this the case? It seems like a pretty major oversight. I mean, even C/C++ does this more explictly with having two const to indicate that we have a const pointer to const data:

const int * const ptr_foo = &foo;
const int * ptr_bar = &bar;

Is there a better way of thinking about this?

Photophore answered 13/1, 2020 at 10:16 Comment(4)
isnt mut there to indicate mutability?Silber
c++ also doesnt have const int& const is it that what you are missing?Silber
to eloborate on the last comment: you are comparing oranges with apples. The exmple for rust is about references (and there are no two consts in C++ for references) and the C++ example is about pointersSilber
In C++, references are fixed, they can not be reassigned to refer to something else. Therefore the one const associated with the & operator just refers to whether the data it is bound to is const.Photophore
C
21

Mutability is a property of a binding in Rust, not a property of the type.

The sole owner of a value can always mutate it by moving it to a mutable binding:

let s = "Hi".to_owned();  // Create an owned value.
s.push('!');              // Error because s is immutable.
let mut t = s;            // Move owned value to mutable binding.
t.push('!');              // Now we can modify the string.

This shows that mutability is not a property of the type of a value, but rather of its binding. The code of course only works if the value isn't currently borrowed, which would block moving the value. A shared borrow is still guaranteed to be immutable.

Mutability of references is orthogonal to mutability of bindings. Rust uses the same mut keyword to disambiguate the two types of references, but it's a separate concept.

The interior mutability pattern is again orthogonal to the above, as it is part of the type. Types containing a Cell, RefCell or similar can be modified even when only holding a shared reference to them.

It's a common pattern to rebind a value as immutable once you are done mutating a value:

let mut x = ...;
// modify x ...
let x = x;

Ownership semantics and the type system in Rust are somewhat different than C++, and I prefer the Rust way. I don't think it's inherently less expressive, as you seem to suggest.

Colorcast answered 13/1, 2020 at 11:15 Comment(3)
"Mutability of references is orthogonal to mutability of bindings." this really needs to be emphasized more clearly in the book.Photophore
One thing that I would like to have added to this answer is to state concisely what each mut on this line means: let mut ref_bar : &mut i32 = &mut bar;. To start with/for example: ref_bar is a reference that is mutable to a mutable i32, which is (mutually) exclusively bound to a i32 named bar?Photophore
@allsey87 The first mut (Reading from left to right) is part of the binding as shown above. The second mut belongs to &mut meaning the type of ref_bar is some kind of unique reference (or mutable reference, however you want to look at it) to some data (In this case an i32). The last mut once again belongs to &mut but this time it is an operator which means "take the unique reference to" (or mutable reference). The &mut operator creates &mut values, the & operator creates & values.Spermatophore
D
4

Constants in C++ and Rust are fundamentally different. In C++ constness is a property of any type, while in Rust it is a property of a reference. Thus, in Rust there are not true constant types.

Take for example this C++ code:

void test() {
    const std::string x;
    const std::string *p = &x;
    const std::string &r = x;
}

Variable x is declared of constant type, so any reference created to it will be also to constant, and any attempt to modify it (with const_cast for exampe) will render undefined behavior. Note how const is part of the type of the object.

In Rust, however, there is no way to declare a constant variable:

fn test() {
    let x = String::new();
    let r = &x;

    let mut x = x; //moved, not copied, now it is mutable!
    let r = &mut x;
}

Here, the const-ness or mut-ness is not part of the type of the variable, but a property of each reference. And even the original name of the variable can be considered a reference.

Because when you declare a local variable, either in C++ or Rust, you are actually doing two things:

  • Creating the object itself.
  • Declaring a name to access the object, a reference of sorts.

When you write a C++ constant you are making both constant, the object and the reference. But in Rust there are no constant objects, so only the reference is constant. If you move the object you dispose the original name and bind to a new one, that may or may not be mutable.

Note that in C++ you cannot move a constant object, it will remain constant forever.

About having two consts for pointers, they are just the same in Rust, if you have two indirections:

fn test() {
    let mut x = String::new();
    let p: &mut String = &mut x;
    let p2: &&mut String = &p;
}

About what is better, that is a matter of taste, but remember all the weird things that a constant can do in C++:

  • A constant object is always constant, except when it is not: constructors and destructors.
  • A constant class with mutable members is not truly constant. mutable is not part of the type system, while Rust's Cell/RefCell are.
  • A class with constant member is a pain to work with: default constructors and copy/move operators do not work.
Drescher answered 13/1, 2020 at 11:0 Comment(4)
That's all correct and fine, but how does it answer the question in the title? Why do both ref_foo and ref_bar seem to have the type &i32 even only the latter can be used as mutable?Ritualist
@BartekBanachewicz: It is because in C++ const is part of the type (const int & is a reference to a const int), while in Rust it is a property of the reference, &i32 is a const-reference to an i32.Drescher
"But in" what exactly?Medium
@Aiueiia: Do you mean the "But in" at the end of the paragraph? I think it is a copy/paste typo, fixing it now.Drescher
D
0

In C++ everything is mutable by default and the const keyword indicates that you want to change that behavior.

In Rust everything is immutable by default, and the mut keyword indicates that you want to change that behavior.

Note that for pointers, Rust does require either the mut or const keyword:

let ref_foo : *const i32 = &foo;
let mut ref_bar : *const i32 = &bar;

Your examples are therefore equivalent, but Rust is less verbose as it defaults to immutable.

even C/C++ does this better

Years of experiences in C++ and Rust development have convinced me that Rust's way of dealing with mutability (eg. defaulting to immutable, but there are other differences) is far better.

Derian answered 13/1, 2020 at 10:29 Comment(8)
This doesn't answer the question of why the fact that ref_bar is declared mut doesn't seem to be observed.Ritualist
What do you mean it's not observed?Derian
that the compiler lists both of those as &i32, even though it only allows modification to the latter.Ritualist
Probably reflected (in the type signature) is a better word/phrasing than observedPhotophore
@BartekBanachewicz The difference is one of the &i32 has mutable owner (the value that owner holds may change), and it doesn't allow modification to the latter please see : play.rust-lang.org/…Ganiats
@ÖmerErden not what I meant. I wasn't talking about modification to the pointee, but to the ref itself.Ritualist
@BartekBanachewicz the mutability of a binding doesn't need to be reflected in its type in Rust (what could you do at a type level with the knowledge that the binding is mutable that you can't do right now?)Derian
@Derian Anything you can do with the knowledge about mutability of any other object, I suppose? Thing is, it seems that references are a a special case that doesn't have this information available.Ritualist

© 2022 - 2024 — McMap. All rights reserved.