luxc 0.8.3

A small teaching language that runs anywhere and transpiles to Rust, Swift, and Go
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
# Learn lux

lux is a small language built to be a great *first* language and then to be
outgrown. Every feature is the simplest version of something shared by
Rust, Swift, and Go, so what you learn here carries straight over when you move
on to one of those.

These topics explore the whole language, one short section at a time. Every example is
real lux that runs — the test suite runs them all. Each topic is a short card
you can read in under a minute; add `more` to any of them — `lux learn match
more` — for the deeper explanation and where the idea goes next. Read it in your
terminal: `lux learn` for the menu, `lux learn <topic>` for one card, `lux learn
basics` for the handful of shapes every language shares, `lux learn tour` for
the whole thing.

<!-- topic: hello -->
## hello — your first program

lux runs your statements from top to bottom, and `print` shows text on the
screen.

```lux
// Two slashes start a comment. There is no block comment — one way to do it.
print("Hello, world!")
print("two", "words")   // print separates its arguments with spaces
```

There is no setup to write first: the file is the program.

<!-- more -->
Most languages make you write a `main` function and a little setup before
anything runs — that is the boilerplate lux leaves out, so your first program is
just the line you care about. When you move on to Rust, Go, Java, or C, `main`
comes back, and now you will know what it is for: the one place the
program is told where to begin.

<!-- topic: errors -->
## errors — reading what lux says back

When lux can't do what a line asks, it stops and tells you why instead of
guessing. The message names what went wrong, the `-->` line points at the spot,
and the caret underlines it. Often a `note:` offers a fix, and a `help:` opens a
trail to the idea behind it.

```lux
print("this line works")
print("so does this one")
```

> try: break the second line on purpose — delete its closing `"`, or add a line `print(missing)` for a name you never made — and read what lux says back.

<!-- more -->
Every language has errors, and most do exactly what lux does: stop and say what
went wrong. As you learn other languages the
timing shifts — Rust and Go catch many mistakes the moment they compile, before
the program is ever run, so the message meets you even earlier. What you are
doing now, reading it and trying the next thing, is the same move there.

> see: result — the other side of errors: the ones your program expects and handles as values

<!-- topic: variables -->
## variables — let and var

Variables allow you to give a name to values you want to use in your program.
`let` names a value that stays the same throughout your code (it stays constant). `var` names a value 
that can change while your program is running (it is variable). Every variable has a type;
lux has four basic types — int, float, string, and bool.

```lux
let pi = 3.14159       // float — a let never changes
let name = "Ada"       // string
var score = 0          // int — a var can be reassigned
score = score + 10
print(name, "has", score, "points; pi is", pi)
```

> try: add the line `pi = 3.0` and run it — lux stops you, because a `let` never changes.

<!-- more -->
Giving a value a name is *assignment*, the oldest idea in programming. The line
lux draws — `let` for a name that holds still, `var` for one that moves — shows
up nearly everywhere: Rust's `let` and `let mut`, Swift's `let` and `var`,
`const` in many others. Reaching for `let` first and `var` only when something
truly must change is a habit all of those languages reward, because a name that
cannot change is one less thing that can surprise you.

> see: scope — a name you make has to live somewhere, and that somewhere is its scope

<!-- topic: numbers -->
## numbers — arithmetic

Your program can do arithmetic, but lux has two different types of numbers: int and float. 
An int represents a whole number. A float represents a number with a decimal point in it.
Other programming languages have more numeric types. The language does not convert between
int and float automatically. That's why division with int throws away the remainder.

```lux
print("2 + 3   =", 2 + 3)
print("7 / 2   =", 7 / 2)         // 3 — int division drops the remainder
print("7 % 2   =", 7 % 2)         // 1 — the remainder
print("7.0/2.0 =", 7.0 / 2.0)     // 3.5 — float division keeps the fraction
print("mix on purpose:", float(7) / 2.0)
```

