123456789_123456789_123456789_123456789_123456789_

Class: IO::Buffer

Relationships & Source Files
Namespace Children
Exceptions:
Super Chains via Extension / Inclusion / Inheritance
Instance Chain:
self, ::Comparable
Inherits: Object
Defined in: io_buffer.c,
io_buffer.c

Overview

Buffer is a low-level efficient buffer for input/output. There are three ways of using buffer:

  • Create an empty buffer with .new, fill it with buffer using #copy or #set_value, #set_string, get buffer with #get_string;

  • Create a buffer mapped to some string with .for, then it could be used both for reading with #get_string or #get_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.

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::SchedulerInterface#io_read and Fiber::SchedulerInterface#io_write.

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 = 'buffer'
buffer = IO::Buffer.for(string)
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# ...
buffer
# =>
# #<IO::Buffer 0x00007f3f02be9b18+4 SLICE>
# 0x00000000  64 61 74 61                                     buffer

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---"

Buffer from file:

File.write('test.txt', 'test buffer')
# => 9
buffer = IO::Buffer.map(File.open('test.txt'))
# =>
# #<IO::Buffer 0x00007f3f0768c000+9 MAPPED IMMUTABLE>
# ...
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--- buffer"

The class is experimental and the interface is subject to change.

Constant Summary

Class Method Summary

Instance Attribute Summary

Instance Method Summary

::Comparable - Included

#<

Compares two objects based on the receiver’s #<=> method, returning true if it returns a value less than 0.

#<=

Compares two objects based on the receiver’s #<=> method, returning true if it returns a value less than or equal to 0.

#==

Compares two objects based on the receiver’s #<=> method, returning true if it returns 0.

#>

Compares two objects based on the receiver’s #<=> method, returning true if it returns a value greater than 0.

#>=

Compares two objects based on the receiver’s #<=> method, returning true if it returns a value greater than or equal to 0.

#between?

Returns false if obj #<=> min is less than zero or if obj #<=> max is greater than zero, true otherwise.

#clamp

In (min, max) form, returns min if obj #<=> min is less than zero, max if obj #<=> max is greater than zero, and obj otherwise.

Constructor Details

