Brief Haxe Tutorial

John Gabriele


This is a concise, fast-paced, limited tutorial covering just enough Haxe to get you productive right away.

Haxe is a modern, general-purpose, high-level, statically-typed, object-oriented programming language with a familiar syntax. It can be compiled to native, to VM bytecode (its own HashLink VM, or to the JVM), to numerous target programming languages1, or can run code using its own built-in interpreter.

For a short sample of some Haxe code, visit and scroll down.

It’s assumed here that you’re already familiar with some other JavaScript-/Java-/ActionScript3-style language.

In the text below where I write, “${feature} works just as you’d expect it ought to”, that’s intended to mean that Haxe does just what you’d think a sensible, block-structured, lexically-scoped, modern language would do. :)

This document covers Haxe version 4. If you don’t already have it installed, and you’re on GNU/Linux, you might have a look at my short getting-started guide.

Language Overview

Some characteristics of Haxe:

See also the official Language Introduction.

Pros and Cons of Haxe



Languages Comparable to Haxe

Haxe first appeared in 2006. The following are a handful languages (roughly in order by date-first-appeared) that are comparable to Haxe (that is, being statically-typed, GC’d, OO, “application-level”, modern languages with familiar syntax):

Language First Appeared License Comments
Dart 2011 BSD From Google
Kotlin 2011 Apache 2.0 From JetBrains
TypeScript 2012 Apache 2.0 From Microsoft
Swift 2014 Apache 2.0 From Apple
ReasonML 2016 MIT From Facebook

Two observations:

Setup for this Tutorial

This tutorial was written using Haxe 4 on GNU/Linux, utilizing haxe’s built-in interpreter (“interp”, aka “eval”). To install Haxe, see my getting started doc.

To get set up targetting Haxe’s own HashLink VM, see my getting started with HashLink doc. If you’d like to target some other environment, visit the Haxe introduction and follow the link buttons under “Setup your development environment”.

Create and populate your project directory:

mkdir my-proj
cd my-proj
touch interp.hxml
mkdir src
touch src/Main.hx

with src/Main.hx containing:

class Main {
    public static function main() {
        trace("Hello, World!");

and interp.hxml containing:

-p src
-m Main

Run your code on the command line from the project directory like so:

haxe interp.hxml

Note that when not using interp (Haxe’s built-in interpreter) — that is, when compiling to some target platform — you’d usually have to type two separate commands:

  1. a haxe invocation to compile/build your program, and then
  2. a target-specific command to run or otherwise use the output that haxe just produced.

Using interp (aka eval), it’s just the one step though.

hxml files

For invoking the haxe command, rather than type out the necessary command line options every time, you can instead put those options into a .hxml (“hax em ell”) file. For example, instead of typing:

haxe -p src -m Main --some-target whatev

you can instead create a target-specific some-target.hxml file containing those options,

-p src
-m Main
--some-target whatev

and then type haxe some-target.hxml to get the same result.

If you build your project for multiple different targets, you’ll likely have multiple hxml files — one for each target (ex., interp.hxml, hl.hxml, lua.hxml, etc.), or maybe one for each domain (ex., desktop.hxml, mobile.hxml, web.hxml).

hxml files can contain comments, which start with #.

The Language


// single-line comment

A multi-line

/* Some people like to write
 * them with asterisks on the
 * left-hand-side.

A multi-line _doc_ comment.
Indent however you like.

Haxe is not whitespace-sensitive.

Haxe is case-sensitive. Variable names are often written camelCase, with types capitalized LikeThis.

null is used to indicate when a variable refers to no object.

Void is used to indicate the absence of type (ex. when used as the return type of a function).

Basic Types

The so-called “basic types” are Bool, Int, and Float, with literals like:

true, false
1, 0xffff
1.2, -3.4, 1e3

On HashLink, Ints are 32-bit signed (so, -2,147,483,648 → 2,147,483,647, and they wrap), and Floats are 64-bit.


Strings are immutable and Unicode. Not a “basic type”, but there’s a built-in literal syntax for strings, and string interpolation is supported:

"a string"                     // double-quoted (no interpolation)
'allows ${foo} interpolation'  // single-quoted

"concat" + "enate strings"

"both single- and double-quoted strings
can be written across multiple lines and
both can contain escapes like \t and \n."

See the String API docs for full list of String fields (ex. length, charAt(), charCodeAt(), indexOf(), split(), …).

Variables and Constants

You use var or final to define variables. Variables are typed. Thanks to type inferencing, you don’t often need to explicitly specify the type of a variable:

var x = 3.2;
var a = 5, b = 6, c = 7;
final n = 160;  // A constant.

final variables can’t be reassigned to another object, but they can refer to a mutable object (which could itself be modified).

On HashLink (and other statically-typed targets):

var x = 5;
x = null;  // ERROR, `null` can't be used as a basic type.

When types cannot be inferred you must include them explicitly. Type names come after the variable name and colon, for example:

// (The type can be inferred here, but to show the syntax...)
var x:Int = 5;
// You could put in extra spaces if you want, but that's not customary.
var x : Int = 5;

To see the type of any variable, use $type(x); which will print out a warning line to the console at compile-time indicating x’s type.


A class defines a new type, and typically goes into its own like-named file.

Classes have fields which may be variable fields or function fields (aka methods). Fields also may be member (aka, non-static, aka instance) or static. Summarizing:

  instance static
variable: member variable static variable
function: (member) method static method

Terminology: class fields that are functions (that is, functions belonging to a class) are referred to as “methods”. In Haxe you can create local functions as well, but those aren’t considered methods.

By default, all fields are private and member.

As an example, from your project directory make a src/my_pkg/MyClass.hx file:

package my_pkg;

// Classes are public by default.
class MyClass {
    var memberVariable = 3; // private member variable

    static var staticVariable = "yo";

    // Constructor must be public.
    public function new(n) {
        trace("Hi from MyClass ctor!", n);
        this.memberVariable = n;

    public function incr() {
        trace("Incrementing our member variable.");

    public function show() {
        trace("member variable's value:", this.memberVariable);
        trace("static variable's value:", staticVariable);

    public static function staticMethod() {
        trace("Hi from MyClass.staticMethod()!");

and use it from Main.hx:

import my_pkg.MyClass;

class Main {
    public static function main() {
        var mc = new MyClass(9);


In Haxe, functions are defined within classes (or within other functions). You’ve seen examples above.

Function args can have default values.

// Here, `x` is an Int. On static platforms (like HashLink),
// you may not pass in `null` as a value for basic types.
public function foo(x     = 7) {...}
public function foo(x:Int = 7) {...} // same

// Function args can be made optional. If omitted, gets assigned
// `null` (`Null<Int>` on static platforms).
public function foo(?x:Int) {...}

// Optional function arg with default value. You can pass in `null`
// on static platforms (`x` is `Null<Int>` "nullable").
public function foo(?x = 7) {...}

When necessary you can explicitly specify return type:

function foo()      : String     {...}
function bar(x:Int) : Array<Int> {...}

You return values with a return statement.

Regarding Copying

var x = 5;
var y = x;
y++;       // `y` is now 6
trace(x);  // `x` is still 5
// Similarly for Strings, as they're immutable.

// But for other objects:
var w = new Whatev();
var v = w;  // Both `w` and `v` refer to the same instance of Whatev.
// The object to which `v` refers is mutated too, of course.


var a = [5, 66, 7];
a[1];      //=> 66
a[1] = 6;  // [5, 6, 7]
a.push(8); // [5, 6, 7, 8]

var x = a.pop(); //=> 8, and it's removed from `a`

a.unshift(4); // prepends the 4: [4, 5, 6, 7, 8]
a.shift();    //=> 4, and leaves `a` as `[5, 6, 7, 8]`.

a.insert(idx, 99); // Inserts 99 into `a` at `a[idx]`.
a.remove(99);      // Removes first occurrence of 99.

// Removes `len` elements from an array, starting at position `pos`.
var x = a.splice(pos, len);

// So,
//   * add stuff with: push, unshift, and insert:
//         "push/unshift onto", "insert into"
//   * remove stuff with: pop,  shift, splice, and remove:
//         "pop/shift off", "splice out of", "remove from"

a.length;      // the length of the array
a[0];          // first elem of the array
a[a.length-1]; // last  elem of the array

// Print an array:
// Another way:
// Another way, to see how the sausage is made:

// Array comprehension (creates an array)
var a = [for (i in 5...10) i];

// sort an array, in-place
// or, if you want to write your own comparison function:
a.sort((a, b) -> a - b) // (works for sorting numbers only)
// or, more generally:
    (a, b) -> {
        if (a < b) return -1;
        else if (a > b) return 1;
        else return 0;

// Note, Haxe doesn't do array index bounds checking:
a = [5, 6, 7];
a[3]; //=> 0 (same result for an array of floats)

a = [true, true, true];
a[3]; //=> false

a = ["i", "j", "k"];
a[3]; //=> null

See the arrays article of the code cookbook for more examples.

See also the Array API docs.


var m = ["a" => 1, "b" => 2];
// Note though how it's formatted with curlies when printed out (as of Haxe 4.0.5):
trace(m); // {b => 2, a => 1}

m["a"];     //=> 1
m["c"] = 3; // sets a key/value pair.

// Note, not bounds-checked:
m["d"]; //=> null

// Map comprehension.
var m = [for (i in 5...8) i => '*${i}*'];

// If you really need to know the length of one...

See the maps article in the code cookbook for more examples.

See also the Map API docs.


Haxe doesn’t come with a Set type out of the box, but you can use the thx.Set type from the thx.core library, as described later in the section about using libraries from the Haxe library repo.

Usage looks like:

var s = Set.createInt(); // a set of Ints
s.add(5); // returns `true` if `s` is changed
s.add(5);  // no effect (returns `false`)
s.length;  // 3

See the thx.Set API docs.

Type Parameters

If you want to create a new empty Array or Map, you can use the new keyword and also specify what type the Array or Map will contain. The syntax for that is:

// A new empty array of Strings.
var a = new Array<String>(); // though, you can also do:
var a:Array<String> = [];  // and get the same thing.


// A new Map where the keys are Strings and the values are Ints.
var m = new Map<String, Int>(); // though, you can also do:
var m:Map<String, Int> = [];  // and get the same thing.
m["a"] = 1;
m["b"] = 2;

The types written in the angle brackets are the type parameters.


Built-in support for enums:

enum Size {
var s = Size.Small;  // Ok
var s = Small;       // Ok too
var s = Size.XLarge; // Error
var s = XLarge;      // Error

But they can do more than that. See:

Anonymous Structures

If you just need a simple structure to group heterogeneous data (recall, Maps’ values must be all of the same type), and you don’t need a class, you might use an anonymous structure:

var p1 = {n: 10, name: "Ron"};
var p2 = {n: 12, name: "Tammy 1"};
var p3 = {n: 15, name: "Tammy 2"};

p1.n;    //=> 10; //=> Tammy 1

They can contain and be nested in other data structures, as you’d expect.

Don’t confuse anonymous structures with maps even though they print out similarly in the terminal:

var m = ["a" => 1, "b" => 2];
// prints as {b => 2, a => 1}        <-- curlies!
// type is haxe.ds.Map<String, Int>

var x = {a: 1, b: 2}; // This is an anon struct, not a map!
// prints as {a : 1, b : 2}
// type is {b : Int, a : Int}

(Well, and if you’re familiar with Python: Haxe anon structs look very similar to Python maps/dicts.)

Note that usually in Haxe when you see a : there’s a type after it — not so for anon structs though. My advice: when writing types after a variable, omit any extra spaces. When writing anon structs, put that space after the colon. I believe the Haxe code formatter does this as well.

To avoid repetition you can use typedef:

typedef SomeType = {n:Int, name:String};
// or
typedef SomeType = {
    var n:Int;
    var name:String;

Note, that typedef cannot be declared inside a class.

For more info, see the anon structs manual chapter.


Haxe has the usual operators you’d expect.

It also supports both pre- and post- increment/decrement, and they work like you’d expect.

Haxe supports the ternary operator (foo ? bar : baz), but you can often nearly as easily use if/else as an expression instead:

var x = someCond ? "this" : "that";
// or
var x = if (someCond) "this" else "that";

Division of numbers always gets you a Float.

Logical “and”, “or”, and “not” are spelled &&, ||, !. These and also the comparison operators (<, >, etc.) result in a Bool.

&& and || short-circuit, like you’d expect.

All the usual compound assignment operators (like +=, *=, etc.) are supported.

Haxe also supports the range operator, 5 ... 8, which gets you 5, 6, 7.

There’s no operator for exponents; explicitly use Math.pow().

Flow Control

Haxe supports the usual flow control statements, most (all?) of which are actually expressions in Haxe:

// Loop over an array.
for (e in someArray)            { /*...*/ }
for (i in 0...someArray.length) { /*... someArray[i] ...*/}

for (i in 0...5) { /* 0 to 4 */ } // or
for (_ in 0...5) { /* if you don't care about that idx value */ }

// You can use `break` and `continue` while looping.

// Loop over a map.
for (k => v in someMap)   { /*...*/ }
for (v in someMap)        { /* just the values */ }
for (k in someMap.keys()) { /* just the keys */ }

while (someCond) { /*...*/ }

do {
} while (someCond);

Flow control statements that expect a Bool must get a Bool (for example, as returned by the equality and comparison operators); there’s no notion of “truthiness” and “falsiness” like in scripting languages.

if (someCond) someExpr;

if (someCond) { /*...*/ }
else if (otherCond)  { /*...*/ }
else if (yetAnother) { /*...*/ }
else { /*...*/ }

Note, since blocks evaluate to an expression, if you only have one expression in the if body anyway, you can do:

if (someCond) someThing
else if (otherCond) somethingElse
else if (yetAnother) somethingOther
else thatsIt;


switch x {
    case 1: trace("x was 1!");
    case 2: trace("x was 2!");
    default: trace("Hm...");

There’s much more to the switch statement though, since it supports pattern matching.


Scoping is lexical, and works just as you’d expect it ought to.

Further, you can create a block with its own local scope, and it works like you’d expect:

var x = 3;

    var x = 18;  // new local, shadows outer `x`
    var y = 5;   // new local

    trace(x);    // 18
    trace(y);    //  5

trace(x);  // 3
trace(y);  // ERROR, `y` out of scope here

The value of the block is the value of its last expression.

var x = {

trace(x); // 7

Loop variables are scoped to inside the loop body:

for (i in 5 ... 8) {
trace(i);  // ERROR, `i` is out of scope here

Regular Expressions

Haxe has built-in support for and literal syntax for regexes (ex. var rx = ~/[a-f]+/;). For more info, see:


Haxe has try/catch and throw for exception handling. See the try/catch manual chapter for more info.

Libraries, Packages, and Modules

Haxe packages correspond to directories, and Haxe modules correspond to .hx files. Package names (and their corresponding directory names) are lowercase and may contain underscores. Module names (and their corresponding filenames) are capitalized and CamelCase. Modules contain types, and type names are also written CamelCase.

Take for example the file foo/bar/Baz.hx containing class Baz. The module Baz resides in package (and at the top of Baz.hx it says package;). The fully-qualified module name is The fully-qualified typename for class Baz is In general, you access the types in a module like so:


However, for that special case where module name == type name, Haxe allows you the shortcut of writing to mean

Haxe finds modules by searching its classpath (see the --class-path|-p compiler option), and also by looking where --library|-L specifies.

Name Visibility

Importing a module allows you to use its unqualified module name (without its package name) in your code. You could go ahead and use modules without importing them, but then you’d have to fully-qualify them with their package name every time you use them.


// then access `` as just `Baz`.

If your module contains more than one type, any types named differently than the module name are called “sub-types”. If you import a module containing subtypes, they’ll be available unqualified just like the type named after the module. That is to say, when you import a module, you get all of its types.


Libraries are archive files you download from the online Haxelib library repository using the haxelib tool. They contain modules residing within packages.

Alas, because many types of archive files are sometimes referred to as “packages”, the term “package” is sometimes casually used as a synonym for “library”.

Using the Standard Library

Using the standard library (documented at doesn’t require any special imports or compiler arguments.

See the Haxe code cookbook for examples using the standard libary, though here’s a couple of choice tidbits anyway.

Math, Random, and Parsing Numbers from Strings

var s = Std.string(whatev);     // Whatev to String, or
var s = '${whatev}';            // this. There's also a
var s = '$whatev';              // shorthand for simple case.

var n =;           // Float to Int, but truncates (so, 5)
var n = Math.round(5.8);        // Float to Int                (so, 6)
var x = Math.fround(5.8);       // Float to Float              (so, 6.0)
var x = n * 1.0;                // Int to Float  :)

var n = Std.parseInt("5");      // String to Int
var n = Std.parseInt("05");     // Same. Leading zeros are ignored.
var n = Std.parseInt("5.7");    // String to Int, and truncates
var x = Std.parseFloat("5.2");  // String to Float

// Note that `Std.parseInt("")`   ==> null
// and       `Std.parseFloat("")` ==> NaN

var r1 = Math.random();  // Float, 0 to < 1
var r2 = Std.random(n);  // Int,   0 to < n

stdin, stdout, stderr

Reading from stdin and writing to stdout and stderr.


You can read from stdin interactively from the command line. You can also pipe input to your Haxe program just as you would with any other command line utility.

To read in one line:

Sys.println("Enter your name:");
var ans = Sys.stdin().readLine();
// `ans` is just the text --- no newline

If you want to iteratively read in lines:

var line:String;
var lines:Array<String> = [];
try {
    while (true) {
        line = Sys.stdin().readLine(); // a String, no newline
catch ( {

You could also read in all the input in one shot:

var content = Sys.stdin().readAll().toString();


There’s a few ways to write to stdout:

trace("Hello, trace!"); // Provides filename, line number, and a newline.
Sys.println("Hello, println!"); // Provides a newline.
Sys.print("Hello, print!");     // No added newline.

You can also use Sys.stdout() to grab the stdout object and call its write methods (see


To write to stderr:


Command Line Args

Sys.args(); //=> array of args passed (Strings)



Static Extension

Haxe can do a special trick, strictly for convenience: If there’s a Type (typically not one you’ve written yourself; call it, say, SomeClass) to which you’d like to add member functions, you can:

  1. Create a SomeStaticExtClass containing static methods which you wish SomeClass had as member functions. Those static methods should take an instance of SomeClass as their first argument.
  2. Back in your Main.hx, instead of import some_pkg.SomeStaticExtClass, do using some_pkg.SomeStaticExtClass. This makes the magic happen.
  3. Now when you make these wished-for method calls on instances of SomeClass, Haxe will behind-the-scenes translate them into calls to those static methods with the instance passed in as the first argument.

A few modules in the standard library were written with this usage in mind. See the static extension chapter of the manual.

Using Libraries from Haxelib

The online repository of Haxe libraries and also the library management command line tool are both called “haxelib”. When you install the binary release of Haxe onto GNU/Linux, it comes with both haxe (the compiler) and haxelib (the library management tool). See also haxelib --help.

Libraries you install via haxelib are often (though not always) named lowercase with dashes where necessary. They are sometimes named using dots instead of dashes.

Note that the name of a library does not necessarily have anything to do with the packages and modules it contains. For example, the “thx.core” library contains modules in the thx package (there’s no “thx.core” package).

Unfortunately, if you name libraries with dots they tend to look like package names, which can be confusing. I think using dashes makes more sense.

A library archive file itself is a .zip file, and its haxelib.json file is the library’s config file.

By default, haxelib installs and unpacks libraries into your ~/haxelib directory. This is your local haxelib-installed libraries repo. Haxelib can also manage project-specific repos.

To use a library from the haxelib library repository:

  1. install the library you want, ex.: haxelib install cool-stuff
  2. Into your .hxml build file add a line like --library cool-stuff
  3. In your code, you can now use that lib’s fully-qualified typenames (or, if you’d rather not have to fully-qualify names, import modules from the library).

Using the haxelib command


haxelib list    # to see the list of all packages installed
haxelib update  # to update all packages

It’s often very useful to install packages directly from their git repo, rather than waiting for their package to be updated. To do that, for example, for the thx.core library:

haxelib git thx.core

(see docs for haxelib git)

If you’re working on a library on your own system, which you’d like to install (locally, into your ~/haxelib) straight from its project directory and without having to first publish it to, you can do:

haxelib dev my-proj ~/path/to/my-proj

For more info, see the haxelib docs.

Example: CSV files

To work with csv files, try the thx.csv module. Install the lib:

haxelib install thx.csv  # or,
haxelib git thx.csv

To your .hxml file, add the line --library thx.csv

In your code, import thx.csv.Csv and use this module like so:

// Read in the csv content as a string.
var s = "Size,Color,Shape

// Gets you an array of array of strings.
var aoa = Csv.decode(s);


Example 2: Set Type

Install haxelib install thx.core, and import thx.Set. See Sets above for usage.

--class-path vs --library

Note the difference between the two following haxe compiler options:

This specifies any additional directories where the compiler is to look for classes (the classpath).
This specifies a particular library (typically in your ~/haxelib) that you want included into your build.

Note, you don’t add the path to the Haxe standard library to your classpath — that’s what the $HAXE_STD_PATH environment variable is for. For example, with my installation the Haxe standard library is located in ~/opt/haxe/std, and so in my ~/.bashrc I have:

export HAXE_STD_PATH="$HOME/opt/haxe/std"

Documenting Your Library

To generate HTML API docs, use dox.

It works like this:

  1. document your libraries with doc comments like /** ... */.

  2. to be continued

Recommended Libraries


For coloring output in the terminal, try Console.hx. Super easy to use.

Utility Libraries


Unit Testing

One popular testing library is munit.

See also discussion on other options.




HaxeUI (GUI toolkit):

More links:

What’s Next?

  1. Start a fun project.
  2. Go read the Haxe Manual to learn about all the more advanced stuff not covered in this tutorial!
  3. Peruse the Haxe code examples cookbook.
  4. Keep the Haxe API docs handy.
  5. Enjoy, and see you at the forum!


  1. See the full list of Haxe compiler targets.

  2. Via externs, which are currently out of the scope of this tutorial.

  3. See Editors and IDEs.