> try: change `float(7) / 2.0` to `7 / 2.0` and run — lux won't mix an int and a float unless you say so.

<!-- more -->
int and float are *scalar* types — single values, the atoms everything larger is
built from, alongside bool. The rule that lux will not mix them without
`float(...)` looks strict, but it is the same line Rust and Go draw: a silent
jump from whole numbers to fractions is a classic place for bugs to hide, so the
languages that care make you say when you mean it. The `%` remainder and
integer division that drops the fraction are everywhere too — they are how you
ask "is this even?" or "what is left over?" in almost any language.

> see: strings — the scalar that holds text, and why turning a number into one is on you · conversions — `int`/`float` between numbers, and `parseInt` to read one out of text

<!-- topic: strings -->
## strings — text

Strings are values that contain text.
Join strings with `+` (both sides must already be strings), turn a number into
text with `string(...)`, and count characters with `length`.

```lux
let name = "Ada"
print("Hello, " + name + "!")
print("Score: " + string(42))         // convert the number first
print("letters in café:", length("café"))   // 4 — characters, not bytes
```

> try: drop the `string(...)` so it reads `"Score: " + 42` and run — lux won't glue a string to an int.

<!-- more -->
A string is really a *sequence* of characters — a compound value wearing a
friendly face, which is why `length` measures it the way it measures an array.
Most languages give strings their own type with a pile of built-in operations,
but the constant across all of them is that text is data you measure, join, and
take apart — never something the language quietly turns into a number for you,
which is why lux makes you ask for `string(...)`.

> see: arrays — a string is a sequence of characters, measured the way an array is · conversions — going the other way, reading a number out of text with `parseInt`

<!-- topic: booleans -->
## booleans — true and false

`&&`, `||`, and `!` combine true and false, and the comparisons `> < >= <= == !=`
produce them.

```lux
let sunny = true
let warm = false
print("go outside?", sunny && warm)    // and — both must be true
print("either one?", sunny || warm)    // or  — one is enough
print("not sunny?", !sunny)
print("3 > 2 is", 3 > 2)
```

<!-- more -->
bool is the third scalar type and the smallest — just `true` or `false`. Every
decision a program makes comes down to one: a comparison like `3 > 2` produces a
bool, and `if`, `while`, and the logic operators all run on them. Once you can
see the bool flowing out of a condition and into the control flow, the branching
in any language stops being mysterious — it is always a bool steering the road.

> see: if — a bool is exactly what an `if` tests · while — and what a loop tests to keep going

<!-- topic: if -->
## if — making decisions

`if` runs a block when its condition is true; there are no parentheses around
the condition, and the braces are always required.

```lux
let score = 75
if score >= 90 {
    print("A")
} else if score >= 60 {
    print("passing")
} else {
    print("try again")
}
```

<!-- more -->
`if` is *selection*, one of the two control structures every procedural language
is built from. It is also called *conditionals*. The shape is universal — test a bool, run a block, optionally
run another instead — and what changes between languages is only paint, like
whether the condition needs parentheses around it. Read one if/else ladder and
you can read the branching in all of them on sight.

> see: while — the other control structure: repeating instead of choosing · booleans — the bool an `if` runs on

<!-- topic: while -->
## while — repeating

`while` runs its block over and over as long as the condition stays true.

```lux
var n = 0
while n < 5 {
    print("n is", n)
    n += 1
}
```

<!-- more -->
`while` is *iteration*, another foundational control structure. It is also called *loops*:
keep going as long as a bool stays true. The `for` loop you will meet next is
usually just a tidier `while` for a known range or a collection. Go drops the
word `while` entirely and writes every loop with `for` — seeing that the two are
the same idea underneath is the whole lesson.

> see: for — the tidier loop for a range or a collection · if — the control structure that chooses instead of repeats

<!-- topic: arrays -->
## arrays — many values of one type

