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.