Skip to main content

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.

C++
int f() {	
int a[3] = {0, 1, 2};
return a[0];
}
int main() {
return f();
}
C++ AOT compiler:
  1. Preprocessor resolves #include and macros.
  2. Compiler checks types (e.g., int[3]) and generates object code.
  3. Linker combines object files into an executable.

At runtime, the program executes directly with no further compilation.

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.

Python
def f():
a = [0, 1, 2] # List, not a fixed array
a.append(3) # Modified at runtime
return a[0]

print(f())
Python interpreter and dynamic compilation:
  1. No ahead-of-time type checking—a is dynamically typed.
  2. The list a can grow or change during execution.
  3. Performance is slower due to runtime interpretation, but flexibility is high.

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.

Terra
Terra’s JIT process:
  1. Lua runs and defines f at compiletime.
  2. On first call to f(), Terra’s JIT compiler checks types (e.g., int[3]), evaluates array(0, 1, 2), and generates machine code.
  3. The compiled f executes with fixed types and sizes, reused on subsequent calls unless redefined.

For AOT, terralib.saveobj compiles f into a shared library (e.g., f.so), which can be linked into other programs, bypassing JIT at runtime.

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., via quote) 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.

Terra
C++
// function/global declaration context:
typedef int MyInt;
MyInt x;

int f() {
// C++ code context:
MyInt bar = x + 1;
// ~~~~~ C++ type context

return bar;
}

struct S {
// struct definition context:
int a;
// ~~~ type context
float b;
};

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.

Terra
C++
#include "myfile.h"



void f() {
myfunction();
}

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.

Terra
C++
#include <stdio.h>
#include <malloc.h>
int main() {
printf("hello, world\n");
}

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: [ ... ].

Terra
C++
#define X (3+3)

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++.

Terra
C++
#define F(a,b) a + b

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.

Terra
C++
// Use #ifdef to control how functions are defined
#ifdef __WIN32
char * getOS() { return "Windows"; }
#else
char * getOS() { return "Linux"; }
#endif

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-TerraDescription
255, 0377, 0xff255, 0377, 0xffInteger literals in decimal (255), octal (0377), and hexadecimal (0xff) are identical in both languages, as Terra inherits C-like syntax for these.
2147483647LL, 0x7ffffffful2147483647LL, 0x7fffffffULLLong 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.23e2123.0, 1.23e2Floating-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, falsetrue, falseBoolean 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' as char, "hello" as const 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 distinct char type. You could define a Terra function like terra 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, use escape ... 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.

C++
void f() {
int x;
int y = 255;
auto z = 255;
}
Terra
Metaprogrammed

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.

C++
short s; long l;
Terra

Note: C++’s short and long sizes depend on the compiler and architecture, whereas Terra’s int16 and int64 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.

C++
char c = 'a'; 
float f; double d;
bool b;
Terra

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.

C++
int a = 1,b = 2,c = 3;
Terra

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.

C++
int a[10];
int a[]={0,1,2};
float a[]={0,1,2};
int a[2][3]={ {1,2,3},{4,5,6} };
Terra

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.

C++
int* p; 

char* s ="hello";

