abstract class IO

Overview

The IO class is the basis for all input and output in Crystal.

This class is inherited by types like File, Socket and IO::Memory and provides many useful methods for reading from and writing to an IO, like #print, #puts, #gets and #printf.

The only requirement for a type including the IO module is to define these two methods:

For example, this is a simple IO on top of a Bytes:

class SimpleSliceIO < IO
  def initialize(@slice : Bytes)
  end

  def read(slice : Bytes)
    slice.size.times { |i| slice[i] = @slice[i] }
    @slice += slice.size
    slice.size
  end

  def write(slice : Bytes) : Nil
    slice.size.times { |i| @slice[i] = slice[i] }
    @slice += slice.size
  end
end

slice = Slice.new(9) { |i| ('a'.ord + i).to_u8 }
String.new(slice) # => "abcdefghi"

io = SimpleSliceIO.new(slice)
io.gets(3) # => "abc"
io.print "xyz"
String.new(slice) # => "abcxyzghi"

Encoding

An IO can be set an encoding with the #set_encoding method. When this is set, all string operations (#gets, #gets_to_end, #read_char, <<, #print, #puts #printf) will write in the given encoding, and read from the given encoding. Byte operations (#read, #write, #read_byte, #write_byte, #getb_to_end) never do encoding/decoding operations.

If an encoding is not set, the default one is UTF-8.

Mixing string and byte operations might not give correct results and should be avoided, as string operations might need to read extra bytes in order to get characters in the given encoding.

Direct Known Subclasses

Defined in:

io.cr
io/encoding.cr:5
io/encoding.cr:28
io/error.cr

Constant Summary

DEFAULT_BUFFER_SIZE = 32768

Default size used for generic stream buffers.

Class Method Summary

Instance Method Summary

Instance methods inherited from class Reference

==(other : self)
==(other : JSON::Any)
==(other : YAML::Any)
==(other)
==
, dup dup, hash(hasher) hash, initialize initialize, inspect(io : IO) : Nil inspect, object_id : UInt64 object_id, pretty_print(pp) : Nil pretty_print, same?(other : Reference) : Bool
same?(other : Nil)
same?
, to_s(io : IO) : Nil to_s

Constructor methods inherited from class Reference

new new, unsafe_construct(address : Pointer, *args, **opts) : self unsafe_construct

Class methods inherited from class Reference

pre_initialize(address : Pointer) pre_initialize

Instance methods inherited from class Object

! : Bool !, !=(other) !=, !~(other) !~, ==(other) ==, ===(other : JSON::Any)
===(other : YAML::Any)
===(other)
===
, =~(other) =~, as(type : Class) as, as?(type : Class) as?, class class, dup dup, hash(hasher)
hash
hash
, in?(collection : Object) : Bool
in?(*values : Object) : Bool
in?
, inspect(io : IO) : Nil
inspect : String
inspect
, is_a?(type : Class) : Bool is_a?, itself itself, nil? : Bool nil?, not_nil!(message)
not_nil!
not_nil!
, pretty_inspect(width = 79, newline = "\n", indent = 0) : String pretty_inspect, pretty_print(pp : PrettyPrint) : Nil pretty_print, responds_to?(name : Symbol) : Bool responds_to?, tap(&) tap, to_json(io : IO) : Nil
to_json : String
to_json
, to_pretty_json(indent : String = " ") : String
to_pretty_json(io : IO, indent : String = " ") : Nil
to_pretty_json
, to_s(io : IO) : Nil
to_s : String
to_s
, to_yaml(io : IO) : Nil
to_yaml : String
to_yaml
, try(&) try, unsafe_as(type : T.class) forall T unsafe_as

Class methods inherited from class Object

from_json(string_or_io, root : String)
from_json(string_or_io)
from_json
, from_yaml(string_or_io : String | IO) from_yaml

Macros inherited from class Object

