Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
961 lines (589 sloc) 40.4 KB

Language Features

Abstract types:

An abstract type is a compile-time construct which is represented in a different way at runtime. This allows giving a whole new meaning to existing types.

Extern classes:

Externs can be used to describe target-specific interaction in a type-safe manner.

Anonymous structures:

Data can easily be grouped in anonymous structures, minimizing the necessity of small data classes.

var point = { x: 0, y: 10 };
point.x += 10;

Array Comprehension:

Create and populate arrays quickly using for loops and logic.

var evenNumbers = [ for (i in 0...100) if (i & 1 == 0) i ];

Map Comprehension:

Create and populate maps quickly using for loops and logic.

var primality = [ for (i in 0...100) i => isPrime(i) ];

Classes, interfaces and inheritance:

Haxe allows structuring code in classes, making it an object-oriented language. Common related features known from languages such as Java are supported, including inheritance and interfaces.

Conditional compilation:

Conditional Compilation allows compiling specific code depending on compilation parameters. This is instrumental for abstracting target-specific differences, but can also be used for other purposes, such as more detailed debugging.

#if js
  js.Browser.alert("Hello");
#elseif sys
  Sys.println("Hello");
#end

(Generalized) Algebraic Data Types:

Structure can be expressed through algebraic data types (ADT), which are known as enums in the Haxe Language. Furthermore, Haxe supports their generalized variant known as GADT.

enum Result {
  Success(data:Array<Int>);
  UserError(msg:String);
  SystemError(msg:String, position:PosInfos);
}

Inlined calls:

Functions can be designated as being inline, allowing their code to be inserted at call-site. This can yield significant performance benefits without resorting to code duplication via manual inlining.

Iterators:

Iterating over a set of values, e.g. the elements of an array, is very easy in Haxe courtesy of iterators. Custom classes can quickly implement iterator functionality to allow iteration.

for (i in [1, 2, 3]) {
  trace(i);
}

Local functions and closures:

Functions in Haxe are not limited to class fields and can be declared in expressions as well, allowing powerful closures.

var buffer = "";
function append(s:String) {
  buffer += s;
}
append("foo");
append("bar");
trace(buffer); // foobar

Metadata:

Add metadata to fields, classes or expressions. This can communicate information to the compiler, macros, or runtime classes.

class MyClass {
  @range(1, 8) var value:Int;
}
trace(haxe.rtti.Meta.getFields(MyClass).value.range); // [1,8]

Static Extensions:

Existing classes and other types can be augmented with additional functionality through using static extensions.

using StringTools;
"  Me & You    ".trim().htmlEscape();

String Interpolation:

Strings declared with a single quotes are able to access variables in the current context.

trace('My name is $name and I work in ${job.industry}');

Partial function application:

Any function can be applied partially, providing the values of some arguments and leaving the rest to be filled in later.

var map = new haxe.ds.IntMap();
var setToTwelve = map.set.bind(_, 12);
setToTwelve(1);
setToTwelve(2);

Pattern Matching:

Complex structures can be matched against patterns, extracting information from an enum or a structure and defining specific operations for specific value combination.

var a = { foo: 12 };
switch (a) {
  case { foo: i }: trace(i);
  default:
}

Properties:

Variable class fields can be designed as properties with custom read and write access, allowing fine grained access control.

public var color(get,set);
function get_color() {
  return element.style.backgroundColor;
}
function set_color(c:String) {
  trace('Setting background of element to $c');
  return element.style.backgroundColor = c;
}

Access control:

The access control language feature uses the Haxe metadata syntax to force or allow access classes or fields.

Type Parameters, Constraints and Variance:

Types can be parametrized with type parameters, allowing typed containers and other complex data structures. Type parameters can also be constrained to certain types and respect variance rules.

class Main<A> {
  static function main() {
    new Main<String>("foo");
    new Main(12); // use type inference
  }

  function new(a:A) { }
}

Conditional Compilation

Haxe allows conditional compilation by using #if, #elseif and #else and checking for compiler flags.

Define: Compiler Flag

