Skip to content

Ruby has block comments and a data section

Level: 101 · anyone reading a Ruby file that ends in __END__

One line: Ruby comments with #, with =begin/=end blocks that must start at column 0, and with magic comments the parser obeys; after __END__ the rest of the file is data the program reads through DATA — while Python has # alone, keeps a bare triple-quoted string as a real expression statement, and documents a function in a docstring the program can read back, which Ruby cannot.

A # comments to the end of the line in both languages, and a #! shebang on line one is a comment in both. From there they part. Ruby has a block comment, =begin … =end, with the odd rule that both words must sit at column 0 — an indented =begin is a syntax error. Ruby has magic comments on the first lines that change how the file is parsed: # frozen_string_literal: true freezes its string literals and # encoding: iso-8859-1 sets the source encoding (Python has the second, as # coding: latin-1, and nothing like the first). And Ruby has __END__: everything after it is not code, and the constant DATA is a File positioned at the first line after the marker — a habit inherited from Perl, and a neat way to keep a template or a fixture inside a script. What Ruby lacks is a docstring: documentation is a comment above the def, read by RDoc and YARD from the source, and a running method cannot be asked for it. The program measures each of those on this file and on small child files.

Verified output of comments_and_documentation_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

 1. x = 1 # trailing comment                       x = 1
 2. =begin ... =end around x = 2                   x = 1  (still 1: the block did not run)
 3. an indented =begin (not at column 0)           exit 1, syntax errors found (SyntaxError)
 4. # frozen_string_literal: true / no comment     true / false
 5. # encoding: iso-8859-1, then 'caf' + byte E9   ISO-8859-1 / 4 / 4  (__ENCODING__ / length / bytesize)
 6. __END__ then DATA.read                         "line one\nline two\n"
 7. #!/usr/bin/env ruby as line 1                  the shebang line is a comment
 8. method(:documented).respond_to?(:__doc__)      false
 9. # @param line above def double; double(4)      8  (a YARD tag is an ordinary comment)

Reading the output

  • Row 1: the code before a trailing # ran.
  • Row 2: x = 2 sits inside =begin/=end in this very file, and x is still 1.
  • Row 3: the same block indented by two spaces is a syntax error in a child Ruby — the child exited 1 and the parser's message is shown.
  • Row 4: the magic comment made 'lit' frozen; the same literal in a file without it is not.
  • Row 5: with # encoding: iso-8859-1, __ENCODING__ reports that encoding and the literal 'caf' plus the single byte E9 is four characters and four bytes — that byte is one character in Latin-1.
  • Row 6: DATA.read returned the two lines after __END__ at the bottom of the program file — text that was never parsed as Ruby.
  • Row 7: a shebang first line is a comment; the child printed its second line.
  • Row 8: a Method has no __doc__; the documentation is the comment above def documented, and only a tool reading the source sees it.
  • Row 9: a YARD @param tag is an ordinary comment; the method it documents behaves as it would without it.

Compared with Python

Python has one comment syntax. The common belief that a triple-quoted string is a block comment is measured in row 2: it is an Expr statement holding a Constant, evaluated and discarded, which is harmless at module level and surprising inside an expression. Rows 3 and 6 are the two Ruby markers fed to a Python child: =begin and __END__ are both a SyntaxError, because Python has no block comment and no data section — the idiom is a module-level string or a file beside the script. Row 4 has nothing to freeze: str has no __setitem__. Row 5 is the one magic comment the languages share: # coding: latin-1 makes the byte e9 one character (four characters, four bytes), and without it a non-UTF-8 source file fails to compile. Row 7 is identical. Rows 8–9 are the reverse of Ruby's: the docstring is the first statement of the function and the program reads it back through __doc__, while a # type: comment is an ordinary comment that leaves __annotations__ empty.

Verified output of comments_and_documentation_py.py — regenerated by tools/run_examples.py, never hand-typed.

 1. x = 1  # trailing comment                      x = 1
 2. """ ... """ around x = 2                       x = 1  (still 1: the string is an Expr statement, its value a Constant)
 3. =begin ... =end in a Python file               exit 1, SyntaxError
 4. no frozen-literal comment: str is immutable    hasattr(str, '__setitem__') is False
 5. # coding: latin-1, then 'caf' + byte e9 / without it 4 / 4  (length / bytes) / exit 1, SyntaxError
 6. __END__ in a Python file                       exit 1, SyntaxError  (no data section: use a string or a file)
 7. #!/usr/bin/env python3 as line 1               the shebang line is a comment
 8. documented.__doc__                             'Return a unchanged; this docstring is the documentation.'
 9. # type: comment above; double(4), __annotations__ 8  {}  (a type comment is an ordinary comment)

RDoc ships with Ruby and reads the # comment above each definition; YARD adds @param and @return tags to that comment; ri String#upcase shows what RDoc collected. PEP 257 is the docstring convention Python tools read. (Not machine-checked here.)

The Python library's string literals ↗ page covers the triple-quoted and raw forms that row 2's bare string uses.

Try it

  1. Put a small CSV table — a header line and three rows — after __END__ in a script, read it back through DATA, and print it as an aligned table with a dashed line under the header and a count of the rows read.
  2. Write a "doc by convention" reader: given a Method object, use source_location to open its file, walk upwards from the def line collecting the # comment lines directly above it, and print them. Test it on two methods, one documented and one not.
Solution to 1

comments_and_documentation_kata_rb.rb in full — pasted here by tools/run_examples.py from the file CI runs.

# Kata: the data section as a tiny table. Everything after __END__ is read
# back through DATA, one line at a time, and never parsed as Ruby.

rows = DATA.each_line(chomp: true).reject(&:empty?).map { |line| line.split(",") }
header, *body = rows

widths = header.each_index.map { |i| rows.map { |r| r[i].length }.max }
line = ->(cells) { cells.each_with_index.map { |c, i| c.ljust(widths[i]) }.join("  ") }

puts line.call(header)
puts widths.map { |w| "-" * w }.join("  ")
body.each { |cells| puts line.call(cells) }
puts "#{body.size} rows read from the data section"

__END__
name,language,year
Matz,Ruby,1995
Guido,Python,1991
Larry,Perl,1987

Verified output of comments_and_documentation_kata_rb.rb — regenerated by tools/run_examples.py, never hand-typed.

name   language  year
-----  --------  ----
Matz   Ruby      1995
Guido  Python    1991
Larry  Perl      1987
3 rows read from the data section

See also