Trying to understand Haxe's treatment of structs

Hello. Coming from a C/C++/Python background I find it curious that Haxe actually presents structs as anonymous by nature and if you want you typedef them. To me this sounds like functions are lambdas (as in Python) by nature and if you want give them a name!

So is it because in C++ structs and classes aren’t really much different (and hence Python has no structs) so Haxe promotes classes as the main aggregate data type?

Anyhow, I also noted that the documentation says that structs have a performance penalty and this penalty is not present in class fields.

Do I understand this that because structs are intended as an ad-hoc convenience type for aggregate data without any necessity of pre-defined structure, so they are represented internally as a mapping of field name to data, hence the lookup is dynamic and causes the penalty?

Also if there are two instances of a similar “anonymous” struct {x: 2, y: 3} {x: 4, y: 5} then they don’t have any unification in the internal representation?

Sorry if my question in vague but I’m new to Haxe and trying to grok things here.

1 Like

Two structs with identical names inferred types will absolutely unify; the values don’t matter. And structs with extra fields will generally unify as well, but I’m less clear on the rules there.

Anonymous structures are basically compile time duck typing. On dynamic targets, they cause no performance penalty, on static targets, it’s a bit trickier. Some rely on reflection, others generate adhoc classes/interfaces to achieve the same performance characteristics. When in doubt, measure.

Anonymous structures use structural subtyping, so for { x:Int }, both “another” { x:Int } as well as { x:Int, y:Int } are valid subtypes.

If you wish to use structure notation for initialization and still have class intances (for performance reasons), you should look into @:structInit. Note that occasionally you may have to be more explicit about typing, since inference may sometimes type an object literal as an anonymous object and subsequent unification with the class type will then fail:

class Test {
  static function main() {
    var p1:Point = { x: 123.4, y: 432.1 };
    var p2 = { x: 123.4, y: 432.1 };
    $type(p1);// Point
    $type(p2);// { y : Float, x : Float }
    p2 = p1;// Ok, because structure is compatible
    p1 = p2;// Not ok, because nominal subtyping is stricter

@:structInit class Point {
  public var x:Float;
  public var y:Float;

I always get confused by this terminology, but aren’t they supertypes because you can assign subtypes to supertypes?