class_getter(*names, &block) class_getter, class_getter!(*names) class_getter!, class_getter?(*names, &block) class_getter?, class_property(*names, &block) class_property, class_property!(*names) class_property!, class_property?(*names, &block) class_property?, class_setter(*names) class_setter, def_clone def_clone, def_equals(*fields) def_equals, def_equals_and_hash(*fields) def_equals_and_hash, def_hash(*fields) def_hash, delegate(*methods, to object) delegate, forward_missing_to(delegate) forward_missing_to, getter(*names, &block) getter, getter!(*names) getter!, getter?(*names, &block) getter?, property(*names, &block) property, property!(*names) property!, property?(*names, &block) property?, setter(*names) setter

Class Method Detail

def self.copy(src, dst, limit : Int) : Int64 #

Copy at most limit bytes from src to dst.

io = IO::Memory.new "hello"
io2 = IO::Memory.new

IO.copy io, io2, 3

io2.to_s # => "hel"

def self.copy(src, dst) : Int64 #

Copy all contents from src to dst.

io = IO::Memory.new "hello"
io2 = IO::Memory.new

IO.copy io, io2

io2.to_s # => "hello"

def self.pipe(read_blocking = false, write_blocking = false) : Tuple(IO::FileDescriptor, IO::FileDescriptor) #

Creates a pair of pipe endpoints (connected to each other) and returns them as a two-element Tuple.

reader, writer = IO.pipe
writer.puts "hello"
writer.puts "world"
reader.gets # => "hello"
reader.gets # => "world"

def self.pipe(read_blocking = false, write_blocking = false, &) #

Creates a pair of pipe endpoints (connected to each other) and passes them to the given block. Both endpoints are closed after the block.

IO.pipe do |reader, writer|
  writer.puts "hello"
  writer.puts "world"
  reader.gets # => "hello"
  reader.gets # => "world"
end

def self.same_content?(stream1 : IO, stream2 : IO) : Bool #

Compares two streams stream1 to stream2 to determine if they are identical. Returns true if content are the same, false otherwise.

File.write("afile", "123")
stream1 = File.open("afile")
stream2 = IO::Memory.new("123")
IO.same_content?(stream1, stream2) # => true

Instance Method Detail

def <<(obj) : self #

Writes the given object into this IO. This ends up calling to_s(io) on the object.

io = IO::Memory.new
io << 1
io << '-'
io << "Crystal"
io.to_s # => "1-Crystal"

def close #

Closes this IO.

IO defines this is a no-op method, but including types may override.


def closed? : Bool #

Returns true if this IO is closed.

IO defines returns false, but including types may override.


def each_byte(&) : Nil #

Invokes the given block with each byte (UInt8) in this IO.

io = IO::Memory.new("aあ")
io.each_byte do |byte|
  puts byte
end

Output:

97
227
129
130

def each_byte #

Returns an Iterator for the bytes in this IO.

io = IO::Memory.new("aあ")
iter = io.each_byte
iter.next # => 97
iter.next # => 227
iter.next # => 129
iter.next # => 130

def each_char(&) : Nil #

Invokes the given block with each Char in this IO.

io = IO::Memory.new("あめ")
io.each_char do |char|
  puts char
end

Output:

あ
め

def each_char #

Returns an Iterator for the chars in this IO.

io = IO::Memory.new("あめ")
iter = io.each_char
iter.next # => 'あ'
iter.next # => 'め'

def each_line(*args, **options, &block : String -> ) : Nil #

Invokes the given block with each line in this IO, where a line is defined by the arguments passed to this method, which can be the same ones as in the #gets methods.

io = IO::Memory.new("hello\nworld")
io.each_line do |line|
  puts line
end
# output:
# hello
# world

def each_line(*args, **options) #

Returns an Iterator for the lines in this IO, where a line is defined by the arguments passed to this method, which can be the same ones as in the #gets methods.

io = IO::Memory.new("hello\nworld")
iter = io.each_line
iter.next # => "hello"
iter.next # => "world"

def encoding : String #

Returns this IO's encoding. The default is UTF-8.


def flush #

Flushes buffered data, if any.

IO defines this is a no-op method, but including types may override.


def getb_to_end : Bytes #

