When lux Feels Small

The day you outgrow this language, and what to do about it.

At some point lux stops being big enough for what you want to build. You will want to hold a thousand rooms without writing a thousand lines, or split your program across two files, or pull in something somebody else already wrote, or let a friend walk through your keep from their own computer — and lux will not do any of it.

That day is not a failure, and it is not something that happened to you. It is the plan. lux was built small so that you would reach the end of it, and reaching the end of it is what the whole thing was for.

This page is about what to do next: how to be sure that is where you are, what you take with you, what is genuinely new, how to pick the language you move to, and what the first day looks like.

How you know

Outgrowing a language does not feel like a graduation. It feels like being annoyed, over and over, at the same four or five things. So here is what being finished with lux actually sounds like from the inside.

You want to take a sentence apart. Your keep accepts take key and refuses get key, and you have wanted, more than once, to look at the first word of what somebody typed and treat the rest as the name of a thing. lux cannot cut a string into pieces. This is the wall most people hit first, and hitting it means your world got big enough to need real words.

Your match arms have gone past what you can read. Five rooms fit in exit comfortably. Thirty do not, and by then what you want is a way to look a room up directly instead of matching your way down a list. Every other language has that. lux does not.

One file is not enough. Your keep is nine hundred lines and you are scrolling past the same four functions all day. Or worse and better: you and a friend each built a wing and you want to join them into one world, and the only way is to open both files and copy by hand. There is no import in lux.

You want something lux was never going to do. Two people in the same keep at once, over a network. A window with pictures in it instead of text. A program that keeps doing something while it waits for you to type. None of those are missing lux features. They are different kinds of program, and wanting one is the clearest signal there is.

If none of that sounds like you yet, you are not behind. Keep building. The walls come to you; you do not have to go looking for them.

This was always the plan

Most first languages want to keep you. They are built to be the language you use for everything, so they grow and grow, and leaving one means admitting you picked wrong.

lux took the other bet. It is small on purpose, and the things it leaves out are not being hidden from you — they are the ideas the bigger languages are built around, waiting for you on the other side. Leaving is not a break in the plan. It is the last step of it.

Here is the part that surprises people. Your keep comes with you. lux can rewrite your program in Rust, Swift, or Go, and the result is not a sketch or an approximation: it is source code in that language, which compiles, and which plays exactly the same keep.

$ lux convert swift world.lux > Keep.swift
$ lux convert go    world.lux > keep.go
$ lux convert rust  world.lux > keep.rs

So on your first day in a new language you are not starting at hello, world. You are reading a three-hundred-line program that you wrote, that you know by heart, wearing clothes you have never seen. That is a much better place to start than an empty file.

In your terminal: lux learn main — the last lesson lux teaches, and the one written for this exact moment · lux learn beyond — the short version of this page. Beyond at the end of the introduction says it a third way.

What you take with you

It is worth being specific about this, because "the concepts transfer" is the sort of thing people say without meaning anything by it. Here is what you actually know. Some of it you will meet again within a week in whichever language you pick; all of it you keep either way.

You know that a type is a promise about what a value is, and that the machine will hold you to it. Every language you are considering does this, and the ones that do not will feel loose and slightly dangerous to you now, which is a good instinct to have acquired early.

You know that a function is a boundary — what goes in through the parameters, what comes out through the return, and nothing else. That one line at the top of a function tells you what it can and cannot get up to. You will read unfamiliar code faster than people who never learned to look there.

You know that a closed set of possibilities beats a piece of text. An enum with five rooms in it cannot hold a typo. You will meet enums in all three languages, and in all three the compiler will make you cover every case, exactly the way lux did.

You know that missing is a shape, not a hole. When a lookup might have no answer, the type says so and you have to open it. Rust calls it Option, Swift calls it Optional, Go does it with a second return value. You will also meet null out there, in older code, and you will recognize it immediately for what it is — the same idea with the check left out.

And you know the shape of a program that works by handing back new values instead of poking old ones. A world goes in, a world comes out, undo is a spare variable. That idea has names — people building websites call it a reducer — and it is the organizing principle of an enormous amount of modern software.

None of that was lux. Those are the shared bones. lux was just the gentlest place to meet them.

What is actually new

