class IO::Buffer
IO::Buffer is the common byte-view interface. Its concrete subclasses are IO::Buffer::Storage, which manages backing storage, and IO::Buffer::Slice, which represents a parent-relative view. Both are IO::Buffer instances.
IO::Buffer.new, IO::Buffer.for, and IO::Buffer.map create Storage objects. The abstract base itself cannot be allocated directly. Each factory creates one storage-bearing object, with no additional view needed for byte access.
Storage#resize may reallocate; Storage#free and Storage#transfer manage the backing storage. Slice#resize and Slice#advance only change a view, and Slice never acquires its own storage. Duplicating Storage copies its bytes; duplicating a Slice copies its range and source reference without copying bytes.
IO::Buffer is an efficient zero-copy buffer for input/output. There are typical use cases:
-
Create an empty buffer with
::new, fill it with buffer usingcopyorset_value,set_string, get buffer withget_stringor write it directly to some file withwrite. -
Create a buffer mapped to some string with
::for, then it could be used both for reading withget_stringorget_value, and writing (writing will change the source string, too). -
Create a buffer mapped to some file with
::map, then it could be used for reading and writing the underlying file. -
Create a string of a fixed size with
::string, thenreadinto it, or modify it usingset_value.
Interaction with string and file memory is performed by efficient low-level C mechanisms like memcpy.
The class is meant to be an utility for implementing more high-level mechanisms like Fiber::Scheduler#io_read and Fiber::Scheduler#io_write and parsing binary protocols.
MemoryView Support
IO::Buffer supports the C-level MemoryView protocol, so C extensions can use +rb_memory_view_get()+ to access the buffer’s memory directly (zero-copy) as a 1-dimensional contiguous array of bytes. The memory view is writable if the buffer is not readonly? and RUBY_MEMORY_VIEW_WRITABLE is specified.
While a MemoryView is exported, the buffer is locked.
Examples of Usage
Empty buffer:
buffer = IO::Buffer.new(8) # create empty 8-byte buffer # => # #<IO::Buffer 0x0000555f5d1a5c50+8 INTERNAL> # ... buffer # => # <IO::Buffer 0x0000555f5d156ab0+8 INTERNAL> # 0x00000000 00 00 00 00 00 00 00 00 buffer.set_string('test', 2) # put there bytes of the "test" string, starting from offset 2 # => 4 buffer.get_string # get the result # => "\x00\x00test\x00\x00"
Buffer from string:
string = 'data' IO::Buffer.for(string) do |buffer| buffer # => # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> # 0x00000000 64 61 74 61 data buffer.get_string(2) # read content starting from offset 2 # => "ta" buffer.set_string('---', 1) # write content, starting from offset 1 # => 3 buffer # => # #<IO::Buffer 0x00007f3f02be9b18+4 SLICE> # 0x00000000 64 2d 2d 2d d--- string # original string changed, too # => "d---" end
Buffer from file:
File.write('test.txt', 'test data') # => 9 buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) # => # #<IO::Buffer 0x00007f3f0768c000+9 EXTERNAL MAPPED FILE SHARED READONLY> # ... buffer.get_string(5, 2) # read 2 bytes, starting from offset 5 # => "da" buffer.set_string('---', 1) # attempt to write # in `set_string': Buffer is not writable! (IO::Buffer::AccessError) # To create writable file-mapped buffer # Open file for read-write, pass size, offset, and flags=0 buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 9, 0, 0) buffer.set_string('---', 1) # => 3 -- bytes written File.read('test.txt') # => "t--- data"
The class is experimental and the interface is subject to change, this is especially true of file mappings which may be removed entirely in the future.
Constants
- BIG_ENDIAN
-
Refers to big endian byte order, where the most significant byte is stored first. See
get_valuefor more details. - DEFAULT_SIZE
-
The default buffer size, typically a (small) multiple of the
PAGE_SIZE. Can be explicitly specified by setting the RUBY_IO_BUFFER_DEFAULT_SIZE environment variable. - EXTERNAL
-
Indicates that the memory in the buffer is owned by someone else. See #external? for more details.
- HOST_ENDIAN
-
Refers to the byte order of the host machine. See
get_valuefor more details. - INTERNAL
-
Indicates that the memory in the buffer is owned by the buffer. See #internal? for more details.
- LITTLE_ENDIAN
-
Refers to little endian byte order, where the least significant byte is stored first. See
get_valuefor more details. - MAPPED
-
Indicates that the memory in the buffer is mapped by the operating system. See #mapped? for more details.
- MAP_ALIGNMENT
-
The alignment required for file mapping offsets. Mapping sizes do not need to be aligned.
- NETWORK_ENDIAN
-
Refers to network byte order, which is the same as big endian. See
get_valuefor more details. - PAGE_SIZE
-
The operating system page size. Used for efficient page-aligned memory allocations.
- PRIVATE
-
Indicates that the memory in the buffer is mapped privately and changes won’t be replicated to the underlying file. See #private? for more details.
- READONLY
-
Indicates that the memory in the buffer is read only, and attempts to modify it will fail. See
readonly?for more details. - SHARED
-
Indicates that the memory in the buffer is also mapped such that it can be shared with other processes. See #shared? for more details.
- VERSION
-
The
IO::Bufferinterface version.
Public Class Methods
(String) → Buffer
Source
VALUE
rb_io_buffer_type_for(VALUE klass, VALUE string)
{
klass = io_buffer_storage_class(klass);
StringValue(string);
// If the string is frozen, both code paths are okay.
// If the string is not frozen, if a block is not given, it must be frozen.
if (rb_block_given_p()) {
struct io_buffer_for_yield_instance_arguments arguments = {
.klass = klass,
.string = string,
.instance = Qnil,
.flags = 0,
};
return rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments);
}
else {
// Use a Ruby-visible frozen snapshot as the backing source. A hidden
// temporary frozen String cannot be returned by IO::Buffer#source.
string = rb_str_new_frozen(string);
return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY);
}
}
Creates a zero-copy IO::Buffer from the given string’s memory. Without a block, a frozen snapshot of the string is used as the buffer source, so later changes to the original string do not affect the buffer. When a block is provided, the buffer is associated directly with the string’s internal buffer and updating the buffer will update the string.
In the block form, the string is locked and cannot be modified while the block is executing.
If the string is frozen, it will create a read-only buffer which cannot be modified. If the string is shared, it may trigger a copy-on-write when using the block form.
string = 'test' buffer = IO::Buffer.for(string) buffer.external? #=> true buffer.get_string(0, 1) # => "t" string # => "test" buffer.resize(100) # in `resize': Cannot resize external buffer! (IO::Buffer::AccessError) IO::Buffer.for(string) do |buffer| buffer.set_string("T") string # => "Test" end
static VALUE
io_buffer_map(int argc, VALUE *argv, VALUE klass)
{
klass = io_buffer_storage_class(klass);
rb_check_arity(argc, 1, 4);
// We might like to handle a string path?
VALUE io = argv[0];
rb_off_t file_size = rb_file_size(io);
// Compiler can confirm that we handled file_size <= 0 case:
if (UNLIKELY(file_size <= 0)) {
rb_raise(rb_eArgError, "Invalid negative or zero file size!");
}
// Here, we assume that file_size is positive:
else if (UNLIKELY((uintmax_t)file_size > SIZE_MAX)) {
rb_raise(rb_eArgError, "File larger than address space!");
}
size_t size;
if (argc >= 2 && !RB_NIL_P(argv[1])) {
size = io_buffer_extract_size(argv[1]);
if (UNLIKELY(size == 0)) {
rb_raise(rb_eArgError, "Size can't be zero!");
}
if (UNLIKELY(size > (size_t)file_size)) {
rb_raise(rb_eArgError,
"Size (%" PRIuSIZE ") can't be larger than "
"file size (%" PRIuSIZE ")",
size,
(size_t)file_size);
}
}
else {
// This conversion should be safe:
size = (size_t)file_size;
}
// This is the file offset, not the buffer offset:
rb_off_t offset = 0;
if (argc >= 3) {
offset = NUM2OFFT(argv[2]);
if (UNLIKELY(offset < 0)) {
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be negative!",
argv[2]);
}
if (UNLIKELY(offset >= file_size)) {
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be larger than "
"file size (%" PRIuSIZE ")",
argv[2],
(size_t)file_size);
}
if (RB_NIL_P(argv[1])) {
// Decrease size if it's set from the actual file size:
size = (size_t)(file_size - offset);
}
else if (UNLIKELY((size_t)(file_size - offset) < size)) {
size_t maximum_offset =
((size_t)file_size - size) / RUBY_IO_BUFFER_MAP_ALIGNMENT *
RUBY_IO_BUFFER_MAP_ALIGNMENT;
rb_raise(rb_eArgError,
"Offset (%" PRIsVALUE ") can't be larger than "
"%" PRIuSIZE " for requested size (%" PRIuSIZE ")",
argv[2],
maximum_offset,
size);
}
}
enum rb_io_buffer_flags flags = 0;
if (argc >= 4) {
flags = io_buffer_extract_flags(argv[3]);
}
flags = io_buffer_flags_for_map(flags);
return io_buffer_storage_map(klass, io, size, offset, flags);
}
Create an IO::Buffer for reading from file by memory-mapping the file. file should be a File instance, opened for reading or reading and writing.
Optional size and offset of mapping can be specified. The offset must be a multiple of IO::Buffer::MAP_ALIGNMENT. The size does not need to be aligned. Trying to map an empty file or specify size of 0 will raise an error.
By default, the buffer is writable and expects the file to be writable. It is also shared, so several processes can use the same mapping.
The mapping mode may be explicitly selected with IO::Buffer::SHARED or IO::Buffer::PRIVATE, but the two flags are mutually exclusive. IO::Buffer::MAPPED is accepted but redundant because this method always creates a mapped buffer. IO::Buffer::INTERNAL and IO::Buffer::EXTERNAL cannot be specified.
You can pass IO::Buffer::READONLY in flags argument to make a read-only buffer; this allows to work with files opened only for reading. Specifying IO::Buffer::PRIVATE in flags creates a private mapping, which will not impact other processes or the underlying file. It also allows updating a buffer created from a read-only file.
File.write('test.txt', 'test') buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) # => #<IO::Buffer 0x00000001014a0000+4 EXTERNAL MAPPED FILE SHARED READONLY> buffer.readonly? # => true buffer.get_string # => "test" buffer.set_string('b', 0) # 'IO::Buffer#set_string': Buffer is not writable! (IO::Buffer::AccessError) # create read/write mapping: length 4 bytes, offset 0, flags 0 buffer = IO::Buffer.map(File.open('test.txt', 'r+'), 4, 0) buffer.set_string('b', 0) # => 1 # Check it File.read('test.txt') # => "best"
Note that some operating systems may not have cache coherency between mapped buffers and file reads.
static VALUE
io_buffer_s_new(int argc, VALUE *argv, VALUE klass)
{
if (klass == rb_cIOBuffer) klass = rb_cIOBufferStorage;
return rb_class_new_instance_kw(argc, argv, klass, RB_PASS_CALLED_KEYWORDS);
}
Creates an IO::Buffer::Storage with the given size and flags. Buffer is the common byte-view interface; this factory creates one storage-bearing object, not a separate view and allocation. See IO::Buffer::Storage.new.
static VALUE
io_buffer_size_of(VALUE klass, VALUE buffer_type)
{
if (RB_TYPE_P(buffer_type, T_ARRAY)) {
size_t total = 0;
for (rb_len_t i = 0; i < RARRAY_LEN(buffer_type); i++) {
total += io_buffer_buffer_type_size(TYPE_ID(RARRAY_AREF(buffer_type, i)));
}
return SIZET2NUM(total);
}
else {
return SIZET2NUM(io_buffer_buffer_type_size(TYPE_ID(buffer_type)));
}
}
Returns the size of the given buffer type(s) in bytes.
IO::Buffer.size_of(:u32) # => 4 IO::Buffer.size_of([:u32, :u32]) # => 8
(int) { (Buffer) → void } → String
Source
VALUE
rb_io_buffer_type_string(VALUE klass, VALUE length)
{
klass = io_buffer_storage_class(klass);
VALUE string = rb_str_new(NULL, RB_NUM2LEN(length));
struct io_buffer_for_yield_instance_arguments arguments = {
.klass = klass,
.string = string,
.instance = Qnil,
};
rb_ensure(io_buffer_for_yield_instance, (VALUE)&arguments, io_buffer_for_yield_instance_ensure, (VALUE)&arguments);
return string;
}
Creates a new string of the given length and yields a zero-copy IO::Buffer instance to the block which uses the string as a source. The block is expected to write to the buffer and the string will be returned.
IO::Buffer.string(4) do |buffer| buffer.set_string("Ruby") end # => "Ruby"
Public Instance Methods
Source
static VALUE
io_buffer_and(VALUE self, VALUE mask)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
io_buffer_check_mask_size(mask_size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
memory_and(output_buffer->base, base, size, mask_base, mask_size);
return output;
}
Generate a new buffer the same size as the source by applying the binary AND operation to the source, using the mask, repeating as necessary.
IO::Buffer.for("1234567890") & IO::Buffer.for("\xFF\x00\x00\xFF") # => # #<IO::Buffer 0x00005589b2758480+10 INTERNAL> # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
(Buffer) → Integer
Source
static VALUE
rb_io_buffer_compare(VALUE self, VALUE other)
{
const void *ptr1, *ptr2;
size_t size1, size2;
rb_io_buffer_get_bytes_for_reading(self, &ptr1, &size1);
rb_io_buffer_get_bytes_for_reading(other, &ptr2, &size2);
if (size1 < size2) {
return RB_INT2NUM(-1);
}
if (size1 > size2) {
return RB_INT2NUM(1);
}
if (size1 == 0) {
return RB_INT2NUM(0);
}
RUBY_ASSERT(ptr1 != NULL);
RUBY_ASSERT(ptr2 != NULL);
return RB_INT2NUM(memcmp(ptr1, ptr2, size1));
}
Returns a negative integer, zero, or a positive integer if the receiver is less than, equal to, or greater than other, respectively.
Buffers are compared by size first, and if the sizes are equal, by the exact contents of the memory they are referencing using memcmp. Only the sign of the returned integer is meaningful; the result of memcmp is returned as is.
IO::Buffer.for("abc") <=> IO::Buffer.for("abc") # => 0 IO::Buffer.for("abc") <=> IO::Buffer.for("ab") # => 1 IO::Buffer.for("abc") <=> IO::Buffer.for("abd") # => -1
Source
static VALUE
io_buffer_xor(VALUE self, VALUE mask)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
io_buffer_check_mask_size(mask_size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
memory_xor(output_buffer->base, base, size, mask_base, mask_size);
return output;
}
Generate a new buffer the same size as the source by applying the binary XOR operation to the source, using the mask, repeating as necessary.
IO::Buffer.for("1234567890") ^ IO::Buffer.for("\xFF\x00\x00\xFF") # => # #<IO::Buffer 0x000055a2d5d10480+10 INTERNAL> # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0
Source
static VALUE
io_buffer_or(VALUE self, VALUE mask)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
io_buffer_check_mask_size(mask_size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
memory_or(output_buffer->base, base, size, mask_base, mask_size);
return output;
}
Generate a new buffer the same size as the source by applying the binary OR operation to the source, using the mask, repeating as necessary.
IO::Buffer.for("1234567890") | IO::Buffer.for("\xFF\x00\x00\xFF") # => # #<IO::Buffer 0x0000561785ae3480+10 INTERNAL> # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
Source
static VALUE
io_buffer_not(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
VALUE output = rb_io_buffer_new(NULL, size, io_flags_for_size(size));
struct rb_io_buffer_view *output_buffer = get_io_buffer_view(output);
memory_not(output_buffer->base, base, size);
return output;
}
Generate a new buffer the same size as the source by applying the unary NOT operation to the source.
~IO::Buffer.for("1234567890") # => # #<IO::Buffer 0x000055a5ac42f120+10 INTERNAL> # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
Source
static VALUE
io_buffer_advance(VALUE self, VALUE amount)
{
rb_check_frozen(self);
size_t size = io_buffer_extract_amount(amount);
// Argument conversion may call Ruby code which freezes the receiver.
rb_check_frozen(self);
rb_io_buffer_advance(self, size);
return self;
}
Advances the beginning of a non-owning buffer view by amount bytes, reducing its size by the same amount. The backing memory is not moved or modified. The buffer must refer to memory managed elsewhere or be a slice; a buffer which owns its allocation cannot be advanced.
The source and allocation lock count are unchanged. Advancing is allowed while the bytes are read-only or the allocation is locked, because it only changes the view. If amount equals the current size, the buffer becomes an empty view at its previous end.
Raises ArgumentError if amount is negative or exceeds the current size, IO::Buffer::InvalidatedError if the view is invalid, or IO::Buffer::AccessError if the buffer owns its allocation. A frozen buffer cannot be advanced.
buffer = IO::Buffer.for("test") buffer.advance(1) buffer.get_string # => "est"
Source
static VALUE
io_buffer_and_inplace(VALUE self, VALUE mask)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
memory_and_inplace(base, size, (unsigned char *)mask_base, mask_size);
return self;
}
Modify the source buffer in place by applying the binary AND operation to the source, using the mask, repeating as necessary.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy. # => # #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL> # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 source.and!(IO::Buffer.for("\xFF\x00\x00\xFF")) # => # #<IO::Buffer 0x000056307a0d0c20+10 INTERNAL> # 0x00000000 31 00 00 34 35 00 00 38 39 00 1..45..89.
static VALUE
io_buffer_bit_count(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 2);
size_t offset, length;
struct rb_io_buffer_view *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
io_buffer_validate_range(buffer, offset, length);
const void *base;
size_t size;
io_buffer_get_bytes_for_reading(buffer, &base, &size);
if (length == 0) return SIZET2NUM(0);
RUBY_ASSERT(base != NULL);
size_t count = memory_bit_count((const unsigned char *)base + offset, length);
return SIZET2NUM(count);
}
Returns the number of set bits (1s) in the buffer, also known as the Hamming weight or population count. An optional offset and length can be provided to count bits in a subrange of the buffer.
IO::Buffer.for("\xFF\x00\x0F").bit_count # => 12 IO::Buffer.for("\xFF\x00\x0F").bit_count(1, 2) # => 4
static VALUE
io_buffer_clear(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 3);
uint8_t value = 0;
if (argc >= 1) {
value = NUM2UINT(argv[0]);
}
size_t offset, length;
io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
rb_io_buffer_clear(self, value, offset, length);
return self;
}
Fill buffer with value, starting with offset and going for length bytes.
buffer = IO::Buffer.for('test').dup # => # <IO::Buffer 0x00007fca40087c38+4 INTERNAL> # 0x00000000 74 65 73 74 test buffer.clear # => # <IO::Buffer 0x00007fca40087c38+4 INTERNAL> # 0x00000000 00 00 00 00 .... buf.clear(1) # fill with 1 # => # <IO::Buffer 0x00007fca40087c38+4 INTERNAL> # 0x00000000 01 01 01 01 .... buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes # => # <IO::Buffer 0x00007fca40087c38+4 INTERNAL> # 0x00000000 01 02 02 01 .... buffer.clear(2, 1) # fill with 2, starting from offset 1 # => # <IO::Buffer 0x00007fca40087c38+4 INTERNAL> # 0x00000000 01 02 02 02 ....
static VALUE
io_buffer_copy(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 4);
VALUE source = argv[0];
struct io_buffer_copy_arguments arguments = {
.destination = self,
.argc = argc-1,
.argv = argv+1,
};
// Lock the source first, then io_buffer_copy_from_readable nests the
// destination lock. The scoped helpers use rb_ensure, so the destination
// is unlocked before the source on both normal and exceptional returns.
// If both buffers share an allocation, its reference-counted lock is
// acquired and released twice.
return rb_io_buffer_locked_for_reading(source, io_buffer_copy_from_readable, (VALUE)&arguments);
}
Efficiently copy from a source IO::Buffer into the buffer, at offset using memmove. For copying String instances, see set_string.
buffer = IO::Buffer.new(32) # => # #<IO::Buffer 0x0000555f5ca22520+32 INTERNAL> # 0x00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ * buffer.copy(IO::Buffer.for("test"), 8) # => 4 -- size of buffer copied buffer # => # #<IO::Buffer 0x0000555f5cf8fe40+32 INTERNAL> # 0x00000000 00 00 00 00 00 00 00 00 74 65 73 74 00 00 00 00 ........test.... # 0x00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ *
copy can be used to put buffer into strings associated with buffer:
string = "data: " # => "data: " buffer = IO::Buffer.for(string) do |buffer| buffer.copy(IO::Buffer.for("test"), 5) end # => 4 string # => "data:test"
Attempt to copy into a read-only buffer will fail:
File.write('test.txt', 'test') buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY) buffer.copy(IO::Buffer.for("test"), 8) # in `copy': Buffer is not writable! (IO::Buffer::AccessError)
See ::map for details of creation of mutable file mappings, this will work:
buffer = IO::Buffer.map(File.open('test.txt', 'r+')) buffer.copy(IO::Buffer.for("boom"), 0) # => 4 File.read('test.txt') # => "boom"
Attempt to copy the buffer which will need place outside of buffer’s bounds will fail:
buffer = IO::Buffer.new(2) buffer.copy(IO::Buffer.for('test'), 0) # in `copy': Specified offset+length is bigger than the buffer size! (ArgumentError)
It is safe to copy between memory regions that overlaps each other. In such case, the data is copied as if the data was first copied from the source buffer to a temporary buffer, and then copied from the temporary buffer to the destination buffer.
buffer = IO::Buffer.new(10) buffer.set_string("0123456789") buffer.copy(buffer, 3, 7) # => 7 buffer # => # #<IO::Buffer 0x000056494f8ce440+10 INTERNAL> # 0x00000000 30 31 32 30 31 32 33 34 35 36 0120123456
Source
static VALUE
io_buffer_each(int argc, VALUE *argv, VALUE self)
{
RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
struct io_buffer_each_arguments arguments = {
.self = self,
.argc = argc,
.argv = argv,
};
rb_io_buffer_lock(self);
return rb_ensure(io_buffer_each_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
}
Iterates over the buffer, yielding each value of buffer_type starting from offset.
If count is given, only count values will be yielded.
IO::Buffer.for("Hello World").each(:U8, 2, 2) do |offset, value| puts "#{offset}: #{value}" end # 2: 108 # 3: 108
static VALUE
io_buffer_each_byte(int argc, VALUE *argv, VALUE self)
{
RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);
struct io_buffer_each_arguments arguments = {
.self = self,
.argc = argc,
.argv = argv,
};
rb_io_buffer_lock(self);
return rb_ensure(io_buffer_each_byte_locked, (VALUE)&arguments, rb_io_buffer_locked_ensure, self);
}
Iterates over the buffer, yielding each byte starting from offset.
If count is given, only count bytes will be yielded.
IO::Buffer.for("Hello World").each_byte(2, 2) do |offset, byte| puts "#{offset}: #{byte}" end # 2: 108 # 3: 108
() → bool
Source
static VALUE
rb_io_buffer_empty_p(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
return RBOOL(buffer->size == 0);
}
static VALUE
io_buffer_get_string(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 3);
struct io_buffer_get_string_arguments arguments;
io_buffer_extract_offset_length(self, argc, argv, &arguments.offset, &arguments.length);
// Encoding coercion may invoke Ruby and change the source. Do it before
// locking and resolving the current view; retain no pointer across it.
arguments.encoding = argc >= 3 ? rb_find_encoding(argv[2]) : rb_ascii8bit_encoding();
return rb_io_buffer_locked_for_reading(self, io_buffer_get_string_locked, (VALUE)&arguments);
}
Read a chunk or all of the buffer into a string, in the specified encoding. If no encoding is provided Encoding::BINARY is used.
buffer = IO::Buffer.for('test') buffer.get_string # => "test" buffer.get_string(2) # => "st" buffer.get_string(2, 1) # => "s"
static VALUE
io_buffer_get_value(VALUE self, VALUE type, VALUE _offset)
{
const void *base;
size_t size;
size_t offset = io_buffer_extract_offset(_offset);
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
return rb_io_buffer_get_value(base, size, TYPE_ID(type), &offset);
}
Read from buffer a value of type at offset. buffer_type should be one of symbols:
-
:U8: unsigned integer, 1 byte -
:S8: signed integer, 1 byte -
:u16: unsigned integer, 2 bytes, little-endian -
:U16: unsigned integer, 2 bytes, big-endian -
:s16: signed integer, 2 bytes, little-endian -
:S16: signed integer, 2 bytes, big-endian -
:u32: unsigned integer, 4 bytes, little-endian -
:U32: unsigned integer, 4 bytes, big-endian -
:s32: signed integer, 4 bytes, little-endian -
:S32: signed integer, 4 bytes, big-endian -
:u64: unsigned integer, 8 bytes, little-endian -
:U64: unsigned integer, 8 bytes, big-endian -
:s64: signed integer, 8 bytes, little-endian -
:S64: signed integer, 8 bytes, big-endian -
:u128: unsigned integer, 16 bytes, little-endian -
:U128: unsigned integer, 16 bytes, big-endian -
:s128: signed integer, 16 bytes, little-endian -
:S128: signed integer, 16 bytes, big-endian -
:f32: float, 4 bytes, little-endian -
:F32: float, 4 bytes, big-endian -
:f64: double, 8 bytes, little-endian -
:F64: double, 8 bytes, big-endian
A buffer type refers specifically to the type of binary buffer that is stored in the buffer. For example, a :u32 buffer type is a 32-bit unsigned integer in little-endian format.
string = [1.5].pack('f') # => "\x00\x00\xC0?" IO::Buffer.for(string).get_value(:f32, 0) # => 1.5
static VALUE
io_buffer_get_values(VALUE self, VALUE buffer_types, VALUE _offset)
{
size_t offset = io_buffer_extract_offset(_offset);
const void *base;
size_t size;
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
}
VALUE array = rb_ary_new_capa(RARRAY_LEN(buffer_types));
for (rb_len_t i = 0; i < RARRAY_LEN(buffer_types); i++) {
VALUE type = rb_ary_entry(buffer_types, i);
VALUE value = rb_io_buffer_get_value(base, size, TYPE_ID(type), &offset);
rb_ary_push(array, value);
}
return array;
}
Similar to get_value, except that it can handle multiple buffer types and returns an array of values.
string = [1.5, 2.5].pack('ff') IO::Buffer.for(string).get_values([:f32, :f32], 0) # => [1.5, 2.5]
static VALUE
rb_io_buffer_hexdump(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 3);
size_t offset, length;
struct rb_io_buffer_view *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
size_t width = RB_IO_BUFFER_HEXDUMP_DEFAULT_WIDTH;
if (argc >= 3) {
width = io_buffer_extract_width(argv[2], 1);
}
// This may raise an exception if the offset/length is invalid:
io_buffer_validate_range(buffer, offset, length);
VALUE result = Qnil;
void *base = NULL;
size_t size = 0;
if (io_buffer_try_get_bytes(buffer, &base, &size) && base) {
result = rb_str_buf_new(io_buffer_hexdump_output_size(width, length, 1));
io_buffer_hexdump(result, width, base, offset+length, offset, 1);
}
return result;
}
Returns a human-readable string representation of the buffer. The exact format is subject to change.
Returns nil if the buffer does not reference any memory, that is, if null? returns true (for example after #free or #transfer).
buffer = IO::Buffer.for("Hello World") puts buffer.hexdump # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
As buffers are usually fairly big, you may want to limit the output by specifying the offset and length:
puts buffer.hexdump(6, 5) # 0x00000006 57 6f 72 6c 64 World
() → String
Source
VALUE
rb_io_buffer_inspect(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
VALUE result = rb_io_buffer_to_s(self);
void *base = NULL;
size_t total = 0;
if (io_buffer_try_get_bytes(buffer, &base, &total) && base) {
// Limit the maximum size generated by inspect:
size_t size = buffer->size;
int clamped = 0;
if (size > RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE) {
size = RB_IO_BUFFER_INSPECT_HEXDUMP_MAXIMUM_SIZE;
clamped = 1;
}
io_buffer_hexdump(result, RB_IO_BUFFER_INSPECT_HEXDUMP_WIDTH, base, size, 0, 0);
if (clamped) {
rb_str_catf(result, "\n(and %" PRIuSIZE " more bytes not printed)", buffer->size - size);
}
}
return result;
}
Inspect the buffer and report useful information about it’s internal state. Only a limited portion of the buffer will be displayed in a hexdump style format.
buffer = IO::Buffer.for("Hello World") puts buffer.inspect # #<IO::Buffer 0x000000010198ccd8+11 EXTERNAL READONLY SLICE> # 0x00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 Hello World
[A] () { (IO::Buffer) → A } → A
Source
VALUE
rb_io_buffer_locked(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
// Only yield the block for a currently valid view. In particular, an
// invalid slice should not lock its source.
io_buffer_validate_for_reading(buffer);
io_buffer_lock(buffer);
return rb_ensure(rb_yield, self, rb_io_buffer_locked_ensure, self);
}
Prevents the buffer or its buffer source from being moved or freed while the block is executing. Locks are nested and shared with slices backed by the same buffer source. The source remains locked until every nested lock has been released.
Locking protects allocation lifetime; it does not serialize access to the bytes. Code that shares mutable buffer contents between threads must still use appropriate synchronization.
buffer = IO::Buffer.new(4) buffer.locked? #=> false Fiber.schedule do buffer.locked do buffer.write(io) # theoretical system call interface end end Fiber.schedule do buffer.locked do buffer.set_string("test", 0) # Nested locking is allowed. end end
() → bool
Source
static VALUE
rb_io_buffer_locked_p(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
return RBOOL(io_buffer_locked(buffer));
}
If the buffer is locked, its underlying allocation cannot be resized, freed or transferred. Locks are shared with slices and may be nested.
Locking is a lifetime mechanism used to ensure buffers don’t move while being used by a system call or other native operation.
buffer.locked do buffer.write(io) # theoretical system call interface end
Source
static VALUE
io_buffer_not_inplace(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
memory_not_inplace(base, size);
return self;
}
Modify the source buffer in place by applying the unary NOT operation to the source.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy. # => # #<IO::Buffer 0x000056307a33a450+10 INTERNAL> # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 source.not! # => # #<IO::Buffer 0x000056307a33a450+10 INTERNAL> # 0x00000000 ce cd cc cb ca c9 c8 c7 c6 cf ..........
() → bool
Source
static VALUE
rb_io_buffer_null_p(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
void *base = NULL;
size_t size = 0;
io_buffer_try_get_bytes(buffer, &base, &size);
return RBOOL(base == NULL);
}
Returns whether the buffer has no recorded base address.
A buffer is null if it was freed with #free, transferred with #transfer, or was never allocated in the first place. A zero-sized buffer or slice may have a non-null address, so null? and empty? are distinct properties.
buffer = IO::Buffer.new(0) buffer.null? #=> true buffer = IO::Buffer.new(4) buffer.null? #=> false buffer.free buffer.null? #=> true
Source
static VALUE
io_buffer_or_inplace(VALUE self, VALUE mask)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
memory_or_inplace(base, size, (unsigned char *)mask_base, mask_size);
return self;
}
Modify the source buffer in place by applying the binary OR operation to the source, using the mask, repeating as necessary.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy. # => # #<IO::Buffer 0x000056307a272350+10 INTERNAL> # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 source.or!(IO::Buffer.for("\xFF\x00\x00\xFF")) # => # #<IO::Buffer 0x000056307a272350+10 INTERNAL> # 0x00000000 ff 32 33 ff ff 36 37 ff ff 30 .23..67..0
(untyped, untyped, untyped) → untyped
Source
static VALUE
io_buffer_pread(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 2, 4);
VALUE io = argv[0];
rb_off_t from = NUM2OFFT(argv[1]);
size_t offset, length;
io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length);
return rb_io_buffer_pread(self, io, from, offset, length);
}
Perform one read operation of at most length bytes from io at from into the buffer starting at offset. A short read is a normal result and the IO’s current position is not modified. If an error occurs, return -errno.
If offset is not given, it defaults to zero, i.e. the beginning of the buffer. If length is not given, it defaults to the size of the buffer minus the offset. A zero length is a no-op.
IO::Buffer.for('test') do |buffer| p buffer # => # <IO::Buffer 0x00007fca40087c38+4 SLICE> # 0x00000000 74 65 73 74 test # take 2 bytes from the beginning of urandom, # put them in buffer starting from position 2 buffer.pread(File.open('/dev/urandom', 'rb'), 0, 2, 2) p buffer # => # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE> # 0x00000000 05 35 73 74 te.5 end
(untyped, untyped, untyped) → untyped
Source
static VALUE
io_buffer_pwrite(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 2, 4);
VALUE io = argv[0];
rb_off_t from = NUM2OFFT(argv[1]);
size_t offset, length;
io_buffer_extract_offset_length(self, argc-2, argv+2, &offset, &length);
return rb_io_buffer_pwrite(self, io, from, offset, length);
}
Perform one write operation of at most length bytes to io at from from the buffer starting at offset. A short write is a normal result and the IO’s current position is not modified. If an error occurs, return -errno.
If offset is not given, it defaults to zero, i.e. the beginning of the buffer. If length is not given, it defaults to the size of the buffer minus the offset. A zero length is a no-op.
If the from position is beyond the end of the file, the gap will be filled with null (0 value) bytes.
out = File.open('output.txt', File::RDWR) # open for read/write, no truncation IO::Buffer.for('1234567').pwrite(out, 2, 1, 3)
This leads to 234 (3 bytes, starting from position 1) being written into output.txt, starting from file position 2.
(untyped, untyped) → untyped
Source
static VALUE
io_buffer_read(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 3);
VALUE io = argv[0];
size_t offset, length;
io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
return rb_io_buffer_read(self, io, offset, length);
}
Perform one read operation of at most length bytes from io into the buffer starting at offset. A short read is a normal result. If an error occurs, return -errno.
If offset is not given, it defaults to zero, i.e. the beginning of the buffer. If length is not given, it defaults to the size of the buffer minus the offset. A zero length is a no-op.
IO::Buffer.for('test') do |buffer| p buffer # => # <IO::Buffer 0x00007fca40087c38+4 SLICE> # 0x00000000 74 65 73 74 test buffer.read(File.open('/dev/urandom', 'rb'), 0, 2) p buffer # => # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE> # 0x00000000 05 35 73 74 .5st end
() → bool
Source
static VALUE
io_buffer_readonly(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
return RBOOL(io_buffer_readonly_p(buffer));
}
If the buffer is read only, meaning the buffer cannot be modified using set_value, set_string or copy and similar.
A buffer created by IO::Buffer.for without a block is read-only, as is one backed by a frozen string or a read-only file.
A slice derives read-only access from its current source. Replacing the source’s storage can therefore change the slice’s read-only status.
(*untyped) → untyped
Source
static VALUE
io_buffer_set_string(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 4);
struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
VALUE string = rb_str_to_str(argv[0]);
const void *source_base = RSTRING_PTR(string);
size_t source_size = RSTRING_LEN(string);
VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
RB_GC_GUARD(string);
return result;
}
Efficiently copy from a source String into the buffer, at offset using memmove.
buf = IO::Buffer.new(8) # => # #<IO::Buffer 0x0000557412714a20+8 INTERNAL> # 0x00000000 00 00 00 00 00 00 00 00 ........ # set buffer starting from offset 1, take 2 bytes starting from string's # second buf.set_string('test', 1, 2, 1) # => 2 buf # => # #<IO::Buffer 0x0000557412714a20+8 INTERNAL> # 0x00000000 00 65 73 00 00 00 00 00 .es.....
See also copy for examples of how buffer writing might be used for changing associated strings and files.
static VALUE
io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
size_t offset = io_buffer_extract_offset(_offset);
rb_io_buffer_set_value(buffer, type, &offset, value);
return SIZET2NUM(offset);
}
Write to a buffer a value of type at offset. type should be one of symbols described in get_value. Returns the offset just after the written value.
buffer = IO::Buffer.new(8) # => # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL> # 0x00000000 00 00 00 00 00 00 00 00 buffer.set_value(:U8, 1, 111) # => 2 buffer # => # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL> # 0x00000000 00 6f 00 00 00 00 00 00 .o......
Note that if the type is integer and value is Float, the implicit truncation is performed:
buffer = IO::Buffer.new(8) buffer.set_value(:U32, 0, 2.5) buffer # => # #<IO::Buffer 0x0000555f5c9a2d50+8 INTERNAL> # 0x00000000 00 00 00 02 00 00 00 00 # ^^ the same as if we'd pass just integer 2
static VALUE
io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
}
size_t offset = io_buffer_extract_offset(_offset);
if (!RB_TYPE_P(values, T_ARRAY)) {
rb_raise(rb_eArgError, "Argument values should be an array!");
}
if (RARRAY_LEN(buffer_types) != RARRAY_LEN(values)) {
rb_raise(rb_eArgError, "Argument buffer_types and values should have the same length!");
}
for (rb_len_t i = 0; i < RARRAY_LEN(buffer_types); i++) {
VALUE type = rb_ary_entry(buffer_types, i);
VALUE value = rb_ary_entry(values, i);
rb_io_buffer_set_value(buffer, type, &offset, value);
}
return SIZET2NUM(offset);
}
Write values of buffer_types at offset to the buffer. buffer_types should be an array of symbols as described in get_value. values should be an array of values to write. Returns the offset just after the last written value.
buffer = IO::Buffer.new(8) buffer.set_values([:U8, :U16], 0, [1, 2]) # => 3 buffer # => # #<IO::Buffer 0x696f717561746978+8 INTERNAL> # 0x00000000 01 00 02 00 00 00 00 00 ........
() → Integer
Source
VALUE
rb_io_buffer_size(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
return SIZET2NUM(buffer->size);
}
Returns the size of the buffer that was explicitly set (on creation with ::new or on #resize), or deduced on buffer’s creation from string or file.
static VALUE
io_buffer_slice(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 0, 2);
size_t offset, length;
struct rb_io_buffer_view *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);
return rb_io_buffer_slice(buffer, self, offset, length);
}
Produce another IO::Buffer which is a slice (or view into) the current one starting at offset bytes and going for length bytes.
Slicing does not copy memory. The slice retains self as its source and tracks a logical offset and length within that view. Nested slices retain their immediate parent rather than being flattened.
A slice becomes invalid if its source is freed, transferred, resized so that the slice is outside its bounds, or otherwise invalidated. It becomes valid again if the source becomes valid and the range fits within it. Reallocating the underlying storage does not invalidate the slice.
If the offset is not given, it will be zero. If the offset is negative, it will raise an ArgumentError.
If the length is not given, the slice will be as long as the original buffer minus the specified offset. If the length is negative, it will raise an ArgumentError.
Raises RuntimeError if the offset+length is out of the current buffer’s bounds.
string = 'test' buffer = IO::Buffer.for(string).dup slice = buffer.slice # => # #<IO::Buffer 0x0000000108338e68+4 SLICE> # 0x00000000 74 65 73 74 test buffer.slice(2) # => # #<IO::Buffer 0x0000000108338e6a+2 SLICE> # 0x00000000 73 74 st slice = buffer.slice(1, 2) # => # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE> # 0x00000000 65 73 es # Put "o" into 0s position of the slice slice.set_string('o', 0) slice # => # #<IO::Buffer 0x00007fc3d34ebc49+2 SLICE> # 0x00000000 6f 73 os # it is also visible at position 1 of the original buffer buffer # => # #<IO::Buffer 0x00007fc3d31e2d80+4 INTERNAL> # 0x00000000 74 6f 73 74 tost
static VALUE
io_buffer_source(VALUE self)
{
return get_io_buffer_view(self)->source;
}
Returns the object backing this view, or nil for a source-less buffer. A slice’s source is the buffer on which slice was called, including when that buffer is itself a slice. The source is retained while the view lives.
root = IO::Buffer.new(8) parent = root.slice(1, 6) child = parent.slice(1, 2) child.source.equal?(parent) # => true parent.source.equal?(root) # => true root.source # => nil
For a String-backed buffer the source is its backing String. Without a block, IO::Buffer.for may use a frozen internal copy rather than the original String. A source-less buffer may own or borrow its memory; a nil source does not imply allocation ownership.
An invalid slice still retains and returns its source. Storage#free or Storage#transfer clears that Storage object’s source; Slice has neither operation. There is no source setter.
() → String
Source
VALUE
rb_io_buffer_to_s(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
VALUE result = rb_str_new_cstr("#<");
rb_str_append(result, rb_class_name(CLASS_OF(self)));
// Resolve the current base (following slice indirection) for display:
void *base = NULL;
size_t size = 0;
io_buffer_try_get_bytes(buffer, &base, &size);
rb_str_catf(result, " %p+%"PRIdSIZE, base, buffer->size);
if (base == NULL) {
rb_str_cat2(result, " NULL");
}
if (buffer->flags & RB_IO_BUFFER_EXTERNAL) {
rb_str_cat2(result, " EXTERNAL");
}
if (buffer->flags & RB_IO_BUFFER_INTERNAL) {
rb_str_cat2(result, " INTERNAL");
}
if (buffer->flags & RB_IO_BUFFER_MAPPED) {
rb_str_cat2(result, " MAPPED");
}
if (buffer->flags & RB_IO_BUFFER_FILE) {
rb_str_cat2(result, " FILE");
}
if (buffer->flags & RB_IO_BUFFER_SHARED) {
rb_str_cat2(result, " SHARED");
}
if (io_buffer_locked(buffer)) {
rb_str_cat2(result, " LOCKED");
}
if (buffer->flags & RB_IO_BUFFER_PRIVATE) {
rb_str_cat2(result, " PRIVATE");
}
if (io_buffer_readonly_p(buffer)) {
rb_str_cat2(result, " READONLY");
}
if (buffer->source != Qnil) {
rb_str_cat2(result, " SLICE");
}
if (!io_buffer_validate(buffer)) {
rb_str_cat2(result, " INVALID");
}
return rb_str_cat2(result, ">");
}
Short representation of the buffer. It includes the address, size and symbolic flags. This format is subject to change.
puts IO::Buffer.new(4) # uses to_s internally # #<IO::Buffer 0x000055769f41b1a0+4 INTERNAL>
() → bool
Source
static VALUE
rb_io_buffer_valid_p(VALUE self)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view(self);
return RBOOL(io_buffer_validate(buffer));
}
A buffer without a source is always valid, including a null buffer. A source-backed buffer is valid when its offset and length fit within its source’s current size.
Relocating a source does not invalidate a source-backed buffer. Freeing, transferring, or shrinking the source can make it invalid; if the same source later grows to include the range again, the buffer becomes valid and refers to the current contents at its original offset.
An empty source-backed range at offset zero can be valid even when its source has no storage. valid?, null? and empty? describe distinct properties: a buffer can be valid, null, and empty at the same time.
static VALUE
io_buffer_values(int argc, VALUE *argv, VALUE self)
{
const void *base;
size_t size;
rb_io_buffer_get_bytes_for_reading(self, &base, &size);
ID buffer_type;
if (argc >= 1) {
buffer_type = TYPE_ID(argv[0]);
}
else {
buffer_type = RB_IO_BUFFER_DATA_TYPE_U8;
}
size_t offset, count;
io_buffer_extract_offset_count(buffer_type, size, argc-1, argv+1, &offset, &count);
VALUE array = rb_ary_new_capa(count);
for (size_t i = 0; i < count; i++) {
VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
rb_ary_push(array, value);
}
return array;
}
Returns an array of values of buffer_type starting from offset.
If count is given, only count values will be returned.
IO::Buffer.for("Hello World").values(:U8, 2, 2) # => [108, 108]
(untyped, untyped) → untyped
Source
static VALUE
io_buffer_write(int argc, VALUE *argv, VALUE self)
{
rb_check_arity(argc, 1, 3);
VALUE io = argv[0];
size_t offset, length;
io_buffer_extract_offset_length(self, argc-1, argv+1, &offset, &length);
return rb_io_buffer_write(self, io, offset, length);
}
Perform one write operation of at most length bytes to io from the buffer starting at offset. A short write is a normal result. If an error occurs, return -errno.
If offset is not given, it defaults to zero, i.e. the beginning of the buffer. If length is not given, it defaults to the size of the buffer minus the offset. A zero length is a no-op.
out = File.open('output.txt', 'wb') IO::Buffer.for('1234567').write(out, 0, 3)
This leads to 123 being written into output.txt
Source
static VALUE
io_buffer_xor_inplace(VALUE self, VALUE mask)
{
struct rb_io_buffer_view *buffer = get_io_buffer_view_for_writing(self);
struct rb_io_buffer_view *mask_buffer = get_io_buffer_view(mask);
io_buffer_check_mask_size(mask_buffer->size);
io_buffer_check_overlaps(buffer, mask_buffer);
void *base;
size_t size;
io_buffer_get_bytes_for_writing(buffer, &base, &size);
const void *mask_base;
size_t mask_size;
io_buffer_get_bytes_for_reading(mask_buffer, &mask_base, &mask_size);
memory_xor_inplace(base, size, (unsigned char *)mask_base, mask_size);
return self;
}
Modify the source buffer in place by applying the binary XOR operation to the source, using the mask, repeating as necessary.
source = IO::Buffer.for("1234567890").dup # Make a read/write copy. # => # #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL> # 0x00000000 31 32 33 34 35 36 37 38 39 30 1234567890 source.xor!(IO::Buffer.for("\xFF\x00\x00\xFF")) # => # #<IO::Buffer 0x000056307a25b3e0+10 INTERNAL> # 0x00000000 ce 32 33 cb ca 36 37 c7 c6 30 .23..67..0