Ruby 2.3.0 preview 1 was announced and, as usual it includes plenty of improvements and new features.

Three new features are highlighted in the announcement email:

did_you_mean.gem

Safe navigation operator

Frozen String Literal Pragma

Did you mean

did_you_mean.gem is an aid to debugging Ruby programs for cases when the error is related to NameError and NoMethodError .

When a Ruby program breaks due to one of these exceptions, you will receive help in the form of a suggestion for a class name, method name or variable name.

class Account def initialize @full_name = 'John Doe' end def name full_name end end > account = Acount.new NameError: uninitialized constant Acount Did you mean? Account > account = Account.new > account.nams NoMethodError: undefined method `nams' for #<Account:0x007fb2291db068> Did you mean? name > account.name NameError: undefined local variable or method `full_name' for #<Account:0x007fb2292b00d8 @full_name="John Doe"> Did you mean? @full_name

Simple but handy tool for debugging.

Safe navigation operator

If you are a Rails developer you might have used Object#try in you programs.

Object#try is used to call a method on an object when this object might be nil . Instead of raising an exception, Object#try responds with nil .

@user = nil @user.profile # this will raise NoMethodError: undefined method `profile' for nil:NilClass @user.try(:profile) # this will return nil

If you are not using Rails nor including ActiveSupport in your program, then you are left to test if the caller is nil before trying to call a method on it.

@user = nil @user.profile if !@user.nil? # as a shorthand this could be if @user

Ruby 2.3.0 includes a new operator: Safe navigation operator.

Operator &. will work in the same way as Object#try works in ActiveSupport .

class Profile attr_accessor :full_name def initialize self.full_name = 'John Doe' end end class User attr_accessor :profile def build_profile self.profile = Profile.new end end > user = User.new > puts "Full name: #{user&.profile&.full_name}" Full name: > user.build_profile > puts "Full name: #{user&.profile&.full_name}" Full name: John Doe

&. will even work with chained calls.

Frozen String Literal Pragma

Ruby 3.0 might have strings being immutable by default, this is a big change that will cause incompatibility and might make harder to upgrade to Ruby 3.0.

In Ruby, having immutable strings is an opt-in functionality but it requires you to explicitly mark any string with Object#freeze .

Freezing strings that will not change in our programs has the advantage of reducing object allocation and memory usage.

# memory.rb require 'get_process_mem' mem = GetProcessMem.new GC.start GC.disable # Not freeze strings before_mem = mem.mb before_stats = GC.stat 1_000.times { 'hello' } after_stats = GC.stat after_mem = mem.mb delta_allocated_objects = after_stats[:total_allocated_objects] - before_stats[:total_allocated_objects] delta_memory = after_mem - before_mem puts "Without frozen strings, allocated objects: #{delta_allocated_objects} - used memory: #{delta_memory}" # Freeze strings before_mem = mem.mb before_stats = GC.stat 1_000.times { 'hello'.freeze } after_stats = GC.stat after_mem = mem.mb delta_allocated_objects = after_stats[:total_allocated_objects] - before_stats[:total_allocated_objects] delta_memory = after_mem - before_mem puts "With frozen strings, allocated objects: #{delta_allocated_objects} - used memory: #{delta_memory}" GC.enable # memory.rb > ruby memory.rb Without frozen strings, allocated objects: 1001 - used memory: 0.0390625 With frozen strings, allocated objects: 1 - used memory: 0.0

Ruby 2.3.0 helps you prepare for the string change in Ruby 3.0.

First, it introduces a pragma that we can use per ruby file to tell Ruby that we want to freeze strings by default: # frozen_string_literal: true

# string_change.rb class StringChange def change(value) value << ' changed' end end # string_frezze.rb # frozen_string_literal: true load './string_change.rb' class StringFreeze def hit_change value = 'freeze' new_value = StringChange.new.change(value) puts new_value end end StringFreeze.new.hit_change

Running this program with ruby string_frezze.rb gives you the following error:

$ ruby string_freeze.rb /Users/mariochavez/Development/temp/string_change.rb:4:in `change': can't modify frozen String (RuntimeError) from string_freeze.rb:8:in `hit_change' from string_freeze.rb:14:in `<main>'

string_frezze.rb which include the pragma, creates all strings literals frozen but string_change.rb tried to change the string.

For a very small program it is easy to see where the string was created and where there was an attempt to modify it. For larger programs, this might not be that intuitive. This is way the --enable-frozen-string-literal-debug flag was introduced.

If you run the same program with the --enable-frozen-string-literal-debug flag, then the error is more helpful.

$ ruby --enable-frozen-string-literal-debug string_freeze.rb /Users/mariochavez/Development/temp/string_change.rb:4:in `change': can't modify frozen String, created at string_freeze.rb:7 (RuntimeError) from string_freeze.rb:8:in `hit_change' from string_freeze.rb:14:in `<main>'

It not only tells you where the attempt to change the string was done, but also where the string was originally created.

Finally for those who want to run their programs as if they were running on Ruby 3.0 - without the need of pragma to freeze strings - you can run your programs with --enable-frozen-string-literal .

ruby --enable-frozen-string-literal --enable-frozen-string-literal-debug string_freeze.rb

Conclusions

Its always nice to receive new Ruby versions more than improvements or performance benefits, but also with new features and functionality. As Matz mentioned during his RubyConf 2014 keynote:

We have to feed the sharks, if not they will go away for new shining things.

But in this case, is especially important to have the chance to try and start preparing for the future, easing as much as possible, the transition to Ruby 3.0.

If you want to play with Ruby 2.3.0 preview1, in the announce email you will find the link to download it. If you are using rbenv and/or ruby-build you can use this custom definition to install it.

# 2.3.0-preview1 install_package "openssl-1.0.1p" "https://www.openssl.org/source/openssl-1.0.1p.tar.gz#bd5ee6803165c0fb60bbecbacacf244f1f90d2aa0d71353af610c29121e9b2f1" mac_openssl --if has_broken_mac_openssl install_package "ruby-2.3.0-preview1" "http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.0-preview1.tar.gz#dc8f9d48392a2bb226df5f4b4fd2074d81af155cdf3f3799139a6e31e012aefe" ldflags_dirs autoconf standard verify_openssl

Just save it to a file, like 2.3.0-preview1 , and install it with the following command:

$ rbenv install ./2.3.0-preview1

Enjoy Ruby!