An array holds many values of the same type, written `[int]`, and you read an
element by position, counting from 0.

```lux
let primes: [int] = [2, 3, 5, 7, 11]
print("first:", primes[0], "count:", length(primes))
var queue = [1, 2, 3]
queue += 4                      // add to a var array
print("queue:", queue)
```

> try: read `primes[10]` and run — lux stops you at an index past the end of the array.

<!-- more -->
An array is a *data structure* — the simplest compound type, many values of one
type laid in a row and reached by an index that counts from 0. That zero-based
indexing and the square brackets are nearly universal; what later languages add
is variety — lists, slices, vectors, arrays that grow — but the mental model you
have here, "a numbered row of values," carries straight into all of them.

> see: for — the loop built for walking an array · structs — the other compound type, a few different things instead of many of the same

<!-- topic: for -->
## for — looping over things

`for` is another kind of loop that works together with collections (compound types like arrays).
Its purpose is to perform the same action for each element within the collection.

`for x in xs` walks every element of an array; `for i in 0..5` counts over a
range whose end is not included.

```lux
let primes = [2, 3, 5, 7, 11]
var sum = 0
for p in primes {
    sum += p
}
print("sum of primes:", sum)
for i in 0..3 {
    print("tick", i)
}
```

<!-- more -->
`for x in xs` is *iteration over a collection* — the loop you will write most,
visiting each item once, in order. The range form `0..n` is the same loop
counting instead of walking. Languages spell it differently — Go writes `for i,
x := range xs` and Rust writes `for x in xs` just like lux — but the job never
changes, and underneath it is still the `while` loop you already know.

> see: while — what a `for` loop is underneath · arrays — the collection it walks

<!-- topic: functions -->
## functions — name a piece of work

*Functions* allow you to organize and re-use your code. Functions are *defined* in
one place, and then *called* from other parts of the code as needed. Functions receive
values as *parameters*, and hand back a *return value*. You must specify the
type of each parameter and return value.

To define a function, write `func name(p: type) -> type { ... }`. Call it by name when you need to use it. A function
may call itself, a process known as recursion.

```lux
func factorial(n: int) -> int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n - 1)
}
print("5! =", factorial(5))
```

<!-- more -->
A function is *packaged work* with a name, *parameters* going in and a result
coming back — the unit every language leans on to keep a program from becoming
one endless script. Recursion, a function calling itself, and the fact that each
call gets its own fresh set of names are not lux quirks; they are how functions
behave everywhere, and that fresh set of names is exactly what `lux learn scope`
is about.

> see: scope — each call gets its own, which is why a name made inside stays inside

<!-- topic: scope -->
## scope — where a name lives

A name lives only inside the block where you declare it — the `{ }` of a
function, an `if`, or a loop. Step outside that block and the name is gone.

```lux
let planet = "Earth"            // planet lives in the whole program
func loud(word: string) -> string {
    let banged = word + "!"     // banged lives only inside loud
    return banged
}
print(loud(planet))
print(planet, "is still here")  // planet is still in scope out here
```

> try: add `print(banged)` as the last line and run — lux says `banged` isn't defined out here, because it only ever existed inside `loud`.

<!-- more -->
Every language has *scope* — the region of a program where a name means
something. lux keeps it simple: a fresh scope opens at every `{` and closes at
its matching `}`, and an inner scope can see the names around it but never the
other way around. The bigger languages add more kinds — file and module scope,
namespaces, and *closures* that let a function carry its scope around with it —
but the rule you just learned is the spine that all of them are built on.

> see: functions — the most common fresh scope, one per call · variables — the names that live in a scope

<!-- topic: structs -->
## structs — your own types

A struct groups related values into one type; build it by naming its fields,
and read a field with a dot.

```lux
struct Point {
    x: int
    y: int
}
let here = Point(x: 3, y: 4)
print("x is", here.x, "y is", here.y)
print("same point?", here == Point(x: 3, y: 4))
```

