class IO::Buffer - Documentation for Ruby 4.1
Description: class IO::Buffer: output. new, fill it with buffer using #copy or #set_value, #set_string, get buffer with #get_string or write it directly to some file with #write
Keywords: ruby,class,IO::Buffer

🔍

No recent searches

# class IO::Buffer

`IO::Buffer` is a efficient zero-copy buffer for input/output. There are typical use cases:

- Create an empty buffer with `::new`, fill it with buffer using `copy` or `set_value`, `set_string`, get buffer with `get_string` or write it directly to some file with `write`.

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

- Create a string of a fixed size with `::string`, then `read` into it, or modify it using `set_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.

## 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_value` for 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_value` for 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_value` for more details.

LOCKED

  Indicates that the memory in the buffer is locked and cannot be resized or freed. See `locked?` and `locked` for more details.

MAPPED

  Indicates that the memory in the buffer is mapped by the operating system. See `mapped?` for more details.

NETWORK_ENDIAN

  Refers to network byte order, which is the same as big endian. See `get_value` for 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.

### Public Class Methods

IO::Buffer.for(string) → readonly io_buffer
IO::Buffer.for(string) {|io_buffer| ... read/write io_buffer ...}
Source

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

Creates a zero-copy `IO::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
    # => "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

IO::Buffer.map(file, [size, [offset, [flags]]]) → io_buffer
Source

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

        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 can't be larger than 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 can't be negative!");
            }
            if (UNLIKELY(offset >= file_size)) {
                rb_raise(rb_eArgError, "Offset too large!");
            }
            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)) {
                rb_raise(rb_eArgError, "Offset too large!");
            }
        }

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

        return rb_io_buffer_map(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. Trying to map an empty file or specify `size` of 0 will raise an error. Valid values for `offset` are system-dependent.

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.

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.

IO::Buffer.new([size = DEFAULT_SIZE, [flags = 0]]) → io_buffer
Source

    VALUE
    rb_io_buffer_initialize(int argc, VALUE *argv, VALUE self)
    {
        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 = io_buffer_extract_flags(argv[1]);
        }
        else {
            flags |= io_flags_for_size(size);
        }

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

        return self;
    }

Create a new zero-filled `IO::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 `IO::Buffer::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 `IO::Buffer::MAPPED` as a second parameter.

    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

size_of(buffer_type) → byte size
size_of(array of buffer_type) → byte size
Source

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

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

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

IO::Buffer.string(length) {|io_buffer| ... read/write io_buffer ...} → string
Source

    VALUE
    rb_io_buffer_type_string(VALUE klass, VALUE length)
    {
        VALUE string = rb_str_new(NULL, RB_NUM2LONG(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 & mask → io_buffer
Source

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

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.

<=>(other) → true or false
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);
        }

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

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

source ^ mask → io_buffer
Source

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

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 | mask → io_buffer
Source

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

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 → io_buffer
Source

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

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

and!(mask) → io_buffer
Source

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

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.

bit_count([offset, [length]]) → integer
Source

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

        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

clear(value = 0, [offset, [length]]) → self
Source

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

copy(source, [offset, [length, [source_offset]]]) → size
Source

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

        VALUE result = io_buffer_copy_from(buffer, source_base, source_size, argc-1, argv+1);
        RB_GC_GUARD(source);
        return result;
    }

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

each(buffer_type, [offset, [count]]) {|offset, value| ...} → self
each(buffer_type, [offset, [count]]) → enumerator
Source

    static VALUE
    io_buffer_each(int argc, VALUE *argv, VALUE self)
    {
        RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);

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

        for (size_t i = 0; i < count; i++) {
            size_t current_offset = offset;
            VALUE value = rb_io_buffer_get_value(base, size, buffer_type, &offset);
            rb_yield_values(2, SIZET2NUM(current_offset), value);
        }

        return 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

each_byte([offset, [count]]) {|byte| ...} → self
each_byte([offset, [count]]) → enumerator
Source

    static VALUE
    io_buffer_each_byte(int argc, VALUE *argv, VALUE self)
    {
        RETURN_ENUMERATOR_KW(self, argc, argv, RB_NO_KEYWORDS);

        const void *base;
        size_t size;

        rb_io_buffer_get_bytes_for_reading(self, &base, &size);

        size_t offset, count;
        io_buffer_extract_offset_count(RB_IO_BUFFER_DATA_TYPE_U8, size, argc, argv, &offset, &count);

        for (size_t i = 0; i < count; i++) {
            unsigned char *value = (unsigned char *)base + i + offset;
            rb_yield(RB_INT2FIX(*value));
        }

        return 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

empty? → true or false
Source

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

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.)

external? → true or false
Source

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

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.

free → self
Source

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

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 be performed on it.

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

    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

get_string([offset, [length, [encoding]]]) → string
Source

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

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"

get_value(buffer_type, offset) → numeric
Source

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

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

get_values(buffer_types, offset) → array
Source

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

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]

hexdump([offset, [length, [width]]]) → string
Source

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

        if (io_buffer_validate(buffer) && buffer->base) {
            result = rb_str_buf_new(io_buffer_hexdump_output_size(width, length, 1));

            io_buffer_hexdump(result, width, buffer->base, offset+length, offset, 1);
        }

        return result;
    }

Returns a human-readable string representation of the buffer. The exact format is subject to change.

    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

dup → io_buffer
clone → io_buffer
Source

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

        const void *source_base;
        size_t source_size;

        rb_io_buffer_get_bytes_for_reading(source, &source_base, &source_size);

        io_buffer_initialize(self, buffer, NULL, source_size, io_flags_for_size(source_size), Qnil);

        VALUE result = io_buffer_copy_from(buffer, source_base, source_size, 0, NULL);
        RB_GC_GUARD(source);
        return result;
    }

Make an internal copy of the source buffer. Updates to the copy will not affect the source buffer.

    source = IO::Buffer.for("Hello World")
    # =>
    # #<IO::Buffer 0x00007fd598466830+11 EXTERNAL READONLY SLICE>
    # 0x00000000  48 65 6c 6c 6f 20 57 6f 72 6c 64                Hello World
    buffer = source.dup
    # =>
    # #<IO::Buffer 0x0000558cbec03320+11 INTERNAL>
    # 0x00000000  48 65 6c 6c 6f 20 57 6f 72 6c 64                Hello World

inspect → string
Source

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

        VALUE result = rb_io_buffer_to_s(self);

        if (io_buffer_validate(buffer)) {
            // 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, buffer->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

internal? → true or false
Source

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

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 `IO::Buffer::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.

