Luau – Fast, Small, Safe, Gradually Typed Scripting Language Derived From Lua
Posted4 months agoActive3 months ago
luau.orgTechstoryHigh profile
calmmixed
Debate
40/100
Programming LanguagesLuaType Systems
Key topics
Programming Languages
Lua
Type Systems
The HN community discusses Luau, a gradually typed scripting language derived from Lua, with some users praising its performance and type system, while others raise concerns about its complexity and compatibility.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
46m
Peak period
79
Day 1
Avg / period
18.4
Comment distribution92 data points
Loading chart...
Based on 92 loaded comments
Key moments
- 01Story posted
Sep 18, 2025 at 9:38 AM EDT
4 months ago
Step 01 - 02First comment
Sep 18, 2025 at 10:24 AM EDT
46m after posting
Step 02 - 03Peak activity
79 comments in Day 1
Hottest window of the conversation
Step 03 - 04Latest activity
Sep 29, 2025 at 4:22 PM EDT
3 months ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
ID: 45289558Type: storyLast synced: 11/20/2025, 4:53:34 PM
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
I do wonder if we could reuse TypeScript in other dynamic languages.
Transform Luau to a subset of TypeScript, check with tsc, transform errors and results back to Luau. In the same way, one could reuse a TypeScript language server. This way of utilising TypeScript's engine could jump-start many other type checkers for other dynamic languages.
My thinking in this space has always started from a type inferred MetaLanguage but starting from a dynamic language does enable some interesting options. I tend not to touch dynamic languages, even going so far as to use transpilers, but I definitely would be more open to the idea of working with them if they had TypeScript level of gradual type checking and tool support. As you mention such a bidirectional transpiler would work I guess for things that don’t translate it could just give up and that’ll be part of the gradual typing aspect.
I would love to have TypeScripts type system on a Lua runtime, so I’ve been keeping an eye on Luau.
This would be a very cool project, but depending on what you actually want out of it is basically open research in programming languages. Racket #langs and their general approach to type-checking are one sort of answer to this: it's basically a "language-building lab" with an underlying philosophy that we should all be approaching programming in a language-oriented way, that is, by building domain-specific languages that raise the level of abstraction for what you're talking about. That's probably the most developed approach to this sort of thing, but it's still very much something that the Racket folks continue to iterate on and design further.
If you want more conventional _static_ type systems with a much harder delineation between "compile-time" and "runtime" than exists in Racket, you start getting into a sort of compiler-generator-framework territory with the type system, and you'd probably want a language for metatheory to program the type system in, and that starts to look like compiler-generator frameworks embedded into proof assistants like Agda or Rocq. There's some research in that space as well, but certainly nothing that has anything near universal agreement that it works well.
Right now, the state-of-the-art for designing type systems for any language is essentially "employ a bunch of people with very, very deep specialization in this subject matter to painstakingly design and implement the type system with careful consideration of the runtime semantics of the language it's being built for." It's not easy, and it's not cheap, which is why the successful instantiations of this kind of thing are all massive corporate-backed projects. As someone who is really a type system designer first and a corporate-tech-worker a very distant second, I'd love to be able to democratize building type systems more (or to see that be democratized), but it feels very, very far away from being solved.
EDIT: Oh Hi! I didn't realize you're from the Luau team, cool, while Roblox isn't as big as Microsoft I wonder if it's a large enough multi-national to make a real dent in the research, I do hope you'll get your wish. I know I don't have the resources to take on such a project myself. I do type directed code generation which is about as close to the sun as I can safely fly.
I had a pretty good experience with it while trying out Love2D.
What I meant was transpiling Luau (in memory or cached to disk) -> TypeScript -> typecheck with tsc -> take error outputs and line numbers -> transform back to Luau code via sourcemaps etc. This is potentially way easier than making your own checker for another structurally typed language.
User only sees Luau script in their editor, but it gets checked by TSC in the background.
Roblox might is such a big maker that they can re-invent the whole structural typing themselves, so they don't need to do that.
IMO a better approach is the one used by rescript and gleam. With a few careful restrictions of the target language you can fit it into a hindley-milner type system. These are extremely well understood, robust & usable, and give you a much smaller interface than the expansive turing complete one of TS.
I'm kind of surprised there's not an active project for a small ML language outputting lua code. I really wish gleam could pick it up as a third backend, it would be an amazing fit.
[0] https://teal-language.org/
Luau is a backwards compatible superset of Lua that comes with it's own performance-tuned runtime. It offers more than just gradual typing.
So they are very different things. You can use Teal in cases when you don't control the runtime. Like write a Love2d game or your neovim config in it. Anywhere where Lua runs, you can use teal.
On the other hand Luau can offer superior developer experience because you don't have a separate compile step. They can do a lot more things that are impossible with teal as they have their own runtime and types do not get erased by compiling.
Roblox has a market cap near $100B and has multiple developers working full-time on Luau.
But I think that complexity is unavoidable for a gradually- or statically-typed language. Any language with a reasonably-complete type system is inevitably going to be much more complex than a dynamically-typed scripting language.
[0] Counting *.cpp files in the "Analysis", "AST", "Compiler" and "VM" directories
- Analysis: 62821 lines of C++ code, 9254 lines of C headers
- Ast: 8444 lines of C++, 2582 lines of C headers
- CodeGen: 21678 lines of C++, 4456 lines of C headers
- Compiler: 7890 lines of C++, 542 lines of C headers
- VM: 16318 lines of code, 1384 lines of C headers
Compare to Lua 5.1, which tokei says has 11104 lines of C and 1951 lines of C headers in the src/ directory.
If you look purely at the VM and things necessary to compile bytecode (AST, Compiler and VM) then the difference in code size isn't as stark.
Having worked with both Lua 5.1 and Luau VM code, Luau's codebase is a heck of a lot nicer to work on than the official Lua implementation even if it is more complex in performance-sensitive places. I have mixed feelings on the structural typing implementation, but the VM itself is quite good.
The REPL that we offer in the distribution doesn't include any of the analysis logic and it's just 1.7mb once compiled (on my M1 Macbook). I'm not sure how much smaller it gets if you omit CodeGen.
Luau can be pretty small if you need it to be.
The Lua REPL executable on my cellphone is 0.17 megabytes.
I mean, it's smaller than bash? But even your mom is smaller than bash.
I suspected as much, but I didn't want to guess since I'm not familiar with either codebase. Thanks for the info!
Probably also worth mentioning that Analysis currently contains two full type system implementations because we've spent the better part of the past three years building a new type system to address a lot of the fundamental limitations and architectural issues that we'd run into after years of working on the original one. The new one is not without issues still, but is definitely very usable, and gets better and better every week. At some point in the future, we will clip the old one altogether, and that'll shave off some 30,000 lines of code or something.
If performance, are you comparing against a baseline that does the classic Self-style optimizations (like what JS engines do)?
FWIW I don't consider LuaJIT to be an interesting baseline because of what I've heard about it being overtuned to specific benchmark that the author(s) care about. (Too much of an attitude where if your code doesn't run well then it's your fault.)
Longer-term, there's definitely some interest in how we could leverage types to support more optimized code, but it's been a notoriously difficult problem for gradually-typed languages in general, see [Is sound gradual typing dead?](https://dl.acm.org/doi/pdf/10.1145/2837614.2837630)
> primary interest in building the type system is in supporting a better developer experience
Yeah this is the right reason to do it :-)
> there's definitely some interest in how we could leverage types to support more optimized code, but it's been a notoriously difficult problem for gradually-typed languages in general
I know. I still get folks asking why TypeScript types can't be used to make JS fast and it's quite exhausting to explain this.
Hard to imagine gradual types beating what you get from the combo of PICs, speculative JITing, and static analysis.
(I worked on JavaScriptCore's optimizer, hence why I'm curious about where y'all are coming from on this)
I think you can implement a Hindley–Milner type checker in about a page of code, not the 2000 pages of code you're talking about.
I'm not sure what you mean by "complete". H–M is complete in the sense that it's decidable and doesn't leave any holes: programs that check statically are guaranteed not to have type errors at runtime. It handles higher-order functions and parametric polymorphism (generics) out of the box, it doesn't suffer from null pointers, and it can even handle mutability. And it's fully inferrable. There are various extensions to make it more expressive (GADTs, typeclasses, subtyping, linearity, tagged arguments) but even the basic HM system is already a lot more powerful than something like the type system of C or Java 1.7.
You're right, that statement was too general. Python is a dynamically-typed scripting language (if you exclude external tools like MyPy), and is one of the most complex languages out there.
I should have been more specific: "Any language with a reasonably-complete type system is inevitably going to be much more complex than Lua".
> I agree that static and especially gradual typing add complexity, but it's a very much smaller amount of complexity than we're talking about here [...] I think you can implement a Hindley–Milner type checker in about a page of code
aw1621107's comment shows that Luau's type checker (the "Analysis" directory) is ~60% of the project's code. Maybe there are languages where the equivalent is just a single page, but even then, type checking makes a language implementation more complex in other ways as well.
For example, Luau's AST implementation alone is 75% the size of the whole of Lua 5.1. By deferring type-checking to runtime, Lua can avoid the need to build an AST at all: the compiler can go straight from source code to bytecode.
But, in fact, writing a compiler, with an AST and static types, is a common single-person term project for undergraduates, and it's something people have been doing for 70 years, since computers used magnetic drums and acoustic delay lines for RAM. We've learned techniques since then that make it easier, which is why undergraduates can do it now.
One notable example is Stephen C. Johnson's "portable C compiler", which was the main C compiler in the late 70s and early 80s. By my count the current version of it at https://github.com/PortableCC/pcc is a bit under 50,000 lines of code, including C, some kind of C++, and Fortran frontends and backends for 18 architectures, but not including lex and yacc. I just built it here on my phone, which required implementing getw() and putw() (maybe I don't know the right feature test macros) and #including <strings.h> with the "s" in aarch64/local2.c. The executables total about 320K, including C and C++ (no Fortran) and only aarch64.
[1] https://github.com/thomasmueller/bau-lang [2] https://github.com/thomasmueller/bau-lang/blob/main/src/test... [3] https://www.reddit.com/r/ProgrammingLanguages/
See https://luajit.org/extensions.html.
https://github.com/luau-lang/lute
https://github.com/lune-org/lune
i use it for build scripts and automating tasks for Roblox place files, it's pretty good for my use-case
That being said, it has been historically hard to get major investment into work actively supporting growth of the language off-platform since our entire team is employed to work on the project by Roblox. We are nevertheless changing this though, and investing in the language outside of the platform. As some folks have already mentioned here, we have a general-purpose standalone runtime that we're developing called Lute that's focused on using Luau outside of Roblox to write general-purpose programs, and we're building a whole suite of Luau-programmable developer tools for the language atop it.
It takes time to build things, and the Luau ecosystem is definitely still very young as you've noted, but it's something that we care a lot about and are investing in considerably going forward. We 100% believe that the best thing for the health of the language and the ecosystem is to support more diverse users and more diverse use-cases.
Also strategically, wasm is a massive project coordinated by a large number of companies, and it doesn't seem especially prudent to bet the success of a single multibillion dollar company and their entire platform on a project that they don't control the destiny of.
The scripting engine is an integral part of your product, and you need to "own" it end-to-end. Any bugs that creep into new versions of your scripting engine, any API breakage or design changes that impact your usecase are things that you are responsible for. Roblox owns the entire toolchain for Luau, and it's relatively small compared to the set of libraries required to compile to and execute WASM in a performant way.
The nuances of your typical JITing WASM runtime or V8 are pretty hard to learn compared to a simpler VM like Luau, it's a big reason why I've used Luau in my own projects.
"LunarEngine: An open source, Roblox-compatible game engine" (2025) https://news.ycombinator.com/item?id=44995147
But otherwise I really like it. My Luau code hot reloads almost instantly while the game is running, and my C++ project compiles quickly when I update it. I like that it provides sandboxing if you want to support modding. And there's an active community of people on the official Discord, which is better than nothing.
Do you have a link for the official Luau Discord? I can't see anything on their webpage or github and google is not helpful either.
Sounds like the Discord I mentioned is technically the Roblox discord. The #luau-lang and #luau-lsp channels are where I lurk.
They have "type hints" which looks to be pretty close to what luau has except they use `any` instead of `unknown` it seems
[1]: https://lune-org.github.io/docs/
[2]: https://lune-org.github.io/docs/the-book/5-networking/#runni...
But when I tried it without reading to much, I got this (bug or feature)?
Code:
Output: There wasn't any error message or warning when I pressed the run button, and that certainly violated my expectations, regardless whether the code and behavior may be considered "correct" (=documented) or not.https://www.freelists.org/post/luajit/Looking-for-new-LuaJIT...
I assume he's off somewhere with _why, questing for chunky bacon.
Kind of surprising if no one picked it up when it's so actively used by major projects (like neovim and others). It blocks using more recent Lua for them.
See https://luajit.org/extensions.html.
If you want to use a more recent Lua version, of course that's available. If you want to use LuaJIT then you know exactly what you're dealing with and that there won't be any weird surprises in the future.
LuaJIT’s code is very complex and esoteric, I don’t think anyone other than Mike Pall has done any significant work on it.
Second Life runs on hundreds of thousands of tiny programs, all event driven and running on the servers. Managing that is tricky, since server side resources are limited. Yet it works well, even if user programs are compute-bound. Actually, the biggest problem is that each idle program uses about 1us per frame, which adds up.
We haven't even used Luau's JIT at all yet, but preemptive scheduling of what's typically trivial glue code is much cheaper and easier with a VM that supports it as a natural consequence of its design versus transforming everything into a state machine at the AST or bytecode level for a VM that doesn't.
> Actually, the biggest problem is that each idle program uses about 1us per frame, which adds up.
More scheduler overhead to resolve :)
The only desire I have is if they could adopt FiveM-style helper functions which help wrap coroutines, namely being CreateThread(fn) and Wait(ms) (wrapper around yield inside that "thread") and Await/Promises (seems like someone already made an implementation for Luau? https://github.com/evaera/roblox-lua-promise)
FiveM adopting these makes it easy to make better performing scripts without having to mangle coroutines, which is vital given the Lua VM has to finish its current task before the frame is allowed to render.
https://github.com/citizenfx/fivem/blob/master/data/shared/c...
We're still in figuring out our async strategy for user-facing APIs to be honest, so these references are super helpful. We already have preemptive scheduling of execution, but it's most likely to be some kind of wrapper around `coroutine.create()` where an event loop is responsible for driving execution flow and internal `coroutine.yield()`s let you specify what you're `await`ing.
We'll likely have an RFC for how that will all work within the year, but several users have written their own bespoke `async` / `await` implementations for SL in Lua already.
I did see these as well as some eventloop-like wrappers, but it's cool to have a built in implementation would be great so each script doesn't need to ship it.
How do you plan on migrating data from Mono's VM to Luau? Off the top of my head I can't think of any method that would be 100% reliable.
It helps a lot that you're only dealing with LSL that was compiled to .NET CIL by a single compiler and transformed into a state machine via an internal tool that predates `async` / `await` in .NET. Luckily you don't need a strategy that works for arbitrary .NET assemblies.
We can inspect those assemblies and the saved script state is stored in an LL-defined serialization format that includes everything on the stack / reachable via the heap. That could be converted to the script state serialization scheme we created for Luau.
The biggest complication would be that .NET CIL presents a stack-based bytecode whereas Lua(u) bytecode is register-based. There's prior art there, IIRC Android's Dalvik bytecode format is register-based and isn't generally compiled directly, you compile stack-based Java bytecode and the Android devkit has tools that convert it to stack-based Dalvik. We could use a similar scheme to convert the limited subset of CIL we need into Luau bytecode, possibly with some Dalvik-like extensions that allow use of "extended" registers for cases where we'd run into Luau's 255 register limit.
I'd like to eventually open-source SL's existing internal tooling for Mono so that people can get a better sense of the problem space and how that conversion would work. It really should have been public from the outset, and I believe the original author of SL's Mono integration wanted it to be.
Migrating existing Mono scripts onto Luau is a bit far out though, since we're still working on the core VM stuff.
IIRC one of the biggest fail points in region crossings is that the source simulator has to serialise and send the state of all scripts attached an agent to the target simulator, and if this fails the crossing fails and the user logs out (and in many cases the script will get marked as not running)
They're about the same as before, they weren't terribly large to begin with though. From what I've seen the region crossing issues aren't caused by script state serialization, but hard to track down issues in edge cases in object handoff that're outside the scope of my contract.
To me, this is the more interesting bit of luau
The performance page[1] contains a pretty good explanation of the work they have done. Pretty impressive engineering if you ask me.
[1] https://luau.org/performance
Unfortunately, there is not a Luau distribution of windows like Luarocks.
Eventually we may see something in this place.
The well known libraries, IUP, CD, IM have not been ported to Luau.
But code is Free Open Source, who knows.