A compiler flag is a configurable value which may influence the compilation process. Such a flag can be set by invoking the command line with -D key=value or just -D key, in which case the value defaults to "1". The compiler also sets several flags internally to pass information between different compilation steps.

This example demonstrates usage of conditional compilation:

code asset

Compiling this without any flags will leave only the trace("ok"); line in the body of the main method. The other branches are discarded while parsing the file. These other branches must still contain valid Haxe syntax, but the code is not type-checked.

The conditions after #if and #elseif allow the following expressions:

  • Any identifier is replaced by the value of the compiler flag by the same name. Note that -D some-flag from command line leads to the flags some-flag and some_flag to be defined.
  • The values of String, Int and Float constants are used directly.
  • The boolean operators && (and), || (or) and ! (not) work as expected, however the full expression must be completely contained by parentheses.
  • The operators ==, !=, >, >=, <, <= can be used to compare values.
  • Parentheses () can be used to group expressions as usual.

The Haxe parser does not parse some-flag as a single token and instead reads it as a subtraction binary operator some - flag. In cases like this the underscore version some_flag has to be used.

Working with compiler flags

Compiler flags are available at compile time, the following methods only work in macro context:

  • To see if a compiler flag is set, use haxe.macro.Context.defined("any_flag").
  • To get the value of a compiler flag, use haxe.macro.Context.definedValue("any_flag").
  • To get a map of all compiler flags with its value use haxe.macro.Context.getDefines().
Haxelibs

By default, each used haxelib version is automatically added as flag, e.g. when you add -L actuate, the compiler adds -D actuate=1.8.7. To test if a library exists in current context, use #if actuate. To check a specific haxelib version, use the operators, for example #if (actuate <= "1.8.7")

Built-in Compiler Flags

An exhaustive list of all built-in defines can be obtained by invoking the Haxe Compiler with the --help-defines argument. The Haxe Compiler allows multiple -D flags per compilation.

Related content

Target defines

Depending on the current target, at least one of the following flags will be defined. Note that they are the same as the argument provided to the compiler to specify the output.

  • as3 Defined when generating ActionScript 3 code.
  • cpp Defined when generating C++ code or a cppia script.
  • cppia Defined when generating a cppia script.
  • cs Defined when generating C# code.
  • eval Defined when running the code with --interp, or when running in a macro context.
  • hl Defined when generating HashLink code.
  • java Defined when generating Java code.
  • js Defined when generating JavaScript code.
  • lua Defined when generating Lua code.
  • neko Defined when generating a Neko binary.
  • php Defined when generating PHP code.
  • python Defined when generating Python code.
  • swf Defined when generating a SWF file.

Additionally, parts of code may be used in a macro context or during display completion. These flags can be used to check if this is the case:

  • display Defined when providing code completion.
  • macro Defined in a macro context.
Supported features
  • sys Defined if the target supports the sys API.
since Haxe 4.0.0

To provide more fine-grained knowledge about the features supported on the current target without having to manually check which target supports what, Haxe 4 provides the target.* defines:

  • target.static (or static) Defined if the target is static.
  • target.sys (or sys as noted above) Defined if the target supports the sys API.
  • target.utf16 (or utf16) Defined if the target uses UTF-16 for its internal string representation.
  • target.threaded Defined if the target supports the unified threading API.
  • target.name Defined to be the name of the target, e.g. js.

Target-Specific Files

since Haxe 4.0.0

In addition to surrounding individual pieces of code with compile-time checks, it is possible to provide completely separate target-specific module alternatives to the compiler. This feature works as follows:

  • When the compiler finds the file <Module>.hx, it then checks the containing directory for a file called <Module>.<target>.hx, where <target> is the name of the current target.
  • The main file for the module (without a taget-specific extension) must exist.
  • If a target-specific file is found for the module, the main file is not loaded at all. Keep in mind that this means errors are not checked in the main file.

As an example, we can have the following directory structure:

/somepackage/Example.hx
/somepackage/Example.js.hx
/Main.hx

In Main.hx we can can use somepackage.Example module. This module is defined in the file somepackage/Example.hx. However, if we compile for JavaScript, the module is instead defined in the file somepackage/Example.js.hx.

