Skip to content

Instantly share code, notes, and snippets.

@radgeRayden
Last active April 8, 2022 18:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save radgeRayden/665552cff838a014ba817f2660ff78ad to your computer and use it in GitHub Desktop.
Save radgeRayden/665552cff838a014ba817f2660ff78ad to your computer and use it in GitHub Desktop.
about let, mutable variables and pointers in Scopes

The truth about the let keyword

In Scopes, the let keyword is an immutable binding to a name. Sometimes we call them register variables, which is an LLVM-ism. Because it's just a binding with no location in memory and a completely abstract construct, you can alias things that aren't necessarily values, like types or keywords. You can bind anything to a valid symbol; you could even shadow let itself.

Then how do I mutate data?

You can mutate 3 different types of values in Scopes: stack variables, global state variables and mutable pointers.

  • Stack variables are declared with the local keyword. They expire when you exit the function frame (ie. return).
  • Global state variables are declared with the global keyword. Their memory location is static and always available. It's always safe to take a pointer to global data (although it might not be to do so from multiple threads).
  • Pointers are addresses to a memory location. You can take the address from a local, global or through memory allocation (malloc, or using data types like Array, which implies to pointer). Taking the address of a variable will yield a mutable pointer by default, but you can cast it to an immutable one like we'll explain in the next section.

A bit of nomenclature: locals and globals are references. When you pass a reference as a function argument, it is still mutable inside the function body. You can't return a reference from a function - it is automatically dereferenced. To explicitly dereference a reference, use the deref keyword, which copies the value. To create a reference type:

let T = (& i32)

Pointers, how do they work?

The pointer supertype can be used to create other pointer types. The mutable keyword makes it so the value pointed to can be modified. For example:

let pT = (pointer i32) # alternatively, (@ i32)

# memory leak for educational purposes only, don't try this at home
let iptrT = (typeof (malloc i32))
print (iptrT == (mutable pointer i32) and iptrT == (mutable@ i32))
  

One takes the address of a mutable variable with the & prefix, and dereferences it with @:

local a : i32 = 10
let ptr = &a # or (& a)
print @a # 10; alternatively (@ a)

Extra credits:

# get a function pointer:
fn A (val)
    print val
    val + 1
let fptr = (static-typify A i32) # return type is inferred based on argument types, so you just supply the arg types
print ((typeof fptr) == (pointer (function i32 i32))) # note that when constructing the type from scratch the return type is required

# pointers always cast to each other. Do this with care.
local a : i32
let v* = (&a as voidstar) # equivalent to C's void*
print (@ (v* as (mutable@ u32)))

# coerce an Array to access its raw memory:
using import Array
local arr : (Array i32)
let arrptr = (imply arr pointer) # becomes (@ i32)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment