"Any" and "{ }" types

Hej !

Can anyone explain me what is the concrete difference between Dynamic and Any please ?
What I’ll be able or not able to do with one and another ?
And the same with the { } type please ?
I’ve read the documentation, but I don’t really understand.
With some examples I’ll understand I think if you please ?

Thanks a lot.

The difference is that Dynamic allows the access of any field, where Any never allows field access. and {} types only allow specified field access. all of this applies to compile-time checks

var foo:Dynamic = {x:5, y: 10};
trace(foo.x, foo.y, foo.z); // Outputs: 5, 10, null (js)

var bar:Any = foo;
trace(bar.x, bar.y);// Compile Error

var qux:{x:Int, y:Int} = foo;
trace(qux.x, qux.y); // 5, 10
trace(qux.z); // Compile error

Thanks for your reply.
Ok so If I undrstand well, Any is a type like Dynamic but without field access. So we can write something like that :

var o : Any = 1;
o = "foo"; // it's ok
o.foo = "bar"; // Error

If that’s it, I understand now.

And by {} I mean something like that :

function foo( o : {} ){

}

What I can pass as argument to this function and what can I access inside this function please ?

class Test {
  static function main() {
    var s	= "foo";
    var n	= 1;
    var o	= {
			foo : "bar"
    }
    foo( s );	// OK
    foo( o );	// OK
    foo( n );	// Error
  }
  
  static function foo( o : {} ){ }
}

This is the first I’ve seen o:{} but it should behave similar to Any, as its a type with no fields. It’s worth noting that Any was added to haxe much later than Dynamic so {} may have been used before it was available

I still don’t understand the { } type as shown in the example I posted. Why “1” is not accepted ?
If no field access, so what the difference with Any at the end ?

The best way to think of the difference is that Dynamic is an assumed type, while Any is an explicit type that the compiler detects and reads as a real type that can be inferred.

For example, your { } with field members in an “anonymous structure” is of a Dynamic type and can be assigned to such. This is legal and the Haxe compiler accepts this.

The Any type only accepts types that are known at compile-time, not runtime, but accepts “Any” type, like a Float, an Int, any other class, etc.

The example you gave when assigning “1” to what is basically an anonymous structure. Obviously, that isn’t going to work because Haxe expects an object, not an Integer value. A string works I suspect because strings are internally recognised as a series of characters rather than a single value, like with the integer, so I suspect that the {} type declaration for the o variable accepts a string due to it being a series of characters (hence an object assigned to an anonymous structure), although I’d be curious what a trace result on that would do.

Also, to note, Dynamic isn’t a particularly useful type these days. Dynamic is only really useful in scenarios where you do not know the type at compile-time, like if you are reading a structure retrieved through a REST API, or you are unsure of the compile-time type when creating extern classes and their respective field members.

This is where Reflect comes in handy.

Most of the time, since the addition of the Any time, it is more beneficial to use this as supposed to Dynamic when you know the compile-time type.

Thanks Luke for your answer !

I think I’ve understood the difference between Dynamic and Any, it’s ok. And even my explanation " Any is a type like Dynamic but without field access" is not good : It accepts it but you will not be able to access any property because compiler just doesn’t know about what it is “inside”. It’s a type and that’s it, do with it.

Coming to the { } type I still wanted a more precise definition (especially when to use it and for what exactly), with maybe an example.
When you “trace” the { } type at compiler time it just tells “{ }” :
Here you can maybe see where from comes my confusion with the { } type : Try Haxe !

class Test {
  static function main() {
    var a : {} = "foo";
    $type( a );	// Warning : { }
    var o : { name : String }	= "foo";	// Error : String should be { name : String }
    $type( o );	// Warning : { name : String }
  }
}

And here is a last “whole” example of my try to understand the precise function of the { } type : Try Haxe !
Because for now I just think like you, it accepts all types, it’s just like Any but not "basic ones like Int, Bool and so on. Is it a good definition ?

class A {
	public static var foo = "";

	public function new() {}
}

class Test {
	static function main() {
		var o = {
			foo: "bar"
		}
		test("foo");
		test(1);
		test(o);
		test(A);
		test(new A());

		test2("foo");
		test2(o);
		test2(A);
		test2(new A());
		test2(1);                 // Error : Int should be { }
	}

	static function test(o:Any) {
		trace(o);
	}

	static function test2(o:{}) {
		trace(o);
	}
}

If someone can enlight me a bit more about this { } type it would be cool.

The { } is not a Type itself, it’s an expression which represents an anonymous structure.

Let’s take a better example:

typedef NamedStructure = {
    var name:String;
}

The above is just like the {} expression, except now we are telling the Haxe compiler that our object can be identified with a given name, which is NamedStructure.

The same code in an anonymous structure form is simply:

var someObject:Dynamic = {
    name: "My Name",
}

var named:NamedStructure = {
    name: "My Name",
};

Both of these examples will compile, except one is known at compile-time while the other isn’t. Dynamic takes any expression, including objects, which are not known at compile-time.

The issue with a typedef is that you have to set all defaults, because default values are not inferred by the compiler. However, on some targets, there are performance benefits to using a typedef structure versus a real class.

To solve the default values issue with a typedef, you can assign @:optional to its member variables:

typedef NamedStructure = {
    var name:String;
    @:optional var type:Int;
}

Similar to the NamedStructure example above, your test2 function is asking for a structure (or technically an empty structure), but this doesn’t make sense because what are you going to do with an empty structure?

Taking the example above, if we were to instead require specific types:

static function test2(o:{ name: String })
{

}

Now the Haxe compiler will require the object being passed in to be a structure taking the name variable and assigning something to it. The empty structure case is not a real type, so I think Haxe may just be guessing it’s type or inferring the type in a way that is preventing certain values from being accepted.

Thanks again Luke,

Yes I know the about Dynamic, anonymous structure and typedef, even you can write both :

typedef NamedStructure = {
    var name:String;
    @:optional var type:Int;
}

And

typedef NamedStructure = {
    name : String,
    ?type : Int;
}

And I think it’s the same (please correct me if there is a difference)

But sorry, I ask about the { } type seeing sometimes things like that (here in the official haxe.Serializer for example) : https://github.com/HaxeFoundation/haxe/blob/177dbe330dc3fea7e1bc26ff3a2fc371bebd832e/std/haxe/Serializer.hx#L211-L217

function serializeFields(v:{}) {
	for (f in Reflect.fields(v)) {
		serializeString(f);
		serialize(Reflect.field(v, f));
	}
	buf.add("g");
}

Here you can pass anything, almost like Dynamic, so why this one and not Dynamic ?
And it’s not the only one cas I see, that’s why I just ask to clarify what is it for, why this one ?

Is the only one difference about not being able to get and set a field through the dot notation ? AND not be able to pass a “basic” type like Int or Bool (so typically here using Reflect.fields to ensure that it’s a type with fields right ? )

In fact I think I begin understanding, {} is a type that “unify” any type that “has fields”. Try Haxe !

class A {
  public var name :String;
  public function new(){}
}

class Test {
  static function main() {
    var o = {
			name : "foo"
    }
    var o2 : Dynamic = {
      namy : "foo"
    }
        
    var o3 = {}
    var a = new A();
	foo( o );	// ok
    foo( o2 );	// ok Dynamic unify with this because it "maybe" has this field "name" ?
    foo( a );	// ok
    foo( o3 );	// Error
  }
  
  static function foo( o : { name : String } ){}
}

Here all that compiler permits unify with "having field “name”.
And { } type, unify all types that have fields but not naming a special field… Something like that.