.new([size = DEFAULT_SIZE, [flags) ⇒ Buffer

Create a new zero-filled Buffer of #size bytes. By default, the buffer will be internal: directly allocated chunk of the memory. But if the requested #size is more than OS-specific PAGE_SIZE, the buffer would be allocated using the virtual memory mechanism (anonymous mmap on Unix, VirtualAlloc on Windows). The behavior can be forced by passing MAPPED as a second parameter.

Examples

buffer = IO::Buffer.new(4)
# =>
# #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
# 0x00000000  00 00 00 00                                     ....

buffer.get_string(0, 1) # => "\x00"

buffer.set_string("test")
buffer
# =>
# #<IO::Buffer 0x000055b34497ea10+4 INTERNAL>
# 0x00000000  74 65 73 74                                     test
[ GitHub ]

  
# File 'io_buffer.c', line 670

VALUE
rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
{
    io_buffer_experimental();

    rb_check_arity(argc, 0, 2);

    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    size_t size;
    if (argc > 0) {
        size = io_buffer_extract_size(argv[0]);
    }
    else {
        size = RUBY_IO_BUFFER_DEFAULT_SIZE;
    }

    enum rb_io_buffer_flags flags = 0;
    if (argc >= 2) {
        flags = RB_NUM2UINT(argv[1]);
    }
    else {
        flags |= io_flags_for_size(size);
    }

    io_buffer_initialize(buffer, NULL, size, flags, Qnil);

    return self;
}

Class Method Details

.for(string) ⇒ Buffer .for(string) {|io_buffer| ... }

Creates a Buffer from the given string’s memory. Without a block a frozen internal copy of the string is created efficiently and used as the buffer source. When a block is provided, the buffer is associated directly with the string’s internal buffer and updating the buffer will update the string.

Until #free is invoked on the buffer, either explicitly or via the garbage collector, the source string will be locked and cannot be modified.

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
# => "best"

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
[ GitHub ]

  
# File 'io_buffer.c', line 497

VALUE
rb_io_buffer_type_for(VALUE klass, VALUE string)
{
    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 {
        // This internally returns the source string if it's already frozen.
        string = rb_str_tmp_frozen_acquire(string);
        return io_buffer_for_make_instance(klass, string, RB_IO_BUFFER_READONLY);
    }
}

.map(file, [size, [offset, [flags]]]) ⇒ Buffer

Create an Buffer for reading from file by memory-mapping the file. file_io should be a ::File instance, opened for reading.

Optional #size and offset of mapping can be specified.

By default, the buffer would be immutable (read only); to create a writable mapping, you need to open a file in read-write mode, and explicitly pass flags argument without IO::Buffer::IMMUTABLE.

Example:

File.write('test.txt', 'test')

buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::READONLY)
# => #<IO::Buffer 0x00000001014a0000+4 MAPPED READONLY>

buffer.readonly?   # => true

buffer.get_string
# => "test"

buffer.set_string('b', 0)
# `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.

[ GitHub ]

  
# File 'io_buffer.c', line 590

static VALUE
io_buffer_map(int argc, VALUE *argv, VALUE klass)
{
    rb_check_arity(argc, 1, 4);

    // We might like to handle a string path?
    VALUE io = argv[0];

    size_t size;
    if (argc >= 2 && !RB_NIL_P(argv[1])) {
        size = io_buffer_extract_size(argv[1]);
    }
    else {
        rb_off_t file_size = rb_file_size(io);

        // Compiler can confirm that we handled file_size < 0 case:
        if (file_size < 0) {
            rb_raise(rb_eArgError, "Invalid negative file size!");
        }
        // Here, we assume that file_size is positive:
        else if ((uintmax_t)file_size > SIZE_MAX) {
            rb_raise(rb_eArgError, "File larger than address space!");
        }
        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]);
    }

    enum rb_io_buffer_flags flags = 0;
    if (argc >= 4) {
        flags = RB_NUM2UINT(argv[3]);
    }

    return rb_io_buffer_map(io, size, offset, flags);
}

.size_of(buffer_type) ⇒ byte size .size_of(array of buffer_type) ⇒ byte size

Returns the size of the given buffer type(s) in bytes.

Example:

IO::Buffer.size_of(:u32) # => 4
IO::Buffer.size_of([:u32, :u32]) # => 8
[ GitHub ]

  
# File 'io_buffer.c', line 1719

static VALUE
io_buffer_size_of(VALUE klass, VALUE buffer_type)
{
    if (RB_TYPE_P(buffer_type, T_ARRAY)) {
        size_t total = 0;
        for (long i = 0; i < RARRAY_LEN(buffer_type); i++) {
            total += io_buffer_buffer_type_size(RB_SYM2ID(RARRAY_AREF(buffer_type, i)));
        }
        return SIZET2NUM(total);
    }
    else {
        return SIZET2NUM(io_buffer_buffer_type_size(RB_SYM2ID(buffer_type)));
    }
}

Instance Attribute Details

#empty?Boolean (readonly)

If the buffer has 0 size: it is created by .new with size 0, or with .for from an empty string. (Note that empty files can’t be mapped, so the buffer created with .map will never be empty.)

[ GitHub ]

  
# File 'io_buffer.c', line 930

static VALUE
rb_io_buffer_empty_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(buffer->size == 0);
}

#external?Boolean (readonly)

The buffer is external if it references the memory which is not allocated or mapped by the buffer itself.

A buffer created using .for has an external reference to the string’s memory.

External buffer can’t be resized.

[ GitHub ]

  
# File 'io_buffer.c', line 950