Externs

Externs can be used to describe target-specific interaction in a type-safe manner. They are defined like normal classes, except that

  • the class keyword is preceded by the extern keyword,
  • methods have no expressions,
  • all argument and return types are explicit, and
  • the default visibility is public (private must be specified explicitly).

A common example from the Haxe Standard Library is the Math class, as an excerpt shows:

extern class Math
{
	static var PI(default,null) : Float;
	static function floor(v:Float):Int;
}

We see that externs can define both methods and variables (actually, PI is declared as a read-only property). Once this information is available to the compiler, it allows field access accordingly and also knows the types:

code asset

This works because the return type of method floor is declared to be Int.

The Haxe Standard Library comes with many externs for the Flash and JavaScript target. They allow accessing the native APIs in a type-safe manner and are instrumental for designing higher-level APIs. There are also externs for many popular native libraries on haxelib.

The Flash, Java and C# targets allow direct inclusion of native libraries from command line. Target-specific details are explained in the respective sections of Target Details.

Some targets such as Python or JavaScript may require generating additional "import" code that loads an extern class from a native module. Haxe provides ways to declare such dependencies also described in respective sections Target Details.

Rest arguments and type choices
since Haxe 3.2.0

The haxe.extern package provides two types that help mapping native semantics to Haxe:

  • Rest<T>: This type can be used as a final function argument to allow passing an arbitrary number of additional call arguments. The type parameter can be used to constrain these arguments to a specific type.
  • EitherType<T1,T2>: This type allows using either of its parameter types, thus representing a type choice. It can be nested to allow more than two different types.

We demonstrate the usage in this code sample:

code asset

Visibility

Externs support the private visibility modifier. However, because the default visibility in an extern class is public, private needs to be explicitly specified.

Specifying private members is helpful when an API intends to allow overriding functions. Also, Haxe cannot prevent subclasses from reusing field names unless if the fields are included in the extern definition. This is important on targets such as JavaScript where reusing a super class’s field name as a new field in a subclass is not supported.

extern class ExampleSuperClass
{
	private function new(); // Require subclassing to use.
	// Only allow subclasses access to this overridable function.
	private function overridableFunction():String;
	// This function is implicitly public:
	function doSomething():String;
}

Native Metadata

When creating externs it is possible that certain fields will conflict with Haxe keywords, even though they are valid identifiers for the given target. These problems can be resolved by marking the conflicting fields with the metadata :native, with the intended identifier provided as a parameter.

code asset

The generated JavaScript output shows that we are actually assigning to the final field:

(function ($global) { "use strict";
var Test = function() { };
Test.main = function() {
	new A().final = 3;
};
Test.main();
})({});

This metadata can be applied to class and interface fields, but not the fields of a structure type.

Implementing Dynamic

Extern classes can implement Dynamic and Dynamic<T> which enables arbitrary field access. In the former case, fields can have any type, in the latter, they are constrained to be compatible with the parameter type:

code asset

Implementing Dynamic does not satisfy the requirements of other implemented interfaces. The expected fields still have to be implemented explicitly.

Trivia: Implementing Dynamic on non-externs

Starting with Haxe 4, implementing Dynamic is only allowed on extern classes. In previous versions any class could implement Dynamic, allowing arbitrary fields to be read or written. Additionally a special resolve method could be used to resolve read access to a non-existend field. A similar feature is now available as field access operator overload on abstracts.

Static Extension

Define: Static Extension

A static extension allows pseudo-extending existing types without modifying their source. In Haxe this is achieved by declaring a static method with a first argument of the extending type and then bringing the defining class into context through using.

Static extensions can be a powerful tool which allows augmenting types without actually changing them. The following example demonstrates the usage:

code asset

Clearly, Int does not natively provide a triple method, yet this program compiles and outputs 36 as expected. This is because the call to 12.triple() is transformed into IntExtender.triple(12). There are three requirements for this:

  1. Both the literal 12 and the first argument of triple are known to be of type Int.
  2. The class IntExtender is brought into context through using Main.IntExtender.
  3. Int does not have a triple field by itself (if it had, that field would take priority over the static extension).

