Unsafe code

Unsafe functions

Keen provides some safety guarantees:

  • Every value is what the type says it is.
  • No value is null.
  • Immutable objects never change.
  • Non-global functions don't have global side effects. (See Global side effects.)
  • Non-global functions behave the same on all targets.

Some functions may break these guarantees, so they are marked unsafe.
Some common unsafe functions are in keen/unsafe.

unsafe is a spec (see Specs), so any function can be marked unsafe with the syntax for using a spec.
An unsafe function can call any other unsafe function.

import keen/unsafe main void() unsafe # Bad idea: log reinterpret-cast::string 1

You can mark your own functions unsafe for any reason.
For example, a function that exposes implementation details of a library could be marked unsafe even if the library is written using only safe code.

Using unsafe functions safely

There would be no point to unsafe functions if there weren't some way to use them safely.
How to do so depends on the function, so read the documentation carefully.

To call an unsafe function from a non-unsafe function, wrap the call in a trusted expression.

main void() log two-of 2 two-of nat[](a nat) buf nat buffer = trusted uninitialized-buffer 2 buf[0] := a buf[1] := a trusted cast-immutable buf

The above calls unsafe functions safely:

  • uninitialized-buffer is unsafe in general because it returns uninitialized memory.
    (That means a default value such as null, but that is still unsafe by Keen's standards.)
    It's safe in this case because all buffer elements are initialized before using them.
  • cast-immutable is unsafe in general because it treats a buffer as an immutable array (without copying).
    That's unsafe because immutable data should never change.
    It's safe in this case because there is no remaining reference to the buffer.

The argument to trusted can be an indented block:

main void() log two-of 2 two-of nat[](a nat) trusted buf nat buffer = uninitialized-buffer 2 buf[0] := a buf[1] := a cast-immutable buf

Some important unsafe functions

gc-safe-value returns an arbitrary constant value for a type.
This is useful to overwrite unused elements of a buffer so that they don't hold on to memory.

import keen/unsafe main void() unsafe buf string buffer = "foo", "bar" # Release the reference so it can be garbage collected buf[1] := gc-safe-value # Still safe to access other elements log buf[0]

reference-equal returns true if two references point to the same place in memory.
This can vary depending on how well Keen optimizes constants.

import keen/unsafe main void() unsafe a nat[] = 1, 2 b nat[] = 1, 2 # 'true' thanks to optimization log reference-equal a, b c nat[] = for x in a then x * 10 / 10 # 'false' since Keen can't optimize that well log reference-equal a, c # They are still logically equal log a == c

reinterpret-cast treats any type as any other type.
This can lead to bytecode validation errors given a Java target, or unpredictable behavior when the target is JavaScript.

import keen/unsafe main void() unsafe a int32 = -1 log reinterpret-cast::nat32 a