<!-- more -->
A struct is a *record* — the other compound type, gathering values that belong
together under one name. Where an array is many of the *same* thing, a struct is
a few *different* things treated as a unit: a point is its `x` and its `y`.
Almost every language has this shape, under names like record, struct, or data
class, and it is the first step toward objects — which the larger languages
build by attaching behaviour to the data a struct holds.

> see: enums — its partner: a struct is "this and that," an enum is "this or that"

<!-- topic: enums -->
## enums — one of several shapes

An enum says a value is exactly one of a fixed set of cases, and each case can
carry its own values — the idea that makes illegal states impossible.

```lux
enum Shape {
    circle(radius: float)
    rectangle(width: float, height: float)
    dot
}
let c = Shape.circle(radius: 2.0)
print(c)
```

<!-- more -->
An enum whose cases carry values is a *sum type*, also called a *tagged union*:
a value that is exactly one of several shapes, each with its own data. It is the
partner to the struct — a struct is "this *and* that," an enum is "this *or*
that." Rust and Swift have it and lean on it hard; Go does not, which is why
`lux convert go` has to fake it with structs and a tag field — a clear look at
what the feature actually buys you.

> see: match — how you take an enum apart, one case at a time · structs — its partner shape, "and" to the enum's "or"

<!-- topic: match -->
## match — take a value apart

`match` picks the arm for the case in hand and binds the values inside it, and
you must cover every case or lux refuses to run. It works on plain values too —
strings and numbers, not only enum cases — where a final `_` arm catches
everything you did not name. That wildcard is how you cover an open set like
text, which has no fixed list of cases to spell out.

```lux
enum Shape {
    circle(radius: float)
    square(side: float)
}
func area(s: Shape) -> float {
    return match s {
        circle(let r) => 3.14159 * r * r
        square(let a) => a * a
    }
}
print("circle:", area(Shape.circle(radius: 2.0)))
print("square:", area(Shape.square(side: 3.0)))
```

> try: delete the `square` arm and run — lux refuses a match that leaves a case unhandled.

<!-- more -->
`match` is *pattern matching*: it picks the arm for the shape in hand and unpacks
the values inside in a single move. The rule that you must cover every case is
where its power comes from — the language itself, not your memory, guarantees
nothing slips through. Rust and Swift's `switch` work this way; the older
`switch` of C and Java does not, so forgetting a case there is a quiet bug
instead of a refusal to run.

> see: enums — the shapes a `match` takes apart · option — the missing-or-present value you match on

<!-- topic: option -->
## option — a value that might be missing

There is no null. A value that might be missing has type `Option<int>`: either
`some(x)` or `none`, and `match` makes you handle the empty case.

```lux
func firstEven(xs: [int]) -> Option<int> {
    for x in xs {
        if x % 2 == 0 {
            return some(x)
        }
    }
    return none
}
match firstEven([1, 3, 4, 7]) {
    some(let x) => print("first even:", x)
    none        => print("no even number")
}
```

> try: search `[1, 3, 5]` instead — the `none` arm is how lux makes sure you handled "nothing there".

<!-- more -->
`Option` is how lux says "this might be missing" without a null. Null — a value
that pretends to be there and is not — is famous enough to have a nickname, the
billion-dollar mistake, for all the crashes it has caused. Languages that
learned from it make "missing" a shape you have to open with `match` before you
can reach what is inside: Rust's `Option`, Swift's `Optional`. That forced
check, paid once up front, is the whole point.

> see: result — its sibling, for failing with a reason instead of just being absent · match — how you open one safely

<!-- topic: conversions -->
## conversions — between numbers and text

`int`, `float`, and `string` turn one kind of value into another. Converting
between numbers, or rendering a number as text, always works — there is no way
for it to fail. Reading a number *out of* text is the different one: the text
might not be a number, so `parseInt` and `parseFloat` hand back an `Option` you
match, never a crash.