locked { ... }
Source

    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;

        return rb_ensure(rb_yield, self, rb_io_buffer_locked_ensure, self);
    }

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.

    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

locked? → true or false
Source

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

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.

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

mapped? → true or false
Source

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

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

Mapped buffers are either anonymous, if created by `::new` with the `IO::Buffer::MAPPED` flag or if the size was at least `IO::Buffer::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.

not! → io_buffer
Source

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

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

null? → true or false
Source

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

If the buffer was freed with `free`, transferred with `transfer`, or was never allocated in the first place.

    buffer = IO::Buffer.new(0)
    buffer.null? #=> true

    buffer = IO::Buffer.new(4)
    buffer.null? #=> false
    buffer.free
    buffer.null? #=> true

or!(mask) → io_buffer
Source

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

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

pread(io, from, [length, [offset]]) → read length or -errno
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 length, offset;
        io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset);

        return rb_io_buffer_pread(self, io, from, length, offset);
    }

Read at least `length` bytes from the `io` starting at the specified `from` position, 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 `pread` 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

      # 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

private? → true or false
Source

    static VALUE
    rb_io_buffer_private_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_PRIVATE);
    }

If the buffer is private, meaning modifications to the buffer will not be replicated to the underlying file mapping.

    # Create a test file:
    File.write('test.txt', 'test')

    # Create a private mapping from the given file. Note that the file here
    # is opened in read-only mode, but it doesn't matter due to the private
    # mapping:
    buffer = IO::Buffer.map(File.open('test.txt'), nil, 0, IO::Buffer::PRIVATE)
    # => #<IO::Buffer 0x00007fce63f11000+4 MAPPED PRIVATE>

    # Write to the buffer (invoking CoW of the underlying file buffer):
    buffer.set_string('b', 0)
    # => 1

    # The file itself is not modified:
    File.read('test.txt')
    # => "test"

pwrite(io, from, [length, [offset]]) → written length or -errno
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 length, offset;
        io_buffer_extract_length_offset(self, argc-2, argv+2, &length, &offset);

        return rb_io_buffer_pwrite(self, io, from, length, offset);
    }

Write at least `length` bytes from the buffer starting at `offset`, into the `io` starting at the specified `from` position. 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 `pwrite` operation will occur.

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

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, 3, 1)

This leads to `234` (3 bytes, starting from position 1) being written into `output.txt`, starting from file position 2.

read(io, [length, [offset]]) → read length or -errno
Source

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

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

readonly? → true or false
Source

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

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.

resize(new_size) → self
Source

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

        return 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.

set_string(string, [offset, [length, [source_offset]]]) → size
Source

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

        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.

set_value(type, offset, value) → offset
Source

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

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

set_values(buffer_types, offset, values) → offset
Source

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

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.

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

shared? → true or false
Source

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

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

    # Create a test file:
    File.write('test.txt', 'test')

    # Create a shared mapping from the given file, the file must be opened in
    # read-write mode unless we also specify IO::Buffer::READONLY:
    buffer = IO::Buffer.map(File.open('test.txt', 'r+'), nil, 0)
    # => #<IO::Buffer 0x00007f1bffd5e000+4 EXTERNAL MAPPED SHARED>

    # Write to the buffer, which will modify the mapped file:
    buffer.set_string('b', 0)
    # => 1

    # The file itself is modified:
    File.read('test.txt')
    # => "best"

size → integer
Source

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

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.

slice([offset, [length]]) → io_buffer
Source

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

Produce another `IO::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 `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

to_s → string
Source

    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_FILE) {
            rb_str_cat2(result, " FILE");
        }

        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_PRIVATE) {
            rb_str_cat2(result, " PRIVATE");
        }

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

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>

transfer → new_io_buffer
Source

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

Transfers ownership of the underlying memory to a new buffer, causing the current buffer to become uninitialized.

    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

valid? → true or false
Source

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

Returns whether the buffer buffer is accessible.

A buffer becomes invalid if it is a slice of another buffer (or string) which has been freed or re-allocated at a different address.

values(buffer_type, [offset, [count]]) → array
Source

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

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]

write(io, [length, [offset]]) → written length or -errno
Source

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

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`

xor!(mask) → io_buffer
Source

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

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