Scoping

Identifiers in Shim can be referenced within the block they are defined and any blocks nested inside that block.

{
    let x = 3
    print(x)
    {
        print(x)
        let y = 4
        print(y)
    }
    print(y)  // <--- This will fail since `let y = 4` is inside the nested block
}
Press the play button to execute

Importantly, the variables do not need to have a unique name inside a nested block. Instead, if a variable is declared with let x = ... inside the block it means that the x variable outside of the block won’t be changed.

let x = 3
print("x:", x)
{
    let x = x
    x += 1
    print("x:", x)
    x += 1
    print("x:", x)
    x += 1
    print("x:", x)
}
// This refers to the `x` from the top of the snippet, which should be unchanged
print("x:", x)
Press the play button to execute

Functions declared in the same block are able to call each other. The only thing is that a function needs to have been declared before it can be called.

fn func_a(count) {
    if count < 0 {
        return
    }
    print(count)
    count -= 1
    func_b(count)
}

// NOTE: We can't call `func_a` here since it needs to call `func_b` which is not defined

fn func_b(count) {
    print(count)
    count -= 1
    func_a(count)
}

func_a(5)
Press the play button to execute

Advanced Scoping

Scoping comes into greater focus when you look at closures.

fn get_funcs() {
    let x = 13;
    fn ret_x1() {
        x
    }

    // This closure will capture the new `x`. The `let` that shadows the original
    // `x` is treated as a completely distinct location for values to be stored.
    let x = 42
    fn ret_x2() {
        x
    }
    (ret_x1, ret_x2)
}
let (a, b) = get_funcs()
print(a())
print(b())
assert(a() == 13)  // Bug in runtime
assert(b() == 42)
Press the play button to execute

NOTE: The runtime has a bug here. Closures referencing shadowed variables don’t reference the original. This should be fixed when bytecode generation is updated to reference locals by id rather than by name.