```lux
var price = 3.99            // a float
let whole = int(price)      // 3 — a conversion can't fail
let label = string(whole)   // "3"
print(label, "dollars, rounded down")

match parseInt("17") {
    some(let n) => print("the number is", n)
    none        => print("that wasn't a number")
}
```

> try: change `"17"` to `"seven"` and run — the `none` arm answers instead of the program falling over.

<!-- more -->
The split is one every careful language draws. A *conversion* is total
— `int(3.9)` is always `3`, `string(42)` is always `"42"` — so it just hands the
value back. *Parsing* is reading structure out of text that may not have it, and
that can fail, so its answer is an `Option`: `some(n)` when the text was a
number, `none` when it was not. Folding the two together is where the crash
hides — a language that lets `int("oops")` blow up has smuggled a failure into
something that looked safe. lux keeps them apart: `parseInt` wears its
fallibility in its type, the same `Option` you already match on. Down the ladder
this is Rust's total `as` against its fallible `.parse()`, and Swift's
`Int(3.9)` against its failable `Int("17")`.

> see: option — the shape a parse hands back · strings — the text you parse from · numbers — what you parse into

<!-- topic: result -->
## result — a value that might fail

When something can fail with a reason, use `Result<int, string>`: either `ok(x)`
or `err(reason)`. Errors are just values you match on, not a hidden mechanism.

```lux
func half(n: int) -> Result<int, string> {
    if n % 2 == 0 {
        return ok(n / 2)
    }
    return err("not even")
}
for n in [8, 7] {
    match half(n) {
        ok(let h)  => print(n, "halves to", h)
        err(let e) => print(n, "can't:", e)
    }
}
```

<!-- more -->
`Result` is the same trick as `Option`, but for failure: a value that is either
`ok` with the answer or `err` with a reason, the reason carried as ordinary data
rather than thrown across the program. Go's `(value, error)` return is this idea
in different clothes, and Rust's `Result` is `Option`'s sibling. Treating a
failure as a value you `match` on, instead of a hidden mechanism that jumps out
of your code, is what keeps the failure path in plain sight.

> see: option — its sibling, for "missing" rather than "failed" · match — how you handle both arms

<!-- topic: io -->
## io — the outside world

The world beyond your program is where things go wrong: a file might not be
there. So reading one hands back a `Result` — `ok` with the text, or `err` with
the reason — and `match` makes you face both. It is the `result` you just
learned, now earning its keep. Reading a line of input is the same idea for
absence rather than failure: `readLine()` returns an `Option<string>` — a line,
or `none` when the input runs out — the call a command loop reads each turn.

```lux
match readFile("poem.txt") {
    ok(let text) => print("read", length(text), "characters")
    err(let e)   => print("no poem today:", e)
}
```

> try: point it at a file that really exists — the `ok` arm runs instead, and `length(text)` counts what you read.

<!-- more -->
The whole outside world is five calls, and they share one rule: anything that
can fail hands the failure back as a value, never a surprise sprung on you.
`writeFile` returns a `Result` whose success carries nothing — saving a file has
no answer to give back, only a yes-or-no that it worked, so you still match the
`err` arm. `readLine` returns an `Option<string>`: a line, or `none` at the end
of input, which is why a loop over it reads the same whether a person is typing
or a file is piped in. That is the quiet point of I/O — your output is the next
program's input. `print` writes that output and `eprint` writes errors to a
separate stream, stderr, so the stream the next program reads stays clean; and
`args()` is your command line, with your own program sitting at index 0, a
footgun worth meeting early. Down the ladder these are Rust's `std::fs` and
`std::env`, Go's `os` package handing back its `(value, error)` pairs, and
Swift's `Foundation` with its throwing file calls and a `readLine()` that is
already an `Optional`.

> see: result — the shape every fallible call hands back · option — what readLine gives at end of input · match — how you face both arms

