Do whatever works for you, for me these “rules” tend to be convenient:
- Pass an allocator when the caller cannot know the required buffer size ahead of time, whether you do this with explicit arg or default arg it’s up to you, the former isn’t much different then passing a buffer explicitly.
- Pass a buffer when the caller dictates how much data you give back.
- Return a type with an Arena inside when every field must be dropped at once (e.g. graph, string heavy struct).
- Forget scratch buffers, take a temp allocator.
- Sometimes you just want a custom iterator and not allocate at all.