abstract class IO

Overview

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

This class is inherited by types likeFile,Socket andIO::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 theIO module is to define these two methods:

For example, this is a simpleIO on top of aBytes:

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

AnIO 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 : String | IO, root : String)
from_json(string_or_io : String | 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 : IO, dst : IO, limit : Int) : Int64 #

Copy at mostlimit bytes fromsrc todst.

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

IO.copy io, io2, 3

io2.to_s # => "hel"

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

Copy all contents fromsrc todst.

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

IO.copy io, io2

io2.to_s # => "hello"

def self.pipe(read_blocking : Bool | Nil = nil, write_blocking : Bool | Nil = nil) : Tuple(IO::FileDescriptor, IO::FileDescriptor) #

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

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

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

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 streamsstream1 tostream2 to determine if they are identical. Returnstrue 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 thisIO. This ends up callingto_s(io) on the object.

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

def close #

Closes thisIO.

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


def closed? : Bool #

Returnstrue if thisIO is closed.

IO defines returnsfalse, but including types may override.


def each_byte(&) : Nil #

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

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

Output:

97
227
129
130

def each_byte #

Returns anIterator for the bytes in thisIO.

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 eachChar in thisIO.

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

Output:

あ
め

def each_char #

Returns anIterator for the chars in thisIO.

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 eachline in thisIO, 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 anIterator for thelines in thisIO, 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 thisIO's encoding. The default isUTF-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 thisIO data as a writableBytes.

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(delimiter : Char, limit : Int, chomp = false) : String | Nil #

Reads untildelimiter is found,limit bytes are read, or the end of theIO is reached. Returnsnil if called at the end of thisIO.

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(chomp : Bool = true) : String | Nil #

Reads a line from thisIO. A line is terminated by the\n character. Returnsnil if called at the end of thisIO.

By default the newline is removed from the returned string, unlesschomp isfalse.

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(limit : Int, chomp : Bool = false) : String | Nil #

Reads a line of at mostlimit bytes from thisIO. A line is terminated by the\n character. Returnsnil if called at the end of thisIO.

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, chomp : Bool = false) : String | Nil #

Reads untildelimiter is found, or the end of theIO is reached. Returnsnil if called at the end of thisIO.

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 untildelimiter is found or the end of theIO is reached. Returnsnil if called at the end of thisIO.

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

def gets_to_end : String #

Reads the rest of thisIO data as aString.

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 returnsnil, but IO implementations that provide buffering or wrap other IOs should override this method.


def pos #

Returns the current position (in bytes) in thisIO.

TheIO class raises on this method, but some subclasses, notable IO::FileDescriptor andIO::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 thisIO.

TheIO class raises on this method, but some subclasses, notable IO::FileDescriptor andIO::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 thisIO by invokingto_s(io) on each of the objects.

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

def printf(format_string : 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 : 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 #

Writesstring to thisIO, 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 #

Writesobj to thisIO, 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 #

Writesobjects to thisIO, each followed by a newline character unless the object is aString 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 mostslice.size bytes from thisIO intoslice. 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 anIO to read a section inside this IO.

TheIO class raises on this method, but some subclasses, notable File andIO::Memory implement it.

Multiple sections can be read concurrently.


def read_byte : UInt8 | Nil #

Reads a single byte from thisIO. Returnsnil 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 giventype from thisIO using the specifiedformat.

This ends up invokingtype.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.

SeeInt.from_io andFloat.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 singleChar from thisIO. Returnsnil 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 exactlyslice.size bytes from thisIO intoslice. RaisesEOFError if there aren'tslice.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

#read_greedy also tries to fill the entire buffer if possible, but still allows the partially filled slice to be used if an early EOF was reached.


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

Tries to read exactlyslice.size bytes from thisIO intoslice. Returnsnil if there aren'tslice.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

#read_greedy also tries to fill the entire buffer if possible, but still allows the partially filled slice to be used if an early EOF was reached.


def read_greedy(slice : Bytes) : Int32 #

Similar to#read, but with the additional guarantee that either the buffer will be entirely filled or the EOF will be reached while trying.

Calling this method may result in multiple calls to#read if necessary.

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

#read_fully and#read_fully? also try to fill the entire buffer but error on unexpected EOF.


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

Same as#gets, but raisesEOFError if called at the end of thisIO.


def read_string(bytesize : Int) : String #

Reads an UTF-8 encoded string of exactlybytesize bytes. RaisesEOFError 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) : Int32 #

Reads UTF-8 decoded bytes into the givenslice. 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 thisIO. Returnsnil 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 thisIO. By default this method raises, but including types may implement it.


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

Seeks to a givenoffset (in bytes) according to thewhence argument.

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

Returnsself.

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

Theinvalid 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 exactlybytes_count bytes. RaisesIO::EOFError if there aren't at leastbytes_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 fromself until there are no more bytes.


def tell #

Same as#pos.


def tty? : Bool #

Returnstrue if thisIO is associated with a terminal device (tty),false otherwise.

IO returnsfalse, but including types may override.

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

abstract def write(slice : Bytes) : Nil #

Writes the contents ofslice into thisIO.

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

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 thisIO using the specifiedformat.

This ends up invokingobject.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.

SeeInt#to_io andFloat#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 ofslice, interpreted as a sequence of UTF-8 or ASCII characters, into thisIO. The contents are transcoded into thisIO'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 ofslice, interpreted as a sequence of UTF-8 or ASCII characters, into thisIO. The contents are transcoded into thisIO'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.