The address of a variable and the address of its value

There's a code:

fn main() {
    let val_a: i32 = 10;
    let ref_a = &val_a;
    println!("{}", *ref_a);
}
  1. The &val_a is getting the address of a variable;
  2. The *ref_a is dereferencing the reference ref_a, namely getting the value pointed to by ref_a.
  3. Do I understand correctly that it is impossible to obtain the address of a variable’s value on the stack, but it is possible on the heap?

Obtaining the address of the value of a variable on the heap:

fn main() {
    let val_a: Box<i32> = Box::new(10);   // Create a value on the heap
    let address_of_val_a = &*val_a;       // Get the address of the value on the heap

    println!("Address of value val_a: {:p}", address_of_val_a);
}

Is this right?

Works for me:

fn main() {
    let val_a: i32 = 10;
    let address_of_val_a = &val_a;

    println!("Address of value val_a: {:p}", address_of_val_a);
}

Playground.

1 Like

I mean a little different. Are the address of a variable and the address of its value on the stack the same thing or not?

Rust doesn't have a runtime in which variables would still exist in the generated artefact. So no, the variable doesn't have an address.

2 Likes

Is the &val_a getting the address of a variable's value ?

Yes. From the reference:

A local variable (or stack-local allocation) holds a value directly, allocated within the stack's memory. The value is a part of the stack frame.

What you get with &val_a is the address of the value in the stack frame.

1 Like

Thanks!

1 Like

What if the variable is on the heap? Then the variable also has no address?

Something to perhaps be aware of is that addresses in Rust aren't as, how to say, central as they can be in languages that box everything and don't have zero-sized types, etc. For example, &x == &y doesn't compare the addresses of the references; instead it does the same thing as x == y.

Trying to use pointer identity has some hitches around zero-sized types, slices, and trait objects. It's not impossible to work with, but it's more niche than some languages.

Finally a note about the examples in this thread: If you don't take a reference to a local variable,[1] the local variable might not have an address at all -- it could live entirely in a register (or be entirely optimized away for that matter).


  1. or conceivably even if you do but the compiler can determine the address itself isn't "observed" ↩︎

7 Likes

I see. Thanks!

And if the variable is on the heap? Considering that its data will also be on the stack (ptr, len, capacity ...), it turns out that the variable also has no address?

Is this also true for a global variable or only for a local one?

What is true? Static items are baked into your binary[1] and not allocated on the stack.


  1. sorry, I don't really have the vocabulary, I'm not a compiler engineer ↩︎

1 Like

A value stored in the heap has an address.[1] There's either no such thing as a variable on the heap, or I'm failing to understand what you mean by "variable [...] on the heap".

Since you mention pointer/length/capacity, perhaps you mean a Vec<T>.

The pointer/length/capacity of Vec<T> are stored inline. The Ts (if there are any) are stored on the heap.[2] This is a guarantee of Vec<_> and not a general statement about Rust. For example, the pointer of a &[T] can point to the heap, the stack, static memory, etc.

What's the address of the pointer/length/capacity triple that comprises the Vec<_>? It depends. Those could be in registers and have no address. It could be in static memory. It could be on the stack. You could put the Vec in a Box or Arc or HashMap or another Vec, etc, in which case it would be on the heap.

Globals aren't on the stack. Arguably they shouldn't be called variables.

It's relatively rare to have globals which can be mutated, though there are some niche exceptions. The most common exceptions are behind some sort of abstraction.


  1. Insert here a number of caveats about zero-sized types. ↩︎

  2. Unless they're zero-sized, in which case, they need not be stored at all. ↩︎

6 Likes

In

let val = vec![0i32];

roughly the following logically happens:

  • A heap allocation is made, and 0i32 is written to it.
  • Stack space is reserved, and written to it is the pointer to, inhabited length of, and capacity of the heap allocation.
  • The name val is recorded in the compiler as referring to the value in this stack space.

In

let r1 = &val;

roughly the following logically happens:

  • Stack space is reserved and remembered by the name r1.
  • The compiler looks up the name val.
  • The address of the stack space called val is written to the stack space called r1.

In

let r2 = &*val;

roughly the following happens:

  • Stack space is reserved and remembered by the name r2.
  • The compiler looks up the name val and loads the value stored there.
  • The loaded value is dereferenced, finding the place previously allocated.
  • The address of that place is written into r2.

Much of this bookkeeping happens ahead of time when you build the code (none of the variable names "exist" at runtime, only the stack space they name), and even more of it when you compile in release mode with optimizations. So long as the output executable has the same observable behavior as this abstract description of what happens, the compiler is allowed to "cheat" in arbitrary ways.

Disclaimer: I'm a member of T-opsem, but this is just an informal and non-normative description of what happens.

5 Likes

I apologize for the inaccuracy. I thought too late about the stupidity of the question. Of course, global variables are not stored on the stack

Let me see if I understand this

That's correct. Here another diagram.

1 Like

Thanks)! :slightly_smiling_face: