kuda.ai

Thoughts on programming and music theory.

dark mode

Go Pointer Challenge

Created on May 28, 2026

golang

Types / Structs for the examples:

type Interpreter struct {
	environment Environment
}


type Environment struct {
	values    map[string]any
	enclosing *Environment
}

func NewEnvironment(enclosing *Environment) Environment {
	return Environment{
		values:    make(map[string]any),
		enclosing: enclosing,
	}
}

This does not work:

func (i *Interpreter) visitBlockStatement(s Stmt) error {
	block, ok := s.(*blockStmt)
	if !ok {
		return errors.New("not a blockStmt")
	}

	return i.executeBlock(block.statements, NewEnvironment(&i.environment))
}

func (i *Interpreter) executeBlock(statements []Stmt, env Environment) error {
	var err error

	previous := i.environment
	i.environment = env

	for _, statement := range statements {
		err = i.execute(statement)
		if err != nil {
			i.environment = previous
			return err
		}
	}

	i.environment = previous

	return nil
}

It leads to nasty STDOUT:

runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0x14020160370 stack=[0x14020160000, 0x14040160000]
fatal error: stack overflow

runtime stack:
runtime.throw({0x100262ed7?, 0x200000008?})
        /usr/local/go/src/runtime/panic.go:1094 +0x34 fp=0x16fc4aa70 sp=0x16fc4aa40 pc=0x10021caf4
runtime.newstack()
        /usr/local/go/src/runtime/stack.go:1159 +0x44c fp=0x16fc4aba0 sp=0x16fc4aa70 pc=0x100205c5c
runtime.morestack()
        /usr/local/go/src/runtime/asm_arm64.s:392 +0x70 fp=0x16fc4aba0 sp=0x16fc4aba0 pc=0x100221100

goroutine 1 gp=0x140000021c0 m=0 mp=0x10034b420 [running]:

This works:

func (i *Interpreter) visitBlockStatement(s Stmt) error {
	block, ok := s.(*blockStmt)
	if !ok {
		return errors.New("not a blockStmt")
	}

	enclosing := i.environment
	return i.executeBlock(block.statements, NewEnvironment(&enclosing))
}

func (i *Interpreter) executeBlock(statements []Stmt, env Environment) error {
	var err error

	previous := i.environment
	i.environment = env

	for _, statement := range statements {
		err = i.execute(statement)
		if err != nil {
			i.environment = previous
			return err
		}
	}

	i.environment = previous

	return nil
}

The only difference is here:

	enclosing := i.environment
	return i.executeBlock(block.statements, NewEnvironment(&enclosing))

What also works, is using:

type Interpreter struct {
	environment *Environment
}


type Environment struct {
	values    map[string]any
	enclosing *Environment
}

func NewEnvironment(enclosing *Environment) *Environment {
	return &Environment{
		values:    make(map[string]any),
		enclosing: enclosing,
	}
}


	return i.executeBlock(block.statements, NewEnvironment(i.environment))

I am not yet able to “teach” this, probably because I don’t understand it well enough, but for me, this situation still qualifies as a meaningful TIL.