static VALUE
rb_io_buffer_external_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(buffer->flags & RB_IO_BUFFER_EXTERNAL);
}

#internal?Boolean (readonly)

If the buffer is internal, meaning it references memory allocated by the buffer itself.

An internal buffer is not associated with any external memory (e.g. string) or file mapping.

Internal buffers are created using .new and is the default when the requested size is less than the PAGE_SIZE and it was not requested to be mapped on creation.

Internal buffers can be resized, and such an operation will typically invalidate all slices, but not always.

[ GitHub ]

  
# File 'io_buffer.c', line 975

static VALUE
rb_io_buffer_internal_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(buffer->flags & RB_IO_BUFFER_INTERNAL);
}

#locked (readonly)

Allows to process a buffer in exclusive way, for concurrency-safety. While the block is performed, the buffer is considered locked, and no other code can enter the lock. Also, locked buffer can’t be changed with #resize or #free.

The following operations acquire a lock: #resize, #free.

Locking is not thread safe. It is designed as a safety net around non-blocking system calls. You can only share a buffer between threads with appropriate synchronisation techniques.

Example:

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
  # in `locked': Buffer already locked! (IO::Buffer::LockedError)
  buffer.locked do
    buffer.set_string("test", 0)
  end
end
[ GitHub ]

  
# File 'io_buffer.c', line 1158

VALUE
rb_io_buffer_locked(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    if (buffer->flags & RB_IO_BUFFER_LOCKED) {
        rb_raise(rb_eIOBufferLockedError, "Buffer already locked!");
    }

    buffer->flags |= RB_IO_BUFFER_LOCKED;

    VALUE result = rb_yield(self);

    buffer->flags &= ~RB_IO_BUFFER_LOCKED;

    return result;
}

#locked?Boolean (readonly)

If the buffer is locked, meaning it is inside #locked block execution. Locked buffer can’t be resized or freed, and another lock can’t be acquired on it.

Locking is not thread safe, but is a semantic used to ensure buffers don’t move while being used by a system call.

Example:

buffer.locked do
  buffer.write(io) # theoretical system call interface
end
[ GitHub ]

  
# File 'io_buffer.c', line 1038

static VALUE
rb_io_buffer_locked_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(buffer->flags & RB_IO_BUFFER_LOCKED);
}

#mapped?Boolean (readonly)

If the buffer is mapped, meaning it references memory mapped by the buffer.

Mapped buffers are either anonymous, if created by .new with the MAPPED flag or if the size was at least PAGE_SIZE, or backed by a file if created with .map.

Mapped buffers can usually be resized, and such an operation will typically invalidate all slices, but not always.

[ GitHub ]

  
# File 'io_buffer.c', line 997

static VALUE
rb_io_buffer_mapped_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(buffer->flags & RB_IO_BUFFER_MAPPED);
}

#null?Boolean (readonly)

If the buffer was freed with #free or was never allocated in the first place.

[ GitHub ]

  
# File 'io_buffer.c', line 914

static VALUE
rb_io_buffer_null_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(buffer->base == NULL);
}

#readonly?Boolean (readonly)

If the buffer is read only, meaning the buffer cannot be modified using #set_value, #set_string or #copy and similar.

Frozen strings and read-only files create read-only buffers.

[ GitHub ]

  
# File 'io_buffer.c', line 1064

static VALUE
io_buffer_readonly_p(VALUE self)
{
    return RBOOL(rb_io_buffer_readonly_p(self));
}

#shared?Boolean (readonly)

If the buffer is shared, meaning it references memory that can be shared with other processes (and thus might change without being modified locally).

[ GitHub ]

  
# File 'io_buffer.c', line 1013

static VALUE
rb_io_buffer_shared_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(buffer->flags & RB_IO_BUFFER_SHARED);
}

#valid?Boolean (readonly)

Returns whether the buffer buffer is accessible.

A buffer becomes invalid if it is a slice of another buffer which has been freed.