<!-- topic: shell -->
## shell — running other programs

Your program can run other programs and read back what they made. `run` takes a
command and a list of arguments and hands back a `Result`: `ok` with an
`Output` — its exit status and the text it wrote — or `err` if it could not even
start. That `Output` is a struct, so you read its parts by name.

```lux
match run("echo", ["hello from lux"]) {
    ok(let result) => print("status", result.status, "->", result.stdout)
    err(let e)     => print("could not launch echo:", e)
}
```

> try: change `"echo"` to `"false"` — it launches fine but comes back with status 1, the command's own way of saying it failed.

<!-- more -->
`run` is the first built-in that succeeds with a *struct*: an `Output` holding
`status`, `stdout`, and `stderr`, three named fields you read off the result.
There are two layers of truth to keep apart. The `Result` says whether the
command *launched* — the program might not exist, and that is the `err` arm.
The `status` inside says whether the command *succeeded* — a program can launch
perfectly and still report failure with a non-zero exit code, the way `false`
does, so you read the status even when the launch went fine. The arguments are a
list, never one shell string, which is deliberate: there is no shell in the
middle to misread a space or a quote, so a filename with a space in it is just an
argument, and there is nothing to inject. `stdout` and `stderr` come back
separate, the same two streams `print` and `eprint` write to. One real limit:
`run` collects all of a command's output and hands it back when the command
finishes — it is not a live pipe feeding another program character by character.
It gives you the Unix mindset, one program's output becoming your input, without
the streaming. Down the ladder this is Rust's `std::process::Command`, Go's
`os/exec`, and Swift's `Foundation` `Process`.

> see: result — the shape run hands back · structs — what an Output is, a value with named fields · match — how you read both arms

<!-- topic: crawl -->
## crawl — build a small world

Run `lux crawl` and you get a tiny text adventure to play. Its secret is that the
whole world is one lux file you can open and change: the rooms are an enum, where
you stand and what you carry is a struct, and a function matches on the room to
say what you see. Nothing is hidden away in an engine — once you can read this,
you can build it.

```lux
enum Room {
    cell
    yard
}

struct Spot {
    room: Room
    locked: bool
}

func describe(room: Room) -> string {
    return match room {
        cell => "A cold stone cell, with a door to the east."
        yard => "Open sky at last."
    }
}

let here = Spot(room: Room.cell, locked: true)
print(describe(here.room))
```

> try: add a third room to the enum, then give `describe` a line for it — match will refuse to run until you do, so you can't forget.

<!-- more -->
A whole game is that one idea, repeated. The player types a line, and a single
function — call it `step` — takes the world and the command and hands back the
*next* world: a new value, not a changed one, since lux never alters a struct in
place. Run `lux crawl` and read its `world.lux` from top to bottom; every piece
is something you already have a card for. And when you want it to do something
lux can't quite manage — a room that remembers, a command typed in plain English
— that wish is the next language calling.

> see: enums — the set of rooms · structs — where you stand and what you hold · match — how a room becomes a description · option — a door that may lead nowhere · functions — every action is one

## The shape every language shares

lux is a launch pad, and so is this page. Almost every language you will meet is
built from the same short list of parts. Learn them once here and most of the
next language is just new spelling for things you already understand.

| every language has | what it is | learn it here |
|---|---|---|
| scalar values | int, float, bool, or text | `numbers`, `booleans`, `strings` |
| compound values | many values gathered up | `arrays`, `structs` |
| assignment | giving a value a name | `variables` |
| selection | choosing what runs next | `if` |
| iteration | repeating work | `while`, `for` |
| packaged work | functions with parameters | `functions` |
| scope | where each name means something | `scope` |

Spot those few shapes and most of any procedural language is readable; the rest
is punctuation and keywords, the syntax that makes the same ideas look
different. That is what lets you open a "learn X in Y minutes" page for a
language you have never seen and follow along.

