freinn
freinn

Reputation: 1079

How can a function argument allow mutating a variable but not be capable of pointing to any other variable?

I want a function that calls other functions that are mutually recursive, but I already have that with type signatures like:

fn f1(mut index: &mut usize, ..)
fn f2(mut index: &mut usize, ..)

I really want a set of mutually recursive functions that can only mutate the index variable I've defined in my main() function, and are not capable of pointing to any other variable.

I've read the 2 answers from What's the difference in `mut` before a variable name and after the `:`?, and I've tried several approaches but still I can't achieve what I want. I think I don't really understand the concepts.

Here is evidence of my current understanding:

// with &mut I can change the referred value of n, but then I can't
// pass a mutable reference anywhere
fn mutate_usize_again(n: &mut usize) {
    *n += 1;
    // n += 70; ^ cannot use `+=` on type `&mut usize`
}

fn mutate_usize_two_times(mut n: &mut usize) {
    *n = 8;
    // if I don't write mut n, I can't pass a mutable reference to
    // the mutate_usize_again function
    mutate_usize_again(&mut n);
}

fn mutate_usize_one_time_referred_value(n: &mut usize) {
    *n += 25;
}

// this changes the referred value of n
fn mutate_usize_one_time_mutable_pointer(mut n: usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = 48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

// doesn't work because of lifetimes
// this changes where is pointing a (Copy?) reference of n
// passed value does not change
/*
fn mutate_usize_one_time(mut n: &usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = &48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}
*/

fn main() {
    let mut index = 0;
    mutate_usize_one_time_mutable_pointer(index);
    println!("index after mutate_usize_one_time_mutable_pointer = {}", index);
    mutate_usize_two_times(&mut index);
    println!("index after mutate_usize_two_times = {}", index);
    mutate_usize_one_time_referred_value(&mut index);
    println!("index after mutate_usize_ = {}", index);
}

I would really appreciate a nice explanation of what is going on in my code if I've misunderstood.

I'm starting to think that what I want is already done given:

  1. index must refer to its updated value => mut index
  2. index must be capable of changing the referred value and pass mutable references to the other functions. => &mut usize
  3. If it were another function parameter with the same type (mut index2: &mut usize), the compiler would not let me have 2 mutable references pointing to the same memory location.

Upvotes: 0

Views: 1442

Answers (3)

Vitali Pom
Vitali Pom

Reputation: 602

Considering your title, you're confused on the very principle. Short answer that strikes it all:

You shall always follow the even rule. You have same amount of slashes and same amount of dereferences. Together they give an even number.

Upvotes: -5

Tatsuyuki Ishi
Tatsuyuki Ishi

Reputation: 4031

A reference is a variable that points to something else. References do not collapse, so in Rust you can have a reference to a reference.

Your linked question describes what's different between x: &mut T and mut x: T. mut x: &T means you can change where the reference points to, but you can't change the value what it points to.

Non-reference values will be copied when passed to functions (or moved, if not implementing Copy).

A working example:

// with &mut I can change the referred value of n
fn mutate_usize_again(n: &mut usize) {
    *n += 1;
    *n += 70;
}

fn mutate_usize_two_times(n: &mut usize) {
    *n = 8;
    // just pass it along
    mutate_usize_again(n);
}

fn mutate_usize_one_time_referred_value(n: &mut usize) {
    *n += 25;
}

// this changes the **local** value of n
fn mutate_usize_one_time_mutable_pointer(mut n: usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = 48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

fn main() {
    let mut index = 0;
    mutate_usize_one_time_mutable_pointer(index);
    println!("index after mutate_usize_one_time_mutable_pointer = {}", index);
    mutate_usize_two_times(&mut index);
    println!("index after mutate_usize_two_times = {}", index);
    mutate_usize_one_time_referred_value(&mut index);
    println!("index after mutate_usize_one_time_referred_value = {}", index);
}

Upvotes: 2

Matthieu M.
Matthieu M.

Reputation: 300229

Ah! The old pointer/pointee issue. Don't worry, it's a common stumbling block. Keep at it, at some point it'll click and then it'll just seem obvious.

What is a reference?

A reference is an indirection. The address of a thing that exists somewhere else.

Let's use an analogy:

  • a function frame is a closet, with lots of drawers,
  • each drawer may contain one value.

For the moment, let's forget about types, stack and heap: we only have closets and drawers:

  • a value is in a drawer, it may moved or copied to another drawer,
  • a reference is the address of this value, represented as a pair of IDs (closet ID, drawer ID).

Note: it is nonsensical to mention just a drawer ID, all closets have a drawer 0...


How to use a drawer?

Let us imagine a (typeless) simple example, the addition function:

fn add(left, right) { left + right }

Now, what happens when we call add(3, 4)?

Function
  call

+-add-+  <-- New closet
|  3  |  <-- Drawer 0: value 3
+-----+
|  4  |  <-- Drawer 1: value 4
+-----+

Note: in the following, I'll represent a closet as an array. This closet would be [3, 4]. I'll also "number" the closets using letters, to avoid mixing closets and drawers, so this closet could be d, and the first drawer of d would be 0@d (which contains 3 here).

The function is defined as "take the value in drawer 0, take the value in drawer 1, add them together in drawer 2, return content of drawer 2".

Let's spice things up; and introduce references:

fn modify() {
    let x = 42;
    let y = &x;
    *y = 32;
}

What does modify do?

  • call modify => a fresh closet is given to us a: [],
  • let x = 42; => our closet is now a: [42],
  • let y = &x; => our closet is now a: [42, 0@a],

And now comes the crux: *y = 32;. This means put 32 in the drawer pointed to by y. And thus the closet is now: a: [32, 0@a].


Your first example

Let's look at your first example, put in situation with a main:

// with &mut I can change the referred value of n, but then I can't
// pass a mutable reference anywhere
fn mutate_usize_again(n: &mut usize) {
    *n += 1;
    // n += 70; ^ cannot use `+=` on type `&mut usize`
}

fn main() {
    let mut x = 24;
    mutate_usize_gain(&mut x);
}

So, what's going on here?

  • call main => a fresh closet is allocated a: [],
  • let mut x = 24; => a: [24],
  • call mutate_usize_again => a fresh closet allocated b: [],
  • with &mut x => b: [0@a],
  • *n += 1; => add 1 to the drawer pointed to by n (0@a), this means that b does not change, but a does! Now a: [25].
  • n += 70 => add 70 to a reference... this does not make sense, a reference does not have arithmetic operations.

Your second example

Let's move on to your second example, augmented:

// Parameter renamed because it's confusing to always use the same letter
fn mutate_usize_again(z: &mut usize) { *z += 1; }

fn mutate_usize_two_times(mut n: &mut usize) {
    *n = 8;
    // if I don't write mut n, I can't pass a mutable reference to
    // the mutate_usize_again function
    mutate_usize_again(&mut n);
}

fn main() {
    let mut x = 24;
    mutate_usize_two_times(&mut x);
}

We'll need 3 closets!

  • call main => a fresh closet is allocated a: [],
  • let mut x = 24 => a: [24],
  • call mutate_usize_two_times => a fresh closet is allocated b: [],
  • with n: &mut usize = &mut x => b: [0@a],
  • *n = 8 => stores 8 in the drawer pointed to by n: a: [8], b unchanged,
  • call mutate_usize_again => a fresh closet is allocated c: [],
  • with z: &mut usize = &mut n => ERROR. The type of &mut n is &mut &mut usize which cannot be assigned to a parameter of type z,
  • let's pretend you used z: &mut usize = n => c: [0@a],
  • *z += 1 => adds 1 to the value in the drawer pointed to by z: a: [9], b and c unchanged.

You can take a reference to a reference, but it's not what you meant to do here. We can indeed twist the example to making it work though by defining:

 fn mutate_usize_again(z: &mut &mut usize) { **z += 1; }
                          ^                   ^~~ dereference TWICE
                          |~~~~~~~~~~~~~~ two levels of reference

In this case, starting again from the call to mutate_usize_again:

  • before call, we had: a: [8], b: [0@a]
  • call mutate_usize_again => a fresh closet is allocated c: [],
  • with z: &mut &mut usize = &mut n => c: [0@b] <- a reference to a reference!
  • **z += 1 => adds 1 to the value in drawer pointed to by the reference pointed to by z. **z += 1 is **0@b += 1 which is *0@a += 1, which modifies a to be a: [9] and leaves b and c unchanged.

Your third example

Your third example, enhanced:

fn mutate_usize_one_time_mutable_pointer(mut n: usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = 48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

fn main() {
    let x = 42;
    mutate_usize_one_time_mutable_pointer(x);
}

Let's go:

  • call main => a fresh closet is allocated a: [],
  • let x = 42; => a: [42],
  • call mutate_usize_one_time_mutable_pointer => a fresh closet is allocated b: []
  • with n: mut usize = x => b: [42],
  • n = 48 => modifies the value of n: b: [48], note that a is unchanged
  • end of mutate_usize_one_time_mutable_pointer, discards b, a: [42].

There is no pointer involved here, the name of the function is misleading.


Your final example

Your fourth example, augmented:

fn mutate_usize_one_time(mut n: &usize) {
    println!("n before assigning in mutate_usize_one_time = {}", n);
    n = &48;
    println!("n after assigning in mutate_usize_one_time = {}", n);
}

fn main() {
    let x = 42;
    mutate_usize_one_time(&x);
}

Let's go:

  • call main => a fresh closet is allocated a: [],
  • let x = 42; => a: [42],
  • call mutate_usize_one_time => a fresh closet is allocated b: [],
  • n: &size = &x => b: [0@a],
  • 48 => b: [0@a, 48]
  • &48 => b: [0@a, 48, 1@b]
  • n = &48 => would lead to b: [1@b, 48] however lifetimes forbid references pointing to something past them, this is forbidden.

Hope it's starting making sense. If not... then go for a run, go out with friends, clear your head, read another explanation, clear your head again, and come back again. It'll seep in little by little ;)

Upvotes: 7

Related Questions