Haxe Review: Haxe 4 Features and Strengths
The quietly growing number of serious Haxe projects have something new to contend with: the first major compiler release in over three years. What does Haxe 4 bring to the table?
The quietly growing number of serious Haxe projects have something new to contend with: the first major compiler release in over three years. What does Haxe 4 bring to the table?
Kevin has more than 25 years among full-stack, desktop, and indie game development. He lately specializes in PostgreSQL, JavaScript, Perl, and Haxe.
Expertise
Our previous Haxe review ended with a look at the then upcoming Haxe 4. With the official release of Haxe 4 (and shortly thereafter, two bug-patch releases—version 4.0.1 and 4.0.2), it’s time for a new Haxe review. What are the latest additions to this burgeoning programming language? Where is the Haxe programming language community heading? Are Haxe game engines still its mainstay?
Haxe Review: Haxe 4’s New Features
With more than three years of development since the last major release, version 4 of the Haxe programming language improves macro performance, developer experience, and syntax. Three of its enhancements are still considered experimental but worth highlighting: the new JVM bytecode target, support for inline markup, and null safety checks.
The Experimental JVM Bytecode Compilation Target in Haxe 4
Haxe 4’s new JVM bytecode target makes Java development via Haxe quite a bit more efficient by cutting out a major compilation step: There’s no second step of having Java’s own compiler (javac
) compile the Java source code output of Haxe’s transpiler.
This method of compiling with Haxe 4 also entirely removes dependency on the Java developer kit (JDK), and opens the door for interactive debugging to be implemented in the future.
Until hxjava
’s mainstream version is Haxe 4–compatible, basic setup involves installing Haxe and Haxelib, then running haxelib install hxjava 4.0.0-alpha
. With that done, the development flow is simple:
# transpile directly to JVM bytecode with Haxe (-D jvm would also work):
haxe --main HelloWorld --java jar_output --define jvm
# run JVM bytecode with Java:
java -jar jar_output/HelloWorld.jar
Given that direct JVM compilation still has experimental status in Haxe 4, it comes with a couple of caveats:
- There are some issues specific to Android.
- Runtime performance is not quite as good, even if it will eventually be faster than the indirect method.
Nonetheless, it’s a notable step in the right direction for anyone leveraging Java-based technologies.
Experimental Inline Markup Support in Haxe 4
JSX, anyone? Haxe 4 enables inline markup, allowing developers to write, for example, HTML directly within Haxe source code:
var dom = jsx(
<div>
<h1>Hello!</h1>
<p>This is a paragraph.</p>
</div>
);
Since jsx()
here can be a static macro function, this allows a project to have compile-time checks as to whether the markup conforms to whatever XML-ish spec the developer cares to implement. Since XML support itself is built into the Haxe API, the check can leverage Xml.parse()
, but for basic “XML-ish” parseability, not even that is needed:
static macro function jsx(expr) {
return switch expr.expr {
case EMeta({name: ":markup"}, {expr: EConst(CString(s))}):
macro $v{"XML MARKUP: " + s};
case _:
throw new haxe.macro.Expr.Error("not an xml literal", expr.pos);
}
}
The intention with this feature is to help push Haxe out of the game development bubble (although it surely has uses there too). It’s general enough that it’s implemented at the compiler level—hence not needing the Haxe API in the above macro—but checking for specific DSLs is the next question for the compiler team and the community to figure out.
Experimental Null Safety in Haxe 4
Since the invention of the null reference in 1965, the issue of null safety has often been the bane of developers in nullable typed environments like that of the Haxe programming language. Aleksandr Kuzmenko estimates that GitHub commits fixing null pointer reference errors number over 10 million.
Haxe 4 has built-in compile-time null safety macros, which can be enabled by including a @:nullSafety
line just before a given definition. It comes in @:nullSafety(Loose)
(the default) and @:nullSafety(Strict)
modes, and can be disabled as needed with @:nullSafety(Off)
. Strict
mode will look through function calls for field mutations that might assign null, even in a null safety–disabled context.
Ruby developers may wonder if the handy safe navigation operator (?.
in Ruby) is on the radar. Not yet, but as with many aspects of programming in Haxe, there’s a macro for that (note that it uses !.
instead.)
Developer Experience (DX) with Haxe 4: Syntax Additions, Syntactic Sugar, and More
The DX-related additions to the Haxe programming language and Haxe IDE support bring the Haxe 4 experience at least level with other programming languages on various fronts. In some ways, Haxe seeks to be everything to everyone, but the compiler team takes a thoughtful approach toward integrating the most useful features and conventions from other languages.
The result is that the Haxe programming language and standard API evolve without compromising their stability, sensibility, and cohesiveness. Not everything in this Haxe review will seem hype-worthy, and that’s precisely the point: DX is improving, and this is in favor of simply chasing flashy “features du jour.”
There’s a balance to be had, though: Haxe’s changes are done with an awareness of the patterns other languages are following, and Haxe 4 certainly makes an effort to appeal to newcomers from more popular languages.
New “Function Type” Syntax
On that note, Haxe now supports two major ways of representing function types. The old syntax “suggests that auto-currying and partial application are supported, but they aren’t,” according to the original feature proposal:
Int -> String -> Void
The new syntax allows named arguments, which improves DX:
(id:Int, name:String) -> Void
But DX aside, using Haxe 4’s new syntax for function types is a good habit to get into, as the old, inferior syntax may be removed in a future major version.
Syntactic Sugar…Sort of
Perhaps it’s not groundbreaking, but Haxe 4’s syntactic improvements will be welcome news both for existing Haxe developers with certain development backgrounds (ES6, for example) and those who might be coming from them to Haxe for the first time.
Arrow function (“short lambda”) syntax is now supported, which in Haxe’s case is more or less merely a shortcut for typing function
and return
. Key-value and index-value iteration syntaxes (for maps and arrays, respectively) are now supported too. Type declarations using static extensions can just use one using
statement globally instead of needing them everywhere the corresponding static extension methods are used.
Enums and enum abstracts have some other improvements, one of which is that the latter have moved from the purview of macros to having direct compiler support. Other features moved similarly include final classes, final interfaces, and extern fields.
Some features relying on macros stayed reliant on macros, but improved nonetheless. Operator overloading was levelled up to include field setters, and metadata can now be namespaced with .
separators as in @:prefix.subprefix.name
.
Calling the above changes syntactic sugar is admittedly oversimplifying, but readers are welcome to dig into the original proposals linked to from Haxe 4’s release notes where they need more detail.
More Haxe 4 DX Boosts
While interactive debugging was already possible in Haxe for various compiled targets, the new eval
target makes interactive debugging possible for interpreted code. For a simple example, you can take any Haxe “Hello, World” tutorial’s project directory, add a file called whatever-you-want.hxml
looking like this:
--main HelloWorld
--interp
…and get interactive debugging in the VSCode IDE simply by:
- Opening the project directory in VSCode;
- Adding a breakpoint somewhere; and
- Hitting F5 and choosing “Haxe Interpreter” from the dropdown.
This feature also allows you to interactively debug macro code the same way, even if you’re actually compiling for a particular target like java
(rather than using --interp
). The only installation requirement besides Haxe and VSCode themselves is the Haxe VSCode extension.
IDE Services
Speaking of IDEs, Haxe 4 introduces a new IDE services protocol, which is already leveraged in the latest VSCode Haxe extension, vshaxe. Besides a significant performance boost, this allows vshaxe to provide some extremely useful DX improvements, including:
- (Long awaited) automatic imports
- Autocompletion hover hints showing more details, like answering the question, “Where is this field from?”
- Very thorough autocompletion in several slick new ways, like expected type completion, postfix completion, and override completion
- Down-to-the-keystroke optimizations while typing out code
It’s much easier to see the value of these via the excellent visual demos from the relevant vshaxe changelog. vshaxe
with VSCode is not the only Haxe IDE around—HaxeDevelop and Kode Studio are Haxe-specific, and there are Haxe IDE plugins for IntelliJ IDEA, Sublime Text, Atom, etc.—but it does seem to be ahead of the pack in terms of making use of Haxe 4’s new IDE services protocol, followed closely by IntelliJ-Haxe.
Unicode Literals
Developers wanting to use real Unicode string literals will find support for that in Haxe 4, but there are some nuances to be aware of.
Read-only Arrays
The standard Haxe API now has read-only arrays. These are as easy to use as declaring a variable to be of the type, for example, haxe.ds.ReadOnlyArray<Int>
, after which trying to set, push, or pop values results in various compiler errors. Add the final
keyword to the declaration, and reassigning the array itself will be disallowed as well.
Call-site Inlining
Call-site inlining is a new Haxe language feature allowing developers fine-grained control over where functions are inlined, useful when optimizing frequently called functions where the overall size-performance tradeoff might otherwise be a lose-lose decision.
These are worthwhile additions to the already excellent Haxe programming language. What are developers in the Haxe community building now that Haxe 4 is out?
Beyond Haxe Game Engines: Web Development with Haxe 4
Haxe’s user base has historically been dominated by game programmers. But there are plenty of examples of Haxe being used—at scale—in other segments, like business stacks, mobile apps, and the web, both for front- and back-end development.
To that end, Haxe 4 supplies regenerated HTML externs, meaning that Haxe’s js.html
standard API has been brought up to date with the wider web API as MDN defines it, as well as fixing bugs and adding missing APIs. (For example, Haxe 4 now includes the Push API.)
In Juraj Kirchheim’s talk, Weaving a Better Web with Haxe, he points to examples of Haxe-based web solutions being orders of magnitude more efficient—yet also more robust—in an enterprise setting.
He also argues against the Rails architectural approach (in terms of folder hierarchy), but developers who favor a complete web framework a la Rails can still find one. Other times, it may benefit developers to review the source of a complete web project, in which case it’s worth taking a look at the public repo for Giffon, a crowd-gifting platform that supports Haxe 4. Likewise, web-centric, open-source Haxe libraries like the JavaScript-splitting Haxe Modular, the generic thx.core and its sister libraries, and the venerable Haxe web toolkit Tinkerbell all already support Haxe 4. So does the cross-platform UI solution HaxeUI, which supports a web context but targets a much wider scope including business and desktop app development; it in particular has steadily continued to mature leading up to the new Haxe language release.
Web, games, enterprise…regardless of the platform and app flavor a development team—even a team of one—is targeting, Haxe developers will eventually have to grapple with managing dependencies. For this, a helpful resource for Haxe developers to review is the slides from Adam Breece’s talk, Scaling well with others.
Haxe as the Best Programming Language for Games
Does one, single “best” language for game development even exist? It’s a subjective question, and an easy one to find heated debates about. Bigger than one might expect from the size of its community alone, Haxe’s success in the game development sphere is certainly not coincidental. Joe Williamson provides some insight as to why this might be in an interview about winning the Ludum Dare 45 game jam in 2019, and this seems likely to continue with Haxe 4.
Haxe’s original creator, Nicolas Cannasse, is already using Haxe 4 in production with Shiro Games’ Northgard. Motion Twin is also using Haxe 4 in production for Dead Cells. Both games have tens of thousands of positive reviews on Steam, and are available for both PCs (Win, Mac, and Linux) and consoles—a truly formidable result considering that both games have smaller development teams yet user bases in the millions. Dead Cells even has an iOS version, with an Android version on their radar as well.
Library-wise, several major Haxe game engines are definitely keeping step with Haxe 4’s changes. Haxe 4–compatible engines include Kha (and a portion of the many engines built on top of it—e.g., Armory), HaxeFlixel and its main dependency OpenFL, NME, and Heaps—naturally, since that’s what Northgard and Dead Cells use. HaxePunk is also working on Haxe 4 compatibility; in one case, a library, Nape, was forked to work with Haxe 4.
Some developers also make their own engines instead of using one of the many already out there. For example, Kirill Poletaev, who details how and why he wrote his own 3D Haxe game engine. Since said engine is in-house, it makes sense that it’s one example of a project that has not migrated to Haxe 4 yet.
Haxe 4: Continuing the Smooth Progression of an Excellent Toolchain
With Haxe having such wide utility, the most important Haxe 4 features will vary by developer, so this Haxe review is by no means exhaustive. Some of Haxe 4’s changes are missing above, including:
- The addition of ES6 output for the JavaScript target
- The removal of features (some of which are still available via the
hx3compat
library) and targets (PHP5 and soon AS3) - CLI flags being made more consistent with common tools (
-lib
-using.hxml
files will need changing to-L
or--library
, for example). - Besides
final
now being a keyword (which therefore can’t be used as a variable name),operator
andoverload
are also newly reserved keywords.
There were also some breaking changes, but they are so few that many actively maintained libraries aren’t even bothering to explicitly announce Haxe 4 compatibility—in general, migration from Haxe 3 is said to be fairly straightforward. After all, one of Haxe’s goals is stability in the midst of juggling support for a high number of target platforms, and Haxe 4 does not disappoint here.
What about new users? In the end, it’s up to the reader to decide whether Haxe is the best coding language for games, whether the Haxe ecosystem offers the most robust libraries for web development, or whether Haxe-specific tooling gives the most sensible DX for a particular workflow. At the very least, Haxe continues to be a viable contender in many spaces, offering something of a secret advantage for just about any developer.
Further reading: Developers new to Haxe may be interested in a Haxe tutorial by John Gabriele, and also the release notes of Haxe 4.1.0 and later versions.
Further Reading on the Toptal Blog:
Understanding the basics
Which framework can be used to develop cross-platform applications?
There are several cross-platform frameworks like React Native and Xamarin, but Haxe can be used to develop applications for multiple platforms from a single codebase with or without a framework. React Native in particular has Haxe externs available, meaning it’s possible to develop for it using Haxe.
Which programming language is the best for cross-platform development?
While Java is often the first language discussed in a cross-platform context, Haxe has an even wider set of targets: It’s able to transpile to Java, but also C#, C++, JavaScript, PHP, and several others. Since Haxe has managed to keep so many targets while polishing its usability, it’s a definite contender here.
Which programming language is best for games?
Game development spans a huge spectrum, so the best programming language for games depends on both the programmer and the project. The case for Haxe is that it offers a uniquely refined language while being able to simultaneously target Win/Mac/Linux, iOS/Android, HTML5, and console systems from a single codebase.
What is a game development engine?
Game development engines are software libraries that provide common (or sometimes complete) game engine functionality. A game engine consists of the core code needed to process assets, user plugins, etc. to provide a game experience. A game development engine may include its own IDE and other specific tools.
Bergerac, France
Member since January 31, 2017
About the author
Kevin has more than 25 years among full-stack, desktop, and indie game development. He lately specializes in PostgreSQL, JavaScript, Perl, and Haxe.