[ GitHub ]

  
# File 'io_buffer.c', line 899

static VALUE
rb_io_buffer_valid_p(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return RBOOL(io_buffer_validate(buffer));
}

Instance Method Details

#&(mask) ⇒ Buffer

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+4 INTERNAL>
# 0x00000000  31 00 00 34 35 00 00 38 39 00                   1..45..89.
[ GitHub ]

  
# File 'io_buffer.c', line 2966

static VALUE
io_buffer_and(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_and(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);

    return output;
}

#<=>(other) ⇒ Boolean

Buffers are compared by size and exact contents of the memory they are referencing using memcmp.

[ GitHub ]

  
# File 'io_buffer.c', line 1554

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);
    }

    return RB_INT2NUM(memcmp(ptr1, ptr2, size1));
}

#^(mask) ⇒ Buffer

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
[ GitHub ]

  
# File 'io_buffer.c', line 3046

static VALUE
io_buffer_xor(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_xor(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);

    return output;
}

#and!(mask) ⇒ Buffer

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.
[ GitHub ]

  
# File 'io_buffer.c', line 3143

static VALUE
io_buffer_and_inplace(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);
    io_buffer_check_overlaps(buffer, mask_buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_and_inplace(base, size, mask_buffer->base, mask_buffer->size);

    return self;
}

#clear(value = 0, [offset, [length]]) ⇒ self

Fill buffer with value, starting with offset and going for length bytes.

buffer = IO::Buffer.for('test')
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  74 65 73 74         test

buffer.clear
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  00 00 00 00         ....

buf.clear(1) # fill with 1
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  01 01 01 01         ....

buffer.clear(2, 1, 2) # fill with 2, starting from offset 1, for 2 bytes
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  01 02 02 01         ....

buffer.clear(2, 1) # fill with 2, starting from offset 1
# =>
#   <IO::Buffer 0x00007fca40087c38+4 SLICE>
#   0x00000000  01 02 02 02         ....
[ GitHub ]

  
# File 'io_buffer.c', line 2414

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;
}

#copy(source, [offset, [length, [source_offset]]]) ⇒ size

Efficiently copy buffer from a source Buffer into the buffer, at offset using memcpy. 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= "buffer:    "
# => "buffer:    "
buffer = IO::Buffer.for(string)
buffer.copy(IO::Buffer.for("test"), 5)
# => 4
string
# => "buffer: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)
[ GitHub ]

  
# File 'io_buffer.c', line 2273

static VALUE
io_buffer_copy(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 4);

    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE source = argv[0];
    const void *source_base;
    size_t source_size;

    rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size);

    return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
}

#freeself

If the buffer references memory, release it back to the operating system.

  • for a mapped buffer (e.g. from file): unmap.

  • for a buffer created from scratch: free memory.

  • for a buffer created from string: undo the association.

After the buffer is freed, no further operations can’t be performed on it.

You can resize a freed buffer to re-allocate it.

Example:

buffer = IO::Buffer.for('test')
buffer.free
# => #<IO::Buffer 0x0000000000000000+0 NULL>

buffer.get_value(:U8, 0)
# in `get_value': The buffer is not allocated! (IO::Buffer::AllocationError)

buffer.get_string
# in `get_string': The buffer is not allocated! (IO::Buffer::AllocationError)

buffer.null?
# => true
[ GitHub ]

  
# File 'io_buffer.c', line 1204

VALUE
rb_io_buffer_free(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    if (buffer->flags & RB_IO_BUFFER_LOCKED) {
        rb_raise(rb_eIOBufferLockedError, "Buffer is locked!");
    }

    io_buffer_free(buffer);

    return self;
}

#get_string([offset, [length, [encoding]]]) ⇒ String

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"
[ GitHub ]

  
# File 'io_buffer.c', line 2304

