Variables

Terms

  • Place: a location that can hold a value on the stack, heap, registers etc.
  • Pointer: An address to a place
  • Variable: named value slot on the stack
  • Value: combination of type and an element from that types domain of values
  • Representation: Used for a type to turn a sequence of bytes to a value

Example 1

Using that terminology lets break down a simple program


#![allow(unused)]
fn main() {
let x: u32 = 10;
let px: &u32 = &x;
println!("x: {}", px);
}
x: 10

Create variable named x with value 10. The stack now has a place with a sequence of bytes that can be converted back into a value using the u32 types representation


#![allow(unused)]
fn main() {
let x: u32 = 10;
}

Create a variable named px that contains a value of type pointer to the memory address of the place where we created the value 10


#![allow(unused)]
fn main() {
let px = &x;
}

Dereference the variable named px to access the place, printing its value by using the u32 types representation to convert it from a sequence of bytes to a value


#![allow(unused)]
fn main() {
println!("x: {}", px);
}
x: 10

the println macro in rust will dereference any values that are pointers, so you can omit the * in *px. To print the actual address (the value of the pointer) try:


#![allow(unused)]
fn main() {
println!("x: {:p}", px);
}
x: 0x7ffdc852a9ac

Example 2

Important to clearly understand what the value is in a pointer type:


#![allow(unused)]
fn main() {
let x: u32 = 10;
let y: u32 = 20;
let px1 = &x;
// Set the `value` of px2 to an address pointing to the `place` in memory containing the value 10
let mut px2 = &x;
println!("px1 value: {:p} dereferenced: {}", px1, px1);
println!("px2 value: {:p} dereferenced: {}", px2, px2);
// Update the `value` of px2 to an address pointing to the `place` in memory containing the value 20
px2 = &y;
println!("px2 value: {:p} dereferenced: {}", px2, px2);
}
px1 value: 0x7ffd4e2ef4a8 dereferenced: 10
px2 value: 0x7ffd4e2ef4a8 dereferenced: 10
px2 value: 0x7ffd4e2ef4ac dereferenced: 20

The part where people get confused is that rust automatically dereferences a value on a lot of occasions, go over the previous code to convince yourself that px2's value is an address of type &32, not a u32.

Flows

Sometimes described as dependency lines, they track the lifetimes of values. Looking at a simple program:


#![allow(unused)]
fn main() {
// Create 1st flow
let mut x = 5;
//  Continue 1st flow
let y = &x;
// Create a 2nd flow
x = 10;
// Continues 1st flow, conflicting with 2nd flow
println!("{}", y);
}
warning: value assigned to `x` is never read
 --> src/main.rs:7:1
  |
7 | x = 43;
  | ^
  |
  = note: `#[warn(unused_assignments)]` on by default
  = help: maybe it is overwritten before being read?

error[E0506]: cannot assign to `x` because it is borrowed
 --> src/main.rs:7:1
  |
5 | let y = &x;
  |         -- borrow of `x` occurs here
6 | // Now there are two mutable flows. No error yet
7 | x = 43;
  | ^^^^^^ assignment to borrowed `x` occurs here
8 | // Th
9 | println!("{}", y);
  |                - borrow later used here

For more information about this error, try `rustc --explain E0506`.
warning: `output` (bin "output") generated 1 warning
error: could not compile `output` due to previous error; 1 warning emitted

There cannot be exclusive and shared use of a value at the same time. If the print statement was omitted, the compiler would detect that 1st flow wasn't used again, so it would compile:


#![allow(unused)]
fn main() {
let mut x = 5;
// y is never used again
let y = &x;
x = 10;
println!("2nd flow x: {}", x)
}
2nd flow x: 10

#![allow(unused)]
fn main() {
let mut x = 42;
let y = &mut x;
*y = 10;
println!("{}", y)
}
10