Skip to content

String

A String represents an immutable sequence of UTF-8 characters.

A String is typically created with a string literal enclosing UTF-8 characters in double quotes ("):

"hello world"

Escaping

A backslash denotes a special character inside a string, which can either be a named escape sequence or a numerical representation of a unicode codepoint.

Available escape sequences:

"\""                  # double quote
"\\"                  # backslash
"\#"                  # hash character (to escape interpolation)
"\a"                  # alert
"\b"                  # backspace
"\e"                  # escape
"\f"                  # form feed
"\n"                  # newline
"\r"                  # carriage return
"\t"                  # tab
"\v"                  # vertical tab
"\377"                # octal ASCII character
"\xFF"                # hexadecimal ASCII character
"\uFFFF"              # hexadecimal unicode character
"\u{0}".."\u{10FFFF}" # hexadecimal unicode character

Any other character following a backslash is interpreted as the character itself.

A backslash followed by at most three digits ranging from 0 to 7 denotes a code point written in octal:

"\101" # => "A"
"\123" # => "S"
"\12"  # => "\n"
"\1"   # string with one character with code point 1

A backslash followed by a u denotes a unicode codepoint. It can either be followed by exactly four hexadecimal characters representing the unicode bytes (\u0000 to \uFFFF) or a number of one to six hexadecimal characters wrapped in curly braces (\u{0} to \u{10FFFF}.

"\u0041"    # => "A"
"\u{41}"    # => "A"
"\u{1F52E}" # => "🔮"

One curly brace can contain multiple unicode characters each separated by a whitespace.

"\u{48 45 4C 4C 4F}" # => "HELLO"

Interpolation

A string literal with interpolation allows to embed expressions into the string which will be expanded at runtime.

a = 1
b = 2
"sum: #{a} + #{b} = #{a + b}" # => "sum: 1 + 2 = 3"

String interpolation is also possible with String#%.

Any expression may be placed inside the interpolated section, but it’s best to keep the expression small for readability.

Interpolation can be disabled by escaping the hash character (#) with a backslash or by using a non-interpolating string literal like %q().

"\#{a + b}"  # => "#{a + b}"
%q(#{a + b}) # => "#{a + b}"

Interpolation is implemented using a String::Builder and invoking Object#to_s(IO) on each expression enclosed by #{...}. The expression "sum: #{a} + #{b} = #{a + b}" is equivalent to:

String.build do |io|
  io << "sum: "
  io << a
  io << " + "
  io << b
  io << " = "
  io << a + b
end

Percent string literals

Besides double-quotes strings, Crystal also supports string literals indicated by a percent sign (%) and a pair of delimiters. Valid delimiters are parentheses (), square brackets [], curly braces {}, angles <> and pipes ||. Except for the pipes, all delimiters can be nested meaning a start delimiter inside the string escapes the next end delimiter.

These are handy to write strings that include double quotes which would have to be escaped in double-quoted strings.

%(hello ("world")) # => "hello (\"world\")"
%[hello ["world"]] # => "hello [\"world\"]"
%{hello {"world"}} # => "hello {\"world\"}"
%<hello <"world">> # => "hello <\"world\">"
%|hello "world"|   # => "hello \"world\""

A literal denoted by %q does not apply interpolation nor escapes while %Q has the same meaning as %.

name = "world"
%q(hello \n #{name}) # => "hello \\n \#{name}"
%Q(hello \n #{name}) # => "hello \n world"

Percent string array literal

Besides single string literals, there are also percent literals to create an Array of strings. It is indicated by %w or %W followed by a pair of delimiters. Valid delimiters are as same as percent string literals. Inside the delimiters, white space separates individual string values.

The %w literal contains static string values without interpolation or escape sequences except escaping white space. The %W literal allows interpolation and escape sequences.

Case indication is analogue to the percent string literals %q and %Q.

Interpolation syntax works similar to interpolation in string literals. The interpolated values gets stringified and inserted into the current array element. An array element can consist of an combination of interpolations and static components.

%w(foo bar baz)  # => ["foo", "bar", "baz"]
%w(foo\nbar baz) # => ["foo\\nbar", "baz"]
%w(foo(bar) baz) # => ["foo(bar)", "baz"]

# escapes white space
%w(foo\ bar baz) # => ["foo bar", "baz"]

%W(foo bar baz)  # => ["foo", "bar", "baz"]
%W(foo\nbar baz) # => ["foo\\nbar", "baz"]
%W(foo(bar) baz) # => ["foo(bar)", "baz"]

# escapes
%W(foo\ bar baz)  # => ["foo bar", "baz"]
%W(foo 'bar baz') # => ["foo", "'bar", "baz'"]
%W(foo "bar baz") # => ["foo", "\"bar", "baz\""]

Interpolation syntax works similar to interpolation in string literals. The interpolated values gets stringified and inserted into the current array element. An array element can consist of a combination of interpolations and static components.

%W[foo #{"bar"} baz]            # => ["foo", "bar", "baz"]
%W[foo #{1 + 1} baz]            # => ["foo", "2", "baz"]
%W[foo #{"bar"}baz#{"bab"} qux] # => ["foo", "barbazbab", "qux"]
%W[foo _#{"bar"}_ baz]          # => ["foo", "_bar_", "baz"]
%W[foo #{"bar baz"} qux]        # => ["foo", "bar baz", "qux"]

Interpolation syntax also supports splat expansion which inserts multiple elements into the array at the respective position. Splat interpolation does not support static prefix or suffix strings, i.e. it must be surrounded by whitespace or be anchored at the begin or end of the literal.

%W[foo #{*%w[bar baz]} qux] # => ["foo", "bar", "baz", "qux"]

Multiline strings

Any string literal can span multiple lines:

"hello
      world" # => "hello\n      world"

Note that in the above example trailing and leading spaces, as well as newlines, end up in the resulting string. To avoid this a string can be split into multiple lines by joining multiple literals with a backslash:

"hello " \
"world, " \
"no newlines" # same as "hello world, no newlines"

Alternatively, a backslash followed by a newline can be inserted inside the string literal:

"hello \
     world, \
     no newlines" # same as "hello world, no newlines"

In this case, leading whitespace is not included in the resulting string.

Heredoc

A here document or heredoc can be useful for writing strings spanning over multiple lines. A heredoc is denoted by <<- followed by an heredoc identifier which is an alphanumeric sequence starting with a letter (and may include underscores). The heredoc starts in the following line and ends with the next line that contains only the heredoc identifier, optionally preceded by whitespace.

<<-XML
<parent>
  <child />
</parent>
XML

Leading whitespace is removed from the heredoc contents according to the number of whitespace in the last line before the heredoc identifier.

<<-STRING # => "Hello\n  world"
  Hello
    world
  STRING

<<-STRING # => "  Hello\n    world"
    Hello
      world
  STRING

After the heredoc identifier, and in that same line, anything that follows continues the original expression that came before the heredoc. It's as if the end of the starting heredoc identifier is the end of the string. However, the string contents come in subsequent lines until the ending heredoc identifier which must be on its own line.

<<-STRING.upcase # => "HELLO"
hello
STRING

def upcase(string)
  string.upcase
end

upcase(<<-STRING) # => "HELLO WORLD"
  Hello World
  STRING

If multiple heredocs start in the same line, their bodies are read sequentially:

print(<<-FIRST, <<-SECOND) # prints "HelloWorld"
  Hello
  FIRST
  World
  SECOND

A heredoc generally allows interpolation and escapes.

To denote a heredoc without interpolation or escapes, the opening heredoc identifier is enclosed in single quotes:

<<-'HERE' # => "hello \\n \#{world}"
  hello \n #{world}
  HERE