I need to create a “subclass” of StringStream, so that I can add a field to it and associate a value with the stream:
type
ConnectionStream* = ref object of StringStreamObj
conn: Connection
StringStream instances require initialization, but how am I supposed to make that happen in my ConnectionStream? The stream module has no init(StringStream) proc, only newStringStream() that creates and returns a StringStream.
The only way I can think of is a kludge: create a ConnectionStream, then call newStringStream and copy all the fields from the latter to the former. 😬
Why am I subclassing StringStream? I’m using the msgpack4nim package, and implementing custom serialization for a custom type. This means implementing a pack_type proc whose parameters are a stream and my custom type. That proc needs access to some external data associated with the whole serialization task, so I want to be able to cast the stream parameter to ConnectionStream and access it from that.
Ironically, this same problem seems to have come up in the implementation of msgpack4nim: it has a custom stream type MsgStream that associates an EncodingMode with the stream. But MsgStream isn’t a subclass of Stream, it’s just a wholly separate object type whose implementation seems to copy-and-paste part of StringStream’s implementation. And because of that, the package’s API has to make all the functions interacting with streams generic so that they can work with either Stream or MsgStream.
(I’m guessing that Nim’s Stream module predates language support for methods? The implementation uses a bunch of callback functions that get populated by the subclasses; that’s pretty much how you’d implement dynamic method dispatch if you didn’t have it in your language. Nowadays it would make the module a lot cleaner and simpler to use methods, but that would break compatibility with old code…)
For posterity, here’s how to initialize a StringStream subclass. It’s a hack, but it works, and I don’t believe it’s possible to do any better without API changes in the streams package.
type
MyStreamClass* = object of StringStreamObj
myData: int
MyStream* = ref MyStreamClass
proc newMyStream* (input: sink string ="", myData: int) : MyStream =
result = MyStream(myData: myData)
# HACK: Initialize MyStream's StringStream base by copying over an initialized
# StringStream. Necessary bc there is no `initStringStream(var StringStream)` function.
var ss = newStringStream(input)
result.data = ss.data
result.closeImpl = ss.closeImpl
result.atEndImpl = ss.atEndImpl
result.setPositionImpl = ss.setPositionImpl
result.getPositionImpl = ss.getPositionImpl
result.readDataStrImpl = ss.readDataStrImpl
result.readDataImpl = ss.readDataImpl
result.peekDataImpl = ss.peekDataImpl
result.writeDataImpl = ss.writeDataImpl