Lua Creator Rejects Native Compilers for Unrestricted Dynamic Code

Creator of Lua: You Can Interpret C And Compile Python | Roberto Ierusalimschy — Ryan Peterman

Ryan Peterman

Dynamic languages cannot compile to fast machine code through automated type inference because engines achieve optimization only when developers manually follow strict static-type discipline.

AI-assisted summary of Creator of Lua: You Can Interpret C And Compile Python | Roberto Ierusalimschy — original source. Check the original for full context.

Source published:

Key ideas

  • The boundary between compiled and interpreted languages is determined by toolchains and feature sets rather than syntax, as Python can technically be compiled to machine code and C can be run via an interpreter. (0:00)
  • True support for dynamic features like eval requires embedding the full compiler as a runtime library, creating substantial architectural overhead that typically discourages ahead-of-time compilation for dynamic languages. (0:00)
  • Static type systems like C's are purpose-built to enable efficient native compilation by allowing direct CPU register allocation and emitting single-instruction operations, whereas dynamic languages require runtime tag checking and heap memory allocations. (1:40)
  • General-purpose type inference cannot resolve dynamic performance bottlenecks because unrestricted code degenerates into generic types; high-performance JIT compilers like LuaJIT depend on developers maintaining strict, monomorphic coding discipline. (4:24)

Detailed notes

## Background This interview segment was published on YouTube by Ryan Peterman on September 2, 2026. According to the video description, the excerpt features Roberto Ierusalimschy, the creator of the Lua programming language, discussing language design, Lua's strengths, and the impact of AI as part of a longer conversation.

## TL;DR Roberto Ierusalimschy breaks down the practical distinctions between compiled and interpreted languages, explaining that dynamic features like `eval` create substantial architectural overhead by requiring a compiler to be embedded into the runtime. He illustrates how sound static type systems enable critical performance optimizations—such as direct register allocation and single-instruction operations—which languages without strict runtime guarantees cannot exploit. Finally, Ierusalimschy dismisses whole-program type inference as an intractable magic bullet for dynamic languages, arguing that JIT engines like LuaJIT only achieve peak performance when programmers discipline themselves to write dynamic code with predictable, static-like types.

## Atmosphere & style The discussion carries the thoughtful, pragmatic atmosphere of an expert computer science seminar unburdened by academic pretension. Host Ryan Peterman serves as a curious, technically adept sounding board, asking incisive questions that prompt Ierusalimschy to methodically dismantle common compiler misconceptions with calm authority. Listening to the conversation feels intellectually clarifying, marked by the steady, experienced voice of a veteran language designer cutting through theoretical ideals to reveal the mechanical realities of runtime systems.

## Bulletin - [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) The boundary between compiled and interpreted languages is determined by toolchains and feature sets rather than syntax, as Python can technically be compiled to machine code and C can be run via an interpreter. - [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) True support for dynamic features like eval requires embedding the full compiler as a runtime library, creating substantial architectural overhead that typically discourages ahead-of-time compilation for dynamic languages. - [01:40](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=100s) Static type systems like C's are purpose-built to enable efficient native compilation by allowing direct CPU register allocation and emitting single-instruction operations, whereas dynamic languages require runtime tag checking and heap memory allocations. - [04:24](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=264s) General-purpose type inference cannot resolve dynamic performance bottlenecks because unrestricted code degenerates into generic types; high-performance JIT compilers like LuaJIT depend on developers maintaining strict, monomorphic coding discipline.

