Reference

Types

Every built-in type, generics and constraints, conversions between types, typeof, and what the compiler lets you assign.

main.osl
int count = 3
number price = 0.5
string name = "Ada"
boolean ready = true
int[] scores = [90, 85]
string[int] stock = {apples: 4}
string? nickname = null
int | string id = "x1"
log count * price name ready scores stock nickname id
Click the code to edit it

Built-in types#

TypeHoldsExample
intwhole number, 64 bit42
numberdecimal, 64 bit2.5
int8 int16 int32 int64signed whole numbersint8 a = 127
uint uint8 uint16 uint32 uint64unsigned whole numbersuint8 b = 255
bytesame as uint8byte c = 65
number32 number64floating pointnumber32 d = 1.5
stringtext"hi"
boolean or booltrue or falsetrue
T[]array of Tint[] a = [1, 2]
T[n]fixed-size arrayint[2] p = [1, 2]
arrayarray of anything[1, "a"]
V[K] or map<K, V>typed mapstring[int] m = {a: 1}
objectkeys to anything{a: 1}
anyany valueany v = 1
[A, B]tuple: fixed positions of different types[int, string] t = [1, "a"]
T?T or nullint? n = null
A | Ba unionint | string u = 1
def(A, B) Rfunctiondef(int) int f = n -> n + 1
*Tpointer*int p = @n
result<T, E>a value or an error"5".tryAs<int>
option<T>some(x) or none()some(3)
iter<T>lazy sequence[1, 2].iter()
unitnothing to reportunit()
*thread.Thread<T>a running async callasync f() int
main.osl
int8 a = 127
uint8 b = 255
number32 d = 1.5
byte[] bytes = "hi".toBytes()
int[2] pair = [1, 2]
[int, string] row = [1, "a"]
string[int] m = {a: 1}
map<string, int> m2 = {b: 2}
def(int) int inc = n -> n + 1
result<int, error> parsed = "5".tryAs<int>
option<int> maybe = some(3)
iter<int> it = [1, 2].iter()
log a b d bytes.len pair row m m2 inc(1) parsed.unwrap() maybe.unwrap() it.next().unwrap()
Click the code to edit it

set, map, canvas, xml, regex and error are built-in types too; see Built-in functions.

Nullable values#

T? adds null. The compiler won't let you use it as a T until you have checked, given a fallback or asserted.

main.osl
def shout(string? text) string (
  if text == null return "(nothing)"
  return text.toUpper()
)
string? none = null
log shout("hi") shout(none)
log none ?? "fallback"
Click the code to edit it
main.osl
int? maybe = null
int sure = maybe
Click the code to edit it

Unions and any#

A union accepts any of its types. typeof narrows it inside the check. any holds anything, and gives the compiler nothing to check, so prefer a real type.

main.osl
def describe(int | string value) string (
  if typeof(value) == "int" return "int " ++ (value + 1)
  return "string " ++ value.toUpper()
)
log describe(4) describe("ab")
Click the code to edit it

Typeof#

typeof(x) gives the type name as text: int, number, string, boolean, array, object, null, function, byte, option, result, set, map, or the name of a struct, class or enum. .getType() is the method form.

main.osl
class Box ( int v = 0 )
def kind(int | string | Box value) string -> typeof(value)
log kind(1) kind("a") kind(Box())
log 2.5.getType() [1].getType() {a: 1}.getType()
Click the code to edit it

Const values#

const<T> makes a binding and its value read-only. Reads work; methods that change the value are rejected. .clone() gives a copy you can change.

main.osl
const<int[]> primes = [2, 3, 5]
log primes.len primes.sum()
int[] copy = primes.clone()
copy.append(7)
log copy
Click the code to edit it
main.osl
const<int[]> primes = [2, 3, 5]
primes.append(7)
Click the code to edit it

Generics#

A type parameter goes in angle brackets after a name. The compiler infers it from the arguments, or you write it: lastOf<int>(items). A constraint before the name limits it: numeric (numbers only) or an interface name.

main.osl
def lastOf<T>(T[] items) T? (
  if items.len == 0 return null
  return items[-1]
)
def double<numeric T>(T value) T -> value + value
interface Named ( def name() string )
struct Cat ( def name() string -> "Tom" )
def nameOf<Named T>(T item) string -> item.name()
class Box<T> (
  T value
  def init(T value) (
    self.value = value
  )
  def get() T -> self.value
)
Box<int> b = Box<int>(4)
log lastOf([3, 1, 4]) lastOf<string>([]) double(21) double(0.5)
log nameOf(Cat()) b.get()
Click the code to edit it
main.osl
def double<T>(T value) T -> value + value
Click the code to edit it

Conversions#

FormDoes
x.as<T>converts; text that isn't a number becomes 0
x.tryAs<T>converts into a result: an error if it doesn't fit
x.toStr()text; arrays and objects become JSON
x.toInt()whole number; fractions are cut, bad text is 0
x.toNum()decimal; bad text is 0
x.toBool()true only for the text "true"
x.toArray()an array holding x, or x if it already is one
x.toObject()an object from an object, struct or class
s.toBytes(), s.toArr()a string as byte[], or as an array of characters
x.<T>checks the type at run time (never converts)
x.<T>(fallback)the same, giving fallback if the type is wrong
main.osl
log "42".as<int> "x".as<int> 3.9.as<int> 65.as<string> 1.as<boolean>
log "42".tryAs<int>.unwrap() "x".tryAs<int>.isErr() 300.tryAs<uint8>.isErr()
log 5.toStr() [1, 2].toStr() {a: 1}.toStr()
log "7.9".toInt() 7.9.toInt() "3.5".toNum() "true".toBool() "yes".toBool()
log "ab".toArr() 5.toArray()
Click the code to edit it

Assignability#

You writeResult
number n = 5 (int to number)fine
int i = n where n is a numbererror: use .as<int>, .toInt() or .round()
int i = "5"error: convert with .toInt() or .tryAs<int>
string s = null, int j = maybeerror: null needs T?
uint8 u = bigInterror: use .as<uint8>
number[] b = ints (int[] to number[])error: arrays don't change item type
T to T?fine
a value to a union containing its typefine
a struct or class to an interface it satisfiesfine
a literal that fits, such as int8 x = 100fine; int8 x = 300 is an error
main.osl
number n = 5.5
int i = n
Click the code to edit it
main.osl
int8 x = 300
Click the code to edit it