struct foo end
mutable struct bar end
bar()==bar()
#true
bar()===bar()
#true
isbits(bar)
#false
A=Vector{Tuple{bar, Int}}(uninitialized, 1)
#1-element Array{Tuple{bar,Int64},1}:
#undef
A=Vector{Tuple{foo, Int}}(uninitialized, 1)
#1-element Array{Tuple{foo,Int64},1}:
# (foo(), 140167614418144)
What did I expect? Either disallow mutable struct without fields; or make it synonymous to the immutable version; or, much more interesting, make it so that each bar() is distinct under ===. So each call to bar() would basically create a new object id; old IDs can be copied, but IDs will never collide.
With the current behavior, I would think that empty mutable structs make no sense at all.
This is called a “singleton” type. What you are seeing is probably a holdover performance optimization from the early days when Julia did not have immutable types, so “mutable” singletons had to be fast.
Don’t worry, GitHub issue search is notoriously hard. This question led to a good discussion on today’s triage call and a decision to change this behavior. There’s a historical reason why it is the way it is: having zero-size singleton types is really useful, and we originally didn’t have immutable struct types, so the obvious approach was for zero-field structs to be singletons. Now that we’ve got mutable and immutable structs, it makes sense for the mutable ones to be unique and the immutable ones to be singletons.
Wow, that’s interesting! In a package of mines I’m generating random numbers to get unique identifiers, but this seems a much better solution.
I have to understand if I need to ever visualize the ID: a number is much more human-readable, and it’s pretty hard to distinguish between Foo() and Foo(), only Julia knows they’re different guys. If I don’t need to show the ID to the user, this would be perfect.
I think you could just show pointer_from_objref for display (the pointer can be reused only after the old object has been reclaimed, so this preserves identity for objects with overlapping lifetimes). But since the new mutable empty struct is afaik not bitstype you might need to take care about possible performance losses compared to just a 128 bit random number (or whatever kind of crypto nonce you could use). But they can carry finalizers, so yay!
Disclaimer: I haven’t tested this yet, not slacking off, code is compiling.
Pre this update, comparing empty structs (mutable or not) is a NOP if types are inferred (it is literally free and the answer is, they are equal if and only if they have the same type). Post this update, comparing empty immutable structs is still free, and comparing empty mutable structs is exactly as expensive as comparing a pointer or an Int64.
You can maybe expect a speedup for comparison over e.g. 256 bit nonces if you are very paranoid about collisions of random numbers.
Pre this update, mutable empty structs made no sense at all (immutable empty structs were always better) and were a historical leftover. Post this update, mutable empty structs now serve a function different from immutable empty structs, and there was nothing before serving this new function (nonce). GC controlled and finalizable nonces are kinda niche, but this update looks “more coherent” in the sense that default equality for user-defined mutable objects never looks at the fields and only compares pointers; except for the former special case of mutable empty structs.
If your code on 0.6 or 0.7 used mutable empty structs before this update then your code was sub-optimal (could be improved by switching to the semantically equivalent but sometimes more performant immutable variant, because the old mutable empty struct infected composites with non-bitstype-ness for no conceivable upside).
Also, previously the comparison was a no-op and now it’s an op. Does that factor into what you’re seeing? The compiler is pretty good at just not doing things that it knows aren’t necessary.
Not at all. I used interpolation just to avoid problems with global scope. As suggested by foobar_lv2 and StefanKarpinski the key here is that operations with empty mutable structs were no-op before.
No, as I told above I’m currently using plain numbers.