Reads the rest of this IO data as a writable Bytes.

io = IO::Memory.new Bytes[0, 1, 3, 6, 10, 15]
io.getb_to_end # => Bytes[0, 1, 3, 6, 10, 15]
io.getb_to_end # => Bytes[]

def gets(limit : Int, chomp = false) : String | Nil #

Reads a line of at most limit bytes from this IO. A line is terminated by the \n character. Returns nil if called at the end of this IO.

io = IO::Memory.new "hello\nworld"
io.gets(3) # => "hel"
io.gets(3) # => "lo\n"
io.gets(3) # => "wor"
io.gets(3) # => "ld"
io.gets(3) # => nil

def gets(delimiter : Char, limit : Int, chomp = false) : String | Nil #

Reads until delimiter is found, limit bytes are read, or the end of the IO is reached. Returns nil if called at the end of this IO.

io = IO::Memory.new "hello\nworld"
io.gets('o', 3)  # => "hel"
io.gets('r', 10) # => "lo\nwor"
io.gets('z', 10) # => "ld"
io.gets('w', 10) # => nil

def gets(delimiter : Char, chomp = false) : String | Nil #

Reads until delimiter is found, or the end of the IO is reached. Returns nil if called at the end of this IO.

io = IO::Memory.new "hello\nworld"
io.gets('o') # => "hello"
io.gets('r') # => "\nwor"
io.gets('z') # => "ld"
io.gets('w') # => nil

def gets(delimiter : String, chomp = false) : String | Nil #

Reads until delimiter is found or the end of the IO is reached. Returns nil if called at the end of this IO.

io = IO::Memory.new "hello\nworld"
io.gets("wo") # => "hello\nwo"
io.gets("wo") # => "rld"
io.gets("wo") # => nil

def gets(chomp = true) : String | Nil #

Reads a line from this IO. A line is terminated by the \n character. Returns nil if called at the end of this IO.

By default the newline is removed from the returned string, unless chomp is false.

io = IO::Memory.new "hello\nworld\nfoo\n"
io.gets               # => "hello"
io.gets(chomp: false) # => "world\n"
io.gets               # => "foo"
io.gets               # => nil

def gets_to_end : String #

Reads the rest of this IO data as a String.

io = IO::Memory.new "hello world"
io.gets_to_end # => "hello world"
io.gets_to_end # => ""

def peek : Bytes | Nil #

Peeks into this IO, if possible.

It returns:

  • nil if this IO isn't peekable at this moment or at all
  • an empty slice if it is, but EOF was reached
  • a non-empty slice if some data can be peeked

The returned bytes are only valid data until a next call to any method that reads from this IO is invoked.

By default this method returns nil, but IO implementations that provide buffering or wrap other IOs should override this method.


def pos #

Returns the current position (in bytes) in this IO.

The IO class raises on this method, but some subclasses, notable IO::FileDescriptor and IO::Memory implement it.

File.write("testfile", "hello")

file = File.new("testfile")
file.pos     # => 0
file.gets(2) # => "he"
file.pos     # => 2

def pos=(value) #

Sets the current position (in bytes) in this IO.

The IO class raises on this method, but some subclasses, notable IO::FileDescriptor and IO::Memory implement it.

File.write("testfile", "hello")

file = File.new("testfile")
file.pos = 3
file.gets_to_end # => "lo"

def print(obj : _) : Nil #

Same as <<.

io = IO::Memory.new
io.print 1
io.print '-'
io.print "Crystal"
io.to_s # => "1-Crystal"

def print(*objects : _) : Nil #

Writes the given objects into this IO by invoking to_s(io) on each of the objects.

io = IO::Memory.new
io.print 1, '-', "Crystal"
io.to_s # => "1-Crystal"

def printf(format_string, args : Array | Tuple) : Nil #

Writes a formatted string to this IO. For details on the format string, see top-level ::printf.


def printf(format_string, *args) : Nil #

Writes a formatted string to this IO. For details on the format string, see top-level ::printf.


def puts(string : String) : Nil #

Writes string to this IO, followed by a newline character unless the string already ends with one.

