5

I know that a Rust reference is much like a C pointer, and I think of Rust references as C pointers all the time. After some experiments and searching, I'm confused.

I'm familiar with C and I've read What's the difference between placing “mut” before a variable name and after the “:”?, which gives the following table:

// Rust          C/C++
    a: &T     == const T* const a; // can't mutate either
mut a: &T     == const T* a;       // can't mutate what is pointed to
    a: &mut T == T* const a;       // can't mutate pointer
mut a: &mut T == T* a;             // can mutate both

The post is upvoted so I assume that it is correct.

I wrote the following Rust code

fn main() {
    let mut x = 10;
    let x1 = &mut x;
    let x2 = &x1;
    let x3 = &x2;
    ***x3 = 20;
}

in the hope that it is equivalent to the following C code

int main() {
    int x = 10;
    int *const x1 = &x;
    int *const *const x2 = &x1;
    int *const *const *const x3 = &x2;
    ***x3 = 20;
    return 0;
}

The Rust code doesn't compile:

error[E0594]: cannot assign to `***x3` which is behind a `&` reference
 --> src/main.rs:6:5
  |
6 |     ***x3 = 20;
  |     ^^^^^^^^^^ cannot assign

What's wrong here?

Strangely enough, the following code compiles!

fn main() {
    let mut x = 10;
    let mut x1 = &mut x;
    let mut x2 = &mut x1;
    let mut x3 = &mut x2;
    ***x3 = 20;
}

Why should let mut x1/2/3 be used instead of just let x1/2/3? I think of let x1 = &mut x as a constant pointer pointing to a mutable variable x, but it doesn't seem to be right in Rust. Is that Stack Overflow post inaccurate or I misunderstand it?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Dev-XYS
  • 249
  • 2
  • 5

2 Answers2

5

There are a few differences between Rust and C which do not show up in the table you have referenced in your question.

  1. In Rust, mutability is a property of the binding rather than of the type.

  2. Rust has strict aliasing rules, such that you cannot have more than one mutable reference to any variable at one time.

Your question (simplified) is: why can't I have a non mutable reference to a mutable variable, and mutate that variable through it. However, if you could do that you could also have two references which could be used to modify the variable, like this:

let mut x = 10;
let x1 = &mut x;

let x2 = &x1;     // Non mutable reference to x1, ok
let x3 = &x1;     // Another non mutable reference to x1, ok

**x2 = 20;        // uhoh, now I can mutate 'x' via two references ... !
**x3 = 30;

Regarding your C equivalent to the given Rust code - you have not translated it according to the table. Consider this:

let x2 = &x1;

From the table in the answer you quoted:

a: &T == const T* const a; // Can't modify either

In this case, T would be const int*. So, it would be:

const int* const* const x2 = &x1;

Your whole program would be:

int main() {
    // let mut x = 10;
    int x = 10;

    // let x1 = &mut x;
    // a: &mut T == T* const a with T=int
    int* const x1 = &x;

    // let x2 = &x1;
    // a: &T     == const T* const a with T = int* const
    const int* const* const x2 = (const int* const* const) &x1;

    // let x3 = &x2;
    // a: &T     == const T* const a with T = const int* const* const
    const const int* const* const* const x3 = &x2;

    ***x3 = 20;
    return 0;
}

Note that a cast is needed to avoid warnings in the assignment of x2. This is an important clue: we are effectively adding const-ness to the pointed to object.

If you try to compile that you get:

t.c: In function ‘main’:
t.c:17:11: error: assignment of read-only location ‘***x3’
     ***x3 = 20;
           ^
harmic
  • 28,606
  • 5
  • 67
  • 91
  • From the table in the question, `let x1 = &mut x2;` should translate as `int* const x1 = &x2;` (third line in the table). Then each stage should add a `const` in front of the type and one behind (first line). – Jmb Sep 22 '20 at 06:28
  • 1
    I don't think you mean to have `const const`. If `T` is `const int *` then `const T *const` is `const int *const *const`. It would be less confusing to use "east const" in this case: `&T == T const *const`. – Tavian Barnes Sep 22 '20 at 19:46
  • Thanks @Jmb, fixed. That's what comes of trying to write too much C jibberish too quickly! – harmic Sep 22 '20 at 21:45
  • 1
    Thanks @TavianBarnes. I had it correct in the program body but not in the top part. I'm not sure I like the east const version any more than the west though – harmic Sep 22 '20 at 21:48
  • I don't really like east const either, but it does allow simple textual substitution like `T -> T const *const` to do the right thing in more cases. – Tavian Barnes Sep 22 '20 at 21:52
-2

In Rust it's a bit different. The & sign means reference to something and the * sign means dereference something. If my memory serves me correct, the C/C++ syntax makes use of the '->' symbol (Which is a way of dereferencing), which doesn't appear in Rust.

The hard part in Rust is keeping track of what's being borrowed by whom. Once you've understood how that works, and understanding what datatypes make use of what (Vec! makes use of the heap for instance): You should be pretty proficient in Rust!