Ruby Evolution - Ruby Changes

Ask questions Research chat →

https://rubyreferences.github.io/rubychanges/evolution.html · scraped

ruby

Attachments

Scraped Content

— 4643 words · 2026-05-19 12:32:55 UTC ·

Excerpt

![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/4558ae04-8f58-4d1c-9a8c-aee7e4021fcf/evolution.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466RQ2PFW5T%2F20260519%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260519T193239Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEBMaCXVzLXdlc3QtMiJHMEUCIQDMikJJNAtghIGQugdBQC6gc5MN%2Bp1V%2F3OkE9oE%2FhLl1QIgWqyNAyIhhuqeRK8mzyPh5nO1BnvmD83ftOnciQ1qbg0qiAQI3P%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDHMhNW%2BDOds%2F8AgKECrcAwXK2e1EPT9qFSuA85MKOPOeK8q8G9yv0Z560SKG3zi9bsz3aCA9WtO4ZCd7hGeQ8bajEvA1dSKR18pxKJ3cJCG7iqOsVNZ7IOMGkhGgzeAg8ss4nC2P4Qu43qEkMY5CwWa3WWyj2%2FVx3mPj9aPSE%2B6%2FDzlavL0MrMp4npTLi4UMj%2Bza26N00eCyVw0hlbuPCuzi4hJUkaXaBTW9VEbAB4WATvlRP8mVs5pVf8ggoDwFUcduNGN1gbz74RxCzOYTGuhNLEM9Nh0ITRRG%2FGOs%2F9I9e6ZRYKf0SQ6OFwN1oKFuBg6wDTq%2B%2FAeFEA98cF7gUyY8Jc1B6npaC8cc%2FnbqRoWyhNYA3JATeNbaeiWgL8nMWvG0cKm5vIWSK3L4965qPOaPhr61BvI
![](https://prod-files-secure.s3.us-west-2.amazonaws.com/871f1661-80b8-4d0c-ac3b-2adfc6ff4c66/4558ae04-8f58-4d1c-9a8c-aee7e4021fcf/evolution.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466RQ2PFW5T%2F20260519%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260519T193239Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEBMaCXVzLXdlc3QtMiJHMEUCIQDMikJJNAtghIGQugdBQC6gc5MN%2Bp1V%2F3OkE9oE%2FhLl1QIgWqyNAyIhhuqeRK8mzyPh5nO1BnvmD83ftOnciQ1qbg0qiAQI3P%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARAAGgw2Mzc0MjMxODM4MDUiDHMhNW%2BDOds%2F8AgKECrcAwXK2e1EPT9qFSuA85MKOPOeK8q8G9yv0Z560SKG3zi9bsz3aCA9WtO4ZCd7hGeQ8bajEvA1dSKR18pxKJ3cJCG7iqOsVNZ7IOMGkhGgzeAg8ss4nC2P4Qu43qEkMY5CwWa3WWyj2%2FVx3mPj9aPSE%2B6%2FDzlavL0MrMp4npTLi4UMj%2Bza26N00eCyVw0hlbuPCuzi4hJUkaXaBTW9VEbAB4WATvlRP8mVs5pVf8ggoDwFUcduNGN1gbz74RxCzOYTGuhNLEM9Nh0ITRRG%2FGOs%2F9I9e6ZRYKf0SQ6OFwN1oKFuBg6wDTq%2B%2FAeFEA98cF7gUyY8Jc1B6npaC8cc%2FnbqRoWyhNYA3JATeNbaeiWgL8nMWvG0cKm5vIWSK3L4965qPOaPhr61BvIdoWwDVZC9JaFQxmrt33IKfKOU7b%2Fr%2FWJKSTl2N1z9lKCfSyiDyzywQKE4MkGPPAUD45cVDCfR%2FzW%2BQDvKE59pN4MQ%2Fis%2BvQdGmg2WN3uSnPtoFwjQq6Kp0t0KtwuzZlR%2BeEZAWh10QkB%2FSzGEMCN2DRLXkVGQh1fQ%2BRF%2FXvT43LFPUOA3XwrVleabWLyKZdxDjZ9MAjFx1cKNyArBJwRHjs47nbZivz%2FU4unbDtMtvx5F%2FssCMIPbstAGOqUBaJ5wcjt3ilSRq3Jo8TRetcwhIlTB9MWrUzNBEyCEYCH%2BhsQCpdl7iAAYfjZGy%2FFKVxeOluYxPuiP3bTZTs8cTsxJ7iEGAjB33kn4UkC8tWXNY0x0tBiEEMBt3sZBsxq571R4UEbrjllt4yngGExZcA%2Bu8XzuaFqhoZuVJUFerWsPk03GUQ10ZJSxMklvp5aRtjMK1bCqGnb0oonUpWNVT%2Bf9IInl&X-Amz-Signature=f2d8ff9869994e36c1243d3d8126e5dfa18da556f6046ae33b0ca9ec8e34a523&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject) A very brief list of new significant features that emerged in Ruby programming language since version 2.0 (2013). It is intended as a “bird eye view” that might be of interest for Ruby novices and experts alike, as well as for curious users of other technologies. It is part of a bigger Ruby Changes effort, which provides a detailed explanations and justifications on what happens to the language, version by version. The detailed changelog currently covers versions since 2.4, and the brief changelog links to more detailed explanations for those versions (links are under version numbers at the beginning of the list items). The choice of important features, their grouping, and depth of comment provided are informal and somewhat subjective. The author of this list is focused on the changes of the language as a system of thinking and its syntax/semantics more than on a particular implementation. As Ruby is highly object-oriented language, most of the changes can be associated with some of its core classes. Nevertheless, a new method in one of the core classes frequently changes the way code could be written, not just adds some small convenience. 🇺🇦 🇺🇦 This work was started in mid-February, before the start of aggressive full-scale war Russia leads against Ukraine. I am finishing it after my daily volunteer work (delivering food through my district), why my homecity Kharkiv is still constantly bombed. Please care to read two of my appeals to Ruby community before proceeding: first, second.The latest blog post dedicated to the reference creation also juxtaposes the evolution of the language with my personal history and history of my country.🇺🇦 🇺🇦 ## General changes - 2.0 Refinements are introduced as experimental feature. It is meant to be a hygienic replacement for contextual extending of modules and classes. The feature became stable in 2.1, but still has questionable mindshare, so the further enhancements to it are covered in “deeper topics” section. Example of refinements usage: ```plain text # Without refinements: Extending core object to make writing some statistics-heavy report easier: class Numeric def percent_of(other) self.fdiv(other) * 100 end end # Usage: csv << [spent.percent_of(budget), debt.percent_of(budget), budget] # The problem is, Numeric#percent_of is now available in every other module, and depending on the # name and design, might cause problems in unrelated code # With refinements: module Stats refine Numeric do def percent_of(other) self.fdiv(other) * 100 end end end # The "refined" methods are available only in the file that explicitly uses them using Stats csv << [spent.percent_of(budget), debt.percent_of(budget), budget] ``` - 2.6 Non-ASCII constant names allowed - 2.7 “Safe” and “taint” concepts are deprecated in general - 3.0 Class variable behavior became stricter: top-level @@variable is prohibited, as well as overriding in descendant classes and included modules. - 3.0 Type definitions concept is introduced. The discussion of possible solutions for static or gradual typing and possible syntax of type declarations in Ruby code had been open for years. At 3.0, Ruby’s core team made their mind towards type declaration in separate files and separate tools to check types. Example of type definition syntax: ```plain text class Dog attr_reader name: String def initialize: (name: String) -> void def bark: (at: Person | Dog | nil) -> String end ``` ## Expressions ### Pattern-matching - 2.7 Pattern-matching introduced as an experimental feature that allows to deeply unpack/check nested data structures: ```plain text case config in version: 'legacy', username: # matches {version: 'legacy', username: anything} and puts value in `username` puts "Connect with user '#{username}'" in db: {user: } # matches {db: {user: anything}} and puts value in `user` puts "Connect with user '#{user}'" in String => username # matches when config is a String and puts it into `username` puts "Connect with user '#{username}'" else puts "Unrecognized structure of config" end ``` - 3.0 => pattern-matching expression introduced ```plain text {a: 1, b: 2} => {a:} # -- deconstructs and assigns to local variable `a`; fails if pattern not matched long.chain.of.computations => result # can also be used as a "rightward assignment" ``` - 3.0 in pattern-matching expression repurposed as a true/false check ```plain text if {a: 1, b: 2} in {a:} # just "check if match", returning true/false; also deconstructs # ... ``` - 3.0 Find pattern is supported: [*elements_before, <complicated pattern>, *elements_after] ## Kernel Kernel is a module included in every object, providing most of the methods that look “top-level”, like puts, require, raise and so on. - 2.0 #__dir__: absolute path to current source file - 2.0 #caller_locations which returns an array of frame information objects, in a form of new class Thread::Backtrace::Location - 3.2 Thread.each_caller_location as an efficient method to iterate through part of the call stack. - 2.2 #throw raises UncaughtThrowError, subclass of ArgumentError when there is no corresponding catch block, instead of ArgumentError. - 2.3 #loop: when stopped by a StopIteration exception, returns what the enumerator has returned instead of nil - 2.5 #pp debug printing method is available without require 'pp' - 3.1 #load allows to pass module as a second argument, to load code inside module specified Object is a class most other classes are inherited from (save for very special cases when the BasicObject is inherited). So the methods defined in Object are available in most of the objects. Unlike Kernel’s method described above, Object’s methods are public. E.g. every object has private #puts from Kernel that it can use inside its own methods, and every object has public #inspect from Object, that can be called by other objects. - 2.0 #respond_to? against a protected method now returns false by default, can be overrided by respond_to?(:foo, true). - 2.0 #respond_to_missing?, #initialize_clone, #initialize_dup became private. - 2.6 #then (initially introduced as 2.5 #yield_self) for chainable computation, akin to Elixir’s |>: ```plain text [BASE_URL, path].join('/') .then { |url| open(url).read } .then { |body| JSON.parse(body, symbolyze_names: true) } .dig(:data, :items) .then { |items| File.write('response.yml', items.to_yaml) } ``` ## Modules and classes This section lists changes in how modules and classes are defined, as well as new/changed methods of core classes Module and Class. Note that most of module-level “keywords” we regularly use are actually instance methods of the Module class: ```plain text class Foo attr_reader :bar # it is a method Module#attr_reader private # it is a method Module#private include Enumerable # it is a method Module#include def each # def is not a method, it is a real keyword! # ... end define_method(:test, &block) # but it is a method Module#define_method end ``` - 2.0 #prepend introduced: like #include, but adds prepended module to the beginning of the ancestors chain (also #prepended and #prepend_features hooks): ```plain text class A < Array # Only adds new methods the class doesn't define itself include Enumerable def map puts "mapping" end end class B < Array # Goes in front of the class itself in ancestors chain, can redefine its methods prepend Enumerable def map puts "mapping" end end p A.ancestors # [A, Array, Enumerable, ...] p A.new([1, 2, 3]).map(&:to_s) # prints "mapping", returns nil p B.ancestors # [Enumerable, B, Array, ...] p B.new([1, 2, 3]).map(&:to_s) # returns ["1", "2", "3"] ``` - 2.0 #const_get accepts a qualified constant string, e.g. Object.const_get("Foo::Bar::Baz") - 2.1 The ancestors of a singleton class now include singleton classes, in particular itself. - 2.1 #include and #prepend are now public methods, so one can do AnyClass.include AnyModule without resorting to send(:include, ...) (which people did anyway) - 2.5 methods for defining methods and accessors (like #attr_reader and #define_method) became public - 2.7 #const_source_location allows to query where some constant (including modules and classes) was first defined. - 3.0 #include and #prepend now affects modules that already include the receiver: ```plain text module MyEnumerableExtension def each2(&block) each_slice(2, &block) end end Enumerable.include MyEnumerableExtension (1..8).each2.to_a # Ruby 2.7: NoMethodError (undefined method `each2' for 1..8:Range) -- even though Range includes Enumerable # Ruby 3.0: [[1, 2], [3, 4], [5, 6], [7, 8]] ``` - 3.0 Changes in return values/accepted parameters of several methods, making code like private attr_reader :a, :b, :c work (#attr_reader started to return arrays of symbols, and #private accepts arrays) - 3.1 Module#prepend behavior changed to take effect even if the same module is already included. - 3.1 #private and other visibility methods return their arguments, to allow usage in macros like memoize private def my_method... - 3.2 Behavior of module reopening/redefinition with included modules changed: top-level ones wouldn’t conflict with included anymore: ```plain text require 'net/http' include Net # Ruby 3.1: Reopens Net::HTTP # Ruby 3.2: Defines new top-level class HTTP class HTTP end ``` - 3.3 Module#set_temporary_name to set a human-readable name for a module without assigning it to a constant. ## Methods This section lists changes in how methods are defined and invoked, as well as new/changed methods of core classes Method and UnboundMethod. Note: some of the behavior of method definition APIs in context of containing modules is covered in the above section about modules. - 2.0 Keyword arguments. Before Ruby 2.0, keyword arguments could’ve been imitated to some extent with last hash argument without parenthises. In Ruby 2.0, proper keyword arguments were introduced. At first, they could only be optional (default value should’ve always been defined): ```plain text # before Ruby 2.0: def render(data, options = {}) indent = options.fetch(:indent, 2) separator = options.fetch(:separator) # imitation of mandatory arg., will raise if not present # ... end # calling: looks like separate argument due to Ruby allowing to omit {} render(something, indent: 4, separator: "\n\n") # Ruby 2.0: def render(data, indent: 2, separator: nil) raise ArgumentError, "separator is not defined" if separator.nil? # mandatory arguments should still be imitated ``` - 2.1 Required keyword arguments introduced: ```plain text def render(data, separator:, indent: 2) # will raise if `separator:` argument is not passed ``` - 2.0 top-level define_method which defines a global function. - 2.1 def now returns the symbol of its name instead of nil. Usable to use in class-level “macros” method: ```plain text # before: def foo end private :foo # after: private def foo # `private` will receive :foo that `def` returned end ``` - Module#define_method and Object#define_singleton_method also return the symbols of the defined methods, not the methods/procs - 2.2 Method#curry: ```plain text writer = File.method(:write).curry(2).call('test.txt') # curry with 2 arguments, supply first of them # Now, the `writer` can be used as a 1-argument callable object: writer.call('content') # Invokes File.write('test.txt', 'content') ``` - 2.7 Big Keyword Argument Separation: some incompatibilities were introduced by need, so the distinction of keyword arguments and hashes in method arguments was more clear, handling numerous irritating edge cases. - 2.7 Introduce argument forwarding with method(...) syntax. As after the keyword argument separation “delegate everything” syntax became more complicated (you need to use and pass (*args, **kwargs), because just args wouldn’t always work), simplified syntax was introduced: ```plain text def wrap_log(...) # this is literal code that can be used now, not a placeholder for a demo puts "Logging at #{Time.now}" log.call(...) end wrap_log(:info, "Foo", context: some_context) # both positional and keyword args are passed successfully ``` - 2.7 Better Method#inspect with signature and source code location - 3.1 Method#private?, #protected?, #public?, same are defined for UnboundMethod - 3.2 The change was reverted. ## Procs, blocks and Proc class - 2.0 removed Proc#== and #eql? so two procs are equal only when they are the same object. - 2.2 ArgumentError is no longer raised when lambda Proc is passed as a block, and the number of yielded arguments does not match the formal arguments of the lambda, if just an array is yielded and its length matches. - 3.2 Proc#parameters: new keyword argument lambda: true/false, improving introspection of whether arguments have default values or they are just optional because all proc arguments are. - 3.3 Added a warning that since 3.4, it would become a synonym for _1. ## Comparable Included in many classes to implement comparison methods. Once class defines a method #<=> for object comparison (returning -1, 0, 1, or nil) and includes Comparable, methods like ==, <, <= etc. are defined automatically. Changes in Comparable module affect most of comparable objects in Ruby, including core ones like numbers and strings. - 2.3 #== no longer rescues exceptions (so if owner class’ <=> raises, the user will see original exception) ## Numeric - 2.1 Added suffixes for integer and float literals: r, i, and ri: ```plain text 1/3r # => (1/3), Rational 2 + 5i # => (2 + 5i), Complex ``` - 2.7 Integer#[] supports range of bits ## Strings, symbols, regexps, encodings - 2.0 Big encoding cleanup: - Default source encoding is changed to UTF-8 (was US-ASCII) - Iconv has been removed from standard library; core methods like String#encode and String#force_encoding (introduced in 1.9) should be preferred - 2.0 %i symbol array literals shortcut: ```plain text %i[first_name last_name age] # => [:first_name, :last_name, :age] ``` - 2.0 String#b to set string encoding as ASCII-8BIT (aka “binary”, raw bytes). - 2.1 String#scrub and #scrub! to verify and fix invalid byte sequence. - 2.2 Most symbols which are returned by String#to_sym are garbage collectable. While it might be perceived as an implementation detail, it means also the change in language use: there is no need to avoid symbols where they are more expressive, even if there are a lot of them. - 2.3 <<~ HERE-document literal (removing the leading spaces): ```plain text text = <<~HERE The text, indented for readability. No leading spaces please. HERE p text # => "The text, indented for readability.\nNo leading spaces please.\n" ``` - 2.3 String.new accepts keyword argument encoding: - 2.4 Case conversions (String#downcase, String#upcase, and other related methods) fully support Unicode: ```plain text 'Straße'.upcase # => 'STRASSE' 'İzmir'.upcase(:turkic) # => İZMİR -- locale-specific case conversion ``` - 2.4 String.new: capacity: argument to pre-allocate memory if it is known the string will grow - 2.4 String#casecmp?, Symbol#casecmp? as a more expressive version of #casecmp when boolean value is needed (#casecmp returns 1/0/1): ```plain text 'FOO'.casecmp?('foo') # => true 'Straße'.casecmp?('STRASSE') # => true, Unicode-aware ``` - 2.4 String#unpack1 as a shortcut to "foo".unpack(...).first - 2.4 Regexp#match?, String#match?, and Symbol#match? for when it is only necessary to know “if it matches or not”. Unlike =~ and #match, the methds don’t alocate MatchData instance, which might make the check more efficient. - 2.5 String#undump deserialization method, symmetric to #dump - 2.5 String#start_with? accepts a regexp (but not #end_with?) - 2.5 Regexp: absence operator (?~<pattern>): match everything except this particular pattern - 2.6 String#split supports block: ```plain text "several\nlong\nlines".split("\n") { |part| puts part if part.start_with?('l') } # prints: # long # lines # => "several\nlong\nlines" ``` - 2.7 Symbol#end_with? and #start_with? as a part of making symbols as convenient as strings, while maintaining their separate meaning - 3.1 String#unpack and #unpack1 added offset: argument, to unpack data from the middle of a stream. - 3.3 String#bytesplice: additional arguments to select a portion of the inserted string. ## Struct - 2.5 Structs initialized by keywords: ```plain text User = Struct.new(:name, :email, keyword_init: true) User.new(name: 'Matz', email: 'matz@ruby-lang.org') ``` - 3.1 Warning on passing keywords to a non-keyword-initialized struct - 3.2 Struct.new accepts both positional and keyword arguments by default, unless keyword_init: true or false was explicitly specified. ## Data - 3.2 Data: new immutable value object class introduced. It has a stricter and leaner interface than Struct: ```plain text Point = Data.define(:x, :y) # Both positional and keyword arguments can be used p1 = Point.new(1, 0) #=> #<data Point x=1, y=0> p2 = Point.new(x: 0, y: 1) #=> #<data Point x=0, y=1> # all arguments are mandatory Point.new(1) # missing keyword: :y (ArgumentError) # there is no setters or any other way to change already created object p1.x = 5 # undefined method `x=' for #<data Point x=1, y=0> (NoMethodError) p1.instance_variable_set('@z', 100) # can't modify frozen Point: #<data Point x=1, y=0> (FrozenError) # #with method can be used to construct new instances, # replacing only parts of the data: p1.with(y: 100) #=> #<data Point x=1, y=100> ``` - 3.1 .new, .at, and .now: in: time_zone_or_offset parameter for constructing time ```plain text Time.now(in: TZInfo::Timezone.get('America/New_York')) # => 2022-07-09 06:25:06.162617846 -0400 Time.new(2022, 7, 1, 14, 30, in: '+05:00') # => 2022-07-01 14:30:00 +0500 ``` - 3.2 Time.new can parse a string (stricter and more robust than Time.parse of the standard library) ## Enumerables, collections, and iteration - 2.0 A decision was made to make a clearer separation of methods returning enumerators to methods calculating the value and returning array immediately, namely: - 2.0 Binary search introduced in core with Range#bsearch and Array#bsearch. - 2.3 #dig introduced (in Array, Hash, and Struct) for atomic nested data navigation: ```plain text data = { status: 200 body: {users: [ {id: 1, name: 'Victor'}, {id: 2, name: 'Yuki'}, ]} } data.dig(:body, :users, 1, :name) #=> 'Yuki' ``` ### Numeric iteration - 2.1 Numeric#step allows the limit argument to be omitted, producing Enumerator. Keyword arguments to and by are introduced for ease of use: ```plain text 1.step(by: 5) # => #<Enumerator: 1:step({:by=>5})> 1.step(by: 5).take(3) #=> [1, 6, 11] ``` - 2.6 Enumerator::ArithmeticSequence is introduced as a type returned by Range#step and Numeric#step: ```plain text 1.step(by: 5) # => (1.step(by: 5)) -- more expressive representation than above (1..200).step(3) # => ((1..200).step(3)) # It is also more powerful than generic Enumerator, as there is more knowledge about # the nature of the sequence: (1..200).step(3).last(2) # => [196, 199] ``` - 2.6 Range#% alias for Range#step for expressiveness: (1..10) % 2 produces ArithmeticSequence with meaning “from 1 to 10, each second element”; since Ruby 3.0, this can be used to slicing arrays: ```plain text (0..) % 3 letters = ('a'..'z').to_a letters[(0..) % 3] #=> ["a", "d", "g", "j", "m", "p", "s", "v", "y"] ``` ### Enumerable and Enumerator - 2.0 The concept of lazy enumerator introduced with Enumerable#lazy and Enumerator::Lazy: ```plain text # If source is very large or has side effects like network reading, the following code will # first read it all, then produce intermediate array on each step source.select { some_condition }.map { some_transformation }.first(3) # while this code will just stack together operations, and then produce items one by one, till # the first 3 results are received: # vvvv source.lazy.select { some_condition }.map { some_transformation }.first(3) ``` - 2.0 Enumerator#size for on-demand size calculation when possible. The code that creates Enumerator, might pass size argument to Enumerator.new (value or a callable object) if it can calculate the amount of objects to enumerate. - 2.2 Enumerable#slice_after and #slice_when - 2.3 Enumerable#grep_v and #chunk_while - 2.4 Enumerable#sum as a generalized shortcut for reduce(:+); might be redefined in descendants (like Array) for efficiency. - 2.6 Enumerable#filter/#filter! as alias for #select/#select! (as more familiar for users coming from other languages) - 2.7 Enumerable#tally method to count stats (hash of {object => number of occurrences in the enumerable}) - 2.7 Enumerator::Yielder#to_proc - 3.2 Enumerator.product introduced to create a cross-product of Enumerablealike objects. ### Range - 2.6 Endless range: (1..) - 2.6 #=== uses #cover? instead of #include? which means that ranges can be used in case and grep for any types, just checking if the value is between range ends: ```plain text case DateTime.now when Date.new(2022)..Date.new(2023) # wouldn't match in Ruby 2.5, would match in Ruby 2.6 ``` - 2.6 #cover? accepts range argument - 2.7 Beginless range: (...100) - 3.3 Range#reverse_each (specialized form of Enumerable#reverse_each) - 3.4 #size raises TypeError if the range is not iterable. - 3.4 #step allows iterating by using + operator for all types: ```plain text (Time.new(2024, 12, 20)..Time.new(2024, 12, 24)).step(24*60*60).to_a #=> [2024-12-20 00:00:00 +0200, 2024-12-21 00:00:00 +0200, 2024-12-22 00:00:00 +0200, 2024-12-23 00:00:00 +0200, 2024-12-24 00:00:00 +0200] ``` ### Array ### Hash - 2.0 Kernel#Hash, invoking argument’s #to_hash implicit conversion method, if it has one. - 2.2 Change overriding policy for duplicated key: {**hash1, **hash2} contains values of hash2 for duplicated keys. - 2.2 Hash literal: Symbol key followed by a colon can be quoted, allowing code like {"data-key": value} or {"#{prefix}_data": value}. - 2.3 #fetch_values: a multi-key version of #fetch - 2.3 #to_proc: ```plain text ATTRS = {first_name: 'John', last_name: 'Doe', gender: 'Male', age: 27} %i[first_name age].map(&ATTRS) # => ['John', 27] ``` - 3.0 #transform_keys: argument for key renaming ```plain text {first: 'John', last: 'Doe'}.transform_keys(first: :first_name, last: :last_name) #=> {:first_name => 'John', :last_name => 'Doe'} ``` - 3.4 .new accepts an optional capacity: argument - 3.4 #inspect rendering have been changed: ```plain text p({x: 1, 'foo-bar': 2, "baz" => 3}) # Ruby 3.3: {:x=>1, :"foo-bar"=>2, "baz"=>3} # Ruby 3.4: {x: 1, "foo-bar": 2, "baz" => 3} ``` Set was a part of the standard library, but since Ruby 3.2 it became part of Ruby core. A more efficient implementation (currently Set is implemented in Ruby, and stores data in Hash inside), and a separate set literal is up for discussion. That’s why we list Set’s changes are listed briefly here. - 2.5 #=== as alias to #include?, so Set can be used in grep and case: ```plain text file_list.grep(Set['README.md', 'License.txt']) # find an item that matches any of sets elements ``` - 3.0 SortedSet (that was a part of set standard library before) has been removed for dependency and performance reasons (it silently depended upon rbtree gem). - 3.0 #join is added as a shorthand for .to_a.join. - 3.0 #<=> generic comparison operator (separate operators like #< or #> have been worked in previous versions, too) - 3.2 Set became a built-in class ### Other collections ## Filesystem and IO - 2.1 IO#seek improvements: supports SEEK_DATA and SEEK_HOLE, and symbolic parameters (:CUR, :END, :SET, :DATA, :HOLE) for 2nd argument. - 2.1 IO#read_nonblock and #write_nonblock accepts optional exception: false to return symbols - 2.3 New flags/constants for IO opening: File::TMPFILE (open anonymous temp file) and File::SHARE_DELETE (open file that is allowed to delete) - 2.3 IO.new: new keyword argument flags: - 2.4 chomp: option for string splitting: ```plain text File.readlines("test.txt") # => ["foo\n", "bar\n", "baz\n"] File.readlines("test.txt", chomp: true) # => ["foo", "bar", "baz"] ``` - 2.5 IO#write accepts multiple arguments - 2.5 File.open better supports newline: option - 2.5 Dir.glob: base: argument allows to provide a directory to look into instead of constructing a glob string including it. - 2.6 New IO open mode 'x': combined with 'w' (open for writing), requests that file didn’t exist before opening. - 3.1 File.dirname: optional level to go up the directory tree - 3.2 Support for timeouts for blocking IO via IO#timeout=. - 3.3 Create subprocesses with IO.read('| command') and similar methods is deprecated. ## Exceptions This section covers exception raising/handling behavior changes, as well as changes in particular core exception classes. - 2.0 LoadError#path method to return the file name that could not be loaded. - 2.1 Exception#cause provides the previous exception which has been caught at where raising the new exception. - 2.3 NameError#receiver stores an object in context of which the error have happened. - 2.3 NameError and NoMethodError suggest possible fixes with did_you_mean gem: ```plain text 'test'.szie # NoMethodError: undefined method `szie' for "test":String # Did you mean? size ``` - 2.5 rescue/else/ensure are allowed inside blocks: ```plain text # before Ruby 2.5: %w[1 - 3].map do |num| begin Integer(num) rescue 'N/A' end end # Ruby 2.5+: %w[1 - 3].map do |num| Integer(num) rescue 'N/A' end ``` - 2.5 Don’t hide coercion errors in Numeric and Range operations: raise original exception and not “can’t be coerced” or “bad value for range” - 2.6 else in exception-handling context without any rescue is prohibited. - 2.6 #Integer() and other similar conversion methods now have optional argument exception: true/false, defining whether to raise error on input that can’t be converted or just return nil - 2.6 New arguments: receiver: for NameError.new and NoMethodError.new; key: for KeyError.new. It allows user code to construct errors with the same level of detail the language can. - 2.6 Exception#full_message: formatting options highlight: and order: added - 3.1 Thread::Backtrace.limit reader to get the maximum backtrace size set with -backtrace-limit command-line option - 3.2 Exception#detailed_message to separate the original error message and possible contextual additions. - 3.4 Backtrace formatting adjustments: backtick is replaced with singular quote, and module name added to labels in the call stack. ### Warnings - 2.5 Kernel#warn: uplevel: keyword argument allows to tune which line to specify in warning message as a source of warning - 2.7 Warning::[] and Warning::[]= to choose which categories of warnings to show; the categories are predefined by Ruby and only can be :deprecated or :experimental (or none) - 3.0 User code allowed to specify category of its warnings with Kernel#warn and intercept the warning category Warning#warn with category: keyword argument; the list of categories is still closed. ## Concurrency and parallelism - 2.0 Concept of thread variables introduced: methods #thread_variable_get, #thread_variable_set, #thread_variables, #thread_variable?. Note that they are different from variables available via Thread#[], which are fiber-local. - 2.0 #join and #value now raises a ThreadError if target thread is the current or main thread. - 2.5 #fetch is to Thread#[] like Hash#fetch is to Hash#[]: it allows to reliably get Fiber-local variable, raising or providing default value when it isn’t defined ### Process - 2.0 .getsid for getting session id (unix only). ### Fiber - 2.0 #resume cannot resume a fiber which invokes #transfer. - 2.2 callcc is obsolete, and Fiber should be used - 3.0 Non-blocking Fiber and Fiber::SchedulerInterface. This is a big and important change, see detailed explanation and code examples in 3.0’s changelog. In brief, Ruby code now can perform non-blocking I/O concurrently from several fibers, with no code changes other than setting a fiber scheduler, which should be implemented by a third-party library. ### Ractor - 3.0 Ractors introduced. A long-anticipated concurrency improvement landed in Ruby 3.0. Ractors (at some point known as Guilds) are fully-isolated (without sharing GVL on CRuby) alternative to threads. To achieve thread-safety without global locking, ractors, in general, can’t access each other’s (or main program/main ractor) data. - 3.1 Ractors can access module instance variables - 3.4 require works in Ractors ## Debugging and internals - 2.6 RubyVM.resolve_feature_path introduced Binding object represents the execution context and allows to pass it around. Note: in the spirit of the rest of this reference, this section only describes the changes in a garbage collector API, not changes of CRuby GC’s algorithms. ### TracePoint ### RubyVM::AbstractSyntaxTree ### RubyVM::InstructionSequence InstructionSequence is an API to interact with Ruby virtual machine bytecode. It is implementation-specific. - 2.0 .of to get the instruction sequence from a method or a block. - 2.0 #path, #absolute_path, #label, #base_label and #first_lineno to retrieve information from where the instruction sequence was defined. ### ObjectSpace - 3.2 ObjectSpace#dump_all allow to dump object shapes, a concept introduced in Ruby 3.2. ## Deeper topics ### Refinements Refinements are hygienic replacement for reopening of classes and modules. They allow to add methods to objects on the fly, but unlike reopening classes (known as “monkey-patching” and frequently frowned upon), changes made by refinements are visible only in the file and module the refinement is used. As the adoption of refinements seems to be questionable, the details of their adjustments are put in a separate “deeper topics” section. - 2.0 Refinements are introduced as experimental feature with Module#refine and top-level using - 2.1 Module#refine and top-level using are no longer experimental - 2.1 Module#using introduced to activate refinements only in some particular module - 2.4 Refinements are supported in Symbol#to_proc and send - 2.4 #refine can refine modules, too - 2.5 Refinements work in string interpolations - 2.6 Refined methods are achievable with #public_send and #respond_to?, and implicit #to_proc. - 2.7 Refined methods are achievable with #method/#instance_method - 3.1 Refinement class representing the self inside the refine statement. In particular, new method #import_methods became available inside #refine providing some (incomplete) remedy for inability to #include modules while refining some class. - 3.2 Module#refinements to introspect which refinements some module defines; - 3.2 Module.used_refinements to check which refinements are active in the current context; and

Visibility

Visible to everyone

Reading Status

Related Bookmarks

My Note


Saved!

Annotations

Export as Markdown
+ Annotate selection

Add Annotation