Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix B: Coming From Another Language

From Lux

Lux was built to be outgrown, and Go is one of the three languages it was built to be outgrown into. The syntax is close enough that most Lux programs are a mechanical rewrite. The data model is not, and the differences are all in one direction: Lux was strict about a few things Go leaves to you.

LuxGo
let x = 5x := 5 — Go has no immutable local
var x = 5x := 5
int, float, string, boolint, float64, string, bool
[int][]int
[[int]][][]int
queue += 4queue = append(queue, 4)
length(xs)len(xs)
func f(n: int) -> int { }func f(n int) int { }
print("x is", x)fmt.Println("x is", x)
eprint(msg)fmt.Fprintln(os.Stderr, msg)
for x in xsfor _, x := range xs
for i in 0..5for i := range 5
while cfor c
if a && bif a && b
struct Point { x: int }type Point struct { X int }
Point(x: 3, y: 4)Point{X: 3, Y: 4}
enum Shape { circle(radius: float) }an interface plus one struct per case
match s { circle(let r) => ... }switch v := s.(type) { case Circle: ... }
Option<int>, some(x), none(int, bool), or *int and nil
Result<int, string>, ok(x), err(e)(int, error), nil, errors.New(e)
parseInt(s)strconv.Atoi(s), returning a value and an error
int(3.9), string(42)int(3.9), strconv.Itoa(42)
contains(s, sub)strings.Contains(s, sub)
readFile(path)os.ReadFile(path), returning bytes and an error
args()os.Args[1:]
run(cmd)exec.Command(...).Output()
func main() { }func main() { }, and it must be in package main

The mechanical differences are the type after the name with no colon, func without an arrow, capitalized names for anything another package should see, and braces on the same line as the thing they open. Fix those and a Lux program is close to Go.

Four differences are real.

There is no let. Every local variable in Go can be reassigned. const exists but only for compile-time constants, so it cannot hold the result of a function call. The habit Lux taught you — reach for the name that cannot change — has no direct spelling here, and what replaces it is scope: declare the variable as late as possible, in the smallest block that needs it, so there is less room for anything to reassign it.

There is nil. Lux removed null and Go put it back, and a nil pointer dereference is Go’s most common runtime failure. Chapter 5.3 covers the habits that stand in for the guarantee.

Enums become interfaces, and nothing checks your cases. lux convert go on an enum produces an interface with an unexported marker method and one struct per case, and turns match into a type switch. That is the right translation and it is what Chapter 9.3 teaches. What it cannot reproduce is exhaustiveness: Lux refuses to run a match that misses a case, and Go’s type switch quietly falls through to default. This is the single largest thing you are giving up, and it is better learned here than in a debugger.

Option becomes two values or a pointer. Lux’s some/none maps onto either the comma-ok pattern, v, ok := m[k], or a pointer that may be nil, which is what lux convert go emits. The comma-ok form is preferred in Go because it cannot be dereferenced by accident.

You also have a tool. lux convert go program.lux prints the Go translation of anything you have written, and reading a few of those against the originals is the fastest way to learn the mapping in terms of code you already understand. The output is deliberately literal and it declares helper functions a Go programmer would not, so read it as a translation rather than as a model.

From Python

PythonGo
x = 5x := 5
def f(n): return n * 2func f(n int) int { return n * 2 }
print(f"x is {x}")fmt.Printf("x is %d\n", x)
[1, 2, 3][]int{1, 2, 3}
{"a": 1}map[string]int{"a": 1}
{1, 2}map[int]struct{}{1: {}, 2: {}} — no set type
(1, "two")a struct, or two return values
for x in xs:for _, x := range xs {
for i in range(5):for i := range 5 {
[f(x) for x in xs if p(x)]a loop, or slices plus a helper
len(xs)len(xs)
xs.append(v)xs = append(xs, v)
d.get(k)v, ok := d[k]
Nonenil, and only for the types that have it
try / exceptan error return and if err != nil
raisereturn err, or panic for a bug
with open(p) as f:defer f.Close()
class C:type C struct plus methods
isinstance(x, C)a type switch, usually a sign to use an interface
int("42")strconv.Atoi("42"), returning a value and an error
"".join(xs)strings.Join(xs, "")
sorted(xs)slices.Sorted(...), or slices.Sort in place
pip installgo get

The adjustments that take longest: every type is fixed before the program runs, integers overflow instead of growing, a missing dictionary key gives you the zero value rather than an exception, indentation means nothing, and there is no truthiness so every condition must be a real bool.

From JavaScript

JavaScriptGo
const x = 5x := 5 — Go’s const is compile-time only
let x = 5x := 5
function f(n) { return n * 2 }func f(n int) int { return n * 2 }
n => n * 2func(n int) int { return n * 2 }
[1, 2, 3][]int{1, 2, 3}
{a: 1} as a recorda struct
{} as a lookup tablea map
xs.map(f)a loop, or a generic helper you write once
xs.lengthlen(xs)
xs.push(v)xs = append(xs, v)
null and undefinednil, and only one of it
try / catchan error return and if err != nil
throwreturn err, or panic for a bug
=====, which is the only kind there is
typeof x%T, or a type switch
JSON.parsejson.Unmarshal, in the standard library
async / awaitgoroutines and channels, with no function coloring
Promise.alla sync.WaitGroup
npm installgo get

The adjustments: numbers are not all floats and integer division truncates, == refuses to compare different types rather than converting them, there is no prototype chain, and concurrency is not a keyword on the function — any function can be called with go and there is no split between async and sync code.