value_struct: Read-only structs in Ruby
Ruby’s structs are one of my favorite data types in Ruby. They help you to keep some defined structure in the dynamic world of Ruby. Often, it makes sense to use them instead of hashes or arrays. Read-only structs take the idea a level further.
Ruby’s Struct
class
The Struct
class is available in the Ruby core library, which means, you don’t have to require it, it’s just there:
Point = Struct.new :x, :y
# Point is now a new Ruby class that has getters and setters for x and y.
p = Point.new(1,2)
# Creates an instance
p.x #=> 1
p.x = 0 #=> 0
You can also subclass these struct classes or just pass a block in which you define additional methods for your struct class. Structs are implemented in C and are usually faster than hashes. The values get stored in special instance variables that you cannot access from Ruby land, instead, you have to use the getter and setter methods. You can read more about structs in these resources.
Immutable structs
Having immutable data structures is a matter of taste. However, you get the following advantages:
- It’s a concept/aesthetic. You can rely that nobody will ever change the value of a field
- Immutable structs can be thought of as value objects: Small objects that represent a specific kind of data
- Thread safeness
Value structs
value_struct is a small implementation of immutable structs. They behave like structs, except for the last statement in the previous example: They don’t have setters:
require 'value_struct'
Point = ValueStruct.new :x, :y
p = Point.new(1,2)
p.x # => 1
p.x = 0 # NoMethodError
Implementation
The implementation is pretty simple. The first version of the gem looked like that:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class ValueStruct < Struct VERSION = '0.2.0' undef []= def initialize(*args, &block) super(*args, &block) members.each{ |member| instance_eval{ undef :"#{member}=" } } end def inspect super.to_s.sub('struct', 'ValueStruct') end end
However, the current code differs for easy extendibility and performance.
Benchmarks
Usually, benchmarks don’t play a big role in the Ruby community, however, for such a basic class, which is intended to be used for a lot of objects, little overheads can get noticeable. That’s why I did a benchmark that just created many structs and accessed their fields:
Struct: 2.490000 0.000000 2.490000 ( 2.506390)
Value: 11.110000 0.010000 11.120000 ( 11.161834)
ImmutableStruct: 3.360000 0.020000 3.380000 ( 3.396900)
ValueStruct: 2.560000 0.020000 2.580000 ( 2.590096)
[Hash]: 3.710000 0.010000 3.720000 ( 3.733843)
You can find the benchmark file at spec/benchmark.rb
Mixin API
The value_struct gem provides optional mixins that provide useful functionality for working with (value) structs. For example, you can extend the #dup
method to take a hash as parameter for directly changing values in the new copy. Other mixins include a #to_h
method(which will come in 2.0 anyway), auto freezing of new instances or a strict check if new instances get initialized with the correct amount of arguments.
Use (value) structs now!
Normal structs are already available in your Ruby environment.
To get value structs, visit the github page and do:
$ gem install value_struct