## Where each feature takes you

Here is the map of what graduates where, so the next language is never a cold
start.

| lux | Rust | Swift | Go |
|---|---|---|---|
| `let` / `var` | `let` / `let mut` | `let` / `var` | `const` / `:=` |
| `x: int` | `x: i32` | `x: Int` | `x int` |
| `func f(x: int) -> int` | `fn f(x: i32) -> i32` | `func f(x: Int) -> Int` | `func f(x int) int` |
| `for x in xs` | `for x in xs` | `for x in xs` | `for _, x := range xs` |
| `while c` | `while c` | `while c` | `for c` |
| `match` | `match` | `switch` | `switch` (leaner) |
| `enum` with values | `enum` with values | `enum` with values | *fake with structs* |
| `Option` / `Result` | `Option` / `Result` | `Optional` | `(value, error)` |
| `[int]` | `Vec<i32>` | `[Int]` | `[]int` |
| `readFile` / `args` | `std::fs` / `std::env` | `Foundation` / `CommandLine` | `os` package |
| `print` / `eprint` | `println!` / `eprintln!` | `print` / `FileHandle` | `fmt` / `os.Stderr` |
| `run` | `process::Command` | `Process` | `os/exec` |

Going **up** the ladder (Swift, Rust) you gain hard new ideas lux left out on
purpose: classes and protocols are the Swift lesson; ownership and lifetimes are
the Rust lesson. Going **down** (Go) you keep the same shapes but rebuild a few
conveniences by hand — that is the lesson in how they actually work.

## Beyond lux

The last two pages were about other languages. This one is about you.

Underneath the syntax, everything here was the same handful of moves: break a
big problem into smaller ones until each is small enough to solve, say exactly
what you mean so the machine can't guess wrong, and when something breaks, read
what it tells you and try the next thing. Those moves are not really about
programming. They are how you take anything tangled — a plan, an argument, a
broken bike — and make it give way. Programming is just where you practised,
because the feedback is honest: the program runs or it does not, and it never
pretends.

The other half is quieter: you can build the tool you wish you had, instead of
only using the ones handed to you. That is worth more than any single language,
and it is why lux is built to be left behind. When it starts to feel small, that
means it worked — go pick a bigger one, and bring the moves with you.

<!-- learn:end -->

---

## For the maintainer

The sections below are notes on lux itself, not part of `lux learn`.

### Scope

The teaching surface above is the whole language. The interpreter grew toward
it in milestones, simplest first:

1. **`lux run` core**`print`, `let`/`var`, the four basic types, arithmetic,
   strings, `if`/`else`, `while`.
2. **Functions** — definitions, parameters, return, recursion, `for ... in`,
   ranges, arrays.
3. **Types** — structs, then enums and `match`.
4. **No null**`Option`/`Result` and the generics machinery they need.
5. **`lux convert rust`** — the first transpiler backend (also powers
   `lux build`).
6. **`lux convert swift` / `lux convert go`** — the remaining backends.
7. **`lux learn`** — the reference and tutorial, built into the binary and
   cross-referenced from error messages.
8. **`lux learn`, second level** — cards by default, an optional `more` page per
   topic, the `lux learn basics` skeleton, and reason-annotated cross-references.
9. **The outside world**`readFile`/`writeFile`, `args`, `readLine`, and
   `print`/`eprint` across stdout and stderr: fallible I/O modeled as
   `Option`/`Result`, the first surface that genuinely needs them.
10. **Running other programs**`run(program, [args])` returning
    `Result<Output, string>`, where `Output` is the one built-in struct
    (`status`, `stdout`, `stderr`). Teaches the two-layer failure split (did it
    launch vs. did it succeed) and the arg-vector security posture. The limit
    named for learners: it is batch capture, not a live pipe.
