At todays developer discussion in Boston we discussed Structs, both in C and in Ruby. What are they? How should we use them. My notes follow.
Structs in C:
Structs in C are defined entirely by the memory offset of the fields that they contain. They are immutable and make for great value objects. The Objective-C APIs use them quite a bit (Point, CGRect, Origin, etc). Structs support accessing attributes using dot sytnax (rect.origin.x
) but this differs from the dot syntax recently added by Objective-C. On structs you are essentially hopping through memory pointers to access values whereas Objective-C dot syntax is message passing.
** Structs in Ruby:**
Structs in Ruby are nominally supposed to represent a similar concept: a container for a value. Equality ==
does behave as you would expect. They differ greatly in just about every other area, however. Consider the following:
Person = Struct.new(:user) do
def greet
"hello #{user.name}"
end
end
- Ruby allows methods to be defined on structs. See the
greet
method. - Ruby allows structs to be initialized with fewer than the number of arguments the struct was declared with. For instance, you could do
Person.new
and Ruby wouldnāt complain until you tried to callgreet
which would greet you with a sweetNoMethodError
. - Structs in Ruby are mutable. They have public attr accessors.
Person.new(@user).user = @another_user
is just fine by Ruby.
** Whatās an OpenStruct? **
An open struct is essentially syntactic sugar on top of a Hash. It gives you dot syntax access to the members of the hash. Consider:
derek = OpenStruct.new(name: 'Derek')
derek.name # => 'Derek'
derek.age # => nil
derek.age = 33
derek.age # => 33
Notice that my initial hash didnāt include an āageā property, yet my OpenStruct didnāt blow up when I tried to access it. Further, I was able to set it just fine and then access it. I can add to an OpenStruct whenever I please. We played with this some in pry, which showed that derek.age
is defined by OpenStruct as soon as a setter is called. Essentially, itās handling method_missing
for any setter methods and creating them on the fly. This is why OpenStructs (creating and adding methods to) clear Rubyās method cache. Thatās
probably not too important in the world of Rails, but itās something to be aware of.
** What are Ruby structs good for? **
No one really wanted to speak in favor of Structs. Creating a struct is arguably more convenient than creating a class because you get initialize
for free. As we discussed earlier, though, that initialize
isnāt very picky.
Weāve seen Structs for delegators and value object. Delegators may be better served with SimpleDelegator
. If youād prefer to be more explicit about your API, consider just writing a class. Structs arenāt a great fit for any sort of enforceable value object for the reasons we discussed. Consider something like the values gem for these.
We also commonly see people inherit from Struct.new
. That is: class Person < Struct.new(:user)
. This adds an anonymous class to your inheritence tree with no advantage any of us could see. If youāre going to use a Struct, pass
it a block instead.
If youāre scripting, Struct and OpenStruct can be very handy. Use them as you see fit there, but in your application code? Just write initialize. That was our basic conclusion.