For the purpose of this discussion, the only state changing method it supports is 'appendByte()' which returns true if one byte was appended and there was sufficient capacity and false otherwise:
// For discussion purposes only
FixedBuffer buffer(256*1024); // capacity 256Kb from heap initial size 0
while (more work) {
u8 byte = ...
// append byte provided we have spare capacity
assert(buffer.appendByte(byte)); // size increases by 1; capacity decreases by 1
}
The private memory region managed by buffer is not leaked to callers.Inevitably we get to place where this simplicity is silly. If we want to compress this memory or write it to a disk file on any conventional library or OS we'll have to cough up FixedBuffer's private data pointer. That'll mean FixedBuffer will have to leak state, for example, through the data() method:
// Leaky
compress(buffer.data(), buffer.size(), ...
If we absolutely did NOT want to cough up the pointer, what are our options?We use FixedBuffer's iterator which delivers buffer values byte by byte without leaking an address and try to muddle through wrapping compress/write outside FixedBuffer. This has low to zero probability of success. For example, naively writing 1 byte per system write call is not smart.
We could extend FixedBuffer making CompressableFixedBuffer or DiskWriterFixedBuffer and move the read/compress calls inside with class methods that wrap them but where we can leverage our private state smartly.
Extension and encapsulation have good provenance. But it also means developers need to do a bunch of busy work. The probability of extension --- again where we are heavily emphasizing not leaking --- is practically unbounded. Whenever developers have composable things ... well there are pros and cons as always.
Now given unbounded resources what would happen if we made a language supporting clean call injection through argument completion?
// Classic leaky
u64 rc write(fid, buffer.data(), buffer.size()); // buffer is type FixedBuffer
// Classic non leaky
u64 rc = buffer.write(fid); // buffer is type DiskWriterFixedBuffer
versus: // buffer is type FixedBuffer
u64 rc = orchestrate(write, fid, buffer, writeComletion);
where 'orchestrate' is a language key word having the following meaning for the compiler:* In argument 1 we tell the compiler we want to call 'write'. The compiler knows its declaration
* In argument 2 we provide the first of the 3 arguments 'write' requires
* In argument 3 we provide a object reference to our buffer
* In argument 4 we provide a method on 'buffer' that provides the remaining arguments 'write' needs, for example, through new language keyword push. Here the key point is the buffer's address is provided to write, and it's not leaked to the caller.
Pros:
* There's no need to extend FixedBuffer e.g. CompressableFixedBuffer, DiskWriterFixedBuffer, CompressableDiskWriterFixedBufer and so on
* Callers cannot get direct access to the private memory region; there is no 'buffer.data()'
* This orchestration technique is practical because it requires no changes in write, compress e.g. pervasive, large, and hard to change APIs
Cons:
* The buffer's private address is still leaked on the stack to the 'write' call. And while OS 'write' cannot be easily leveraged to corrupt buffer (being in the OS), an adversary could trivially write a library function with 'writes' declaration and still steal the address.
Is another approach through hw