I’m wondering about the proper ‘contract’ or best practice for using IO objects in Ruby.
I have a bunch of helper methods that pass around IO objects. Currently, for my low level methods that consume IO objects, I make sure to rewind them after doing a read. This feels good; it allows me to call these methods multiple times without worrying about modifying the IO object.
Here are some possibilities for the contract:
-
As described above… If a method calls
readon anIOobject — or reads from it in another way — then it shouldrewindit when done. This effectively restores theIOobject to its original state. This is motivated by the Ruby convention of not modifying an argument. -
The calling method should know that the low level method is calling
readand adjust accordingly. (Stated another way: consumer beware!)
Which, if any, is best? Why?
(A quick side note: io_copy = io.dup does not function the same as io.rewind.)
And… you deserve bonus points if you talk about thread safety!
It’s normal not to rewind IO objects, mostly because you can’t guarantee that an IO object is rewindable at all: files are, but pipes and sockets aren’t. So the usual assumption if I pass an IO object is that it will get read (or written to) by the method, and wind up in a modified state. Since it logically represents a stream rather than a state in the first place, that’s OK.
Thread safety doesn’t really affect this decision one way or the other, except that it’s a lot harder to maintain the illusion of an ‘unmodified’ IO object if multiple threads are accessing it. Even if you’re not trying to maintain that illusion, if it’s shared between threads and you’re going to read, write, rewind, or do anything else to modify its internal state, you need to do that while holding an exclusive lock on the object.