Pointer
Every value lives somewhere in memory, and that place has an address: a number that says where it is. A pointer is a value that holds such an address. With one, a part of the program can reach a value that lives somewhere else, read it and — when it is allowed to — change it.
You have met a close relative already: the reference, &T and &var T, from Reference. A pointer does the same job with more freedom and fewer checks. The whole of this part is built on that freedom, so this first lesson takes it slowly.
Taking an address and reaching through it
Two operators do all the work. @score takes the address of score, and *pointer reaches the value at an address:
var score: int = 10;
// Two pointers to one variable. Neither is a copy of `score`; both lead back to it.
let reader: *int = @score;
let writer: *var int = @score;
*writer = 25;
Neither pointer holds a copy of 10. Both hold the address of score, so after *writer = 25 the variable itself says 25, and reading *reader says 25 too.
flowchart LR
reader["reader: *int"] -- "reads" --> score[("score: int<br/>25")]
writer["writer: *var int"] -- "reads and writes" --> score| Operator | Read it as | Gives |
|---|---|---|
@value | "the address of value" | a pointer |
*pointer | "the value at pointer" | the value itself, to read or to assign |
*T reads, *var T writes
Pointers split the same way bindings do. The var after the * describes the value pointed at — the pointee — not the pointer:
| Type | Through it you may | Like a binding made with |
|---|---|---|
*int | read *p | let |
*var int | read and write *p | var |
The split follows the original binding, too. The address of a let binding is always a read-only *int, so a pointer can never be used to change something that was declared unchangeable:
let limit: int = 100;
let limitPointer: *int = @limit;
Asking for a *var int there is an error; the exact message is under Common mistakes below.
Fields through a pointer
To reach a field through a pointer, use a plain .. There is no need to write * first:
var point = Point { x: 1, y: 2 };
let cursor: *var Point = @point;
cursor.x = 7;
The write lands in point itself, which then prints as (7, 2).
null, the pointer to nowhere
Unlike a reference, a pointer can be stored, compared and set to null, which means "no address at all":
var target: *int = null;
PrintLine("target is null {}", target == null);
target = @score;
if target != null {
PrintLine("target now reads {}", *target);
}
Comparing with == and != is always safe. Reaching through null with * is not, and the compiler will not stop you: the program simply crashes when it gets there. Check before you reach.
Two pointers are equal when they hold the same address. That is why the last line prints true — target and reader both lead to score.
Reference &T | Pointer *T | |
|---|---|---|
| Checked by the compiler | yes — it is always valid | no |
Can be null | no | yes |
| Can be stored in a field or returned | no | yes |
| Reached with | the name alone | *p, or p.field |
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A pointer is an address: a plain value that says where another value lives. `@score` takes
// the address of `score`, and `*pointer` reaches the value at that address, to read it or to
// write it.
//
// Pointers split the same way bindings do. A `*int` may only read what it points at; a
// `*var int` may also write. And the address of a `let` binding is always a read-only `*int`,
// so a pointer can never be used to change something declared unchangeable.
//
// A reference (`&T`, from the Types part) also reaches another value, but the compiler checks
// that it stays valid. A pointer is not checked: it can be stored, compared, and set to `null`,
// which means "no address at all". The rest of this part is built on that freedom, and on the
// care it asks for.
import Io::PrintLine;
struct Point {
x: int;
y: int;
}
func Main() -> int {
var score: int = 10;
// Two pointers to one variable. Neither is a copy of `score`; both lead back to it.
let reader: *int = @score;
let writer: *var int = @score;
*writer = 25;
PrintLine("score is now {}", score);
PrintLine("read via pointer {}", *reader);
// The address of a `let` binding is read-only. Asking for `*var int` here is an error:
// "'@limit' yields a read-only '*T'; declare 'limit' with 'var' for a '*var T'".
let limit: int = 100;
let limitPointer: *int = @limit;
PrintLine("limit is {}", *limitPointer);
// A field is reached through a pointer with a plain `.`, without writing `*` first.
var point = Point { x: 1, y: 2 };
let cursor: *var Point = @point;
cursor.x = 7;
PrintLine("point is ({}, {})", point.x, point.y);
// `null` is the pointer that points nowhere. Comparing with `==` is safe; reaching through
// it with `*` is not, and the compiler will not stop you. Check before you reach.
var target: *int = null;
PrintLine("target is null {}", target == null);
target = @score;
if target != null {
PrintLine("target now reads {}", *target);
}
// Two pointers are equal when they hold the same address.
PrintLine("same address {}", target == reader);
return 0;
}
Run it
cd Examples/Memory/Pointer
rux run
score is now 25
read via pointer 25
limit is 100
point is (7, 2)
target is null true
target now reads 25
same address true
Common mistakes
let binding.let limit: int = 100; followed by let w: *var int = @limit; fails with error: cannot assign '*int' to '*var int': '@limit' yields a read-only '*T'; declare 'limit' with 'var' for a '*var T'. Do what the message says, or settle for a *int if you only need to read.*reader = 5; with reader: *int fails with error: cannot modify data through read-only pointer '*int'. The pointee type decides what you may do, whatever the variable it points at was declared with.@.let reader: *int = score; hands over the value 10, not the place it lives, and fails with error: cannot assign 'int' to '*int'. A pointer is made from an address: @score.null.*target on a null pointer compiles without a word and crashes the program when that line runs. Compare with null first wherever a pointer might not have been set.Try it yourself
- Add a second variable,
var bonus: int = 5;, pointtargetat it, and check thattarget == readernow printsfalse. - Write
func Swap(a: *var int, b: *var int)that exchanges two values, and call it asSwap(@first, @second). - A pointer is a value, so it has an address too. Make
var p: *var int = @score;andlet pp: **var int = @p;, then write**pp = 42and printscore.
Learn more
- Pointers, Pointer types and The
nullpointer in the Rux Reference - Reference and Mutable reference — the checked relatives of
*Tand*var T - Out parameter — the next lesson, where a function writes through a pointer it was given
Overview
Pointers, raw memory and allocators: reach values by address, ask for memory while the program runs, and choose where it comes from — from Alloc and Free to Box, Arena, FixedBuffer and Pool.
15.2 Out parameter
Return an answer through a *var T parameter, and see why a fallible T ! E is usually the better choice.