Static extensions are usually considered syntactic sugar and indeed they are, but it is worth noting that they can have a dramatic effect on code readability: Instead of nested calls in the form of f1(f2(f3(f4(x)))), chained calls in the form of x.f4().f3().f2().f1() can be used.

Following the rules previously described in Resolution Order, multiple using expressions are checked from bottom to top, with the types within each module as well as the fields within each type being checked from top to bottom. Using a module (as opposed to a specific type of a module, see Modules and Paths) as static extension brings all its types into context.

The static extension keyword using also implies the effects of an import of the same module.

Related content

In the Haxe Standard Library

Several classes in the Haxe Standard Library are suitable for static extension usage. The next example shows the usage of StringTools:

code asset

While String does not have a replace functionality by itself, the using StringTools static extension provides one. As usual, the JavaScript output nicely shows the transformation:

Main.main = function() {
	StringTools.replace("adc","d","b");
}

The following classes from the Haxe Standard Library are designed to be used as static extensions:

  • StringTools: Provides extended functionality on strings, such as replacing or trimming.
  • Lambda: Provides functional methods on iterables.
  • haxe.EnumTools: Provides type information functionality on enums and their instances.
  • haxe.macro.Tools: Provides different extensions for working with macros (see Tools).
Trivia: "using" using

Since the using keyword was added to the language, it has been common to talk about certain problems with "using using" or the effect of "using using". This makes for awkward English in many cases, so the author of this manual decided to call the feature by what it actually is: Static extension.

Static Extension Metadata

since Haxe 4.0.0
Exclusion from Static Extension

By default, using a class as a static extension brings all of its static methods into the context, allowing them to be used as extensions of the appropriate types. In certain situations, the class can provide other static methods which are not intended for static extension. To make sure they do not interfere with the proper methods of the type, these methods can be marked with @:noUsing:

code asset

Default Static Extension

It is also possible to always enable particular static extensions for a given type, by annotating the type with the @:using(args...) metadata. The arguments are the full dot paths of static extension classes that will be applied on the type:

code asset

Pattern Matching

Introduction

Pattern matching is the process of branching depending on a value matching given, possibly deep patterns. In Haxe, pattern matching is primarily done within switch expressions where the individual case expressions represent the patterns. Here we will explore the syntax for different patterns using this data structure as running example:

code asset

Some pattern matcher basics include:

  • Patterns will always be matched from top to bottom.
  • The topmost pattern that matches the input value has its expression executed.
  • A _ pattern matches anything, so case _: is equal to default:
Related content

Enum matching

Enums can be matched by their constructors in a natural way:

code asset

The pattern matcher will check each case from top to bottom and pick the first one that matches the input value. The following manual interpretation of each case rule helps understanding the process:

  • case Leaf(_): matching fails because myTree is a Node
  • case Node(_, Leaf(_)): matching fails because the right sub-tree of myTree is not a Leaf, but another Node
  • case Node(_, Node(Leaf("bar"), _)): matching succeeds
  • case _: this is not checked here because the previous line matched

Variable capture

It is possible to catch any value of a sub-pattern by matching it against an identifier:

code asset

This would return one of the following:

  • If myTree is a Leaf, its name is returned.
  • If myTree is a Node whose left sub-tree is a Leaf, its name is returned (this will apply here, returning "foo").
  • Otherwise "none" is returned.

It is also possible to use = to capture values which are further matched:

code asset

Here, leafNode is bound to Leaf("foo") if the input matches that. In all other cases, myTree itself is returned: case x works similar to case _ in that it matches anything, but with an identifier name like x it also binds the matched value to that variable.

since Haxe 4.0.0

If the compiler detects that the name of a "catch-all" variable is very similar to an enum constructor, it will warn about a potential typo. This can be avoided by prefixing the variable identifier with var:

code asset

Any capture variable can be prefixed with var, even within sub-patterns.

Structure matching

It is also possible to match against the fields of anonymous structures and instances:

code asset

In the second case we bind the matched name field to identifier n if rating matches "awesome". Of course this structure could also be put into the Tree from the previous example to combine structure and enum matching.