## Key takeaways - [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) The distinction between compiled and interpreted languages resides in the implementation toolchain rather than the language syntax itself. - [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) While it is technically possible to build an interpreter for C, the resulting execution suffers from extremely poor performance. - [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) The eval operation represents the defining feature of dynamic languages by allowing brand-new code generation and execution at runtime. - [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) Natively compiling a language with eval support forces implementations to package an entire compiler inside the runtime environment as a library. - [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) Compilers designed for dynamic languages frequently work around runtime overhead by restricting or forbidding the use of eval. - [01:40](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=100s) Languages like C intentionally construct their type systems to make ahead-of-time machine code compilation straightforward and efficient. - [01:40](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=100s) Knowing variable types ahead of time lets compilers allocate fixed space on the stack or in CPU registers rather than allocating dynamically on the heap. - [01:40](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=100s) Basic operations like A + B can emit single machine instructions in static languages, whereas dynamic engines must inspect operand tags, coerce types, and evaluate metamethods at runtime. - [01:40](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=100s) TypeScript's type system cannot facilitate native machine code compilation because its annotations do not provide hard, machine-level runtime guarantees. - [04:24](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=264s) Whole-program type inference across unrestricted dynamic languages is practically intractable and tends to degenerate into broad, unoptimizable types like any. - [04:24](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=264s) Type inference only succeeds in dynamic languages when programmers adopt self-imposed constraints and write code using static-typing mental models. - [04:24](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=264s) Just-in-time compilers like LuaJIT achieve extreme performance primarily when developers avoid polymorphic variable reuse and maintain uniform data structure types.

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) Compiled vs. Interpreted: Toolchains, `eval`, and Dynamic Execution

The conversation opens with a discussion on the traditional divide between compiled and interpreted languages. Ryan Peterman points out that the distinction often lives within the toolchain rather than the source code's syntax itself—noting that someone could conceivably write a compiler that translates Python directly into machine code. Roberto Ierusalimschy readily agrees, pointing out the reverse: "And you can interpret C code, too. You can write an interpreter for C."

However, Roberto explains that the practical dividing line comes down to what language features are easy to support. Chief among them is `eval`, which he identifies as the definitive hallmark of dynamic languages: the ability to generate and execute brand-new code while the program is already running. If an implementation aims to compile a language to native code while fully supporting `eval`, it must embed the entire compiler as a library within its runtime environment. Because of this architectural overhead, dynamic languages are far more frequently interpreted. When compilers do target dynamic languages, they often impose strict limitations, essentially telling developers, "Oh, you can compile as long as your program doesn't have evals." Conversely, while an interpreter for C can easily be constructed, running C through an interpreter yields extremely poor performance.

```mermaid flowchart TD accTitle: Static Compilation vs Dynamic Runtime Execution accDescr: Flow diagram showing how static C-like languages compile directly to machine code versus dynamic languages requiring an embedded compiler for eval.

subgraph Static ["Static Compilation (e.g., C)"] S_Src["Source Code"] --> S_Comp["Ahead-of-Time Compiler"] S_Comp --> S_Bin["Standalone Machine Code"] end

subgraph Dynamic ["Dynamic Language Execution (e.g., Python / Lua)"] D_Src["Source Code"] --> D_Run["Runtime Engine"] D_Eval["Runtime Code / eval()"] --> D_Lib["Compiler-as-Library"] D_Lib --> D_Run end ```

[01:40](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=100s) How Type Systems Dictate Compilation Efficiency

Turning to the mechanics of compilation, Ryan asks about the role of static type systems and type annotations. Roberto clarifies that the utility of a type system depends heavily on how it was designed; languages like C deliberately structure their type systems to serve as a cornerstone of the compilation pipeline, making the creation of an efficient compiler vastly simpler.

Roberto illustrates this with two fundamental examples: 1. **Memory Allocation:** When a compiler knows upfront whether a variable is an integer, a float, or a double, it knows the exact byte size needed and can assign it directly to stack space or CPU registers. Without that knowledge, implementations are often forced to allocate memory dynamically on the heap, incurring massive performance penalties. 2. **Operation Specialization:** In an expression like `A + B`, a statically typed compiler inspects the types at compile time. If both operands are integers, it emits a single machine instruction for integer addition. In contrast, a dynamic language merely compiles the expression to an abstract virtual opcode. At runtime, the engine must inspect the tags of both operands, handle implicit coercions (such as converting an integer to a float), check whether the language treats `+` as string concatenation, or invoke custom operator metamethods.

Roberto adds a crucial caveat regarding modern languages like TypeScript. TypeScript provides static typing, but its types do not provide hard runtime guarantees. Because a variable typed as an integer cannot be definitively guaranteed at the machine level, the compiler cannot safely assign it to a fixed integer register, limiting the type system's usefulness for native machine code compilation. For types to enable aggressive native compilation, the type system must guarantee complete type soundness.