11. **Errors as trails** — a diagnostic carries an optional `(topic, lure)` trail
    to a `lux learn` topic, rendered as the `help:` line, plus a new `errors`
    topic on how to read one. Wired where an error sits on a concept worth
    following; self-evident fixes are left trail-less on purpose, so the `help:`
    lines stay signal. The lure previews *why* the trail is worth taking, turning
    a pointer into an invitation.
12. **The closing bridge**`lux learn beyond`, a furniture page beside the
    `basics` skeleton and the graduation ladder, and the last note of the tour.
    It names what transfers past lux *and* past code — decomposition, precise
    specification, debugging-as-a-stance, and agency (building your own tools).
    Stated once, in the same terse voice; never preached.
13. **`lux crawl`** — scaffolds a playable, editable text adventure into the
    current directory. The world is `examples/keep.lux` itself, embedded with
    `include_str!`, so the thing you play and the thing the tests run can't drift.
    A `crawl` learn topic teaches building one. Built entirely on today's language
    — no new features — on purpose: the stiffness (exact-match commands) and the
    missing pieces (string parsing, maps, imports) are left as walls a learner
    discovers and asks for, not gifts handed down ahead of the wish.

That second level: every topic is a short *card* by default, with an optional
`more` page carrying the deeper why, the universal name for the concept, and
where it goes in other languages. `lux learn basics` is the procedural-language
skeleton; the cross-references that bind related topics live on the `more` pages,
each with a reason. The `scope` topic was added here, once the interpreter's
block scoping was confirmed to enforce it.

### Transpiler edges

`lux run` — the interpreter — is the complete language. The three transpilers
cover the teaching examples and most real programs, but a richer one (the crawl
world is the first to do it) meets a few genuine boundaries. These are documented
rather than papered over: each is a real seam between lux and a target language,
the kind worth meeting head-on instead of hiding.

- **Reserved-word collisions are handled.** A lux name that is a keyword in the
  target (`go`, `where`, `map`, …) gets a trailing `_` in that backend only, and
  only at value positions — function names, parameters, locals
  (`src/convert/mod.rs`, the `*_ident` helpers). Type names, struct fields, and
  enum cases are deliberately *not* mangled: a struct named `map` or a field
  named `type` is an unsupported name, not a bug.
- **Go, `Option<enum>`.** `Option<T>` lowers to `*T`, but a user enum is already
  a Go interface (a nil interface is its empty), so `Option<Room>` wants the bare
  interface, not a pointer to it. Until that special case exists, a program that
  returns an `Option` of one of its own enums won't compile as Go — though it
  runs under the interpreter and converts to Rust and Swift cleanly.
- **Go, the empty array literal.** `[]` with no elements has no element type to
  infer, so it emits `[]any{}`; assigned to a typed field, the types disagree. A
  non-empty literal, or a future infer-from-context pass, sidesteps it.
- **Rust, a value used after a move.** The Rust backend doesn't track ownership,
  so a value pushed into an array and then read again emits a use-after-move. The
  interpreter and the value-copy backends (Go, Swift) don't mind.

`examples/keep.lux`, the crawl world, hits the last three by virtue of being a
real program, so it is exercised by the interpreter, not the transpile suite; the
`crawl` learn topic's example stays inside the lines and converts to all three.

### Settled syntax decisions

- **Function calls are positional**`factorial(3)`, not `factorial(n: 3)`.
  Matches Rust and Go; Swift's argument labels become a graduation lesson.
- **Struct and enum *construction* names its fields**`Point(x: 0, y: 0)`,
  `Shape.circle(radius: 2.0)`, mirroring Rust.
- **`match` arms use `=>`**, kept distinct from the function return `->`: `->`
  names a return *type*, `=>` maps a pattern to a *value*.
- **Ranges use `0..10`** (end-exclusive), matching Rust.
- **A trailing comma is allowed** in any comma-separated list.
- **No string interpolation**`+` with explicit `string(...)`, plus
  multi-argument `print`, keeps the no-coercion lesson front and center.