io = IO::Memory.new
io.puts "hello\n"
io.puts "world"
io.to_s # => "hello\nworld\n"

def puts(obj : _) : Nil #

Writes obj to this IO, followed by a newline character.

io = IO::Memory.new
io.puts 1
io.puts "Crystal"
io.to_s # => "1\nCrystal\n"

def puts : Nil #

Writes a newline character.

io = IO::Memory.new
io.puts
io.to_s # => "\n"

def puts(*objects : _) : Nil #

Writes objects to this IO, each followed by a newline character unless the object is a String and already ends with a newline.

io = IO::Memory.new
io.puts 1, '-', "Crystal"
io.to_s # => "1\n-\nCrystal\n"

abstract def read(slice : Bytes) #

Reads at most slice.size bytes from this IO into slice. Returns the number of bytes read, which is 0 if and only if there is no more data to read (so checking for 0 is the way to detect end of file).

io = IO::Memory.new "hello"
slice = Bytes.new(4)
io.read(slice) # => 4
slice          # => Bytes[104, 101, 108, 108]
io.read(slice) # => 1
slice          # => Bytes[111, 101, 108, 108]
io.read(slice) # => 0

def read_at(offset, bytesize, & : IO -> ) #

Yields an IO to read a section inside this IO.

The IO class raises on this method, but some subclasses, notable File and IO::Memory implement it.

Multiple sections can be read concurrently.


def read_byte : UInt8 | Nil #

Reads a single byte from this IO. Returns nil if there is no more data to read.

io = IO::Memory.new "a"
io.read_byte # => 97
io.read_byte # => nil

def read_bytes(type, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) #

Reads an instance of the given type from this IO using the specified format.

This ends up invoking type.from_io(self, format), so any type defining a from_io(io : IO, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) method can be read in this way.

See Int.from_io and Float.from_io.

io = IO::Memory.new
io.puts "\u{4}\u{3}\u{2}\u{1}"
io.rewind
io.read_bytes(Int32, IO::ByteFormat::LittleEndian) # => 0x01020304

def read_char : Char | Nil #

Reads a single Char from this IO. Returns nil if there is no more data to read.

io = IO::Memory.new "あ"
io.read_char # => 'あ'
io.read_char # => nil

def read_fully(slice : Bytes) : Int32 #

Tries to read exactly slice.size bytes from this IO into slice. Raises EOFError if there aren't slice.size bytes of data.

io = IO::Memory.new "123451234"
slice = Bytes.new(5)
io.read_fully(slice) # => 5
slice                # => Bytes[49, 50, 51, 52, 53]
io.read_fully(slice) # raises IO::EOFError

def read_fully?(slice : Bytes) : Int32 | Nil #

Tries to read exactly slice.size bytes from this IO into slice. Returns nil if there aren't slice.size bytes of data, otherwise returns the number of bytes read.

io = IO::Memory.new "123451234"
slice = Bytes.new(5)
io.read_fully?(slice) # => 5
slice                 # => Bytes[49, 50, 51, 52, 53]
io.read_fully?(slice) # => nil

def read_line(*args, **options) : String #

Same as #gets, but raises EOFError if called at the end of this IO.


def read_string(bytesize : Int) : String #

Reads an UTF-8 encoded string of exactly bytesize bytes. Raises EOFError if there are not enough bytes to build the string.

io = IO::Memory.new("hello world")
io.read_string(5) # => "hello"
io.read_string(1) # => " "
io.read_string(6) # raises IO::EOFError

def read_utf8(slice : Bytes) #

Reads UTF-8 decoded bytes into the given slice. Returns the number of UTF-8 bytes read.

If no encoding is set, this is the same as #read(slice).

bytes = "你".encode("GB2312") # => Bytes[196, 227]

io = IO::Memory.new(bytes)
io.set_encoding("GB2312")

buffer = uninitialized UInt8[1024]
bytes_read = io.read_utf8(buffer.to_slice) # => 3
buffer.to_slice[0, bytes_read]             # => Bytes[228, 189, 160]

