Packages

ptr

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

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:

main.osl
import "std:ptr"

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

int count = 1
bump(@count)
bump(@count)
log count
Click the code to edit it

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:

main.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
Click the code to edit it

Other types#

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

main.osl
import "std:ptr"

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

string name = "ada"
shout(@name)
log name
Click the code to edit it

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:

main.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()
Click the code to edit it

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

main.osl
import "std:ptr"

auto first = ptr.new(1)
auto second = first
second.set(2)
log first.value()
Click the code to edit it

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

main.osl
import "std:ptr"

auto left = ptr.new("left")
auto right = ptr.new("right")
left.swap(right)
log left.value() right.value()
Click the code to edit it

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:

main.osl
import "std:ptr"

auto a = ptr.new(1)
auto b = ptr.new(1)
auto c = a
log a === b
log a === c
Click the code to edit it

Counters that several functions share#

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

main.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()
Click the code to edit it

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:

main.osl
import "std:ptr"

log ptr.isNull(null) ptr.isNull(5)
log ptr.sizeOf(1)
int[] items = [1, 2, 3]
log ptr.sliceLen(items)
Click the code to edit it
Quick check

What does this print?

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

Double in place

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

main.osl
Ctrl Enter runs
Expected output
42

API reference#

16 members, generated from the std:ptr source of OSL 9.3.0.

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.