void* p = NULL;
Terra

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++’s char* points to a null-terminated string. Terra’s rawstring (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++’s void* is a typeless pointer, set to NULL for null. Terra uses &opaque as a generic pointer type, with nil (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

C++
typedef String char*;
Terra

Terra currently does not have support for const local variables.

C++
const int c = 3;
Terra

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.

C++
enum weekend {SAT,SUN};
weekend f() {
return SAT
}
Terra

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.

C++
int x = 3;
const int y = 3;
int z[] = { 3,4, 5};
const int a[] = { 3,4,5};

void f() {
}
Terra

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.

C++
int x;
static int y;

static void g() {
return x + y;
}
void f() {
static int z = 0;
return g();
}
extern int w;
Terra

Note: In Terra, only x and f are exposed as symbols, but y and g are included internally since they’re used.

Note: C++’s static limits linkage or persistence; Terra’s global is mutable, with saveobj controlling exported symbols.

Note: C++’s extern links globals across files; Terra relies on saveobj 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.

C++
void f() {
static int z = 0;
return z;
}
Terra

Note: Terra uses do/end to scope globals, approximating C++’s local static 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.

C++
x = y;
x += y;
Terra

Declarations

C++ declares variables with type-first syntax or auto; Terra uses var with optional type annotation.

C++
int x;
int y = 1;
auto z = 2;
Terra

Semi-Colons

C++ requires semicolons to separate statements; Terra makes them optional, inherited from Lua, though sometimes useful for clarity.

C++
x = y; y = z;
Terra

Blocks

C++ uses curly braces for blocks; Terra uses do/end.

C++
void f() {
{
printf("hi\n");
printf("hi\n");
printf("hi\n");
}
}
Terra
Meta-programmed

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.

C++
if (x) { 
// <statements>
}
else if (y) {
// <statements>
}
else {
// <statement>
}
Terra

Loops

C++ provides while, for, and do-while; Terra uses Lua’s while, for, and repeat/until, with adjusted bounds.

C++
while(x) {
// <statements>
}

for(int i = 0; i < 100; i++) {
// <statements>
}

do {
// <statements>
} while(b);
Terra

Note: Terra’s for i = 0,100 excludes 100; ~b inverts b for until.

Switch

C++’s switch/case uses braces and colons; Terra’s switch uses then/else, aligning with Lua-like flow.

C++
switch(x) {
case X1: a;
case X2: b;

default: c;
}
Terra

Control Flow

C++ and Terra share break and return. While C++ allows (dead) code after break and return, Terra throws a compile error.

C++
break;
return;
Terra

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.

C++
int f(int x, int y) { 
return x + y;
}
Terra

Metaprogramming in Terra can unroll operations, like summing arguments, using symbol, escape, and emit quote for dynamic code generation.

Terra meta-programm
Generated code

C++ uses empty braces for void returns; Terra employs an empty tuple (: {}) to signify no return value.

C++
void f() {
} // no returns
Terra

Declaring functions

C++ declares functions with prototypes; Terra uses function types (e.g., {args} -> ret), optionally metaprogrammed with Lua variables.

C++
int f(int x, int y);

void g();
Terra
Meta-programmed

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.

C++
inline void f();
Terra

Operator overloading

C++ overloads operators as standalone functions; Terra ties them to a type’s metamethods.

C++
struct T {};
T operator+(T x, T y) {
...
}
Terra

Function overloading

Both languages support overloading, but Terra distinguishes single functions from overloaded sets using terralib.overloadedfunction.

C++
int max(int a, int b) {
return (a > b) ? a : b;
}
float max(float a, float b) {
return (a > b) ? a : b;
}
Terra

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

C++
namespace N {
void f() {}
}
void g() {
N::f()
}
Terra

Pointers and members

C++
&x
*p
t.x

p->x
Terra
Meta-programmed

Array index and function calls

C++
void g(int* a, int i, T t) {
a[i]
f(x,y)
t(x,y)
}
Terra
Meta-programmed

Updates

The increment operator does not exist in Terra. So just use addition or subtraction, respectively.

C++
x++,++x
x--,--x
Terra

RTTI

C++
typeid(x)
dynamic_cast<T>(x)
Terra

The Terra core language has no build-in equivalents to typeid or dynamic_cast, but you can build your own:

Terra
Terra

Casting

C++
(T) x
(T*) x
Terra
Meta-programmed

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

C++
sizeof(T)
sizeof(t)
Terra

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.

C++
#include <iostream>

class HeapInt {
private:
int* data;

public:
// Constructor
HeapInt(int value) {
data = new int(value);
std::cout << "allocate int\n";
}

// Copy constructor
HeapInt(const HeapInt& other) {
data = new int(*other.data);
std::cout << "copy int\n";
}

// Destructor
~HeapInt() {
delete data;
std::cout << "deallocate int\n";
}
};

int main() {
// Creates int with value 42
HeapInt a(42);
// Copies it
HeapInt b = a;
// Cleanup happens automatically
return 0;
}
Terra

Terra's new standard library supports flexible allocators, smart pointers and managed datastructures that are useful for dynamically sized datastructures.

Arithmetic

C++
-x
+x //DNE
x * y
x / y
x % y
x + y
x - y
Terra
Meta-programmed

Comparisons

C++
x < y
x <= y
x > y
x >= y
x == y
x != y
Terra

Logical and Bitwise Operators

C++
~x
!x

x << y
x >> y

x && y
x || y

x & y
x | y

x ^ y
Terra