[04:24](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=264s) The Limits of Type Inference and Dynamic JITs

Ryan asks whether whole-program type inference could bridge the gap: could a compiler run type inference over a dynamic language like Lua, derive an unambiguous set of concrete types, and compile it directly to fast machine code? Roberto answers with an immediate and definitive "No."

While many researchers and engineers have attempted type inference for dynamic languages, Roberto explains that doing so in a general-purpose, high-performance way is an intractable problem. In practice, type inference over unrestricted dynamic code either fails outright or degrades into inferring overly broad, generic types (effectively `any`), which completely defeats optimization. The only scenarios where type inference succeeds are those where programmers adhere to heavily restricted coding conventions—essentially writing dynamic code while strictly thinking in static types.

Roberto observes that this same principle governs the performance of just-in-time compilers like LuaJIT. JIT engines achieve dramatic speedups primarily when programmers exercise internal type discipline: - Avoiding polymorphic variables (e.g., never reusing the same local variable to hold both an integer and a string). - Designing data structures and loops with predictable, uniform types throughout their lifetime. - Adhering to implicit typing rules that match the assumptions of the JIT optimizer, even when the base language does not explicitly enforce them.

## Notable quotes > [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) And you can interpret C code, too. You can write an interpreter for C. — Roberto Ierusalimschy

> [00:00](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=0s) Oh, you can compile as long as your program doesn't have evals. — Roberto Ierusalimschy

