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