Dereference
In Rust, the dereference operator (*
) is crucial when working with references to access the underlying values. It is especially relevant in scenarios involving borrowed data, such as when iterating over collections like arrays or vectors. When using iterators, like in a for
loop, the iterator provides references to the elements, requiring the dereference operator to access the actual values for comparison or modification.
Similarly, when dealing with raw pointers or creating mutable references, the dereference operation becomes essential to interact with the data they point to. Understanding when and how to use the dereference operator is fundamental for effective memory management and manipulation of borrowed data in Rust.
fn main() {
let needle = 88;
let haystack = vec![3, 5, 7, 88, 1, 3, 6];
for ele in haystack {
if ele == needle {
println!("{}", ele);
}
}
println!("{:?}", haystack);
// This will not compile because the loop would consume the Vec
}
The compiler tries to help :
help: consider iterating over a slice of the `Vec<i32>`'s content to avoid moving into the `for` loop
|
6 | for ele in &haystack {
|
If we just add & before haystack we still have a problem however :
fn main() {
let needle = 88;
let haystack = vec![3, 5, 7, 88, 1, 3, 6];
for ele in &haystack {
if ele == needle {
println!("{}", ele);
}
}
println!("{:?}", haystack);
// This will not compile because the loop would consume the Vec
}
if ele == needle {
| ^^ no implementation for `&{integer} == {integer}`
How do we fix it?
fn main() {
let needle = 88;
let haystack = vec![3, 5, 7, 88, 1, 3, 6];
for ele in &haystack {
if *ele == needle {
println!("{}", ele);
}
}
println!("{:?}", haystack);
// This does compile !
}
note the “*” has been added on line 7
Or we user iter()
fn main() {
let needle = 88;
let haystack = vec![3, 5, 7, 88, 1, 3, 6];
for ele in haystack.iter() {
if *ele == needle {
println!("{}", ele);
}
}
println!("{:?}", haystack);
}
A brief note about : println!
In Rust, the println!
macro automatically dereferences references, allowing direct printing of values. It internally uses the std::fmt::Display
trait, which is implemented for many types, including references. This enables seamless display without explicitly using the dereference operator (*
).
Iter method
In Rust, the iter()
method is often used to create an iterator over a collection, allowing for controlled and safe traversal of its elements. The primary scenarios where iter()
is beneficial include:
- Read-Only Iteration: When you want to iterate over the elements of a collection without modifying them, using
iter()
provides immutable references to each element, ensuring the original data remains unchanged. - Working with Borrowed References: When interacting with functions or methods that expect references rather than ownership of the collection,
iter()
allows you to provide references to the elements. - Avoiding Ownership Transfer: When using a
for
loop withoutiter()
, the loop takes ownership of the collection. If you want to retain ownership for further use,iter()
ensures that only references are taken, preserving ownership.
In summary, iter()
is useful for controlled, read-only iteration, working with borrowed references, and avoiding ownership transfer during iteration.
why do we need to use * with numbers, as they’re on the stack surely?
when you have a reference to a primitive numeric type (e.g., i32
, f64
), you do not need to use the dereference operator (*
) when comparing or using them in most contexts. This is because Rust performs automatic dereferencing in certain situations, known as “deref coercion.”
fn main() {
let number = 42;
let reference = &number;
// Automatic dereference, no need for `*`
if *reference == 42 {
println!("It's 42!");
}
}
However, there are cases where you may need to use *
explicitly. For instance, when working with more complex types or custom structs, you might need to dereference to access the underlying value. Additionally, when dealing with raw pointers or specific scenarios, explicit dereferencing may be necessary for clarity.
In summary, for primitive numeric types and common situations, Rust’s automatic dereferencing simplifies code, but in some cases, you may still use *
for clarity or when working with custom types.
Or altering a value “in place” ?
Yes, when you want to modify a value in place through a reference, you would need to use the dereference operator *
to access the underlying value. Here’s an example:
fn main() {
let mut number = 42;
let reference = &mut number;
// Modifying the value in place using explicit dereference
*reference += 10;
println!("Modified value: {}", number); // Prints: Modified value: 52
}
In this example, *reference += 10;
modifies the value referred to by the mutable reference reference
. The dereference operator is used to access the actual value and perform the modification in place.
So is a reference like a pointer?
Yes, in Rust, a reference is similar to a pointer in other languages. It’s a way to refer to a value without taking ownership, allowing borrowing and sharing of data. Unlike raw pointers, Rust’s references are subject to strict ownership and borrowing rules, promoting memory safety.
Do they contain anything else, eg length, capacity?
No, in Rust, references themselves do not contain information about the length or capacity of the data they point to. References are lightweight and essentially just point to a memory location. The length and capacity of a collection, like a Vec
or a String
, are properties of the collection itself, not of the reference to it.
If you need to know the length or capacity, you would typically query those properties directly on the collection, not on the reference to the collection.
let vec = vec![1, 2, 3, 4, 5];
// Length of the vector
let length = vec.len();
// Capacity of the vector
let capacity = vec.capacity();
In this example, length
and capacity
are properties of the Vec
, not of any references to it.
Conclusion
In Rust, the dereference operator (*
) is used to access the value behind a reference. It’s essential for modifying values through references and iterating over collections, while automatic dereferencing simplifies operations on primitive types in certain contexts.