Using reference to do comparison instead of value

I am actually quite surprised that this code works. I was expecting Rust to use one address for 99 that is referenced by needle and another address for 99 that is referenced in the haystack array.
Can i expect that Rust compiler will always use the same address to need the value 99 ? I even changed this code to use Box::new and the behaviour is the same.

fn main() {
    let needle = 99;
    let haystack = [1,2,3,4,99];
    for reference in haystack.iter() {
        if reference == &needle {
            println!("found")
        }
    }
}

References don't behave like pointers in other languages, and comparison of references will never compare addresses.

== acts as if it's calling .partial_eq method, and will compare things semantically as implemented for each type.

4 Likes

If you want a comparison using pointers, you can cast the reference to a raw pointer, so this gives the behaviour you were expecting.

1 Like

Thank you for answering my question. But i am still confused. https://doc.rust-lang.org/reference/types/pointer.html It seems references are pointers.

Would you know how Rust lays out the values or references?
The following is what i thought it should be doing:

99 @ address 001
99 @ address 002

&needle = 001
reference = 002 (this is why *reference is 99)

reference == &needle should be false since 001 is not 002

Depends what you mean by both of those terms.

In the context of Rust, reference types (&T and &mut T) and raw pointer types (*mut T and *const T) are completely different kinds of types that simply aren't the same, just as i32 and u32 are not the same type. In particular, they have very different semantics, e.g. Rust references are guaranteed to always refer to a valid value, while Rust pointers can be null. And &mut T carries a uniqueness guarantee that no other references or pointers are referring/pointing to the same underlying value, but *mut T pointers are allowed to have aliases. These examples are also critical reasons why you can safely dereference a reference in safe Rust, but dereferencing a Rust raw pointer will always require unsafe code.

What is true is that typically most reference values and raw pointer values get compiled down to assembly/hardware pointers, if they didn't get optimized out entirely. But this fact has nothing to do with your original question.

As we've already said, this has nothing to do with any type's layout. In Rust, comparing references always means dereferencing and comparing the underlying values. reference == &needle has the same behavior as *reference == *&needle. This happens long, long before any concept of type layout even enters the picture.


Possibly helpful relevant pithy quotation:

4 Likes

A reference is in fact a kind of pointer, albeit one with extra compile-time safety checks ("the RWLock pattern"). However, this is not why your code behaves differently from what you expect.

Since a reference is most often used as a "handle" or "proxy" to the referred-to value, the equality comparison operator == in Rust (more precisely, its desugaring, the method <&T as PartialEq>::partial_eq()) does not compare the memory addresses of the referents.

If you look at the implementation, you can see that it simply delegates to the underlying type, i.e. it compares for "deep equality" or "semantic equality" of the values of the referents. This is merely a design decision in the implementation of the PartialEq trait, and it could be implemented differently, but it was decided that this makes more sense.

If you are interested in comparing memory addresses for equality, you can still use the function std::ptr::eq().

5 Likes

References are implemented using pointers, but thinking of them as merely C-like pointers will bring you pain and misery in Rust. References are semantically their own thing, with very specific behaviors around ownership and Rust-specific automatic dereferencing. They're more like a "handle" or a "lock" on an object.

In Rust & is not for taking an address. It is for borrowing an object, which creates a lifetime bound to a scope, and locks the object from being moved. The fact that there is some address behind the scenes in compiled code is only a necessary implementation detail, but not the point of references.

By analogy:

C pointers are implemented using integers, but they don't behave like integers (e.g. you can't multiply a pointer by pointer). And Rust references are implemented using pointers, but they don't behave like pointers (e.g. you can't hold data by reference or make a doubly-linked list).

7 Likes

Thank you everyone for taking time to clear my doubts. I think I have better understanding now