Now the honest half, because a page that only tells you it will be easy is not much use on the day it is hard. Four things are genuinely new, and they are new because they are difficult, not because lux was keeping them from you.

Who owns a value. In lux, giving something to a function copies it, always, and two names can never mean the same thing. That rule is unusual, and it was doing a lot of quiet work. Out in the wider world, values get shared, and every language has an answer for what happens when two parts of a program are looking at the same thing and one of them changes it. Rust's answer is the strictest and it is most of what makes Rust hard. This is the big one.

Doing more than one thing at once. A lux program does one thing, in order, until it stops. Waiting for a file, drawing a window, talking to another computer, and listening for what you type — all at the same time — is a whole area of programming that lux has no version of.

Bundling data with the code that works on it. You have written functions that take a struct. Most languages let you attach the function to the struct instead, so it travels with it, and some are built almost entirely around that idea. It is not hard, exactly, but it rearranges how you lay a program out.

Other people's code. There is no import in lux, which means you have never used a library. Out there, most of what a program does is done by code somebody else wrote, and finding it, trusting it, and fitting it together is a real skill that you have not started on yet. It is also the thing that will make you fastest, soonest.

Four things. Everything else is punctuation.

Choosing where to go

People will tell you one of these languages is the right one. Ignore that. What actually matters on your first week is how much of your own program you can still recognize, and you can see that for yourself rather than taking anybody's word for it.

Here is one function from your keep — the six-line helper that answers "is this in my pack?" — in lux and then in each of the three, taken straight from what lux convert produces:

lux

func has(items: [string], thing: string) -> bool {
    for it in items {
        if it == thing {
            return true
        }
    }
    return false
}

Swift

func has(_ items: [String], _ thing: String) -> Bool {
    for it in items {
        if it.unicodeScalars.elementsEqual(thing.unicodeScalars) {
            return true
        }
    }
    return false
}

Go

func has(items []string, thing string) bool {
    for _, it := range items {
        if it == thing {
            return true
        }
    }
    return false
}

Rust

fn has(items: &Vec<String>, thing: String) -> bool {
    for it in (*items).clone() {
        if it == thing {
            return true;
        }
    }
    return false;
}

Read those four again and the choice mostly makes itself.

Swift is nearly the same function with capital letters. The types moved from string to String, there are two underscores that were not there before, and the shape is exactly what you wrote. If you want the shortest distance between the language you know and the next one, this is it.

The one strange line is worth a minute, because it is not Swift being complicated — it is lux being careful. A Swift programmer writing this by hand would put it == thing, the same as you did. But Swift's == on strings does something none of the others do: it treats two different spellings of the same accented letter as equal, because on screen they are the same letter. lux, Rust, and Go all say those are two different strings. So the translation spells out a comparison that matches what your lux program actually did, rather than quietly changing the answer:

let a = "e\u{0301}"   // e, then a combining accent
let b = "\u{00e9}"     // é, as a single character

a == b                                          // Swift says true
a.unicodeScalars.elementsEqual(b.unicodeScalars) // ...and this says false
                                                 // lux says false too

You will not care about this for a long time, and you can write == in your own Swift and be fine. It is here because it is a good early example of something you will meet everywhere once you leave: two languages that look identical, agreeing on almost everything, and disagreeing about one small thing that turns out to matter.

Go is close, with visible seams. bool comes after the parameters instead of behind an arrow, the loop says for _, it := range items, and printing is fmt.Println rather than print. Nothing there is hard; it is a few new spellings for things you already do.

Rust is the furthest, and you can see why in one line: &Vec<String> and (*items).clone(). That & is Rust asking who owns the list — the question lux never made you answer, because in lux the answer was always "you get a copy." Every one of those little marks is Rust being precise about something lux decided for you.

So, ranked by how much of your keep you will recognize on the first morning: Swift, then Go, then Rust.

That is not the same as which is best, and it should not be the only thing you weigh. Pick Swift if you want to make something for a phone, or if you want the gentlest step. Pick Go if you want to make things that run on servers and talk over networks, and you like a language that is deliberately plain. Pick Rust if the reason you are leaving is that you want to know exactly what the machine is doing, and you are willing to argue with a compiler to get it — Rust will teach you more about how computers actually work than the other two, and it will be less fun for the first month.

