4.4.2 Inline

Inline functions

The inline keyword allows function bodies to be directly inserted in place of calls to them. This can be a powerful optimization tool but should be used judiciously as not all functions are good candidates for inline behavior. The following example demonstrates the basic usage:

class Main {
  static inline function mid(s1:Int, s2:Int) {
    return (s1 + s2) / 2;
  }

  static public function main() {
    var a = 1;
    var b = 2;
    var c = mid(a, b);
  }
}

The generated JavaScript output reveals the effect of inline:

(function () { "use strict";
var Main = function() { }
Main.main = function() {
    var a = 1;
    var b = 2;
    var c = (a + b) / 2;
}
Main.main();
})();

As evident, the function body (s1 + s2) / 2 of field mid was generated in place of the call to mid(a, b), with s1 being replaced by a and s2 being replaced by b. This avoids a function call which, depending on the target and frequency of occurrences, may yield noticeable performance improvements.

It is not always easy to judge if a function qualifies for being inline. Short functions that have no writing expressions (such as a = assignment) are usually a good choice, but even more complex functions can be candidates. However, in some cases, inlining can actually be detrimental to performance, e.g. because the compiler has to create temporary variables for complex expressions.

Inline is not guaranteed to be done. The compiler might cancel inlining for various reasons or a user could supply the --no-inline command line argument to disable inlining. The only exception is if the class is extern or if the class field has the extern access modifier, in which case inline is forced. If it cannot be done, the compiler emits an error.

It is important to remember this when relying on inline:

class Main {
  public static function main() {}

  static function test() {
    if (Math.random() > 0.5) {
      return "ok";
    } else {
      error("random failed");
    }
  }

  @:extern static inline function error(s:String) {
    throw s;
  }
}

If the call to error is inlined the program compiles correctly because the control flow checker is satisfied due to the inlined throw expression. If inline is not done, the compiler only sees a function call to error and emits the error A return is missing here.

Since Haxe 4 it is also possible to inline specific calls to a function or constructor, see inline expression.

Inline variables

The inline keyword can also be applied to variables, but only when used together with static. An inline variable must be initialized to a constant, otherwise the compiler emits an error. The value of the variable is used everywhere in place of the variable itself.

The following code demonstrates the usage of an inline variable:

class Main {
  static inline final language = "Haxe";

  static public function main() {
    trace(language);
  }
}

The generated JavaScript shows that the language variable is not present anymore:

(function ($global) { "use strict";
var Main = function() { };
Main.main = function() {
    console.log("root/program/Main.hx:5:","Haxe");
};
Main.main();
})({});

Note that even though we call such kind of fields "variables", inline variables can never be reassigned as the value must be known at compile-time to be inlined at the place of usage. This makes inline variables a subset of final fields, hence the usage of the final keyword in the code example above.

Trivia: inline var

Prior to Haxe 4, there was no final keyword. The inline variables feature however was present for a long time, using the var keyword instead of final. Using inline var still works in Haxe 4 but might be deprecated in the future, because final is more appropriate.