0

What are the pros/cons of having a serialization function accept a sink:

class Foo:
  def toString(self, stringIOSink):
    pass

  def toBytes(self, byteSink):
    pass

versus having the serialisation function return the result:

class Foo:
  def toString(self):
    # ..
    return s

  def toBytes(self):
    # ..
    return bytes

(obviously this approach is the standard when serialising to string)

I've seen both in the wild and up until now have never given it much thought. Note my examples are in python, but I'm asking in a language agnostic context.

Considerations I've thought of:

accepting a sink

  • implementation does not need to allocate memory (and thus client does not need to worry about how memory is managed)
  • can stream output

returning result

  • arguably simpler
  • allows you to further manipulate/use the returned result. EG if we are serializing to JSON (similar to Python's JSON decoding operation), then if Foo holds a list of Bar objects, then we can implement Foo's toJSON method by:
def toJSON(self):
    return {
        #  other members here
        "bars": [b.toJSON() for b in self.bars],
    }

Which wouldn't be possible if toJSON accepted some sort of JSON sink.

  • As you already noted, both have their merits. If you have a choice between the options, try to decide based on expected use cases and data volume. However, in many cases you will simply have to accept a decision others made, for example, in python __repr__() returns a string, you don't have a choice. – Hans-Martin Mosner Jul 3 '19 at 9:14
0

Both aren't "restricting" the user of such API.
In the first case, I can provide a buffer ("sink") to copy the serialized output to, and use it as in the second case.
In the second case, I can just provide the returning result to the sink.

This is from a strictly usability perspective.

You suggest in your question that there is a "leaky abstraction" problem here:
- Memory may be allocated and copied to & from for a temporary object, which may affect performance (if the compiler/JIT can't inline/"see it").
- Providing the second option is restricting/underperforming for objects that their serialized output shall be streamed.

In that case, it's up-to the class writer to provide one/both ways depending on the class usage:
- If it's a simple serialization function for a small output (that can be easily inlined), second choice is simple & enough.
- If it's a performant class with a complex serialization function and an output that will probably be streamed, first choice is more appropriate.

*However, the class writer can always write both of the implementations (with the second one using the first one) - and it'll be the most flexible.

0

When you're serializing to a string, I would err towards just returning the string. Manipulating the result I wouldn't consider a pro. Being able to support more usecases more easily seems like a bigger win.

The bigger benefit for the sink is when you're using non-string representations. Then you can stream and otherwise manipulate the sink to better accommodate the serialization. Deserialization especially can benefit since you can more easily vary on a version header and take advantage of random access to potentially not parse/decode the entire thing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.