Feature #16986


Anonymous Struct literal

Added by ko1 (Koichi Sasada) 12 months ago. Updated 6 months ago.

Target version:



How about introducing anonymous Struct literal such as ${a: 1, b: 2}?
It is almost the same as, :b).new(1, 2).



In many cases, people use hash objects to represent a set of values such as person = {name: "ko1", country: 'Japan'} and access its values through person[:name] and so on. It is not easy to write (three characters [:]!), and it easily introduces misspelling (person[:nama] doesn't raise an error).

If we make a Struct object by doing Person =, :age) and person ='ko1', 'Japan'), we can access its values through naturally. However, it costs coding. And in some cases, we don't want to name the class (such as Person).

Using OpenStruct (person = "ko1", country: "Japan")), we can access it through, but we can extend the fields unintentionally, and the performance is not good.

Of course, we can define a class Person with attr_readers. But it takes several lines.

To summarize the needs:

  • Easy to write
    • Doesn't require declaring the class
    • Accessible through format
  • Limited fields
  • Better performance


Introduce new literal syntax for an anonymous Struct such as: ${ a: 1, b: 2 }.
Similar to Hash syntax (with labels), but with $ prefix to distinguish.

Anonymous structs which have the same member in the same order share their class.

    s1 = ${a: 1, b: 2, c: 3}
    s2 = ${a: 1, b: 2, c: 3}
    assert s1 == s2

    s3 = ${a: 1, c: 3, b: 2}
    s4 = ${d: 4}

    assert_equal false, s1 == s3
    assert_equal false, s1 == s4


Unlike Hash literal syntax, this proposal only allows label: expr notation. No ${**h} syntax.
This is because if we allow to splat a Hash, it can be a vulnerability by splatting outer-input Hash.

Thanks to this spec, we can specify anonymous Struct classes at compile time.
We don't need to find or create Struct classes at runtime.




Matz said he thought about {|a: 1, b: 2 |} syntax.


Surprisingly, Hash is fast and Struct is slow.

Benchmark.driver do |r|
  r.prelude <<~PRELUDE
  st =, :b).new(1, 2)
  hs = {a: 1, b: 2}
  class C
    attr_reader :a, :b
    def initialize() = (@a = 1; @b = 2)
  ob =
  PRELUDE "ob.a" "hs[:a]" "st.a"
Warming up --------------------------------------
                ob.a    38.100M i/s -     38.142M times in 1.001101s (26.25ns/i, 76clocks/i)
              hs[:a]    37.845M i/s -     38.037M times in 1.005051s (26.42ns/i, 76clocks/i)
                st.a    33.348M i/s -     33.612M times in 1.007904s (29.99ns/i, 87clocks/i)
Calculating -------------------------------------
                ob.a    87.917M i/s -    114.300M times in 1.300085s (11.37ns/i, 33clocks/i)
              hs[:a]    85.504M i/s -    113.536M times in 1.327850s (11.70ns/i, 33clocks/i)
                st.a    61.337M i/s -    100.045M times in 1.631064s (16.30ns/i, 47clocks/i)
                ob.a:  87917391.4 i/s
              hs[:a]:  85503703.6 i/s - 1.03x  slower
                st.a:  61337463.3 i/s - 1.43x  slower

I believe we can speed up Struct similarly to ivar accesses, so we can improve the performance.

BTW, OpenStruct (os.a) is slow.

              hs[:a]:  92835317.7 i/s
                ob.a:  85865849.5 i/s - 1.08x  slower
                st.a:  53480417.5 i/s - 1.74x  slower
                os.a:  12541267.7 i/s - 7.40x  slower

For memory consumption, Struct is more lightweight because we don't need to keep the key names.


If we name an anonymous class, literals with the same members share the name.

s1 = ${a:1}
s2 = ${a:2}
p [s1, s2] #=> [#<struct a=1>, #<struct a=2>]
A = s1.class
p [s1, s2] #=> [#<struct A a=1>, #<struct A a=2>]

Maybe that is not a good behavior.


Also available in: Atom PDF