# ptr

> Let a function change a variable its caller owns, with @variable pointers and shared ptr.new cells.

This is the Markdown version of https://osl-lang.org/packages/ptr. Every page of the OSL docs has one: add .md to its path. https://osl-lang.org/llms.txt lists them all.

```osl
import "std:ptr"
```

OSL passes numbers, strings and booleans by value: a function gets its own copy, so changing it doesn't affect the caller. Usually that is what you want. When you want a function to change the caller's variable itself, `std:ptr` gives you two tools:

- `@variable` points at an existing variable. Read and write through it with `ptr.deref` and `ptr.set`.
- `ptr.new(value)` makes a shared cell that doesn't belong to any variable. Copies of the cell share the one value.

Pointers are rare in everyday OSL. Arrays, objects and classes are already shared when you pass them, and a function can return several values in an array or object. Reach for `ptr` for the cases that remain: an out-parameter, a counter many parts of a program update, or an interop boundary where Go expects a pointer.

## Pointing at a variable

`@count` makes a pointer to the variable `count`. A parameter that receives one is typed `*int`. `ptr.deref` reads the variable it points at, and `ptr.set` writes it:

```osl
import "std:ptr"

def bump(*int n) (
  ptr.set(n, ptr.deref(n) + 1)
)

int count = 1
bump(@count)
bump(@count)
log count
```

Output:

```text
3
```

Without the `@`, `bump(count)` would be a type error, and `count` would be a copy anyway. The `@` at the call site makes it obvious that the callee can change the variable.

## Swapping two variables

A function that must change two of the caller's variables is the classic use:

```osl
import "std:ptr"

def swap(*int x, *int y) (
  int saved = ptr.deref(x)
  ptr.set(x, ptr.deref(y))
  ptr.set(y, saved)
)

int a = 1
int b = 2
swap(@a, @b)
log a b
```

Output:

```text
2 1
```

## Other types

`ptr.deref` returns a value of any type, so say what you expect before using it with methods of that type:

```osl
import "std:ptr"

def shout(*string text) (
  ptr.set(text, ptr.deref(text).as<string>.toUpper())
)

string name = "ada"
shout(@name)
log name
```

Output:

```text
ADA
```

## A shared cell

`ptr.new(value)` makes a `*ptr.Pointer`: a cell holding one value. `cell.value()` reads it and `cell.set(v)` replaces it. Unlike `@`, the cell isn't tied to a variable, so it can be handed to many functions and outlives the scope that made it:

```osl
import "std:ptr"

def addTo(*ptr.Pointer total, int amount) (
  total.set(total.value() + amount)
)

auto total = ptr.new(10)
addTo(total, 5)
addTo(total, 100)
log total.value()
```

Output:

```text
115
```

Copying a cell does not copy the value. Both names refer to the same cell:

```osl
import "std:ptr"

auto first = ptr.new(1)
auto second = first
second.set(2)
log first.value()
```

Output:

```text
2
```

`cell.swap(other)` exchanges the values of two cells in place.

```osl
import "std:ptr"

auto left = ptr.new("left")
auto right = ptr.new("right")
left.swap(right)
log left.value() right.value()
```

Output:

```text
right left
```

## Same value or same cell

`==` on two pointers compares what they hold, and `===` asks whether they are the same cell. The compiler warns when you use `==` on cells, so write what you mean:

```osl
import "std:ptr"

auto a = ptr.new(1)
auto b = ptr.new(1)
auto c = a
log a === b
log a === c
```

Output:

```text
false
true
```

## Counters that several functions share

A shared cell is a simple way to count things across callbacks, without a global:

```osl
import "std:ptr"

def makeHandler(*ptr.Pointer seen, string label) def(string) (
  return def(string item) (
    seen.set(seen.value() + 1)
    log label item
  )
)

auto seen = ptr.new(0)
auto onA = makeHandler(seen, "a:")
auto onB = makeHandler(seen, "b:")
onA("x")
onB("y")
onA("z")
log seen.value()
```

Output:

```text
a: x
b: y
a: z
3
```

