Module Iobuf

A non-moving (in the GC sense) contiguous range of bytes, useful for I/O operations.

An iobuf consists of:

All iobuf operations are restricted to operate within the limits. Initially, the window of an iobuf is identical to its limits. A phantom type, the "seek" permission, controls whether or not code is allowed to change the limits and window. With seek permission, the limits can be narrowed, but can never be widened, and the window can be set to an arbitrary subrange of the limits.

A phantom type controls whether code can read and write bytes in the bigstring (within the limits) or can only read them.

To present a restricted view of an iobuf to a client, one can create a sub-iobuf or add a type constraint.

Functions operate on the window unless the documentation or naming indicates otherwise.

module type Basic = sig ... end
module type Bin_io = sig ... end
module type Blit = sig ... end
module type Blit_consume = sig ... end
module type Blit_fill = sig ... end
module type Blit_consume_and_fill = sig ... end
module type Expert = sig ... end
module type Hexdump_all = sig ... end
module type Itoa = sig ... end
module type Date_string = sig ... end
module type Accessors_common = sig ... end

Collections of access functions. These abstract over Iobuf.Consume, Iobuf.Fill, Iobuf.Peek, and Iobuf.Poke.

module type Accessors_read = sig ... end

('d, 'w, 'l) Iobuf.t accessor function manipulating 'a, either writing it to the iobuf or reading it from the iobuf.

module type Accessors_write = sig ... end

('d, 'w, 'l) Iobuf.t accessor function manipulating 'a, either writing it to the iobuf or reading it from the iobuf.

The src_pos argument of Core.Blit.blit doesn't make sense here.

type ('src, 'dst) consuming_blit = src:'src @ local -> (dst:'dst @ local -> (dst_pos:int -> (len:int -> unit) @ local) @ local) @ local
type ('src, 'dst) consuming_blito = src:'src @ local -> (?src_len:int -> (dst:'dst @ local -> (?dst_pos:int -> (unit -> unit) @ local) @ local) @ local) @ local
module type Consuming_blit = sig ... end
module type Consume_safe = sig ... end
module type Fill_safe = sig ... end
module type Peek_common = sig ... end
module type Peek_safe = sig ... end
module type Poke_safe = sig ... end
module type Consume_unsafe = sig ... end
module type Fill_unsafe = sig ... end
module type Peek_unsafe = sig ... end
module type Poke_unsafe = sig ... end
module Repr : sig ... end
type (-'data_perm_read_write, +'seek_permission, 'buffer_locality) t = private 'buffer_locality Repr.t

The first type parameter controls whether the iobuf can be written to. The second type parameter controls whether the window and limits can be changed. The third type parameter controls whether the Bigstring.t buffer must be global or not.

See the Perms module for information on how the first type parameter is used.

To allow no_seek or seek access, a function's type uses _ rather than no_seek as the type argument to t. Using _ allows the function to be directly applied to either permission. Using a specific permission would require code to use coercion :>.

There is no t_of_sexp. One should use Iobuf.Hexdump.t_of_sexp or @sexp.opaque as desired.

include Basic with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t
type nonrec seek
val sexp_of_seek : seek -> Sexplib0.Sexp.t
type nonrec no_seek
val sexp_of_no_seek : no_seek -> Sexplib0.Sexp.t
type nonrec global = Core.Modes.At_locality.global
val sexp_of_global : global -> Sexplib0.Sexp.t
type nonrec local = Core.Modes.At_locality.local
val sexp_of_local : local -> Sexplib0.Sexp.t
val globalize : [ `deprecated ]
  • deprecated [since 2025-09] use [Iobuf.globalize_shared] instead

Globalize as if t had zero type parameters. Works because the parameters are phantom types, and do not represent actual values that need to be globalized.

val globalize_shared : ('rw, _, global) t @ local -> ('rw, _, global) t
val globalize_copied : ('rw, _, _) t @ local -> ('rw, _, _) t
module With_shallow_sexp : sig ... end
val invariant : (_, _, _) t -> unit

Creation

val create : len:int -> (_, _, _) t

create ~len creates a new iobuf, backed by a bigstring of length len, with the limits and window set to the entire bigstring.

val empty : (Core.read, no_seek, global) t

empty is an immutable t of size 0.

val of_bigstring : ?pos:int @ local -> (?len:int @ local -> (Core.Bigstring.t -> ([< Core.read_write ], _, _) t) @ local) @ local

of_bigstring bigstring ~pos ~len returns an iobuf backed by bigstring, with the window and limits specified starting at pos and of length len.

forbid immutable to prevent aliasing

val of_bigstring_sub : pos:int -> len:int -> Core.Bigstring.t -> ([< Core.read_write ], _, _) t

More efficient than the above (optional arguments are costly).

val unsafe_of_bigstring_sub : pos:int -> len:int -> Core.Bigstring.t -> ([< Core.read_write ], _, _) t

Yet more efficient than the above, by skipping bounds checks.

val of_string : string @ local -> (_, _, _) t

of_string s returns a new iobuf whose contents are s. The stack-allocating version still performs global allocation of the backing buffer.

val sub_shared : ?pos:int @ local -> (?len:int @ local -> (('d, _, global) t @ local -> ('d, _, global) t) @ local) @ local

sub_shared t ~pos ~len returns a new iobuf with limits and window set to the subrange of t's window specified by pos and len. sub_shared preserves data permissions, but allows arbitrary seek permissions on the resulting iobuf.

val sub_shared__local : ?pos:int @ local -> (?len:int @ local -> (('d, _, 'loc) t @ local -> ('d, _, 'loc) t @ local) @ local) @ local

sub_shared__local is like sub_shared, but it allocates the iobuf record locally.

val unsafe_sub_shared : pos:int -> len:int -> ('d, _, 'loc) t @ local -> ('d, _, 'loc) t @ local

More efficient than the above (optional arguments are costly).

val copy : (_, _, _) t @ local -> (_, _, _) t

copy t returns a new iobuf whose contents are the same as those in the window of t.

val clone : (_, _, _) t @ local -> (_, _, _) t

clone t returns a new iobuf that is a deep-copy of t including an exact copy of the underlying buffer and bounds. This means data outside the window is copied as well.

val transfer : src:([> Core.read ], _, _) t @ local -> (dst:([> Core.write ], seek, _) t @ local -> unit) @ local

transfer ~src ~dst makes the window of dst into a copy of the window of src. Like blito, transfer will raise if Iobuf.length dst < Iobuf.length src.

It is a utility function defined as reset dst; blito ~src ~dst; flip_lo dst.

val set_bounds_and_buffer : src:([> Core.write ] as 'data, _, global) t @ local -> (dst:('data, seek, _) t @ local -> unit) @ local

set_bounds_and_buffer ~src ~dst copies bounds metadata (i.e., limits and window) and shallowly copies the buffer (data pointer) from src to dst. It does not access data, but does allow access through dst. This makes dst an alias of src.

Because set_bounds_and_buffer creates an alias, we disallow immutable src and dst using [> write]. Otherwise, one of src or dst could be read_write :> read and the other immutable :> read, which would allow you to write the immutable alias's data through the read_write alias.

set_bounds_and_buffer is typically used with a frame iobuf that need only be allocated once. This frame can be updated repeatedly and handed to users, without further allocation. Allocation-sensitive applications need this.

val set_bounds_and_buffer_sub : pos:int -> len:int -> src:([> Core.write ] as 'data, _, global) t @ local -> (dst:('data, seek, _) t @ local -> unit) @ local

set_bounds_and_buffer_sub ~pos ~len ~src ~dst is a more efficient version of set_bounds_and_buffer ~src:(Iobuf.sub_shared ~pos ~len src) ~dst.

set_bounds_and_buffer ~src ~dst is not the same as set_bounds_and_buffer_sub ~dst ~src ~len:(Iobuf.length src) because the limits are narrowed in the latter case.

~len and ~pos are mandatory for performance reasons, in concert with @@inline. If they were optional, allocation would be necessary when passing a non-default, non-constant value, which is an important use case.

Generalization

One may wonder why you'd want to call no_seek, given that a cast is already possible, e.g., t : (_, seek, _) t :> (_, no_seek, _) t. It turns out that if you want to define some f : (_, _, _) t -> unit of your own that can be conveniently applied to seek iobufs without the user having to cast seek up, you need this no_seek function.

read_only is more of a historical convenience now that read_write is a polymorphic variant, as one can now explicitly specify the general type for an argument with something like t : (_ perms, _) t :> (read, _) t.

val read_only : ([> Core.read ], 's, 'l) t -> (Core.read, 's, 'l) t
val read_only__local : ([> Core.read ], 's, 'l) t @ local -> (Core.read, 's, 'l) t @ local
val no_seek : ('r, _, 'l) t -> ('r, no_seek, 'l) t
val no_seek__local : ('r, _, 'l) t @ local -> ('r, no_seek, 'l) t @ local

Accessors

val capacity : (_, _, _) t @ local -> int

capacity t returns the size of t's limits subrange. The capacity of an iobuf can be reduced via narrow.

val length : (_, _, _) t @ local -> int

length t returns the size of t's window.

val length_lo : (_, _, _) t @ local -> int

length_lo t returns the length that t's window would have after calling flip_lo, without actually changing the window. This is the number of bytes between the lower limit and the start of the window.

When you're writing to the window, you can think of this as the number of bytes already written. When reading from the window, this can mean the number of bytes already consumed.

This is equivalent to:

  Iobuf.Expert.(lo t - lo_min t)

.

val length_hi : (_, _, _) t @ local -> int

length_hi t returns the length that t's window would have after calling flip_hi, without actually changing the window. This is the number of bytes between the end of the window and the upper limit of the buffer.

This is equivalent to:

  Iobuf.Expert.(hi_max t - hi t)

.

val is_empty : (_, _, _) t @ local -> bool

is_empty t is length t = 0.

Changing the limits

val narrow : (_, seek, _) t @ local -> unit

narrow t sets t's limits to the current window.

val narrow_lo : (_, seek, _) t @ local -> unit

narrow_lo t sets t's lower limit to the beginning of the current window.

val narrow_hi : (_, seek, _) t @ local -> unit

narrow_hi t sets t's upper limit to the end of the current window.

Comparison

val memcmp : (_, _, _) t @ local -> ((_, _, _) t @ local -> int) @ local

memcmp a b first compares the length of a and b's windows and then compares the bytes in the windows for equivalence.

Changing the window

One can call Lo_bound.window t to get a snapshot of the lower bound of the window, and then later restore that snapshot with Lo_bound.restore. This is useful for speculatively parsing, and then rewinding when there isn't enough data to finish.

Similarly for Hi_bound.window and Lo_bound.restore.

Using a snapshot with a different iobuf, even a sub iobuf of the snapshotted one, has unspecified results. An exception may be raised, or a silent error may occur. However, the safety guarantees of the iobuf will not be violated, i.e., the attempt will not enlarge the limits of the subject iobuf.

module type Bound = sig ... end
module Lo_bound : Bound
module Hi_bound : Bound
val advance : (_, seek, _) t @ local -> (int -> unit) @ local

advance t amount advances the lower bound of the window by amount. It is an error to advance past the upper bound of the window or the lower limit.

val unsafe_advance : (_, seek, _) t @ local -> (int -> unit) @ local

unsafe_advance is like advance but with no bounds checking, so incorrect usage can easily cause segfaults.

val resize : (_, seek, _) t @ local -> (len:int -> unit) @ local

resize t sets the length of t's window, provided it does not exceed limits.

val unsafe_resize : (_, seek, _) t @ local -> (len:int -> unit) @ local

unsafe_resize is like resize but with no bounds checking, so incorrect usage can easily cause segfaults.

val rewind : (_, seek, _) t @ local -> unit

rewind t sets the lower bound of the window to the lower limit.

val reset : (_, seek, _) t @ local -> unit

reset t sets the window to the limits.

val flip_lo : (_, seek, _) t @ local -> unit

flip_lo t sets the window to range from the lower limit to the lower bound of the old window. This is typically called after a series of Fills, to reposition the window in preparation to Consume the newly written data.

The bounded version narrows the effective limit. This can preserve some data near the limit, such as a hypothetical packet header (in the case of bounded_flip_lo) or unfilled suffix of a buffer (in bounded_flip_hi).

val bounded_flip_lo : (_, seek, _) t @ local -> (Lo_bound.t -> unit) @ local
val compact : (Core.read_write, seek, _) t @ local -> unit

compact t copies data from the window to the lower limit of the iobuf and sets the window to range from the end of the copied data to the upper limit. This is typically called after a series of Consumes to save unread data and prepare for the next series of Fills and flip_lo.

val bounded_compact : (Core.read_write, seek, _) t @ local -> (Lo_bound.t -> (Hi_bound.t -> unit) @ local) @ local
val flip_hi : (_, seek, _) t @ local -> unit

flip_hi t sets the window to range from the the upper bound of the current window to the upper limit. This operation is dual to flip_lo and is typically called when the data in the current (narrowed) window has been processed and the window needs to be positioned over the remaining data in the buffer. For example:

  (* ... determine initial_data_len ... *)
  Iobuf.resize buf ~len:initial_data_len;
  (* ... and process initial data ... *)
  Iobuf.flip_hi buf

Now the window of buf ranges over the remainder of the data.

val bounded_flip_hi : (_, seek, _) t @ local -> (Hi_bound.t -> unit) @ local
val protect_window_bounds_and_buffer : ('rw, no_seek, global) t -> f:(('rw, seek, global) t -> 'a) @ local -> 'a

protect_window_bounds_and_buffer t ~f calls f t with t's bounds set to its current window, and restores t's window, bounds, and buffer afterward.

val protect_window_bounds_and_buffer__local : ('rw, no_seek, global) t -> f:(('rw, seek, global) t -> 'a @ local) @ local -> 'a @ local

protect_window_bounds_and_buffer__local is similar to protect_window_bounds_and_buffer except that it returns a local value

val protect_window_bounds_and_buffer_1 : ('rw, no_seek, global) t -> 'a -> f:(('rw, seek, global) t -> ('a -> 'b) @ local) @ local -> 'b

protect_window_bounds_and_buffer_1 t x ~f is a more efficient version of protect_window_bounds_and_buffer t ~f:(fun t -> f t x).

val protect_window_bounds_and_buffer_2 : ('rw, no_seek, global) t -> 'a -> 'b -> f:(('rw, seek, global) t -> ('a -> ('b -> 'c) @ local) @ local) @ local -> 'c

protect_window_bounds_and_buffer_2 t x y ~f is a more efficient version of protect_window_bounds_and_buffer t ~f:(fun t -> f t x y).

val protect_window_bounds_and_buffer_3 : ('rw, no_seek, global) t -> 'a -> 'b -> 'c -> f: (('rw, seek, global) t -> ('a -> ('b -> ('c -> 'd) @ local) @ local) @ local) @ local -> 'd

protect_window_bounds_and_buffer_3 t x y z ~f is a more efficient version of protect_window_bounds_and_buffer t ~f:(fun t -> f t x y z).

Getting and setting data

"consume" and "fill" functions access data at the lower bound of the window and advance the lower bound of the window. "peek" and "poke" functions access data but do not advance the window.

val to_string : ?len:int -> ([> Core.read ], _, _) t @ local -> string

to_string t returns the bytes in t as a string. It does not alter the window.

val to_string_hum : ?max_lines:int -> (_, _, _) t @ local -> string

Equivalent to Hexdump.to_string_hum. Renders t's windows and limits.

val to_bytes : ?len:int -> (_, _, _) t @ local -> Core.Bytes.t

to_bytes t returns the bytes in t as a bytes. It does not alter the window.

val of_bytes : Core.Bytes.t -> (_, _, _) t

of_bytes b returns a new iobuf whose contents is b.

val memset : (Core.read_write, _, _) t @ local -> (pos:int -> (len:int -> (char -> unit) @ local) @ local) @ local

memset t ~pos ~len c fills t with c within the range [pos, pos + len).

val unsafe_memset : (Core.read_write, _, _) t @ local -> (pos:int -> (len:int -> (char -> unit) @ local) @ local) @ local

unsafe_memset t ~pos ~len c fills t with c within the range [pos, pos + len), without bounds checks.

val zero : (Core.read_write, _, _) t @ local -> unit

memsets a buffer to zero.

val concat : ([> Core.read ], _, _) t array -> (_, _, _) t

Create a new iobuf whose contents are the appended contents of the passed array.

val contains : ([> Core.read ], _, _) t -> substring:Core.Bigstring.t @ local -> bool
module Window : Core.Hexdump.S3 with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

Provides a Window.Hexdump submodule that renders the contents of t's window.

module Limits : Core.Hexdump.S3 with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

Provides a Limits.Hexdump submodule that renders the contents of t's limits.

module Hexdump : Hexdump_all with type ('rw, 'seek, 'loc) t = ('rw, 'seek, 'loc) t

Provides a Hexdump submodule that renders the contents of t's window and limits using indices relative to the limits.

module Debug : sig ... end

Provides a Debug.Hexdump submodule that renders the contents of t's window, limits, and underlying bigstring using indices relative to the bigstring.

module Blit : Blit with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

Blit copies between iobufs and advances neither src nor dst.

module Blit_consume : Blit_consume with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

Blit_consume copies between iobufs and advances src but does not advance dst.

module Blit_fill : Blit_fill with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

Blit_fill copies between iobufs and advances dst but does not advance src.

module Blit_consume_and_fill : Blit_consume_and_fill with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

Blit_consume_and_fill copies between iobufs and advances both src and dst.

module Itoa : Itoa
module Consume : Consume_safe with type ('rw, 'seek, 'loc) iobuf := ('rw, 'seek, 'loc) t

Consume.string t ~len reads len characters (all, by default) from t into a new string and advances the lower bound of the window accordingly.

module Fill : Fill_safe with type ('rw, 'seek, 'loc) iobuf := ('rw, 'seek, 'loc) t

Fill.bin_prot X.bin_write_t t x writes x to t in bin-prot form, advancing past the bytes written.

module Peek : Peek_safe with type ('rw, 'seek, 'loc) iobuf := ('rw, 'seek, 'loc) t

Peek and Poke functions access a value at pos from the lower bound of the window and do not advance.

module Poke : Poke_safe with type ('rw, 'seek, 'loc) iobuf := ('rw, 'seek, 'loc) t

Poke.bin_prot X.bin_write_t t x writes x to the beginning of t in binary form without advancing. You can use X.bin_size_t to tell how long it was. X.bin_write_t is only allowed to write that portion of the buffer you have access to.

module Unsafe : sig ... end

Unsafe has submodules that are like their corresponding module, except with no range checks. Hence, mistaken uses can cause segfaults. Be careful!

module Bin_io : Bin_io with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

Expert

module Expert : Expert with type ('rw, 'seek, 'loc) t := ('rw, 'seek, 'loc) t

The Expert module is for building efficient out-of-module Iobuf abstractions.