static VALUE
io_buffer_get_string(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 0, 3);

    size_t offset, length;
    struct rb_io_buffer *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);

    const void *base;
    size_t size;
    io_buffer_get_bytes_for_reading(buffer, &base, &size);

    rb_encoding *encoding;
    if (argc >= 3) {
        encoding = rb_find_encoding(argv[2]);
    }
    else {
        encoding = rb_ascii8bit_encoding();
    }

    io_buffer_validate_range(buffer, offset, length);

    return rb_enc_str_new((const char*)base + offset, length, encoding);
}

#get_value(buffer_type, offset) ⇒ Numeric

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

  • :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.

Example:

string = [1.5].pack('f')
# => "\x00\x00\xC0?"
IO::Buffer.for(string).get_value(:f32, 0)
# => 1.5
[ GitHub ]

  
# File 'io_buffer.c', line 1801

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, RB_SYM2ID(type), &offset);
}

#get_values(buffer_types, offset) ⇒ Array

Similar to #get_value, except that it can handle multiple buffer types and returns an array of values.

Example:

string = [1.5, 2.5].pack('ff')
IO::Buffer.for(string).get_values([:f32, :f32], 0)
# => [1.5, 2.5]
[ GitHub ]

  
# File 'io_buffer.c', line 1825

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 (long 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, RB_SYM2ID(type), &offset);
        rb_ary_push(array, value);
    }

    return array;
}

#hexdump

[ GitHub ]

  
# File 'io_buffer.c', line 841

static VALUE
rb_io_buffer_hexdump(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE result = Qnil;

    if (io_buffer_validate(buffer) && buffer->base) {
        result = rb_str_buf_new(buffer->size*3 + (buffer->size/16)*12 + 1);

        io_buffer_hexdump(result, 16, buffer->base, buffer->size, 1);
    }

    return result;
}

#not!Buffer

Modify the source buffer in place by applying the binary 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                   ..........
[ GitHub ]

  
# File 'io_buffer.c', line 3281

static VALUE
io_buffer_not_inplace(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_not_inplace(base, size);

    return self;
}

#or!(mask) ⇒ Buffer

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
[ GitHub ]

  
# File 'io_buffer.c', line 3189

static VALUE
io_buffer_or_inplace(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);
    io_buffer_check_overlaps(buffer, mask_buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_or_inplace(base, size, mask_buffer->base, mask_buffer->size);

    return self;
}

#read(io, [length, [offset]]) ⇒ read length, -errno

Read at least length bytes from the io, into the buffer starting at offset. If an error occurs, return -errno.

If length is not given or nil, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.

If length is zero, exactly one read operation will occur.

If offset is not given, it defaults to zero, i.e. the beginning of the buffer.

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'), 2)
  p buffer
  # =>
  # <IO::Buffer 0x00007f3bc65f2a58+4 EXTERNAL SLICE>
  # 0x00000000  05 35 73 74         .5st
end
[ GitHub ]

  
# File 'io_buffer.c', line 2606

static VALUE
io_buffer_read(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 3);

    VALUE io = argv[0];

    size_t length, offset;
    io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset);

    return rb_io_buffer_read(self, io, length, offset);
}

#resize(new_size) ⇒ self

Resizes a buffer to a new_size bytes, preserving its content. Depending on the old and new size, the memory area associated with the buffer might be either extended, or rellocated at different address with content being copied.

buffer = IO::Buffer.new(4)
buffer.set_string("test", 0)
buffer.resize(8) # resize to 8 bytes
# =>
# #<IO::Buffer 0x0000555f5d1a1630+8 INTERNAL>
# 0x00000000  74 65 73 74 00 00 00 00                         test....

External buffer (created with .for), and locked buffer can not be resized.

[ GitHub ]

  
# File 'io_buffer.c', line 1540

static VALUE
io_buffer_resize(VALUE self, VALUE size)
{
    rb_io_buffer_resize(self, io_buffer_extract_size(size));

    return self;
}