> [04:24](https://www.youtube.com/watch?v=4cWFOQQjLvk&t=264s) No. — Roberto Ierusalimschy

## Mentioned Roberto Ierusalimschy, Ryan Peterman, Lua, Python, C, TypeScript, LuaJIT

## Video description Roberto Ierusalimschy is the creator of the Lua programming language. I interviewed him about Lua's unique strengths, programming language design and predictions for how AI will impact programming languages.

This is a segment from the full video here: https://www.youtube.com/watch?v=jCZnFKk6M9A

[Source](https://www.youtube.com/watch?v=4cWFOQQjLvk)

中文简报

Lua创始人否定面向无限制动态代码的原生编译路线 动态语言无法通过自动类型推导编译为高效机器码,因为引擎唯有在开发者主动遵循严格静态类型规范时才能实现优化。 Roberto Ierusalimschy深入剖析了编译型语言与解释型语言在实践中的本质区别,指出类似`eval`的动态特性因需要将编译器嵌入运行时,会带来巨大的架构开销。他阐释了健全的静态类型系统如何支撑关键性能优化(例如直接寄存器分配和生成单指令操作),而缺乏严格运行时保证的语言则无法利用这些优势。最后,Ierusalimschy否认全程序类型推导是解决动态语言问题的万能灵药,并指出像LuaJIT这样的JIT引擎只有在程序员严格自律、写出类型行为可预测且接近静态类型的动态代码时,才能发挥出极致性能。 编译型语言与解释型语言的界限取决于工具链和功能特性而非语法本身,因为技术上Python同样可以编译为机器码,而C语言也可以通过解释器运行。 (0:00) 真正支持eval等动态特性需要将完整编译器作为运行时库嵌入,这会产生巨大的架构开销,通常导致动态语言不倾向于采用提前编译(AOT)。 (0:00) 类似C语言的静态类型系统专为高效原生编译而设计,允许直接分配CPU寄存器并生成单指令操作;相比之下,动态语言则需要运行时类型标记检查以及堆内存分配。 (1:40) 通用类型推导无法解决动态性能瓶颈,因为无限制的代码会退化为通用类型;像LuaJIT这样的高性能JIT编译器必须依赖开发者保持严格的单态编码习惯。 (4:24)

Reading summary

Lua creator Roberto Ierusalimschy argues that while any language can theoretically be compiled or interpreted, dynamic features like runtime eval and the lack of sound static types create steep architectural barriers to efficient native compilation. As a result, peak execution speed in dynamic languages requires either restrictive toolchains or voluntary developer discipline. Runtime eval and Dynamic Execution Trade-Offs (0:00) Ryan Peterman and Roberto Ierusalimschy observe that the divide between compiled and interpreted languages is primarily a property of the toolchain rather than source syntax. Direct machine-code compilers can be built for Python, and interpreters can be built for C. However, Ierusalimschy emphasizes that the practical division depends on which language features are feasible to support, identifying runtime code execution through eval as the definitive hallmark of dynamic languages. Compiling a dynamic language to native machine code while preserving eval requires embedding an entire compiler as a library within the runtime environment. Because of this architectural overhead, dynamic languages are typically interpreted, and compilers designed for them frequently forbid eval outright. Conversely, while an interpreter for C is straightforward to build, running C code through an interpreter results in extremely poor performance. Type Soundness and Native Compilation Efficiency (1:40) Ierusalimschy explains that static type systems in languages like C are deliberately engineered to simplify compiler construction and maximize hardware efficiency. He highlights two critical mechanisms: upfront memory allocation and operation specialization. When a compiler knows a variable's exact type, it can assign storage directly to CPU registers or stack space, whereas dynamic typing often forces implementations to allocate variables dynamically on the heap with significant performance penalties. Similarly, an expression like A + B can be emitted as a single integer addition instruction in C, whereas dynamic engines must generate abstract virtual opcodes that perform runtime tag checks, handle implicit type coercions, or resolve custom metamethods. Ierusalimschy also cautions that static type annotations alone do not ensure efficient machine compilation. In systems like TypeScript, types do not offer hard runtime guarantees. Because the underlying execution model cannot guarantee an annotated integer variable at the machine level, the compiler cannot safely assign it to an integer register. To unlock aggressive native machine optimizations, a type system must provide complete type soundness. Limits of Whole-Program Type Inference and JITs (4:24) Addressing whether whole-program type inference could automatically bridge the performance gap for dynamic languages like Lua, Ierusalimschy characterizes general-purpose type inference over unrestricted dynamic code as an intractable problem. In practice, automated inference either fails or defaults to excessively broad types such as any, which eliminates optimization opportunities. Automated inference only succeeds when developers adhere to narrow coding conventions by writing dynamic code while strictly thinking in static types. According to Ierusalimschy, this reliance on programmer discipline directly explains the behavior of just-in-time (JIT) engines such as LuaJIT. JIT compilers attain substantial performance gains primarily when developers self-impose implicit typing rules: avoiding polymorphic variables that reuse storage for different types, keeping data structures uniform, and designing loops with predictable types that align with the JIT's internal optimization assumptions. Ierusalimschy indicates that high execution performance is fundamentally tied to language design rather than compiler capabilities alone. Without sound static types and restrictions on dynamic features, native performance requires programmers to manually write dynamic code as if it were statically typed.

Source quotes

[0:00] Roberto Ierusalimschy: And you can interpret C code, too. You can write an interpreter for C. [0:00] Roberto Ierusalimschy: Oh, you can compile as long as your program doesn't have evals. [4:24] Roberto Ierusalimschy: No.

Original transcript

[0:00] I always hear there's there's compiled [0:02] languages and there's dynamic or [0:04] interpreted languages, I guess. [0:06] But really that distinction is in the [0:09] tool chain, not necessarily in the way [0:11] that the symbols are laid out in the [0:13] source code. Like I could write compiler [0:15] or program that takes in Python code and [0:19] then converts it into machine code and [0:22] then I would have compiled Python. [0:24] >> And you can interpret C code, too. You [0:27] can write an interpreter for C. [0:30] Yes. But the the main difference is [0:32] actually what it's easy to For instance, [0:35] as I said, one of the hallmarks of [0:38] interpreted languages eval. So, [0:41] if you want to compile the language and [0:44] keep eval, you have to have a compiler [0:48] as a library of your [0:51] of your runtime because you may want to [0:53] compile things [0:55] during execution. This is the hallmark [0:58] of a dynamic language. That's So, you [1:00] can create code while running code. So, [1:05] that part is that that's why [1:08] in [1:09] more much more often than not [1:13] dynamic languages are interpreted. [1:17] But you can compile but then sometimes [1:19] or some compilers do not handle eval at [1:23] all. They say, "Oh, you can compile as [1:25] long as your program doesn't have [1:27] evals." The so there is some [1:29] restrictions. And as I said, you can [1:32] interpret C code, I mean, but you'll be [1:36] extremely slowly with it. It doesn't but [1:40] >> I think one big difference I mean, when [1:42] I look at statically you know, static [1:45] languages or compiled languages, [1:47] they they seem to have type systems in [1:49] them or the type annotations. [1:52] What is the role of a type system in the [1:54] compilation process? [1:57] >> Depends a lot of the on the type system. [2:00] Several type systems like in C for [2:03] instance are written to be an very [2:06] important part of the compilation [2:09] process. So, if you have the right type [2:12] system, it's much much easier to write a [2:15] compiler. [2:17] A trivial example is one of the space [2:20] for variables because if you know all [2:22] this is an integer, this is a float, [2:25] this is a double, you know exactly how [2:27] many bytes of memory you need to that. [2:31] Otherwise, it's a mess of more often [2:33] than not you have to keep everything in [2:35] the heap dynamically allocated. And so, [2:40] huge [2:41] penalty in performance. And for [2:44] instance, when you see it as I said, a [2:46] trivial you have an addition. [2:48] A plus B. If you know the type of A [2:52] A plus B, you have the type of A, the [2:54] type of B, you have a compile time, you [2:57] know all this plus is a [2:59] I'm adding two integers, I just generate [3:02] the machine code to add integers, and [3:04] there everything is fine. In a dynamic [3:06] language, I see a plus, [3:09] I don't I I I compile [3:11] to a virtual in the virtual language it [3:14] is just piece of plus, but then how do I [3:17] at run time you have to check what is A [3:20] or A is an integer, B is a float, oh [3:22] yeah, I have to convert or B is a string [3:25] or depending on the language, you're [3:27] you're not doing addition, it can be [3:29] doing concatenation, or you can just [3:31] calling someone, and you have to do all [3:33] that [3:34] at run time. So, types can be very very [3:38] important to compile efficiently, but of [3:43] course you have to [3:45] have a type system that [3:48] that there are several language now like [3:50] type type script that [3:53] types are kind of [3:55] you do not guarantee that everything has [3:58] the types you said [4:00] they have. So, then it's impossible to [4:03] use them to compile because oh, thread [4:05] the BA will be an integer, but if it's [4:08] not I mean it's if it I have an an [4:11] integer register to put 40 [4:14] it must be a register or [4:17] things do not work. So, but if you have [4:19] the right type system they are essential [4:23] for a good compilation. [4:24] >> I know some programming languages have [4:26] type inference. What if you did you run [4:28] type inference and you got a unambiguous [4:32] set of types and then you use that in [4:35] compilation. Like could you do that for [4:37] Lua? [4:39] >> No. [4:41] I I mean that's not computable, but a [4:44] lot of people tried to [4:47] to do [4:48] in type inference for dynamic languages [4:52] and it's really hard pro- problem and [4:55] it's very difficult to do anything [4:57] useful in terms of performance. What you [5:01] sometimes you get, but you have a very [5:04] restrict [5:06] Oh, if you write your program in that [5:08] specific way use for So, it's like you [5:12] don't have the type, but you write the [5:14] program thinking about type and then if [5:18] the program is that very particular [5:20] format, then you can [5:23] do type inference and everything goes [5:26] well. Otherwise, either type inference [5:29] doesn't work or I mean it works, but it [5:32] actually it infers very generic types [5:35] for everything and so you cannot take [5:38] advantage of of of the types because [5:41] exactly the dynamic nature of the [5:44] language. This is also happens with [5:47] legit for instance. [5:49] You can get much much better performance [5:52] if you have a kind of a type system in [5:55] your in your mind. Usually we do that [5:58] and you and you follow that type rules [6:01] even without that. I mean the compiler [6:04] the language does not impose them on [6:07] you, but you assume or I'm going to [6:08] follow those [6:10] sometime discipline. I'm not going to to [6:13] to use the same variable to store [6:15] integers and strings etc. And then you [6:18] can have much better results.