And if you cannot decide: convert your keep to all three and read them. It costs nothing, it needs no installing, and half an hour with three versions of a program you already know will tell you more than any amount of reading about which language is best.

The first day

Do not start with a tutorial. You already have a better first exercise than any tutorial will give you: a program you wrote, that you understand completely, in a language you have never read.

Convert it and read the whole thing. This needs nothing installed — lux convert just prints text, so you can do it right now.

$ lux convert swift world.lux > Keep.swift

Read it top to bottom without trying to change anything. Find describe. Find your rooms. Find the locked door. You will not understand every line, and that is not the point — the point is that you can find your own keep in there, and that the shape of it survived the trip.

One thing you will meet near the top is main — the line that says where a program starts. Rust and Go both require it, and every beginner in those languages is told to copy it on faith before they can possibly know what it means. You do not have to: lux teaches it as its own last lesson, deliberately left until the end so it arrives as a bridge rather than as ceremony. Run lux learn main before you go, and that will be one fewer thing in the new file that you have to take on trust.

Then get it running. This is where you do have to install something: the language's own compiler. That is a real step and it is sometimes fiddly, and it has nothing to do with programming — it is just setup. Ask for help with it if you need to, and do not let it convince you that the language is hard.

Then change one line. Rewrite a room description in the new language and run it again. The moment your words come out of a program that is no longer lux is the moment you have actually moved.

Then break it on purpose. Delete a match arm. Misspell a name. Take out a semicolon, if your new language has them. Read what the compiler says. New error messages are the biggest thing to get used to, and lux has been unusually kind to you — most compilers assume you already know the language, and their messages are written for somebody who does. Meeting them while you still know exactly what you broke is the cheapest possible way to learn to read them.

What will feel bad

Three things, so that when they happen you know they are normal and not a sign you should go back.

You will feel slow. You were fast in lux. You knew where everything was and you could add a room in four minutes. In the new language you will spend twenty minutes on something you could have done in one, and it will be genuinely irritating. That feeling is the cost of the move and it does not last. Everyone pays it, every time, in every language — people who have been doing this for thirty years pay it too.

The errors will be worse. lux's error messages name the rule, explain the reason, and point you at the card that covers it, because lux is a teaching language and that is its whole job. Your new compiler is written for professionals. Some of its messages are excellent and some are a paragraph of jargon about a line you did not write. Read them slowly, take the first line seriously, and ignore the rest until you have to care.

You will miss things you did not know you liked. Some rule lux enforced quietly turns out to have been holding your program together, and now nothing stops you from doing the thing it prevented. That is not the new language being careless. It is you finding out which of your habits were yours and which were the language's — and that is worth knowing, because from here on the good habits have to come from you.

None of this is a reason to stay. It is just what moving feels like, and knowing that in advance makes it much easier to sit through.

The three guides

Reading your converted keep is the best first exercise there is, and it goes better with somebody pointing at things. There is a guide for each of the three: your keep in that language, side by side with the lux you wrote, with the new punctuation named and the genuinely new ideas explained where they first appear.

All three are written. Read the one for the language you picked — or skim all three, since comparing what each does with the same program of yours is its own kind of lesson.

Your Keep in Swift — the shortest move, and the shortest guide, because very little changes. What the underscores and leading dots are, why Option<Room> becomes Room? and why that is an improvement, what you would actually write instead of what the translation produced, and the walls from the top of this page disappearing one at a time.

Your Keep in Go — a plain, small language with a different shape for errors and no enums of the kind you know. Most of it is about what your enum and your Option turn into, and about the one guarantee Go hands back to you: nothing there makes you check for a missing value, and forgetting is the most common bug in the languages that allow it.

Your Keep in Rust — the longest and the most worth it. Every guarantee lux gave you survives here, and the one lux checks late Rust checks before your program runs at all. Then it asks the question lux never did: who owns this value, and what happens when somebody else wants it.

Even with a guide open, the first move is the same. Convert your keep, read it beside the original, and look up whatever you do not recognize. That is exactly what the guides will be doing with you, and doing it yourself first will make them easier reading when they arrive.

In your terminal: lux learn beyond · lux convert to see any of the three. On the web: The same ideas in other languages in the introduction has a short table of the four side by side, and What lux Leaves Out is the long version of why lux is shaped this way, written for somebody who already programs.