Modules, Bubbles, and native execution
Luau modules participate in runtime execution: require runs or retrieves a module and receives its returned value. Pop organizes declarations before runtime and compiles a Bubble into an executable program.
A namespace is not a returned table
Section titled “A namespace is not a returned table”namespace App.Math
function double(value: Int): Int return value * 2endThe namespace gives double a qualified home. It is not constructed at runtime and cannot be stored in a local.
Another file can use it:
namespace App
using App.Math
function main() print(double(21))endusing changes name resolution. It does not execute math.pop, return a value, or perform a dynamic lookup.
Files belong to Modules and Bubbles
Section titled “Files belong to Modules and Bubbles”Pop’s organization proceeds through explicit compiler boundaries:
Item -> Module -> Bubble -> Package -> WorkspaceFunctions, records, classes, and constants are Items. Source ownership gives those Items a Module boundary. A Bubble is the compilation and visibility unit described by bubble.toml.
This hierarchy is not a nested table of runtime exports. It tells the compiler where a declaration belongs and who may name it.
Visibility replaces export-table conventions
Section titled “Visibility replaces export-table conventions”Declarations are internal by default:
function helper()endUse private for the declaring Module and public for other Bubbles:
private function implementationDetail()end
public function supportedApi()endThere is no source-level export modifier. Returning a table is not how declarations become visible.
No computed require
Section titled “No computed require”Pop must resolve reachable code while compiling. It does not load a source module from a runtime-computed string:
local feature = require(path .. featureName)Choose the dependency through declarations and typed control flow instead. If several implementations share behavior, an interface may describe their common nominal contract.
No mutable global environment
Section titled “No mutable global environment”Do not expect a host to populate arbitrary global names. Pop resolves locals, parameters, declarations, namespaces, types, and constants statically.
Shared runtime state belongs in a deliberately passed managed object or another typed value. Constants are compile-time declarations, not mutable entries in _G.
This makes dependency direction visible: a function receives what it uses rather than discovering it through ambient table mutation.
A Bubble manifest
Section titled “A Bubble manifest”The conventional project layout is:
app/├── bubble.toml└── src/ ├── main.pop └── math.pop[package]name = "App"version = "0.1.0"edition = "2026"Run it from the project directory:
pop run --manifestPath bubble.tomlrc.3 recognizes dependency metadata but does not yet resolve and download external packages. The manifest is a real project boundary, not a complete package registry workflow.
Ahead-of-time execution
Section titled “Ahead-of-time execution”pop build produces a native executable through the LLVM path:
pop build src/main.pop --output app./appThe executable does not require the Luau VM to interpret the source. Types and reachable generic specializations are resolved before backend execution.
This execution model rules out several Luau assumptions:
- dynamic source loading;
- replacing functions in an export table after loading;
- discovering fields by arbitrary runtime name;
- relying on module execution for implicit registration;
- inspecting a universal VM value representation.
Roblox APIs are host APIs, not language syntax
Section titled “Roblox APIs are host APIs, not language syntax”Roblox Luau programs receive services, Instances, events, tasks, and Roblox datatypes from their host. Pop does not automatically provide equivalents merely because some syntax is familiar.
Separate a port into two questions:
- Which logic is ordinary typed computation?
- Which behavior depends on Roblox or another Luau host?
The first portion may translate into Pop functions and data models. The second needs an actual Pop library or integration; renaming the API does not create one.
Diagnostics and traps replace dynamic recovery assumptions
Section titled “Diagnostics and traps replace dynamic recovery assumptions”Wrong names, types, calls, aggregate fields, and operators are normally compile-time diagnostics.
Checked runtime failures include overflow, division by zero, invalid explicit conversion, and violated checked collection preconditions. rc.3 does not provide pcall, xpcall, or catchable source exceptions.
Represent recoverable absence as data—often T? or a tagged union. Treat a trap as a failed program invariant, because source cannot catch and reinterpret it in this release.
Backend boundaries remain honest
Section titled “Backend boundaries remain honest”The LLVM/native path supports the documented rc.3 language. The MIR interpreter is an internal conformance engine. The C backend is experimental and accepts only a runtime-free subset.
Unsupported C-backend constructs fail closed. Pop does not silently execute a different meaning just to emit C.
Porting a service-style module
Section titled “Porting a service-style module”A Luau service module often combines:
- singleton mutable state;
- exported methods;
- initialization during
require; - host lookups;
- callbacks registered for side effects.
Split that design in Pop:
- represent state with a class instance;
- expose typed functions or interface behavior;
- create the instance explicitly from
main; - pass dependencies through parameters;
- perform startup in an explicit function call;
- keep namespace declarations free of hidden runtime initialization.
The result may have more visible wiring, but its order and dependencies are checkable without executing module files to discover them.
Next, Porting a Luau program combines the type, control-flow, data, and module decisions into one workflow.