"你".bytes # => [228, 189, 160]

def read_utf8_byte : UInt8 | Nil #

Reads a single decoded UTF-8 byte from this IO. Returns nil if there is no more data to read.

If no encoding is set, this is the same as #read_byte.

bytes = "你".encode("GB2312") # => Bytes[196, 227]

io = IO::Memory.new(bytes)
io.set_encoding("GB2312")
io.read_utf8_byte # => 228
io.read_utf8_byte # => 189
io.read_utf8_byte # => 160
io.read_utf8_byte # => nil

"你".bytes # => [228, 189, 160]

def rewind #

Rewinds this IO. By default this method raises, but including types may implement it.


def seek(offset, whence : Seek = Seek::Set) #

Seeks to a given offset (in bytes) according to the whence argument.

The IO class raises on this method, but some subclasses, notable IO::FileDescriptor and IO::Memory implement it.

Returns self.

File.write("testfile", "abc")

file = File.new("testfile")
file.gets(3) # => "abc"
file.seek(1, IO::Seek::Set)
file.gets(2) # => "bc"
file.seek(-1, IO::Seek::Current)
file.gets(1) # => "c"

def set_encoding(encoding : String, invalid : Symbol | Nil = nil) : Nil #

Sets the encoding of this IO.

The invalid argument can be:

  • nil: an exception is raised on invalid byte sequences
  • :skip: invalid byte sequences are ignored

String operations (#gets, #gets_to_end, #read_char, <<, #print, #puts #printf) will use this encoding.


def skip(bytes_count : Int) : Nil #

Reads and discards exactly bytes_count bytes. Raises IO::EOFError if there aren't at least bytes_count bytes.

io = IO::Memory.new "hello world"
io.skip(6)
io.gets    # => "world"
io.skip(1) # raises IO::EOFError

def skip_to_end : Nil #

Reads and discards bytes from self until there are no more bytes.


def tell #

Same as #pos.


def tty? : Bool #

Returns true if this IO is associated with a terminal device (tty), false otherwise.

IO returns false, but including types may override.

STDIN.tty?          # => true
IO::Memory.new.tty? # => false

abstract def write(slice : Bytes) : Nil #

Writes the contents of slice into this IO.

io = IO::Memory.new
slice = Bytes.new(4) { |i| ('a'.ord + i).to_u8 }
io.write(slice)
io.to_s # => "abcd"

def write_byte(byte : UInt8) : Nil #

Writes a single byte into this IO.

io = IO::Memory.new
io.write_byte 97_u8
io.to_s # => "a"

def write_bytes(object, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) : Nil #

Writes the given object to this IO using the specified format.

This ends up invoking object.to_io(self, format), so any object defining a to_io(io : IO, format : IO::ByteFormat = IO::ByteFormat::SystemEndian) method can be written in this way.

See Int#to_io and Float#to_io.

io = IO::Memory.new
io.write_bytes(0x01020304, IO::ByteFormat::LittleEndian)
io.rewind
io.gets(4) # => "\u{4}\u{3}\u{2}\u{1}"

def write_string(slice : Bytes) : Nil #

Writes the contents of slice, interpreted as a sequence of UTF-8 or ASCII characters, into this IO. The contents are transcoded into this IO's current encoding.

bytes = "你".to_slice # => Bytes[228, 189, 160]

io = IO::Memory.new
io.set_encoding("GB2312")
io.write_string(bytes)
io.to_slice # => Bytes[196, 227]

"你".encode("GB2312") # => Bytes[196, 227]

def write_utf8(slice : Bytes) : Nil #

Writes the contents of slice, interpreted as a sequence of UTF-8 or ASCII characters, into this IO. The contents are transcoded into this IO's current encoding.

bytes = "你".to_slice # => Bytes[228, 189, 160]

io = IO::Memory.new
io.set_encoding("GB2312")
io.write_string(bytes)
io.to_slice # => Bytes[196, 227]

"你".encode("GB2312") # => Bytes[196, 227]

DEPRECATED Use #write_string instead.