i’m having a really hard time getting ByteBuffer
to get along with my RangeReplaceableCollection
-based serializers.
basically, i have an Output
type that looks like:
// module: 'BSON', does not have 'NIOCore' dependency
extension BSON
{
@frozen public
struct Output<Destination>
where Destination:RangeReplaceableCollection<UInt8>
{
public
var destination:Destination
@inlinable public
init(capacity:Int)
{
self.destination = .init()
self.destination.reserveCapacity(capacity)
}
}
}
extension BSON.Output
{
/// Appends a single byte to the output destination.
@inlinable public mutating
func append(_ byte:UInt8)
{
self.destination.append(byte)
}
/// Appends a sequence of bytes to the output destination.
@inlinable public mutating
func append(_ bytes:some Sequence<UInt8>)
{
self.destination.append(contentsOf: bytes)
}
}
and everything else is built around that Output
serializer. for example:
// module: 'MongoWireProtocol', does not have `NIOCore` dependency
import BSON
extension BSON.Output
{
@inlinable public mutating
func serialize(message:Mongo.Message<some RandomAccessCollection<UInt8>>)
{
...
}
}
this works with Array
, ArraySlice
, etc.
now i want to do:
import BSON
import MongoWireProtocol
import NIOCore
var output:BSON.Output<ByteBuffer> = .init(capacity: message.size)
output.serialize(message: message)
self.wrapOutboundOut(output.destination)
but i cannot do that, because as we all know, ByteBuffer
is not a RangeReplaceableCollection<UInt8>
.
on the other hand, ByteBufferView
is range-replaceable. but i also cannot do:
var output:BSON.Output<ByteBufferView> = .init(capacity: message.size)
output.serialize(message: message)
self.wrapOutboundOut(output.destination._buffer)
because ByteBufferView._buffer
is internal. and it also ignores the size hint, because ByteBufferView
has no witness for RangeReplaceableCollection.reserveCapacity(_:)
.
i’m not challenging whether or not ByteBuffer
ought to be range-replaceable or not, and i’m not criticizing that design choice. if we are being honest the real problem here is RangeReplaceableCollection
is way too aggressive in its requirements, we really only need the ability to append to the end of the collection.
protocol AppendableCollection:Collection
{
public mutating
func append(_ element:Element)
public mutating
func append(contentsOf elements:some Sequence<Element>)
}
protocol RangeReplaceableCollection:AppendableCollection
{
}
but this isn’t practical because:
-
AppendableCollection
doesn’t belong inBSON
, or any library (besides the standard library) for that matter. -
even is
BSON
started vendingAppendableCollection
, it can’t makeRangeReplaceableCollection
inherit from it. (and i assume the standard library can’t either, because ABI) -
code importing
BSON
would have to retroactively conform types likeArray
,ArraySlice
, etc. toAppendableCollection
, which runs afoul of the “never extend another module’s type to conform to another module’s protocol” principle.
at this point, i am thinking my best bet is to write a wrapper around ByteBuffer
that is a RangeReplaceableCollection
, and then:
-
copy-and-paste the
replaceSubrange(_:with:)
implementation fromByteBufferView
, and -
add real, not-defaulted witnesses for
reserveCapacity(_:)
,append(_:)
, andappend(contentsOf:)
(usingByteBuffer.writeBytes(_:)
) so thatBSON.Output
can actually write to it efficiently.
but of course, i am not eager to re-invent ByteBufferView
, so i’m wondering if there’s a better way to go about all of this.