#set_string(string, [offset, [length, [source_offset]]]) ⇒ size

Efficiently copy buffer from a source ::String into the buffer, at offset using memcpy.

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.

[ GitHub ]

  
# File 'io_buffer.c', line 2352

static VALUE
io_buffer_set_string(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 4);

    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE string = rb_str_to_str(argv[0]);

    const void *source_base = RSTRING_PTR(string);
    size_t source_size = RSTRING_LEN(string);

    return io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
}

#set_value(type, offset, value) ⇒ offset

Write to a buffer a value of type at offset. type should be one of symbols described in #get_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)
# => 1

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
[ GitHub ]

  
# File 'io_buffer.c', line 2071

static VALUE
io_buffer_set_value(VALUE self, VALUE type, VALUE _offset, VALUE value)
{
    void *base;
    size_t size;
    size_t offset = io_buffer_extract_offset(_offset);

    rb_io_buffer_get_bytes_for_writing(self, &base, &size);

    rb_io_buffer_set_value(base, size, RB_SYM2ID(type), &offset, value);

    return SIZET2NUM(offset);
}

#set_values(buffer_types, offset, values) ⇒ 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.

Example:

buffer = IO::Buffer.new(8)
buffer.set_values([:U8, :U16], 0, [1, 2])
buffer
# =>
# #<IO::Buffer 0x696f717561746978+8 INTERNAL>
# 0x00000000  01 00 02 00 00 00 00 00                         ........
[ GitHub ]

  
# File 'io_buffer.c', line 2101

static VALUE
io_buffer_set_values(VALUE self, VALUE buffer_types, VALUE _offset, VALUE values)
{
    if (!RB_TYPE_P(buffer_types, T_ARRAY)) {
        rb_raise(rb_eArgError, "Argument buffer_types should be an array!");
    }

    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!");
    }

    size_t offset = io_buffer_extract_offset(_offset);

    void *base;
    size_t size;
    rb_io_buffer_get_bytes_for_writing(self, &base, &size);

    for (long 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(base, size, RB_SYM2ID(type), &offset, value);
    }

    return SIZET2NUM(offset);
}

#sizeInteger

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.

[ GitHub ]

  
# File 'io_buffer.c', line 882

VALUE
rb_io_buffer_size(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    return SIZET2NUM(buffer->size);
}

#slice([offset, [length]]) ⇒ Buffer

Produce another Buffer which is a slice (or view into) the current one starting at offset bytes and going for length bytes.

The slicing happens without copying of memory, and the slice keeps being associated with the original buffer’s source (string, or file), if any.

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 offsetlength</tt> is out of the current buffer’s bounds.

Example:

string = 'test'
buffer = IO::Buffer.for(string)

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 SLICE>
# 0x00000000  74 6f 73 74                                     tost

# ...and original string
string
# => tost
[ GitHub ]

  
# File 'io_buffer.c', line 1308

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 *buffer = io_buffer_extract_offset_length(self, argc, argv, &offset, &length);

    return rb_io_buffer_slice(buffer, self, offset, length);
}

#to_sString

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>
[ GitHub ]

  
# File 'io_buffer.c', line 751

VALUE
rb_io_buffer_to_s(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE result = rb_str_new_cstr("#<");

    rb_str_append(result, rb_class_name(CLASS_OF(self)));
    rb_str_catf(result, " %p+%"PRIdSIZE, buffer->base, buffer->size);

    if (buffer->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_SHARED) {
        rb_str_cat2(result, " SHARED");
    }

    if (buffer->flags & RB_IO_BUFFER_LOCKED) {
        rb_str_cat2(result, " LOCKED");
    }

    if (buffer->flags & RB_IO_BUFFER_READONLY) {
        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, ">");
}

#transferBuffer

Transfers ownership to a new buffer, deallocating the current one.

Example:

buffer = IO::Buffer.new('test')
other = buffer.transfer
other
# =>
# #<IO::Buffer 0x00007f136a15f7b0+4 SLICE>
# 0x00000000  74 65 73 74                                     test
buffer
# =>
# #<IO::Buffer 0x0000000000000000+0 NULL>
buffer.null?
# => true
[ GitHub ]

  
# File 'io_buffer.c', line 1416

VALUE
rb_io_buffer_transfer(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    if (buffer->flags & RB_IO_BUFFER_LOCKED) {
        rb_raise(rb_eIOBufferLockedError, "Cannot transfer ownership of locked buffer!");
    }

    VALUE instance = rb_io_buffer_type_allocate(rb_class_of(self));
    struct rb_io_buffer *transferred;
    TypedData_Get_Struct(instance, struct rb_io_buffer, &rb_io_buffer_type, transferred);

    *transferred = *buffer;
    io_buffer_zero(buffer);

    return instance;
}

#values(buffer_type, [offset, [count]]) ⇒ Array

Returns an array of values of buffer_type starting from offset.

If count is given, only count values will be returned.

Example:

IO::Buffer.for("Hello World").values(:U8, 2, 2)
# => [108, 108]
[ GitHub ]

  
# File 'io_buffer.c', line 1943

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 = RB_SYM2ID(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;
}

#write(io, [length, [offset]]) ⇒ written length, -errno

Write at least length bytes from the buffer starting at offset, into the io. If an error occurs, return -errno.

If length is not given or nil, it defaults to the size of the buffer minus the offset, i.e. the entire buffer.

If length is zero, exactly one write operation will occur.

If offset is not given, it defaults to zero, i.e. the beginning of the buffer.

out = File.open('output.txt', 'wb')
IO::Buffer.for('1234567').write(out, 3)

This leads to 123 being written into output.txt

[ GitHub ]

  
# File 'io_buffer.c', line 2825

static VALUE
io_buffer_write(int argc, VALUE *argv, VALUE self)
{
    rb_check_arity(argc, 1, 3);

    VALUE io = argv[0];

    size_t length, offset;
    io_buffer_extract_length_offset(self, argc-1, argv+1, &length, &offset);

    return rb_io_buffer_write(self, io, length, offset);
}

#xor!(mask) ⇒ Buffer

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
[ GitHub ]

  
# File 'io_buffer.c', line 3235

static VALUE
io_buffer_xor_inplace(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);
    io_buffer_check_overlaps(buffer, mask_buffer);

    void *base;
    size_t size;
    io_buffer_get_bytes_for_writing(buffer, &base, &size);

    memory_xor_inplace(base, size, mask_buffer->base, mask_buffer->size);

    return self;
}

#|(mask) ⇒ Buffer

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
[ GitHub ]

  
# File 'io_buffer.c', line 3006

static VALUE
io_buffer_or(VALUE self, VALUE mask)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    struct rb_io_buffer *mask_buffer = NULL;
    TypedData_Get_Struct(mask, struct rb_io_buffer, &rb_io_buffer_type, mask_buffer);

    io_buffer_check_mask(mask_buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_or(output_buffer->base, buffer->base, buffer->size, mask_buffer->base, mask_buffer->size);

    return output;
}

#~Buffer

Generate a new buffer the same size as the source by applying the binary 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                   ..........
[ GitHub ]

  
# File 'io_buffer.c', line 3086

static VALUE
io_buffer_not(VALUE self)
{
    struct rb_io_buffer *buffer = NULL;
    TypedData_Get_Struct(self, struct rb_io_buffer, &rb_io_buffer_type, buffer);

    VALUE output = rb_io_buffer_new(NULL, buffer->size, io_flags_for_size(buffer->size));
    struct rb_io_buffer *output_buffer = NULL;
    TypedData_Get_Struct(output, struct rb_io_buffer, &rb_io_buffer_type, output_buffer);

    memory_not(output_buffer->base, buffer->base, buffer->size);

    return output;
}