Array matching

Arrays can be matched on fixed length:

code asset

This will trace 1 because myArray[1] matches 6, and myArray[0] is allowed to be anything.

Or patterns

The | operator can be used anywhere within patterns to describe multiple accepted patterns:

code asset

If there is a captured variable in an or-pattern, it must appear in both its sub-patterns.

Guards

It is also possible to further restrict patterns with the case ... if(condition): syntax:

code asset

The first case has an additional guard condition if (b > a). It will only be selected if that condition holds, otherwise matching continues with the next case.

Match on multiple values

Array syntax can be used to match on multiple values:

code asset

This is quite similar to usual array matching, but there are differences:

  • The number of elements is fixed, so patterns of different array length will not be accepted.
  • It is not possible to capture the switch value in a variable, i.e. case x is not allowed (case _ still is).

Extractors

since Haxe 3.1.0

Extractors allow applying transformations to values being matched. This is often useful when a small operation is required on a matched value before matching can continue:

code asset

Here we have to capture the argument value of the TString enum constructor in a variable temp and use a nested switch on temp.toLowerCase(). Obviously, we want matching to succeed if TString holds a value of "foo" regardless of its casing. This can be simplified with extractors:

code asset

Extractors are identified by the extractorExpression => match expression. The compiler generates code which is similar to the previous example, but the original syntax was greatly simplified. Extractors consist of two parts, which are separated by the => operator:

  1. The left side can be any expression, where all occurrences of underscore _ are replaced with the currently matched value.
  2. The right side is a pattern which is matched against the result of the evaluation of the left side.

Since the right side is a pattern, it can contain another extractor. The following example "chains" two extractors:

code asset

This traces 12 as a result of the calls to add(3, 1), where 3 is the matched value, and mul(4, 3) where 4 is the result of the add call. It is worth noting that the a on the right side of the second => operator is a capture variable.

It is currently not possible to use extractors within or-patterns:

code asset

However, it is possible to have or-patterns on the right side of an extractor, so the previous example would compile without the parentheses.

Exhaustiveness checks

The compiler ensures that no possible cases are forgotten:

switch(true) {
    case false:
} // Unmatched patterns: true

The matched type Bool admits two values true and false, but only false is checked.

Exhaustiveness is not checked when matching on Int, Float or String if no value from the switch is required, which means that it appears at block-level.

Useless pattern checks

In a similar fashion, the compiler detects patterns which will never match the input value:

switch(Leaf("foo")) {
    case Leaf(_)
       | Leaf("foo"): // This pattern is unused
    case Node(l,r):
    case _: // This pattern is unused
}

Single pattern check

The compiler provides the function match to check if an enum value matches a given pattern:

code asset

As this function only tests if the pattern is matched, guards and variable capture are unavailable.

The match function is equivalent to a switch with a single case for the given pattern, returning true, and a default returning false.

code asset

See the EnumValue API documentation (since Haxe 3.2.1) for more information.

String Interpolation

With Haxe 3 it is no longer necessary to manually concatenate parts of a string due to the introduction of String Interpolation. Special identifiers, denoted by the dollar sign $ within a String enclosed by single-quote ' characters, are evaluated as if they were concatenated identifiers:

var x = 12;
// The value of x is 12
trace('The value of x is $x');

Furthermore, it is possible to include whole expressions in the string by using ${expr}, with expr being any valid Haxe expression:

var x = 12;
// The sum of 12 and 3 is 15
trace('The sum of $x and 3 is ${x + 3}');

String interpolation is a compile-time feature and has no impact on the runtime. The above example is equivalent to manual concatenation, which is exactly what the compiler generates:

trace("The sum of " + x + " and 3 is " + (x + 3));

Of course the use of single-quote enclosed strings without any interpolation remains valid, but care has to be taken regarding the $ character as it triggers interpolation. If an actual dollar-sign should be used in the string, $$ can be used.

Trivia: String Interpolation before Haxe 3

String Interpolation has been a Haxe feature since version 2.09. Back then, the macro Std.format had to be used, being both slower and less comfortable than the new string interpolation syntax.