For state shared between threads, use `std:sync`, which guards access. A plain cell isn't protected against two threads changing it at once.

## Null and low-level queries

`ptr.isNull(value)` is true for `null` and for a null Go pointer. The rest of the package (`sizeOf`, `alignOf`, `offsetOf`, `addressOf`, `sliceLen`, `sliceCap`) answers questions about how a value is laid out in memory. They are for debugging and for talking to Go code. Addresses change from run to run, so never store one or print it in a test:

```osl
import "std:ptr"

log ptr.isNull(null) ptr.isNull(5)
log ptr.sizeOf(1)
int[] items = [1, 2, 3]
log ptr.sliceLen(items)
```

Output:

```text
true false
8
3
```

**Quiz.** What does this print?

```osl
auto first = ptr.new(1)
auto second = first
second.set(2)
log first.value()
```

- [ ] `1`
- [x] `2`
- [ ] It doesn't compile.

Answer: Copying a cell copies the reference, not the value, so `first` and `second` are the same cell.

### Exercise: Double in place

Finish `double` so it doubles the caller's variable. The call passes `@score`, so the function gets a pointer.

Starting code:

```osl
import "std:ptr"

def double(*int n) (
  log "doubling"
)

int score = 21
double(@score)
log score
```

It should print:

```text
42
```

Hint 1: Read the current value with `ptr.deref(n)` and write the new one with `ptr.set(n, ...)`.

A solution:

```osl
import "std:ptr"

def double(*int n) (
  ptr.set(n, ptr.deref(n) * 2)
)

int score = 21
double(@score)
log score
```

## API reference

## API reference

Every member of `std:ptr`, generated from the source of OSL.

### Functions

#### `ptr.addressOf(any value) int`

addressOf returns the address of value as a number: for an object, array,
function or handle, the address of the data it refers to, which every
reference to it shares; for null, 0; for other values, the address of a
temporary copy.

#### `ptr.alignOf(any value) int`

alignOf returns the alignment in bytes of value's Go representation.
Throws ValueError for null.

#### `ptr.deref(any pointer) any`

deref returns the value pointer points at: the variable behind @value, or
what a ptr.new Pointer holds. Throws ValueError for a null pointer and
TypeError for a value that isn't a pointer.

#### `ptr.equalPointers(any a, any b) boolean`

equalPointers reports whether a and b are the same object, array, function
or handle rather than equal copies. Two nulls are the same.

#### `ptr.isNull(any value) boolean`

isNull reports whether value is null or a null Go pointer.

#### `ptr.new(any value) *ptr.Pointer`

new returns a Pointer holding value. Copies of the Pointer share the cell,
so p.set(v) is seen by every holder.

#### `ptr.offsetOf(any value, string field) int`

offsetOf returns the byte offset of the Go field named field
within a Go struct value, such as a package handle. Throws ValueError when
value isn't a struct or has no such field; OSL objects are maps, not
structs.

#### `ptr.set(any pointer, any value) boolean`

set stores value where pointer points, in place: in the variable behind
@value, or in a ptr.new Pointer. Returns true. Throws ValueError for a
null pointer and TypeError for a value that isn't a pointer or a value
the variable's type can't hold.

#### `ptr.sizeOf(any value) int`

sizeOf returns the size in bytes of value's Go representation; for an
array or object, the size of one element. Throws ValueError for null.

#### `ptr.sliceCap(array arr) int`

sliceCap returns how many elements an array can hold before it has to
grow.

#### `ptr.sliceData(array arr) int`

sliceData returns the address of an array's first element, or 0 for an
empty array.

#### `ptr.sliceLen(array arr) int`

sliceLen returns how many elements an array holds.

#### `ptr.stringData(string text) int`

stringData returns the address of a string's first byte, or 0 for "".

### `*ptr.Pointer`

#### `pointer.set(any value)`

set replaces the value the pointer holds, in place.

#### `pointer.swap(*ptr.Pointer other)`

swap exchanges the values held by this pointer and other, in place.
Throws TypeError when other is null.

#### `pointer.value() any`

value returns the value the pointer holds.
