# Mutable struct without fields behaving weirdly

**URL:** <https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752>\
**Category:** General Usage\
**Created:** [February 1, 2018, 7:11pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752 "2018-02-01T19:11:24Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [February 1, 2018, 7:11pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/1 "2018-02-01T19:11:24Z")

</div>

```julia
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.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [February 1, 2018, 7:52pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/2 "2018-02-01T19:52:25Z")

</div>

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.

@vtjnash similarly suggested that mutable singletons should allocate unique objects in: [https://github.com/JuliaLang/julia/issues/17149](https://github.com/JuliaLang/julia/issues/17149)  
but no one has gotten around to working on it.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [February 1, 2018, 8:25pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/3 "2018-02-01T20:25:07Z")

</div>

Thanks! I apologize for failing yet again at finding the answer in the issue tracker.

Additional bonus for the unique objects: They can have finalizers.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [February 1, 2018, 8:33pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/4 "2018-02-01T20:33:41Z")

</div>

> [@foobar\_lv2](#):
>
> I apologize for failing yet again at finding the answer in the issue tracker.

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.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [February 2, 2018, 2:59pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/5 "2018-02-02T14:59:34Z")

</div>

This has now been changed on master:

[https://github.com/JuliaLang/julia/pull/25854](https://github.com/JuliaLang/julia/pull/25854)

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [February 2, 2018, 3:09pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/6 "2018-02-02T15:09:04Z")

</div>

Nice! 👍

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [February 2, 2018, 3:16pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/7 "2018-02-02T15:16:32Z")

</div>

In this way empty mutable structs can be used as unique IDs?

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [February 2, 2018, 3:19pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/8 "2018-02-02T15:19:44Z")

</div>

Yes, exactly. Immutable structs with no fields are still singletons.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [February 2, 2018, 3:28pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/9 "2018-02-02T15:28:28Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [February 2, 2018, 3:44pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/10 "2018-02-02T15:44:56Z")

</div>

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!

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [February 2, 2018, 4:36pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/11 "2018-02-02T16:36:05Z")

</div>

Large part of my enthusiasm was due to the fact that in Julia 0.6 creating and comparing empty mutable structs is very fast:

```julia
julia> using BenchmarkTools

julia> mutable struct Foo end

julia> @benchmark Foo()
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 1.491 ns (0.00% GC)
  median time: 1.782 ns (0.00% GC)
  mean time: 1.759 ns (0.00% GC)
  maximum time: 19.624 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 1000

julia> a = Foo(); b = Foo();

julia> @benchmark $a == $b
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 0.014 ns (0.00% GC)
  median time: 0.021 ns (0.00% GC)
  mean time: 0.022 ns (0.00% GC)
  maximum time: 0.047 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 1000

```

However now on master the situation is much different:

```julia
julia> using BenchmarkTools

julia> mutable struct Foo end

julia> @benchmark Foo()
BenchmarkTools.Trial: 
  memory estimate: 8 bytes
  allocs estimate: 1
  --------------
  minimum time: 4.452 ns (0.00% GC)
  median time: 6.701 ns (0.00% GC)
  mean time: 8.447 ns (25.23% GC)
  maximum time: 20.320 μs (99.96% GC)
  --------------
  samples: 10000
  evals/sample: 1000

julia> a = Foo(); b = Foo();

julia> @benchmark $a == $b
BenchmarkTools.Trial: 
  memory estimate: 0 bytes
  allocs estimate: 0
  --------------
  minimum time: 1.492 ns (0.00% GC)
  median time: 1.499 ns (0.00% GC)
  mean time: 1.538 ns (0.00% GC)
  maximum time: 28.399 ns (0.00% GC)
  --------------
  samples: 10000
  evals/sample: 1000

```

making the difference with random numbers negligible.

---

<div class="post-metadata">

**Author:** ![ssfrr](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ssfrr/32/3736_2.png) [@ssfrr](https://discourse.julialang.org/u/ssfrr)\
**Post date:** [February 2, 2018, 4:44pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/12 "2018-02-02T16:44:16Z")

</div>

does it make a difference if you run your benchmark within a function, rather than in global scope?

---

<div class="post-metadata">

**Author:** ![foobar\_lv2](https://avatars.discourse-cdn.com/v4/letter/f/ee59a6/32.png) [@foobar\_lv2](https://discourse.julialang.org/u/foobar_lv2)\
**Post date:** [February 2, 2018, 5:18pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/13 "2018-02-02T17:18:10Z")

</div>

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).

Note that the new mutable empty struct allocates.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [February 2, 2018, 5:18pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/14 "2018-02-02T17:18:28Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![giordano](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/giordano/32/2166_2.png) [@giordano](https://discourse.julialang.org/u/giordano)\
**Post date:** [February 2, 2018, 5:38pm UTC](https://discourse.julialang.org/t/mutable-struct-without-fields-behaving-weirdly/8752/15 "2018-02-02T17:38:36Z")

</div>

> [@ssfrr](#):
>
> does it make a difference if you run your benchmark within a function, rather than in global scope?

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.

> [@foobar\_lv2](#):
>
> If your code on 0.6 or 0.7 used mutable empty structs before this update then your code was sub-optimal

No, as I told above I’m currently using plain numbers.

> [@foobar\_lv2](#):
>
> Note that the new mutable empty struct allocates.

Yes, noted.
