Terra for C / C++ programmers
Terra is a low-level, statically-typed language designed to interoperate seamlessly with Lua, offering semantics very similar to C/C++. However, its tight integration with Lua means that familiar C++ constructs are often expressed differently. This reference sheet bridges the gap by comparing C++ snippets with their Terra-Lua equivalents, helping C++ programmers transition to Terra. Where applicable, a third column illustrates how to meta-program these constructs using Lua's dynamic capabilities.
Compilation Models
Understanding how code is compiled and executed is key to grasping the differences between C++ and Terra. C++ relies on ahead-of-time (AOT) compilation, where all type checking, code generation, and optimization occur well before the program runs. In contrast, fully dynamic systems, like some interpreted languages, process code at runtime, allowing flexibility but sacrificing performance. Terra uses just-in-time (JIT) compilation, a hybrid approach that compiles code right before execution, blending the benefits of static type safety with runtime flexibility. This section explores these models, showing how they shape C++ and Terra’s behavior.
Ahead-of-Time Compilation (C++)
In AOT compilation, the entire program is translated to machine code during a separate build phase, long before runtime. C++ exemplifies this model, using a preprocessor, compiler, and linker to produce an executable.
Note: AOT compilation ensures static type safety and optimization before execution, but it lacks runtime adaptability. All decisions (e.g., array sizes, types) are fixed at compile time.
Dynamic Compilation (Lua / Python)
Dynamic systems interpret or compile code at runtime, often line-by-line, allowing types and structures to change during execution. This is common in languages like Python or Lua without JIT.
Note: Dynamic compilation allows runtime modifications (e.g., resizing arrays), but it sacrifices the performance and type safety of compiled languages like C++.
Just-In-Time Compilation (Terra)
Terra’s JIT compilation occurs right before runtime, when a terra
function is first invoked or explicitly compiled. Lua drives this process, defining types and generating code that is then fixed for execution, offering a balance between AOT’s performance and dynamic compilation.
Note: Terra’s JIT compilation happens just before runtime, locking in static definitions (e.g.,
int[3]
) once compiled. This contrasts with C++’s AOT (done well before runtime) and dynamic systems (during runtime). Lua’s role enables code generation (e.g., viaquote
) during compile time, but the resulting Terra code is static in the runtime context.
Note: The "just in time" nature defers compilation until needed, improving startup speed for development while delivering near-AOT performance. Unlike dynamic systems, Terra’s arrays and types cannot change mid-execution in the runtime context.
Note: Terra’s unique AOT support allows generating shared libraries (e.g., via
terralib.saveobj
), which can be linked against like C++ libraries. This offloads compilation from JIT to ahead-of-time, keeping JIT fast for development while enabling optimized, reusable binaries for deployment.
Contexts
In multistage programming, we distinguish between compile-time and runtime contexts. In C++, compile-time includes the preprocessor and template metaprogramming, while runtime encompasses regular function and variable definitions. Similarly, in Terra, the Lua context handles compile-time metaprogramming, and Terra functions define runtime behavior. Terra's semantics are very close to C/C++, but its integration with Lua introduces unique metaprogramming techniques through quotes (`) and escapes ([ ]), enabling dynamic code generation. This section compares equivalent representations in C++ and Terra, highlighting how contexts shift between compile-time and runtime.
Preprocessor
C++ uses a static preprocessor to manage file inclusion, macros, and conditional compilation, relying on a multi-step build process with separate compilation for custom C code and linking for standard libraries. Terra achieves similar functionality—such as integrating external code and defining reusable constructs—but replaces this static approach with a dynamic, Lua-driven model. By processing code, including C headers, at runtime within Terra’s just-in-time (JIT) compilation pipeline, Lua offers a flexible alternative to C++’s traditional preprocessing.
Using Multiple Files
In C++, #include
directives bring in external code. Terra uses Lua's require function to load modules, which can contain Terra functions and definitions stored in tables.
Using C Functions
C++ uses #include
to access C standard library functions. Terra provides terralib.includec
and terralib.includecstring
to import C headers, creating a Lua table of functions and types.
Note: Terra's
includecstring
processes C headers at Lua runtime, generating bindings automatically. Unlike C++, no separate compilation step is needed—Terra compiles these into the final executable alongside Terra code.
Preprocessor Macro Equivalents:
Lua variables can hold values that get substituted into Terra functions. The quotation (`) operator creates a Terra expression directly from Lua. This value can be spliced into subsequent terra code using an escape: [ ... ]
.
Macro functions:
In Terra, a macro is a Lua function that returns a Terra expression that is directly spliced into surrounding Terra code. It's closely related to a macro function in C / C++.
Conditional Compilation:
In C++, conditional compilation relies on the preprocessor’s #ifdef
directives, which can feel rigid and obfuscated due to their static, text-based nature. Terra achieves the same functionality—selectively defining code based on conditions—using Lua’s clear, dynamic syntax. By leveraging Lua’s runtime logic, Terra offers a more readable and flexible alternative to C++’s preprocessor calls.
Literals
C++ and Terra both support a variety of literal types (integers, floats, strings, booleans), but Terra’s integration with Lua introduces differences in syntax and behavior. While C++ uses a static, compile-time approach to literals, Terra leverages Lua’s dynamic runtime environment, often requiring escapes or Lua-specific constructs to achieve equivalent results. This table compares common literals in C++ with their Terra-Lua equivalents.
C++ | Lua-Terra | Description |
---|---|---|
255, 0377, 0xff | 255, 0377, 0xff | Integer literals in decimal (255), octal (0377), and hexadecimal (0xff) are identical in both languages, as Terra inherits C-like syntax for these. |
2147483647LL, 0x7ffffffful | 2147483647LL, 0x7fffffffULL | Long integer literals in C++ use LL or ul suffixes. Terra matches LuaJIT’s conventions, using LL for signed long long and ULL for unsigned long long, ensuring compatibility with Lua’s number handling. |
123.0, 1.23e2 | 123.0, 1.23e2 | Floating-point literals (decimal and scientific notation) are the same in both languages, reflecting C’s influence on Terra’s syntax. |
"strings\n" | "strings\n" or 'strings\n' or [[strings\n]] | C++ uses double quotes for strings with escape sequences (e.g., \n ). Terra supports Lua’s string styles: double quotes ("..." ), single quotes ('...' )—which are equivalent—or long brackets ([[...]] ) for raw strings, avoiding escape sequence issues. |
'a' | ("a")[0] | C++ has single-character literals with single quotes. Terra lacks a direct char literal; instead, you index a string (e.g., "a" ) at position 0 to get a byte value, or you could define a function for this purpose. |
"hello" "world" | [ "hello".."world" ] | C++ concatenates adjacent string literals at compile time. Terra uses Lua’s .. operator for string concatenation, wrapped in an escape ([ ] ) to evaluate the Lua expression as a Terra-compatible string at runtime. |
true, false | true, false | Boolean literals are identical in both languages, as Terra adopts C++’s true and false directly. |
Note: C++ literals are resolved statically at compile time, with types inferred by the compiler (e.g.,
'a'
aschar
,"hello"
asconst char*
). Terra’s literals are processed at Lua runtime, often requiring Lua constructs (e.g., string indexing, escapes) to match C++ functionality.
Note: For character literals, Terra’s workaround (
("a")[0]
) returns a numeric byte value (e.g., 97 for'a'
), not a distinctchar
type. You could define a Terra function liketerra char(s : rawstring) : int8 return s[0] end
to mimic C++’s'a'
by converting a string to its first byte.
Note: Long brackets
[[...]]
could be confused with Terra escapes[ ... ]
that operate on Terra quoted expressions. In case of ambiguity, useescape ... end
to apply an escape instead.
Declarations and Type Constructors
C++ and Terra both allow variable declarations within functions, but their syntax and type systems differ due to Terra’s integration with Lua. C++ uses static typing with optional type inference (via auto
), while Terra combines explicit typing, type inference, and Lua-driven metaprogramming for dynamic code generation. This section compares basic variable declarations in C++ with their Terra equivalents, including Terra’s advanced constructs like symbol
and quote
for metaprogramming.
Declaring Variables
C++ declares variables with explicit types or inferred types using auto
, all resolved statically. Terra offers similar functionality with var
, supporting both explicit typing and inference, and extends this with Lua metaprogramming for runtime code generation.
Sizing integer types:
C++ provides keywords like short
and long
for sized integers, with sizes varying by platform. Terra uses explicit, portable type names (e.g., int16
, int64
) for clarity and consistency.
Note: C++’s
short
andlong
sizes depend on the compiler and architecture, whereas Terra’sint16
andint64
guarantee fixed sizes, aligning with modern C++’s<cstdint>
(e.g.,int16_t
,int64_t
).
Non-integer primitive types:
C++ supports a range of non-integer primitives like char
, float
, and bool
. Terra mirrors these but adapts char
handling due to Lua’s string-based approach.
Multiple Declarations
C++ allows comma-separated declarations with a shared type, initialized in one line. Terra supports multiple declarations but requires separate type definition for each variable when initialized together.
Arrays
C++ initializes arrays with a size or an initializer list, with types and sizes resolved statically at compile time. Terra treats array
as an expression to populate arrays, requiring explicit type and size definitions that are fixed during just-in-time (JIT) compilation, which occurs right before runtime. The arrayof
function allows type specification when initializer expressions don’t match the desired type, with all decisions finalized prior to execution.
Pointers and references
Pointers are essential for low-level programming in C++ and Terra, enabling direct memory manipulation. C++ uses statically typed pointers, including raw pointers, null pointers, and references, each with distinct syntax. Terra adapts these to its type system and Lua environment, declaring pointers with &
and streamlining dereferencing, often bypassing C++’s reference syntax. This section compares pointer declarations and usage in C++ with Terra equivalents, highlighting how Terra delivers similar functionality differently.
Note: C++ uses
*
to declare a pointer, while Terra uses&
(read as "address of") before the type, e.g.,&int
for a pointer to an integer. Both are uninitialized by default. Note: C++’schar*
points to a null-terminated string. Terra’srawstring
(equivalent to&int8
, a pointer to an 8-bit integer) handles Lua string literals, which are implicitly null-terminated when passed to Terra. Note: C++’svoid*
is a typeless pointer, set toNULL
for null. Terra uses&opaque
as a generic pointer type, withnil
(Lua’s null value) indicating a null pointer in the Terra context.nil
integrates with Lua’s type system, ensuring compatibility across contexts. Note: C++ references (&
) alias an object, accessed with.
, while pointers use->
. Terra lacks references, using pointers (e.g.,&Vec3
) instead. The.
operator works like C++’s->
, automatically dereferencing the pointer for member access, reducing syntactic noise.
Typedefs, constants, other
Typedefs are just assignments in Lua because Terra types are Lua values
Terra currently does not have support for const
local variables.
Enums
C++ uses enum for static, named constants with integer values. Terra lacks native enums, relying on Lua metaprogramming with tables or macros to achieve similar functionality.
Global (constant) values
C++ defines global constants with const at file scope, ensuring immutability (e.g., const int G = 42). Terra uses Lua variables for mutable globals (e.g., local G = 42), or Terra-specific global(type, expr) for mutable globals with an optional initial expression, and constant(type, expr) for immutable constants requiring both type and value or expression.
Metaprogramming enables you to create sophisticated expressions at compile time that are embedded as constant values in your Terra code:
Storage Classes
C++ uses storage classes (auto, static, extern) to control variable and function scope, lifetime, and linkage, all set at compile time. Terra lacks direct equivalents, relying on Lua’s scoping and global for persistent values. Visibility is managed via terralib.saveobj, while lexical scope can mimic some static behavior using do/end.
Global Variables and Functions
C++ defines globals with implicit external linkage or static for internal linkage, and extern for cross-file access. Terra uses global(type) for mutable globals, with visibility specified in saveobj.
Note: In Terra, only
x
andf
are exposed as symbols, buty
andg
are included internally since they’re used.
Note: C++’s
static
limits linkage or persistence; Terra’sglobal
is mutable, withsaveobj
controlling exported symbols.
Note: C++’s
extern
links globals across files; Terra relies onsaveobj
or external Lua modules for similar access.
Local Static Variables
C++’s static
in functions creates persistent locals. Terra has no direct equivalent but can use Lua’s do/end
to lexically scope a global
for similar effect.
Note: Terra uses
do/end
to scope globals, approximating C++’s localstatic
without a direct keyword.
Statements
C++ and Terra use statements to control program flow, but their syntax reflects different influences: C++’s C heritage versus Terra’s Lua foundations. This section compares assignment, declaration, and control structures, highlighting Terra’s adaptations and metaprogramming capability.
Assignements
C++ offers compound assignment operators like +=
, while Terra follows Lua’s simpler approach, requiring explicit operations.
Declarations
C++ declares variables with type-first syntax or auto
; Terra uses var
with optional type annotation.
Semi-Colons
C++ requires semicolons to separate statements; Terra makes them optional, inherited from Lua, though sometimes useful for clarity.
Blocks
C++ uses curly braces for blocks; Terra uses do/end
.
Note: Terra's metaprogramming with
[stats]
injects quoted statements inside the code block.
Conditionals
C++ uses parentheses and braces for if/else
; Terra adopts Lua’s then/end
, omitting parentheses.
Loops
C++ provides while
, for
, and do-while
; Terra uses Lua’s while
, for
, and repeat/until
, with adjusted bounds.
Note: Terra’s
for i = 0,100
excludes 100;~b
invertsb
foruntil
.
Switch
C++’s switch/case
uses braces and colons; Terra’s switch
uses then/else
, aligning with Lua-like flow.
Control Flow
C++ and Terra share break
and return
. While C++ allows (dead) code after break
and return
, Terra throws a compile error.
Exceptions
C++ supports exceptions with try/catch
for error handling; Terra omits them to maintain simplicity, favoring alternative error strategies. Note that Lua’s pcall
can handle errors externally.
Functions
Functions in C++ and Terra drive program logic, but their definition, declaration, and customization differ. While C++ code generation is static, Terra features powerful abstractions for generating functions dynamically using JIT compilation.
Defining functions
C++ defines functions with a straightforward, type-first syntax and braces. Terra uses terra blocks with Lua-style annotations, supporting metaprogramming for advanced constructs.
Metaprogramming in Terra can unroll operations, like summing arguments, using symbol
, escape
, and emit quote
for dynamic code generation.
C++ uses empty braces for void returns; Terra employs an empty tuple (: {}
) to signify no return value.
Declaring functions
C++ declares functions with prototypes; Terra uses function types (e.g., {args} -> ret
), optionally metaprogrammed with Lua variables.
Note: Terra’s
::
specifies function signatures; metaprogramming builds types dynamically with Lua.
Function Inlining
C++ suggests inlining with inline
, while Terra’s setinlined(true)
forces it, akin to __always_inline__
, for performance-critical code.
Operator overloading
C++ overloads operators as standalone functions; Terra ties them to a type’s metamethods.
Function overloading
Both languages support overloading, but Terra distinguishes single functions from overloaded sets using terralib.overloadedfunction
.
Expressions
Basically same semantics as C++: From the Quick Reference "Operators are grouped by precedence, highest first. Unary operators and assignment evaluate right to left. All others are left to right. Precedence does not affect order of evaluation, which is undefined. There are no run time checks for arrays out of bounds, invalid pointers, etc. "
Working with namespaces
Pointers and members
Array index and function calls
Updates
The increment operator does not exist in Terra. So just use addition or subtraction, respectively.
RTTI
The Terra core language has no build-in equivalents to typeid
or dynamic_cast
, but you can build your own:
Casting
You are applying the Terra type T
like a function.Because type constructors like &T
are Lua expressions, you need to use an escape [T]
in general.
Sizeof
Resource Management
Like C++, Terra supports C-style memory management using malloc
and free
. Since version Terra 1.3, we also support automatic scope-bound resource management (RAII), see the example below. Instead of new / delete Terra just uses malloc
and free
that can be used to build your custom allocator or destructor.
Terra's new standard library supports flexible allocators, smart pointers and managed datastructures that are useful for dynamically sized datastructures.