Array Comprehension

Array comprehension in Haxe combines array declaration and loops to allow concise initialization of arrays. It is identified by for or while constructs:

code asset

Variable a is initialized to an array holding the numbers 0 to 9. The compiler generates code which adds the value of each loop iteration to the array, so the following code would be equivalent:

var a = [];
for (i in 0...10) a.push(i);

Variable b is initialized to an array with the same values, but through a different comprehension style using while instead of for. Again, the following code would be equivalent:

var i = 0;
var b = [];
while(i < 10) b.push(i++);

The loop expression can be anything, including conditions and nested loops, so the following works as expected:

code asset

Map Comprehension

Map comprehension in Haxe is similar to array comprehension, but just like map declaration, it additionally uses the => operator:

code asset

Variable a is initialized to an Map holding keys from 0 to 4 and string values. The compiler generates code which adds the value of each loop iteration to the map, so the following code would be equivalent:

var a = new Map();
for (i in 0...5) a.set(i, 'number ${i}');

Variable b is initialized to an Map with the same keys and values, but through a different comprehension style using while instead of for. Again, the following code would be equivalent:

var i = 0;
var b = new Map();
while(i < 5) b.set(i, 'number ${i++}');

The loop expression can be anything, including conditions and nested loops, so the following works as expected:

code asset

Iterators

With Haxe it is very easy to define custom iterators and iterable data types. These concepts are represented by the types Iterator<T> and Iterable<T> respectively:

typedef Iterator<T> = {
	function hasNext() : Bool;
	function next() : T;
}

typedef Iterable<T> = {
	function iterator() : Iterator<T>;
}

Any class which structurally unifies with one of these types can be iterated over using a for-loop. That is, if the class defines methods hasNext and next with matching return types it is considered an iterator, if it defines a method iterator returning an Iterator<T> it is considered an iterable type.

code asset

The type MyStringIterator in this example qualifies as iterator: It defines a method hasNext returning Bool and a method next returning String, making it compatible with Iterator<String>. The main method instantiates it, then iterates over it.

code asset

Here we do not setup a full iterator like in the previous example, but instead define that the MyArrayWrap<T> has a method iterator, effectively forwarding the iterator method of the wrapped Array<T> type.

since Haxe 4.0.0

The standard library also includes key-value iterators to support key-value iteration:

typedef KeyValueIterator<K, V> = {
	function hasNext() : Bool;
	function next() : {key: K, value: V};
}

typedef KeyValueIterable<K, V> = {
	function iterator() : KeyValueIterator<K, V>;
}
Related content

Function Bindings

Haxe 3 allows binding functions with partially applied arguments. Each function type can be considered to have a bind field, which can be called with the desired number of arguments in order to create a new function. This is demonstrated here:

code asset

Line 4 binds the function map.set to a variable named f, and applies 12 as second argument. The underscore _ is used to denote that this argument is not bound, which is shown by comparing the types of map.set and f: The bound String argument is effectively cut from the type, turning a Int->String->Void type into Int->Void.

A call to f(1) then actually invokes map.set(1, "12"), the calls to f(2) and f(3) are analogous. The last line proves that all three indices indeed are mapped to the value "12".

The underscore _ can be skipped for trailing arguments, so the first argument could be bound through map.set.bind(1), yielding a String->Void function that sets a new value for index 1 on invocation.

Optional arguments

By default, trailing optional arguments are bound to their default values and do not become arguments of the result function. This can be changed by using an explicit underscore _ instead, in which case the optional argument of the original function becomes a non-optional argument of the result function. code asset

Trivia: Callback

Prior to Haxe 3, Haxe used to know a callback-keyword which could be called with a function argument followed by any number of binding arguments. The name originated from a common usage were a callback-function is created with the this-object being bound.

Callback would allow binding of arguments only from left to right as there was no support for the underscore _. The choice to use an underscore was controversial and several other suggestions were made, none of which were considered superior. After all, the underscore _ at least looks like it's saying "fill value in here", which nicely describes its semantics.

Metadata

