In Progress
Unit 1, Lesson 1
In Progress

puts

Video transcript & code

It may seem strange to have an episode dedicated to puts. After all, puts was probably the first Ruby method you ever learned about. But even prosaic methods can have hidden depth, and that's just the case with puts.

First of all, let's talk about the name. I've heard puts pronounced "puts", as in "it puts the lotion in the basket". This is understandable given how the method is spelled, but it's also misleading. puts is short for "PUT String". It's a name inherited from the C standard library of the same name.

$ man 3 puts

When we write puts by itself in a Ruby program, we're implicitly invoking the Kernel#puts method.

puts "Hello, world"
$ ri Kernel#puts
= Kernel#puts

(from ruby site)
------------------------------------------------------------------------------
  puts(obj, ...)    -> nil

------------------------------------------------------------------------------

Equivalent to

  $stdout.puts(obj, ...)

This is just a thin wrapper that forwards to the $stdout global variable.

$stdout.puts "Hello, world"

As we discussed in episode 29, this makes it easy to redirect output by reassigning the $stdout variable. For instance, we could make all puts sends write to a file instead.

$stdout = open("out.log", 'w')
puts "Hello, world"
$stdout.close
File.read("out.log")
# => "Hello, world\n"

Occasionally we'd like to set some output apart using blank lines. An idiomatic way to write a blank line in Ruby is to use puts with no argument.

puts
puts
puts "IMPORTANT: Please read this important notice"
puts
puts
# >> 
# >> 
# >> IMPORTANT: Please read this important notice
# >> 
# >>

Most often when we use puts we are giving it a single string or object to print. But puts can take an arbitrary number of arguments. Each will be converted to a string and then printed on its own line.

puts "The time is now:", Time.now, "Your lucky number is:", rand(10)
# >> The time is now:
# >> 2013-12-24 11:38:24 -0500
# >> Your lucky number is:
# >> 4

Not only will puts print each argument on its own line, it will also flatten arrays and print each element of the flattened array on its own line.

puts "Favorite things:", ["Raindrops on roses", ["whiskers on kittens", "bright copper kettles"]]
# >> Favorite things:
# >> Raindrops on roses
# >> whiskers on kittens
# >> bright copper kettles

This means that loops for printing out collections are often superfluous. Here's a loop that prints out the load path of the current Ruby process.

$:.each do |path|
  puts path
end
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/x86_64-linux

This can be replaced with a simple one-line puts call.

puts $:
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/site_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby/2.0.0/x86_64-linux
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/vendor_ruby
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0
# >> /home/avdi/.rvm/rubies/ruby-2.0.0-p247/lib/ruby/2.0.0/x86_64-linux

puts is also smart about printing strings with newlines on the end. If we tell it to print a line with that has a newline already, puts won't print an extra newline.

puts "Hello, world\n"
# >> Hello, world

This means we can take lines read in from a file and print them directly with puts without worrying about doubled-up newlines.

lines = File.readlines("/etc/hosts")
# => ["127.0.0.1\tlocalhost\n",
#     "127.0.1.1\thazel\n",
#     "\n",
#     "# The following lines are desirable for IPv6 capable hosts\n",
#     "::1     ip6-localhost ip6-loopback\n",
#     "fe00::0 ip6-localnet\n",
#     "ff00::0 ip6-mcastprefix\n",
#     "ff02::1 ip6-allnodes\n",
#     "ff02::2 ip6-allrouters\n"]
puts lines
# >> 127.0.0.1  localhost
# >> 127.0.1.1  hazel
# >> 
# >> # The following lines are desirable for IPv6 capable hosts
# >> ::1     ip6-localhost ip6-loopback
# >> fe00::0 ip6-localnet
# >> ff00::0 ip6-mcastprefix
# >> ff02::1 ip6-allnodes
# >> ff02::2 ip6-allrouters

In our episodes on threading you might have noticed that when we used puts in multiple threads, we'd sometimes see the output get mashed up as one thread dumped its output before another thread added a newline. Here's an example with two threads that each write a line to $stdout 10 times. When we run it we can see that the threads sometimes step on each other's toes.

t1 = Thread.new do
  10.times do puts "Thread 1 is awesome!" end
end

t2 = Thread.new do
  10.times do puts "Thread 2 is the best!" end
end

t2.join
t1.join
$ ruby threads.rb 
Thread 2 is the best!Thread 1 is awesome!

Thread 1 is awesome!
Thread 2 is the best!
Thread 1 is awesome!
Thread 2 is the best!Thread 1 is awesome!
Thread 1 is awesome!

Thread 1 is awesome!
Thread 2 is the best!
Thread 1 is awesome!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!

This happens because puts adds its newline as a separate write after printing the string. So there's a chance for another thread to be scheduled in between writing the string and the newline.

An interesting side-effect of puts rules for strings that already have newlines is that printing them doesn't suffer from this two-step write phenomenon. This means that we can fix our mixed-up output by ensuring the printed strings already have newlines on the end.

t1 = Thread.new do
  sleep 0.001
  10.times do puts "Thread 1 is awesome!\n" end
end

t2 = Thread.new do
  sleep 0.001
  10.times do puts "Thread 2 is the best!\n" end
end

t2.join
t1.join
$ ruby threads2.rb 
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 1 is awesome!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!
Thread 2 is the best!

And that brings us to the end of our little exploration of advanced puts usage. I hope you've learned something you didn't know before. Happy hacking!

Responses