Memory dynamics with reference vs raw pointers

Hello all! I was going through the book, 'Rust in Action', when I came across this code example which was trying to introduce the idea of segmentation faults which may arise due to illegal memory accesses when using a raw pointer in Rust.

fn main() {
    let mut n_nonzero = 0;

    for i in 0..10000 {
        let ptr = i as *const u8;
        let byte_at_addr = unsafe { *ptr };

        if byte_at_addr != 0 {
            n_nonzero += 1;
        }
    }

    println!("non-zero bytes in memory: {}", n_nonzero);
}

This gives a Segmentation fault (core dumped) output. (since ptr is trying to dereference a NULL pointer when i = 0, as stated in the book)
From what I've read, references in Rust are a way of avoiding this by providing more safety guarantees. But how exactly does it do this, e.g. in this example? A quick change to this code example to replace the use of raw pointers with references produces a successful output:

fn main() {
    let mut n_nonzero = 0;

    for i in 0..10000 {
        let ptr = &i;
        let byte_at_addr = *ptr;

        if byte_at_addr != 0 {
            n_nonzero += 1;
        }
    }

    println!("non-zero bytes in memory: {}", n_nonzero);
}

This successfully prints: non-zero bytes in memory: 9999. How has this change from raw pointers to references successfully 'avoided' this illegal memory access and hence the segmentation fault?

These two pieces of code do very different things. The unsafe variant casts an integer to a pointer, and dereferences it. The safe variant creates a reference to an existing variable and dereferences it. So that's why there is the difference. There is no safe way to cast an integer to a reference, so you can't actual do the first variant in safe Rust.

1 Like

Oh, so in this case even if I were to cast an integer to a reference instead of a pointer is still inherently unsafe and does not provide any additional safety guarantees over a raw pointer.

I think I understand now. Thank you!

Well, if it compiled yes. Rust won't conpile int as &u8, you would first have to cast to a raw pointer, then use unsafe to convert that to a reference. But that unsafe conversion is wrong, so this entire operation is UB.

I see, thank you for the clarification!

Just to say it in another way: if you ignore the optimizations enabled by the immutability or uniqueness guarantees of references, two different pieces of Rust source using references vs. raw pointers in the same way will compile down to the same machine code.

So, compared to raw pointers, references don't actively do anything (in particular, they are not doing anything at runtime and do not provide garbage collection); their power and increased safety lies purely in the compile-time verification of lifetimes and exclusive mutability ("the RWLock pattern").

4 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.