Several constructs can be attributed with custom metadata:

  • class and enum declarations
  • Class fields
  • Enum constructors
  • Expressions

These metadata information can be obtained at runtime through the haxe.rtti.Meta API:

code asset

We can easily identify metadata by the leading @ character, followed by the metadata name and, optionally, by a number of comma-separated constant arguments enclosed in parentheses.

  • Class MyClass has an author metadata with a single String argument "Nicolas", as well as a :keep metadata without arguments.
  • The member variable value has a range metadata with two Int arguments 1 and 8.
  • The static method method has a broken metadata without arguments.

The main method accesses these metadata values using their API. The output reveals the structure of the obtained data:

  • There is a field for each metadata, with the field name being the metadata name.
  • The field values correspond to the metadata arguments. If there are no arguments, the field value is null. Otherwise the field value is an array with one element per argument.

Allowed values for metadata arguments are:

Compile-time Metadata

Metadata starting with :, such as @:keep, is available at compile time only; it is omitted at runtime. It may be used by macros or by the Haxe compiler itself. Unlike runtime metadata, arguments to compile-time metadata can be any valid expression.

Built-in Compiler Metadata

An exhaustive list of all defined metadata can be obtained by running haxe --help-metas from command line.

since Haxe 4.0.0

Prior to Haxe 4, metadata names had to be valid identifiers. Starting in Haxe 4, metadata names can consist of multiple identifiers separated by . symbols. This change was primarily intended to make it easier to organize compile-time metadata. Runtime metadata with such a name can only be accessed via dynamic access.

Related content

Access Control

Access control can be used if the basic visibility options are not sufficient. It is applicable at class-level and at field-level and knows two directions:

  • Allowing access: The target is granted access to the given class or field by using the :allow(target) metadata.
  • Forcing access: A target is forced to allow access to the given class or field by using the :access(target) metadata.

In this context, a target can be the dot-path to

  • a class field,
  • a class or abstract type, or
  • a package.

Target does not respect imports, so the fully qualified path has to be used.

If it is a class or abstract type, access modification extends to all fields of that type. Likewise, if it is a package, access modification extends to all types of that package and recursively to all fields of these types.

code asset

Here, MyClass.foo can be accessed from the main-method because MyClass is annotated with @:allow(Main). This would also work with @:allow(Main.main) and both versions could alternatively be annotated to the field foo instead of the class MyClass:

code asset

If a type cannot be modified to allow this kind of access, the accessing method may force access:

code asset

The @:access(MyClass.foo) annotation effectively subverts the visibility of the foo field within the main-method.

Trivia: On the choice of metadata

The access control language feature uses the Haxe metadata syntax instead of additional language-specific syntax. There are several reasons for that:

  • Additional syntax often adds complexity to the language parsing, and also adds (too) many keywords.
  • Additional syntax requires additional learning by the language user, whereas metadata syntax is something that is already known.
  • The metadata syntax is flexible enough to allow extension of this feature.
  • The metadata can be accessed/generated/modified by Haxe macros.

Of course, the main drawback of using metadata syntax is that you get no error report in case you misspell either the metadata key (@:access for instance) or the class/package name. However, with this feature you will get an error when you try to access a private field that you are not allowed to, therefore there is no possibility for silent errors.

since Haxe 3.1.0

If access is allowed to an interface, it extends to all classes implementing that interface:

code asset

This is also true for access granted to parent classes, in which case it extends to all child classes.

Trivia: Broken feature

Access extension to child classes and implementing classes was supposed to work in Haxe 3.0 and even documented accordingly. While writing this manual it was found that this part of the access control implementation was simply missing.

Inline Constructors

since Haxe 3.1.0

If a constructor is declared to be inline, the compiler may try to optimize it away in certain situations. There are several requirements for this to work:

  • The result of the constructor call must be directly assigned to a local variable.
  • The expression of the constructor field must only contain assignments to its fields.

The following example demonstrates constructor inlining:

code asset

A look at the JavaScript output reveals the effect:

Main.main = function() {
	var pt_x = 1.2;
	var pt_y = 9.3;
};
You can’t perform that action at this time.