Have you recently seen the asterisk (*
) symbol in Rust while reading someone else’s code and you are not sure what it means? No worries, this article will explain the meaning of *
and show you how it can become handy during development.
The asterisk (*
) symbol is Rust’s dereference unary operator. The dereference operator extracts the value of a variable omitting the pointer or reference. To use the dereference operator, add the asterisk (*
) symbol right before a given variable T
. This will result in *T
which gets the value of T
.
Table of Contents
Understanding the Dereference Operator
Take a look at the following example to get a better understanding of the dereference operator.
fn main() {
let my_number = 1;
let other_number = *&my_number;
assert_eq!(my_number, other_number);
}
The previous code assigns the value to other_number
by dereferencing the shared reference of my_number
(&my_number
). Finally, the code asserts that the values of my_number
and other_number
are equal.
This is the same as duplicating the value of my_number
to other_number
by implicitly generating a copy.
fn main() {
let my_number = 1;
let other_number = my_number;
assert_eq!(my_number, other_number);
}
If both code snippets are the same, then, is the purpose of the dereference operator to generate a duplicate value?
Kind of.
An important aspect is that the dereference operator is used in pointer types.
Understanding pointers
A pointer is another way to tell what the memory address variables have. An example of an address in memory could be 0x80537ff7fc
or 0x80537ff814
.
If you want to see the pointer or memory location of a variable use the format {:p}
when using the println!
macro. This will display the memory location as a hexadecimal number.
fn main() {
let my_number = 1;
println!("my_number memory location {:p}", &my_number);
}
Dereferencing values from pointers
Rust has different pointer types. These are:
- Shared References (&)
- Mutable References (&mut)
- Raw pointers (*const and *mut)
- Smart Pointers
If I bring back the initial code snippet used to show the behavior of the dereference operator,
fn main() {
let my_number = 1;
let other_number = *&my_number;
assert_eq!(my_number, other_number);
}
you will notice the unary (*
) operator is used on a shared reference &my_number
rather than directly on my_number
. If you attempt to user the unary operator in my_number
, .i.e., *my_number
, you will get the following error:
error[E0614]: type {integer} cannot be dereferenced
This is because the i32
type doesn’t implement the Deref
trait.
To use the dereference operator, the type that the dereference operator is applied to needs to implement the Deref
trait and DerefMut
trait for mutable references.
Since you cannot use the unary (*
) operator on my_number
, you will need to get a shared reference (&
) of the variable, meaning &my_number
to use the dereference operator.
In other words, &my_number
will get an address in memory which serves only as a reference of the location of value 1
.
Now, since the address of &my_number
could be something like 0x6e047ffa14
, to extract the value that is referencing the address 0x6e047ffa14
, you will use the dereference unary operator *&my_number
, which results in the value of 1
.
Derefencing a mutable references
The derefence operator can become handy when updating the value of mutable references. Given the following example,
fn dereferencing_mutable_references() {
let text = &mut String::from("foo");
*text = String::from("bar");
assert_eq!(text, "bar");
}
notice how the unary (*
) operator in here is used to assign a new value String::from("bar")
with a type of &mut String
. This allows you to reassign the value over an over.
fn dereferencing_mutable_references() {
let text = &mut String::from("foo");
*text = String::from("one");
*text = String::from("two");
*text = String::from("three");
*text = String::from("four");
*text = String::from("five");
}
If you attempt to reassign the value of text
without using the unary (*
) operator, like you see in the following example,
fn main() {
let text = &mut String::from("foo");
text = String::from("one");
}
you will get the following error error[E0384]: cannot assign twice to immutable variable text
.
Derefencing a smart pointers
Dereferencing Strings
Here is an example of dereferencing a String, which is a smart pointer.
// Strings are considered smart pointers as they own memory and allow you to manipulate
// it as well as having other metadata such as "capacity"
fn dereferencing_strings() {
let string = String::from("Hello");
// *string will generate a string literal str without a known size at compilation time
// Hence, if you attempt,
// let other_string = *string;
// the code have errors at compilation time.
let other_str = &*string;
let other_string = String::from(&*string);
assert_eq!(string, other_str);
assert_eq!(string, other_string);
}
If you paid closed attention to the code, you will find out that *String::from("Hello")
or *my_string
can’t be used without getting a shared reference &
of the value. The reason for this is, *my_string
will generate a string literal (str
). Unfortunately, it is not possible for a variable to own a string literal. That’s why it is necessary to get the shared reference of *my_string
or &*string;
Derefererencing Structs
Structs are also smart pointers. Custom structs can’t use the dereference operator as they don’t implement the Deref trait by default as you can see in the following snippet of code,
#[derive(Debug)]
struct MyStruct {
value: i32,
another_value: i32
}
fn dereferencing_structs() {
let my_struct = MyStruct {value: 1, another_value: 2};
// this code will fail in here
let value = *my_struct;
assert_eq!(1, value);
}
In this case, it is not possible to know which value from the struct MyStruct
Rust will attempt to dereference as MyStruct
has the keys value
and another_value
.
To allow dereferencing on the struct MyStruct
, implement the Deref
trait.
use std::ops::Deref;
#[derive(Debug)]
struct MyStruct {
value: i32,
another_value: String
}
impl Deref for MyStruct {
type Target = i32;
fn deref(&self) -> &Self::Target {
&self.value
}
}
fn dereferencing_structs() {
let my_struct = MyStruct {value: 1, another_value: String::from("a")};
let test = *my_struct;
assert_eq!(1, test);
}
When implementing Deref
, it is required to define the type Target
. This Target
type should match the dereferenced value of any of the struct MyStruct
keys (value
, another_value
). For this example, the type Target
is based on value
(i32
).
Then, to finalize implementing the Deref
trait, add the deref
function. This function returns the key value
of the struct MyStruct
. Therefore, once you use the dereference operator on an instance of MyStruct
, it will extract the data from the value
key.
let my_struct = MyStruct {value: 1, another_value: String::from("a")};
let test = *my_struct;
assert_eq!(1, test);
Derefencing raw pointers
The types *const T
and *mut T
are considered raw pointers. According to the rust documentation, dereferencing a raw pointer is an unsafe operation. Hence, if you attempt to dereference a raw pointer, such as,
fn dereferencing_raw_pointers() {
let test = "Hello!";
let raw = &test as *const &str;
// This will fail with the error error[E0614]: type `i32` cannot be dereferenced
let another_test = *raw ;
}
Rust will throw the following error at compile time:
error[E0614]: type i32 cannot be dereferenced
However, Rust allows to dereference raw pointers as long as you use the unsafe
keyword.
fn dereferencing_raw_pointers() {
let test = "Hello!";
let raw = &test as *const &str;
let another_test = unsafe { *raw };
assert_eq!("Hello!", another_test);
}
Conclusion
All in all, this article explain the meaning of the asterisk (*
) symbol in Rust, which is the dereference unary operator. The dereference operator is in charge of extracting the value off of a pointer or reference. A pointer is a memory location assigned to a variable in Rust. It is possible to use the dereference operator in all the different pointer types:
- Shared References (&)
- Mutable References (&mut)
- Raw pointers (*const and *mut)
- Smart Pointers
For those Rust types that you cannot use the dereference operator, implement the Deref
and/or DerefMut
traits.
If you want to checkout all the examples used in this article, feel free to find them in my repository https://github.com/arealesramirez/rust-deference-operator-examples.
Do you understand what the asterisks symbol means now?
Share your thoughts, comments, suggestions in the Twitter of Become A Better Programmer or to my personal Twitter account.