1.5 Pointers and References
Let’s revisit how we make references and pointers in Rust:
In Rust, when you take a reference to a type T, the type of the reference is &T. Basically, you add & to both LHS and RHS:
// type annotations are not required; this is just for demonstration
let a: i32 = 5;
let r_a: &i32 = &a; // r_a == &5and when you deference, you use *. Just remember that * is the reverse of &, and every * removes one & from both LHS and RHS:
let r_a: &i32 = &5;
let a: i32 = *r_a; // a == 5
let a: i32 = *&*&*&aYou can coerce a reference into a raw pointer, using either of the two syntaxes:
let a = 5;
let p_a: *const i32 = &a;
// or
let p_a = &a as *const i32;You need to be explicit about mutability:
let a = 5;
let p_a: *mut i32 = &mut a;
// or
let p_a = &mut a as *mut i32;In C++, the difference between a reference and pointer is smaller. Pointers are compatible with C, and references are a C++-only thing.
In C++ (and C), this is how you make a pointer:
int a = 5;
int *p_a = &a;
// or
int * p_a = &a;
// or
int* p_a = &a;You add & to RHS, but you add * to LHS. Despite the fact that the variable name really is p_a, not *p_a, and the type really is int*, many people and formatters prepend the asterisk before the variable name (int *p_a = &a;). There are some discussion at StackOverflow on why people are preferring this style. I personally prefer using the int* p_a = &a style, which is also being used consistently on cppreference, and in Bjarne Stroustrup’s A Tour of C++.
Anyway, you can choose whichever form you like when you write your code, but you need to able to read all forms so that you can read others’ code.
Note that, unlike in Rust, you cannot create a pointer to a literal in C++:
int* p = &5; // not allowed!The reason is that, 5 is an “rvalue,” which is “temporary” and thus it’s impossible to take its memory address with &.
To dereference a pointer, you use the dereference operator, *, which has the same semantics as Rust.
int b = *p_a;References have similar capabilities as pointers, with the only big difference being their semantics. This is how you create a reference and read its referent’s value:
int l = 5;
int& m = l;
assert(m == 5); // not `*m` !You do not need to (and cannot) use the deference operator (*) on a reference to access the value of the referent. In addition, a reference cannot be re-assigned to refer to another value. Apart from these two rules, references are effectively the same as pointers. It can be helpful to see a reference as an alias to a named variable. Indeed, a reference shares the same memory address as its referent.
You can’t create a non-const reference to a literal, but you can create a const one:
int& r = 5; // not allowed
const int& r = 5; // okSince we are Rustaceans, we are sensitive to mutability. Are there any difference between pointers and references in terms of mutability? The answer is no. Both can be used to mutate the referent.
#include <iostream>
int main()
{
int a{5};
int &r_a = a; // create a reference
int *p_a = &a; // create a pointer
puts("| a|r_a|*p_a| p_a |");
a = 10; // mutate the value
printf("|%d| %d| %d|%p|\n", a, r_a, *p_a, p_a);
r_a = 15; // mutate via reference; no asterisk!
printf("|%d| %d| %d|%p|\n", a, r_a, *p_a, p_a);
*p_a = 20; // mutate via pointer
printf("|%d| %d| %d|%p|\n", a, r_a, *p_a, p_a);
}| a|r_a|*p_a| p_a |
|10| 10| 10|0x7ffe09e40404|
|15| 15| 15|0x7ffe09e40404|
|20| 20| 20|0x7ffe09e40404|
I think it would be helpful to make a line-by-line comparison of some common tasks in Rust and in C++:
1.5.0.1 Reading The Value Without Mutation with A Pointer or Reference
| step | C++ (pointer) | C++ (reference) | Rust | Rust (raw pointer) |
|---|---|---|---|---|
| init referent | const int a = 5 |
const int a = 5 |
let a = 5 |
let a = 5 |
| make ptr/ref | const int* p = &a |
const int& r = a |
let r: &i32 = &a |
let p: *const i32 = &a; |
| read referent value | *p |
r |
*r |
*p |
1.5.0.2 Mutating the Value of the Referent with A Pointer or Reference
| step | C++ (pointer) | C++ (reference) | Rust | Rust (raw pointer) |
|---|---|---|---|---|
| init referent | int a = 5 |
int a = 5 |
let mut a = 5 |
let mut a = 5 |
| make ptr/ref | int* p = &a |
int* r = &a |
let r: &mut i32 = &mut a |
let p: *mut i32 = &mut a; |
| mutate | *p = 10 |
r = 10 |
*r = 10 |
*p = 10 |
1.5.1 lvalues, rvalues, and rvalue references
https://google.github.io/styleguide/cppguide.html#Rvalue_references