diff --git a/2.4.md b/2.4.md index c05ad70..81be3a2 100644 --- a/2.4.md +++ b/2.4.md @@ -16,9 +16,9 @@ description: Ruby 2.4 full and annotated changelog --> * **Released at:** Dec 25, 2016 (NEWS file) -* **Status (as of Feb 04, 2023):** EOL, latest is 2.4.10 +* **Status (as of Dec 21, 2023):** EOL, latest is 2.4.10 * **This document first published:** Oct 14, 2019 -* **Last change to this document:** Feb 04, 2023 +* **Last change to this document:** Dec 21, 2023 ## Highlights[](#highlights) @@ -189,6 +189,7 @@ New single-method module was introduced, meant to be overridden in order to cont ``` * In Ruby 2.7, new methods [were added](2.7.html#warning-and-) to `Warning` module allowing control over per-category warning suppression; * In Ruby 3.0, category support [was improved](3.0.html#warningwarn-category-keyword-argument). + * [3.3](3.3.html#new-warning-category-performance): added new warning category: `:performance`. ### `Object#clone(freeze: false)`[](#objectclonefreeze-false) diff --git a/2.7.md b/2.7.md index a24ef90..d6b2114 100644 --- a/2.7.md +++ b/2.7.md @@ -8,9 +8,9 @@ description: Ruby 2.7 full and annotated changelog # Ruby 2.7 * **Released at:** Dec 25, 2019 (NEWS file) -* **Status (as of Sep 20, 2023):** 2.7.8 is EOL +* **Status (as of Dec 21, 2023):** 2.7.8 is EOL * **This document first published:** Dec 27, 2019 -* **Last change to this document:** Sep 20, 2023 +* **Last change to this document:** Dec 21, 2023 @@ -121,7 +121,9 @@ In block without explicitly specified parameters, variables `_1` through `_9` ca # %w[test me].each { _1.each_char { p _1 } } # ^~ ``` -* **Follow-up:** Warning on attempt to assign to numbered parameter [became errors in 3.0](3.0.html#other-changes). +* **Follow-ups:** + * [3.0](3.0.html#other-changes): Warning on attempt to assign to numbered parameter became errors. + * [3.3](3.3.html#standalone-it-in-blocks-will-become-anonymous-argument-in-ruby-34): a warning introduced indicating that in Ruby 3.4, `it` would become an alternative anonymous block parameter (only one, same as `_1`). There is no plan to deprecate numbered parameters. ### Beginless range[](#beginless-range) @@ -799,6 +801,7 @@ Like `Array#union` and `#difference`, [added](2.6.html#arrayunion-and-arraydiffe map[1] # => nil -- value successfully collected, even if key was not GC-able ``` +* **Follow-ups:** [3.3](3.3.html#objectspaceweakkeymap): A new class `ObjectSpace::WeakKeyMap` introduced, more suitable for common use-cases of a "weak mapping." It only has garbage-collectable keys. ### `Fiber#raise`[](#fiberraise) @@ -1028,7 +1031,7 @@ Allows to emit/suppress separate categories of warnings. ``` * **Notes:** * The only existing categories currently are `:deprecated` (covers all deprecations) and `:experimental` (as of 2.7, covers only pattern matching) - * Note that turning of `:deprecated` warning will also mute the warning of features which was deprecated explicitly in your code, for example with Module#deprecate_constant + * Note that turning off `:deprecated` warning will also mute the warning of features which was deprecated explicitly in your code, for example with Module#deprecate_constant ```ruby class HTTP NOT_FOUND = Exception.new @@ -1041,7 +1044,9 @@ Allows to emit/suppress separate categories of warnings. # ...no warning issued... ``` * Another way to turn on and off separate categories of warnings is passing `-W:(no-)` flag to ruby interpreter, e.g. `-W:no-experimental` means "no warnings when using experimental features". -* **Follow-up:** In Ruby 3.0, it was [allowed for user code](3.0.html#warningwarn-category-keyword-argument) to specify warning category, and intercept it. +* **Follow-ups:** + * [3.0](3.0.html#warningwarn-category-keyword-argument): user code is allowed to specify warning category, and intercept it; + * [3.3](3.3.html#new-warning-category-performance): added new warning category: `:performance`. ## Standard library[](#standard-library) diff --git a/3.0.md b/3.0.md index 0ac6a91..7d6ddd6 100644 --- a/3.0.md +++ b/3.0.md @@ -8,9 +8,9 @@ description: Ruby 3.0 full and annotated changelog # Ruby 3.0 * **Released at:** Dec 25, 2020 (NEWS.md file) -* **Status (as of Sep 20, 2023):** 3.0.6 is security maintenance (will EOL soon!) +* **Status (as of Dec 23, 2023):** 3.0.6 is security maintenance (will EOL soon!) * **This document first published:** Dec 25, 2020 -* **Last change to this document:** Sep 20, 2023 +* **Last change to this document:** Dec 23, 2023 ## Highlights[](#highlights) @@ -105,6 +105,7 @@ Just a leftover from the separation of keyword arguments. logged_get('Logging', 'https://example.com', headers: {content_type: 'json'}) ``` * **Notes:** + * The adjustment was considered important enough to be backported to 2.7 branch; * "all arguments splat" `...` should be the last statement in the argument list (both on a declaration and a call) * on a method declaration, arguments before `...` can only be positional (not keyword) arguments, and can't have default values (it would be `SyntaxError`); * on a method call, arguments passed before `...` can't be keyword arguments (it would be `SyntaxError`); @@ -960,6 +961,7 @@ Just fixes an inconsistency introduced by optimization many versions ago. processed.lambda? # => false processed.call # => "nil", it is really still not a lambda ``` +* **Follow-ups:** [3.3](3.3.html#kernellambda-raises-when-passed-proc-instance): The warning was turned into an exception. #### `Proc#==` and `#eql?`[](#proc-and-eql) diff --git a/3.2.md b/3.2.md index fe94b07..99418f2 100644 --- a/3.2.md +++ b/3.2.md @@ -1,6 +1,6 @@ --- title: Ruby 3.2 changes -prev: / +prev: 3.3 next: 3.1 description: Ruby 3.2 full and annotated changelog --- @@ -8,9 +8,9 @@ description: Ruby 3.2 full and annotated changelog # Ruby 3.2 * **Released at:** Dec 25, 2022 (NEWS.md file) -* **Status (as of Sep 20, 2023):** 3.2.2 is current _stable_ +* **Status (as of Dec 24, 2023):** 3.2.2 is current _stable_ * **This document first published:** Feb 4, 2022 -* **Last change to this document:** Sep 20, 2023 +* **Last change to this document:** Dec 24, 2023 + +**🇺🇦 🇺🇦 Before you start reading the changelog: A full-scale Russian invasion into my home country continues, for the second in a row. The only reason I am alive and able to work on the changelog is Armed Force of Ukraine, and international support with weaponry, funds and information. I got into the Army in March, and spent the summer in the frontlines. Now I have moved to another army position which lives me time to work for the Ruby community. You can [read my recent text](https://zverok.space/blog/2023-11-17-not-a-rubyconf.html) (that supposed to be a RubyConf talk) as an appeal to the community. Please [spread information, lobby our cause and donate](https://war.ukraine.ua/).🇺🇦 🇺🇦** + +> **Note:** As already explained in [Introduction](README.md), this site is dedicated to changes in the **language**, not the **implementation**, therefore the list below lacks mentions of lots of important _internal_ changes related to performance optimizations, parser, and JIT that happened in 3.3 (which is, on the other hand, somewhat lighter on the "small quality of life improvement" changes). The changes aren't covered not because they are not important, just because this site's goals are different. See [the official release notes](https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/) that cover those significant internal changes. + +## Highlights[](#highlights) + +* [`it` will become anonymous block argument in 3.4](#standalone-it-in-blocks-will-become-anonymous-argument-in-ruby-34) +* [`Module#set_temporary_name`](#moduleset_temporary_name) +* [`ObjectSpace::WeakKeyMap`](#objectspaceweakkeymap) +* [`Range#overlap?`](#overlap) +* [`Fiber#kill`](#fiberkill) + +## Language changes[](#language-changes) + +### Standalone `it` in blocks will become anonymous argument in Ruby 3.4[](#standalone-it-in-blocks-will-become-anonymous-argument-in-ruby-34) + +In Ruby 3.3, it will just warn to prepare for a change. + +* **Reason:** Numeric designation for anonymous bloc arguments (`_1`, `_2`, and so on) were considered ugly by many people, so after years of discussion, the `it` keyword is to be introduced on the next Ruby version; for now, it just warns _in places where it would be considered an anonymous block argument_. +* **Discussion:** Feature #18980 +* **Code:** In the code below, where Ruby 3.3 currently produces a warning, Ruby 3.4 would treat `it` as an anonymous block argument; where Ruby 3.3 doesn't produce a warning, Ruby 3.4 would treat `it` as a local variable name or a method call (and would look for such names available in the scope). + ```ruby + # The cases that are warned: + # ------------------------- + # warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it() or self.it + + (1..3).map { it } # inside a block without explicit parameters + (1..3).map { it; _1 } # ...even if numbered parameters are used, too + def it; end + (1..3).map { it } # even if a method with name `it` exists in the scope + + # The cases that are not warned: + # ----------------------------- + + it # not inside a block + (1..3).map { |x| it } # inside a block with named parameters + (1..3).map { || it } # ...even if they are empty + (1..3).map { it() } # with parentheses + (1..3).map { it {} } # with a block attached + (1..3).map { it = 5; it } # if a local variable with the same name is created in the block + it = 5 + (1..3).map { it } # if a local variable with the same name is in the scope + ``` +* **Notes:** The new feature isn't expected to conflict with RSpec's [`it`](https://rspec.info/documentation/3.12/rspec-core/RSpec/Core/ExampleGroup.html#it-class_method), as calling that without any block attached, or at least a description for the future example, is useless. + +## Core classes and modules[](#core-classes-and-modules) + +### `Kernel#lambda` raises when passed `Proc` instance[](#kernellambda-raises-when-passed-proc-instance) + +* **Reason:** `lambda`'s goal is to create a lambda from provided literal block; in Ruby, it is impossible to change the "lambdiness" of the block once it is created. But `lambda(&proc_instance)` never notified users of that, which was confusing. +* **Discussion:** Feature #19777 +* **Documentation:** Kernel#lambda _(no specific details are provided, though)_ +* **Code:** + ```ruby + # Intended usage: + l = lambda { |a, b| a + b } + l.lambda? #=> true + l.parameters #=> [[:req, :a], [:req, :b]] + + # Unintended usage: + p = proc { |a, b| a + b } + + # In Ruby 3.2 and below, it worked, but the produced value wasn't lambda: + l = lambda(&p) + l.parameters #=> [[:opt, :a], [:opt, :b]] + l.lambda? #=> false + l.object_id == p.object_id #=> true, it is just the same proc + + # Ruby 3.3: + l = lambda(&p) + # in `lambda': the lambda method requires a literal block (ArgumentError) + + # Despite the message about a "literal block," the method + # works (though has no meaningful effect) with lambda-like Proc objects + other_lambda = lambda { |a, b| a + b } + lambda(&other_lambda) #=> works + lambda(&:to_s) #=> works + lambda(&method(:puts)) #=> works + ``` +* **Notes:** The discussion was once [started](https://bugs.ruby-lang.org/issues/15973) from the proposal to make `lambda` change "lambiness" of a passed block, but it raises multiple issues (changing the block semantics mid-program is just one of them). In general, `lambda` as a _method_ is considered legacy, inferior to the `-> { }` lambda literal syntax, exactly due to problems like this: it looks like a regular method that receives a block, and therefore should be able to accept _any_ block, but in fact it is "special" method. So in 3.0, there was a warning about `lambda(&proc_instance)`, and since 3.3, the warning finally turned into an error. + +### `Proc#dup` and `#clone` call `#initialize_dup` and `#initialize_copy`[](#procdup-and-clone-call-initialize_dup-and-initialize_copy) + +* **Reason:** A fix for a small inconsistency created [in 3.2](3.2.html#procdup-returns-an-instance-of-subclass): Since that version, `#dup` and `#clone` on an object inherited from the `Proc`, rightfully produced an instance of the inherited class. But despite `Object`'s `#dup` and `#clone` methods docs claiming that corresponding copying constructors would be called on object cloning/duplication, it was not true for `Proc`. +* **Discussion:** Feature #19362 +* **Documentation:** — (Adheres to the behavior described for Object#dup and #clone) +* **Code:** + ```ruby + # The examples would work the same way with + # #dup/#initialize_dup and #clone/#initialize_copy + + class TaggedProc < Proc + attr_reader :tag + + def initialize(tag) + super() + @tag = tag + end + + def initialize_dup(other) + @tag = other.tag + super + end + end + + proc = TaggedProc.new('admin') { } + + proc.tag #=> 'admin' + proc.dup.tag + # Ruby 3.1: + # undefined method `tag' for # -- #dup didn't preserve the class + # Ruby 3.2: + # => nil -- the class is preserved, yet the duplication didn't went through #initialize_dup + # Ruby 3.3: + # => "admin" + ``` +* **Notes:** Inheriting from core classes is an advanced technique, and most of the times there are simple ways to achieve same goals (like wrapper objects containing a `Proc` and an additional info). + +### `Module#set_temporary_name`[](#moduleset_temporary_name) + +Allows to assign a string to be rendered as class/module's `#name`, without assigning the class/module to a constant. + +* **Reason:** The feature is useful to provide reasonable representation for dynamically auto-generated classes without assigning them to constants (which pollutes the global namespace and might conflict with existing constants) or redefining `Class#name` (which might break other code and not always respected in the output). +* **Discussion:** Feature #19521 +* **Documentation:** Module#set_temporary_name +* **Code:** + ```ruby + dynamic_class = Class.new do + def foo; end + end + + dynamic_class.name #=> nil + + # For dynamic classes, representation of related values is frequently unreadable: + dynamic_class #=> # + instance = dynamic_class.new #=> #<#:0x0...> + instance.method(:foo) #=> ##foo() ...> + + dynamic_class::Nested = Module.new + dynamic_class::Nested #=> #::Nested + + # After assigning the temporary name, representation becomes more convenient: + dynamic_class.set_temporary_name("MyDSLClass(with description)") + + dynamic_class #=> MyDSLClass(with description) + instance #=> # + instance.method(:foo) #=> # + + # Note that module constant names are assigned at the moment of their creation, + # and don't change when the temporary name is assigned: + dynamic_class::OtherNested = Module.new + + dynamic_class::Nested #=> #::Nested + dynamic_class::OtherNested #=> MyDSLClass(with description)::OtherNested + + # Assigning names that correspond to constant name rules is prohibited: + dynamic_class.set_temporary_name("MyClass") + # `set_temporary_name': the temporary name must not be a constant path to avoid confusion (ArgumentError) + dynamic_class.set_temporary_name("MyClass::NestedName") + # `set_temporary_name': the temporary name must not be a constant path to avoid confusion (ArgumentError) + + # When the module with a temporary name is put into a constant, + # it receives a permanent name, which can't be changed anymore + C = dynamic_class + + # It affects all associated values (including modules) + + dynamic_class #=> C + instance #=> # + instance.method(:foo) #=> # + dynamic_class::Nested #=> C::Nested + dynamic_class::OtherNested #=> C::OtherNested + + dynamic_class.set_temporary_name("Can I have it back?") + # `set_temporary_name': can't change permanent name (RuntimeError) + + # `nil` can be used to cleanup a temporary name: + other_class = Class.new + other_class.set_temporary_name("another one") + other_class #=> another one + other_class.set_temporary_name(nil) + other_class #=> # + ``` +* **Notes:** Any phrase that used as a temporary name would be used verbatim; this might create very confusing `#inspect` results and error messages; so it is advised to use strings somehow implying that the name belong to a module. Imagine we wrap into classes with temporary names RSpec-style examples, and then there is a typo in the body of such example: + ```ruby + it "works as a calculator" do + expec(2+2).to eq 4 + end + # If we assign just the example description as a temp.name, the + # error would look like this: + # + # undefined method `expec' for an instance of works as a calculator + # ^^^^^^^^^^^^^^^^^^^^^ + # + # ...which is confusing. So it is probably better to construct a + # module-like temporary name, to have: + # + # undefined method `expec' for an instance of MyFramework::Example("works as a calculator") + # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ``` + +### `Refinement#refined_class` is renamed to `Refinement#target`[](#refinementrefined_class-is-renamed-to-refinementtarget) + +Just a renaming of the unfortunately named new method that [emerged in Ruby 3.2](3.2.html#refinementrefined_class). + +* **Discussion:** Feature #19714 +* **Documentation:** Refinement#target + +### Strings and regexps[](#strings-and-regexps) + +#### `String#bytesplice`: new arguments to select a portion of the replacement string[](#stringbytesplice-new-arguments-to-select-a-portion-of-the-replacement-string) + +The low-level string manipulation method now allows to provide a coordinates of the part of the replacement string to be used. + +* **Reason:** The new "byte-oriented" methods [were introduced](https://rubyreferences.github.io/rubychanges/3.2.html#byte-oriented-methods) in Ruby 3.2 to support low-level programming like text editors or network protocol implementations. In those use cases, the necessity of copying of a small part of one string into the middle of another is frequent, and producing intermediate strings (by first slicing the necessary part) is costly. +* **Discussion:** Feature #19314 +* **Documentation:** String#bytesplice +* **Code:** + ```ruby + # Base usage + buf1 = "Слава Україні!" + # ^^^^^^^ - bytes 11-24 + buf2 = "Шана Героям" + # ^^^^^^ - bytes 9-20 + + buf1.bytesplice(11..24, buf2, 9..20) + #=> "Слава Героям!" + buf1 + #=> "Слава Героям!" -- The receiver is modified + + # Or, alternatively, with (start, length) pairs + buf1 = "Слава Україні!" + buf1.bytesplice(11, 14, buf2, 9, 12) + #=> "Слава Героям!" + + # Two forms can't be mixed: + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, 9, 12) + # `bytesplice': wrong number of arguments (given 4, expected 2, 3, or 5) (ArgumentError) + + # Index can't be in the middle of the Unicode character: + buf1.bytesplice(11..23, buf2, 9..20) + # ^ + # `bytesplice': offset 24 does not land on character boundary (IndexError) + buf1.bytesplice(11..24, buf2, 9..19) + # ^ + # `bytesplice': offset 20 does not land on character boundary (IndexError) + + # Semi-open ranges work: + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, 9..) + #=> "Слава Героям!" + + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, ...8) + #=> "Слава Шана!" + + # Empty ranges lead to inserting empty strings: + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, 9...8) + #=> "Слава !" + ``` + +#### `MatchData#named_captures`: `symbolize_names:` argument[](#matchdatanamed_captures-symbolize_names-argument) + +* **Discussion:** Feature #19591 +* **Documentation:** MatchData#named_captures +* **Code:** + ```ruby + m = "2023-12-25".match(/(?\d{4})-(?\d{2})-(?\d{2})/) + m.named_captures + #=> {"year"=>"2023", "month"=>"12", "day"=>"25"} + m.named_captures(symbolize_names: true) + #=> {:year=>"2023", :month=>"12", :day=>"25"} + ``` +* **Notes:** While `symbolize_names:` might looks somewhat strange (usually we talk about hash _keys_), it is done for consistency with Ruby standard library's `JSON.parse` signature, which inherited the terminology from the JSON specification. + +### `Time.new` with string argument became stricter[](#timenew-with-string-argument-became-stricter) + +The method now requires fully-specified date-time string. + +* **Discussion:** Bug #19293 +* **Documentation:** Time#new +* **Code:** + ```ruby + Time.new('2023-12-20') + # Ruby 3.2: #=> 2023-12-20 00:00:00 +0200 + # Ruby 3.3: in `initialize': no time information (ArgumentError) + + Time.new('2023-12') + # Ruby 3.2: #=> 2023-12-01 00:00:00 +0200 + # Ruby 3.3: in `initialize': no time information (ArgumentError) + + # Singular year is still works: + Time.new('2023') + #=> 2023-01-01 00:00:00 +0200 + + # ...because it is documented behavior of Time.new to accept + # strings that are numeric and treat them as numbers: + Time.new('2023', '12', '20') + #=> 2023-12-20 00:00:00 +0200 + ``` + +### `Array#pack` and `String#unpack`: raise `ArgumentError` for unknown directives[](#arraypack-and-stringunpack-raise-argumenterror-for-unknown-directives) + +* **Discussion:** Bug #19150 +* **Documentation:** doc/packed_data.rdoc +* **Code:** + ```ruby + [1, 2, 3].pack('r*') + # Ruby 3.1: + # => "", no warning + # Ruby 3.2: + # => "", warning: unknown pack directive 'r' in 'r*' + # Ruby 3.3: + # in `pack': unknown pack directive 'r' in 'r*' (ArgumentError) + + "\x01\x02\x03".unpack("r*") + # Ruby 3.1: + # => [], no warning + # Ruby 3.2: + # => [], warning: unknown unpack directive 'r' in 'r*' + # Ruby 3.3: + # in `unpack': unknown pack directive 'r' in 'r*' (ArgumentError) + ``` + +### Enumerables and collections[](#enumerables-and-collections) + +#### `Set#merge` accepts multiple arguments[](#setmerge-accepts-multiple-arguments) + +* **Documentation:** Set#merge +* **Code:** + ```ruby + Set[1, 2, 3].merge(Set[3, 4, 5], Set[:a, :b, :c]) + #=> # + ``` +* **Notes:** The method's signature (seen in docs) has a rare clause `**nil`. It means "don't accept something that looks like keyword arguments." As `#merge` accept any list of enumerables, this protects from accidentally passing a hash believing it would be keyword arguments with some meaning: + ```ruby + Set[1, 2, 3].merge(Set[3, 4, 5], reorder: false) + # ^^^^^^^^^^^^^^ + # Without **nil, this would be treated implicitly as Hash, while looking like keyword arguments + # But actually, it produces + # no keywords accepted (ArgumentError) + + # When you do mean to merge data from hash, use parentheses to make it explicit + # (its #each would be used to produce set items): + Set[1, 2, 3].merge(Set[3, 4, 5], {some: 'data'}) + #=> # + ``` + +#### `ObjectSpace::WeakKeyMap`[](#objectspaceweakkeymap) + +A new "weak map" concept implementation. Unlike `ObjectSpace::WeakMap`, it compares keys by equality (`WeakMap` compares by identity), and only references to keys are weak (garbage-collectible). + +* **Reason:** The idea of a new class grew out of increased usage of `ObjectSpace::WeakMap` (which was once considered internal). In many other languages, concept of "weak map" implies only key references are weak: this allows to use it as a generic "holder of some additional information related to a set of objects while they are alive," or just a weak set of objects (using them as keys and `true` as values): caches, deduplication sets, etc. +* **Discussion:** Feature #18498 +* **Documentation:** ObjectSpace::WeakKeyMap +* **Code:** + ```ruby + map = ObjectSpace::WeekMap.new + + key = "foo" + map[key] = true + map["foo"] #=> true -- compares by equality, even if two strings are different objects + + # "Just return the equal key" API, always returns the key's object + map.getkey("foo") #=> "foo" + map.getkey("foo").object_id == key.object_id #=> true + + key = nil + GC.start + + map["foo"] #=> nil -- the key was garbage-collected, so the pair was removed + + # One of the possible usages: a lightweight uniqueness cache for + # many small objects: + class Money < Data.define(:amount, :currency) + def self.new(...) + value = super(...) + @cache ||= ObjectSpace::WeakKeyMap.new + if (existing = @cache.getkey(value)) + existing + else + @cache[value] = true + end + end + end + + m1 = Money.new(10, 'USD') + m2 = Money.new(10, 'USD') + m1.object_id #=> 60 + m2.object_id #=> 60 + # Same values, it is the same object, so there wouldn't be a huge memory + # penalty when thousands of similar values are created. + + # No references to "10 USD" object left + m1 = nil + m2 = nil + GC.start + m3 = Money.new(10, 'USD') + m3.object_id #=> 80 + # The unused values got garbage-collected, so the cache wouldn't just grow forever + ``` +* **Notes:** The class interface is significantly leaner than `WeakMap`'s, and doesn't provide any kind of iteration methods (which is very hard to implement and use correctly with weakly-referenced objects), so the new class is more like a black box with associations than a collection. + +#### `ObjectSpace::WeakMap#delete`[](#objectspaceweakmapdelete) + +* **Reason:** `WeakMap` is frequently used to have a loose list of objects that will need some processing at some point of program execution if they are still alive/used (that's why `WeekMap` and not `Array`/`Hash` is chosen in those cases). But it is possible that the code author wants to process objects conditionally, and to remove those which don't need processing anymore—even if they are still alive. `WeekMap` quacks like kind-of simple `Hash`, yet previously provided no way to delete keys. +* **Discussion:** Feature #19561 +* **Documentation:** ObjectSpace::WeakMap#delete +* **Code:** + ```ruby + files_to_close = ObjectSpace::WeakMap.new + file1 = File.new('README.md') + file2 = File.new('NEWS.md') + + files_to_close[file1] = true + files_to_close[file2] = true + + files_to_close.delete(file1) #=> true + + # Attempt to delete non-existing key: + files_to_close.delete(file1) #=> nil + # An optional block can be provided in case the key doesn't exist: + files_to_close.delete(file1) { puts "Already removed"; 0 } + # Prints "Already removed", returns `0` + + # The block wouldn't be called if the deletion was effectful: + files_to_close.delete(file2) { puts "Already removed"; 0 } + # Prints nothing, returns true + ``` + +#### `Thread::Queue#freeze` and `SizedQueue#freeze` raise `TypeError`[](#threadqueuefreeze-and-sizedqueuefreeze-raise-typeerror) + +* **Reason:** The discussion was started with a bug report about `Queue` not respecting `#freeze` in any way (`#push` and `#pop` were still working after `#freeze` call). It was then decided that allowing to freeze a queue like any other collection (leaving it immutable) would have questionable semantics. As `Queue` is meant to be an inter-thread communication utility, freezing a queue while some thread waits for it would either leave this thread hanging, or would require `#freeze`'s functionality to extend for communication with dependent threads. Neither is a good option, so the behavior of the method was changed to communicate that queue freezing doesn't make sense. +* **Discussion:** Bug #17146 +* **Documentation:** Thread::Queue#freeze and Thread::SizedQueue#freeze + +### `Range`[](#range) + +#### `#reverse_each`[](#reverse_each) + +Specialized `Range#reverse_each` method is implemented. + +* **Reason:** Previously, `Range` didn't have a specialized `#reverse_each` method, so calling it invoked a generic `Enumerable#reverse_each`. The latter works by converting the object to array, and then enumerating this array. In case of a `Range` this can be inefficient (producing large arrays) or impossible (when only upper bound of the range is defined). It also went into infinite loop with endless ranges, trying to enumerate it all to convert into array, while the range can say beforehand that it would be impossible. +* **Discussion:** Feature #18515 +* **Documentation:** Range#reverse_each +* **Code:** + ```ruby + # Efficient implementation for integers: + + (1..2**100).reverse_each.take(3) + # Ruby 3.2: hangs on my machine, trying to produce an array + # Ruby 3.3: #=> [1267650600228229401496703205376, 1267650600228229401496703205375, 1267650600228229401496703205374] + # (returns immediately) + + (...5).reverse_each.take(3) + # Ruby 3.2: can't iterate from NilClass (TypeError) + # Ruby 3.3: #=> [5, 4, 3] + + # Explicit error for endless ranges: + + (1...).reverse_each + # Ruby 3.2: hangs forever, trying to produce an array + # Ruby 3.3: `reverse_each': can't iterate from NilClass (TypeError) + + # The latter change affects any type of range beginning: + ('a'...).reverse_each + # Ruby 3.2: hangs forever, trying to produce an array + # Ruby 3.3: `reverse_each': can't iterate from NilClass (TypeError) + ``` +* **Notes:** Other than raising `TypeError` for endless ranges (which works with any type of range beginning), the specialized behavior is only implemented for `Integer`. A possibility of a generalization was [discussed](https://bugs.ruby-lang.org/issues/18515#note-4) by using object's `#pred` method (opposite to `#succ`, which the range uses to iterate forward), but the scope of this change would be bigger, as currently only `Integer` implements such method. It is possible that the adjustments would be made in the future versions. + +#### `#overlap?`[](#overlap) + +Checks for overlapping of two ranges. + +* **Discussion:** Feature #19839 +* **Documentation:** Range#overlap? +* **Code:** + ```ruby + (1..3).overlap?(2..5) #=> true + (1..3).overlap?(4..5) #=> false + (..3).overlap?(3..) #=> true + + (1...3).overlap?(3..5) + #=> false, the first range doesn't include 3 + (1..3).overlap?(3...3) + #=> false, the second range is empty (note it has an exclusive end) + + (1..3).overlap?('a'..'c') + #=> false, ranges are incompatible (but not an exception) + (1..3).overlap?(1) + # `overlap?': wrong argument type Integer (expected Range) (TypeError) + ``` +* **Notes:** As documentation points out, the _technically empty_ `(...-Float::INFINITY)` range (nothing can be lower than `Float::INFINITY`, and it is not included) still considered overlapping with itself by this method: + ```ruby + (...-Float::INFINITY).overlap?(...-Float::INFINITY) #=> true + # Same with other "nothing could be smaller" ranges: + (..."").overlap?(..."") #=> true + ``` + (Though, with Ruby's dynamic nature, one _technically can_ define an object that will report itself to be smaller than an empty string, and therefore belong to a range... Making it non-empty.) + +### Filesystem and IO[](#filesystem-and-io) + +#### `Dir.for_fd` and `Dir.fchdir`[](#dirfor_fd-and-dirfchdir) + +Two methods to accept an integer file descriptor as an argument: `for_fd` creates a `Dir` object from it; `fchdir` changes the current directory to one specified by a descriptor. + +* **Reason:** New methods allow to use UNIX file descriptors if they are returned from a C-level code or obtained from OS. +* **Discussion:** Feature #19347 +* **Documentation:** Dir.for_fd, Dir.fchdir +* **Code:** + ```ruby + fileno = Dir.new('doc/').fileno + # In reality, this #fileno might come from other library + + dir = Dir.for_fd(fileno) + #=> # -- no readable path representation + dir.path #=> nil + dir.to_a + #=> ["forwardable.rd.ja", "packed_data.rdoc", "marshal.rdoc", "format_specifications.rdoc", .... + # It was performed in the Ruby's core folder, and lists the doc/ contents + + # Attempt to use a bogus fileno will result in error: + Dir.for_fd(0) + # `for_fd': Not a directory - fdopendir (Errno::ENOTDIR) + + # Same with fileno that doesn't designate a directory: + Dir.for_fd(Dir.new('README.md').fileno) + # in `initialize': Not a directory @ dir_initialize - README.md (Errno::ENOTDIR) + + # Same logic works for .fchdir + Dir.fchdir(fileno) #=> 0 + Dir.pwd + #=> "/home/zverok/projects/ruby/doc" -- the current path have changed successfully + + # A block form of fchdir is available, like for a regular .chdir: + Dir.fchdir(Dir.new('NEWS').fileno) do |*args| + p args #=> [] -- no arguments are passed into the block + p Dir.pwd #=> "/home/zverok/projects/ruby/doc/NEWS" + 'return value' + end #=> "return value" + Dir.pwd #=> "/home/zverok/projects/ruby/doc" -- back to the path before the block + ``` +* **Notes:** + * The functionality is only supported on POSIX platforms; + * The initial [ticket](https://bugs.ruby-lang.org/issues/19347) only proposed to find a way to be able to change a current directory to one specified by a descriptor (i.e., what eventually became `.fchdir`), but during the discussion a need were discovered for a generic instantiation of a `Dir` instance from the descriptor (what became `from_fd`), as well as a generic way to change the current directory to one specified by `Dir` instance ([`#chdir`](#dirchdir), which is not related to descriptors but is generically useful). + +#### `Dir#chdir`[](#dirchdir) + +An instance method version of Dir.chdir: changes the current working directory to one specified by the `Dir` instance. + +* **Discussion:** Feature #19347 +* **Documentation:** Dir#chdir +* **Code:** + ```ruby + Dir.pwd #=> "/home/zverok/projects/ruby" + dir = Dir.new('doc') + dir.chdir #=> nil + Dir.pwd #=> "/home/zverok/projects/ruby/doc" + + # The block form works, too: + Dir.new('NEWS').chdir do |*args| + p args #=> [] -- no arguments are passed into the block + Dir.pwd #=> "/home/zverok/projects/ruby/doc/NEWS" + 'return value' + end #=> "return value" + Dir.pwd #=> "/home/zverok/projects/ruby/doc" + ``` + +#### Deprecate subprocess creation with method dedicated to files[](#deprecate-subprocess-creation-with-method-dedicated-to-files) + +* **Reason:** Methods that are dedicated for opening/reading a file by name historically supported the special syntax of the argument: if it started with pipe character `|`, the subprocess was created and could've been used to communicate with an external command. The functionality is still explained in Ruby 3.2 docs. It, though, created a security vulnerability: even when the program's author didn't rely on that behavior, the malicious string could've been passed by the attacker instead of an innocent filename. +* **Discussion:** Feature #19630 +* **Affected methods:** + * Kernel#open + * IO.binread + * IO.foreach + * IO.readlines + * IO.read + * IO.write + * URI.open (open-uri standard library) +* **Code:** + ```ruby + IO.read('| ls') + #=> contents of the current folder + + Warning[:deprecated] = true # Or pass -w command-line option + IO.read('| ls') + # warning: Calling Kernel#open with a leading '|' is deprecated and will be removed in Ruby 4.0; use IO.popen instead + #=> contents of the current folder + ``` +* **Notes:** + * The documentation for the corresponding methods was adjusted accordingly. Compare the documentation for `Kernel#open` from 3.2 (explains and showcases the `|` trick) and 3.3 (just mentions that there is a vulnerability to command injection attack). + * As advised by the warning, IO.popen is a specialized method when communicating with an external process is desired functionality: + ```ruby + IO.popen('ls') + #=> contents of the current folder + ``` + * As the impact of the change might be big, note that target version for removal is set to **4.0**. To the best of my knowledge, there are no set date for major version yet. + +### `NoMethodError`: change of rendering logic[](#nomethoderror-change-of-rendering-logic) + +`NoMethodError` doesn't use target object's `#inspect` in its message, and renders "instance of ClassName" instead. + +* **Reason:** While the `#inspect` of the object which failed to respond might be convenient in the error's output, it also might be extremely inefficient and confusing when the object is large and doesn't have `#inspect` redefined to something sensible. It is impossible to require all user objects to redefine `#inspect`, and even if it is redefined, it might be short yet inefficient; so the lesser of evils was chosen and exception's message became more efficient even if less informative. +* **Documentation:** NoMethodError +* **Code:** + ```ruby + "hello".to_ary + # Ruby 3.2: undefined method `to_ary' for "hello":String (NoMethodError) + # Ruby 3.3: undefined method `to_ary' for an instance of String (NoMethodError) + + # But also, for some complicated data structure: + ([{name: 'User 1', role: 'admin'}] * 100).to_josn # typo + # Ruby 3.2: undefined method `to_josn' for [{:name=>"User 1", :role=>"admin"}, {:name=>"User 1", :role=>"admin"}, ... + # ....10 lines of console output.... + # ..., {:name=>"User 1", :role=>"admin"}]:Array (NoMethodError) + # + # Ruby 3.3: undefined method `to_josn' for an instance of Array (NoMethodError) + ``` + +### `Fiber#kill`[](#fiberkill) + +Terminates the Fiber by sending an exception inside it. + +* **Reason:** The method is intended to be used to fibers that represent processes that need to be told explicitly to finalize themselves (invoking any `ensure` operations and cleanups that are necessary). If such fiber just abandoned and collected by a GC, it wouldn't invoke fiber's `ensure`, and therefore the resources wouldn't be cleaned; so there was need for a way to do this explicitly. +* **Discussion:** Bug #595 +* **Documentation:** Fiber#kill +* **Code:** + ```ruby + f = Fiber.new do + (1..).each { Fiber.yield _1 } + ensure + puts "Closing myself" + end + #=> # + + f.resume #=> 1 + f.resume #=> 2 + f #=> # + f.kill + # Prints: "Closing myself" + f #=> # + f.resume + # `resume': attempt to resume a terminated fiber (FiberError) + + # Semi-realistic usage example: + + reader = Fiber.new do + conn = SomeConnection.open(**params) + while conn.open? + Fiber.yield conn.read + end + ensure + conn.close + end + + headers = reader.resume # reads something from the connection + body_line1 = reader.resume # reads some more + # Now, if we want to explicitly stop reading and be sure that the connection + # is closed, we might do this: + reader.kill # invokes #ensure + ``` +* **Notes:** + * The exception sent to Fiber is _uncatchable_ (so no `rescue Exception` will notice it), so it can't be said that it has some _class_; the only usage of the fact that it is raised through exception mechanism is invoking `ensure` block; + * The fibers that was invoking the killed one with `resume` or `transfer`, receives `nil` from that call; + ```ruby + f1 = Fiber.new { + # Instead of yielding something back, the fiber kills itself + Fiber.current.kill + } + + f2 = Fiber.new { + result = f1.transfer + p(result:) + } + + f2.resume + # prints: {:result => nil} + ``` + * Only fibers belonging to the same thread can be killed. + +### Internals[](#internals) + +#### New `Warning` category: `:performance`[](#new-warning-category-performance) + +A new warning category was introduced for a code that is correct but is known to produces a performance problems. One new such warning was added for objects with too many "shape" variations. + +* **Discussion:** Feature #19538 +* **Documentation:** Warning#[category] +* **Code:** Here is an example of the new warning in play: + ```ruby + class C + def initialize(i) + instance_variable_set("@var_#{i}", i**2) + end + end + + Warning[:performance] = true # or pass `-W:performance` command-line argument + + (1..10).map { C.new(_1) } + # warning: Maximum shapes variations (8) reached by C, instance variables accesses will be slower. + ``` + The example is artificial, but it shows the principle: when we have more than 8 instances of the _same_ class, but with different _list of instance variables_ (shape), we might have a performance problem. This means, for example, that a frequently-used class that has many methods with a memoization idiom (`@var ||= value` on the first access) would create the same problem, unless all of them would be initialized in the `initialize`, making all instances having the same shape: + ```ruby + class C + # 9 different getters that create an instance varaible + # on the first access. + def var1 = @var1 ||= rand + def var2 = @var2 ||= rand + def var3 = @var3 ||= rand + def var4 = @var4 ||= rand + def var5 = @var5 ||= rand + def var6 = @var6 ||= rand + def var7 = @var7 ||= rand + def var8 = @var8 ||= rand + def var9 = @var9 ||= rand + end + + Warning[:performance] = true + # Invoking different getters on different instances of the same class makes + # them have different set of instance variables. + (1..9).map { C.new.send("var#{_1}") } + # warning: Maximum shapes variations (8) reached by C, instance variables accesses will be slower. + + # But if we add this to initialize: + class C + def initialize + @var1, @var2, @var3, @var4, @var5, @var5, @var6, @var7, @var8, @var9 = nil + end + end + + (1..9).map { C.new.send("var#{_1}") } + # no warning. All objects have the same list of instance vars = the same shape + ``` +* **Notes:** + * The warning category should be turned on explicitly by providing `-W:performance` CLI option or `Warning[:performance] = true` from the program. +* **Additional reading:** [Performance impact of the memoization idiom on modern Ruby](https://railsatscale.com/2023-10-24-memoization-pattern-and-object-shapes/) by Ruby core team member Jean Boussier. + +#### `Process.warmup`[](#processwarmup) + +A method to call when a long-running application finalized its loading, and before the regular work is started. + +* **Discussion:** Feature #18885 +* **Documentation:** Process.warmup +* **Notes:** Hardly something can be explained or showcased here better than the justification discussion linked above and the method docs are doing it. + +#### `Process::Status#&` and `#>>` are deprecated[](#processstatus--and--are-deprecated) + +* **Reason:** These methods have been treating `Process::Status` as a very thin wrapper around an integer value of the return status of the process; which is unreasonable for supporting Ruby in more varying environments. +* **Discussion:** Bug #19868 +* **Documentation:** Process::Status#&, #>> + +#### `TracePoint` supports `:rescue` event[](#tracepoint-supports-rescue-event) + +Allows to trace when some exception was `rescue`'d in the code of interest. + +* **Discussion:** Feature #19572 +* **Documentation:** TracePoint#Events +* **Code:** + ```ruby + TracePoint.trace(:rescue) do |tp| + puts "Exception rescued: #{tp.raised_exception.inspect} at #{tp.path}:#{tp.lineno}" + end + + begin + raise "foo" + rescue => e + end + # Prints: "Exception rescued: # at example.rb:7 + ``` +* **Notes:** The event-specific attribute for the event is the same as for `:raise`: #raised_exception. + +## Standard library[](#standard-library) + +Since Ruby 3.1 release, most of the standard library is extracted to either _default_ or _bundled_ gems; their development happens in separate repositories, and changelogs are either maintained there, or absent altogether. Either way, their changes aren't mentioned in the combined Ruby changelog, and I'll not be trying to follow all of them. + +> **[stdgems.org](https://stdgems.org/)** project has a nice explanations of default and bundled gems concepts, as well as a list of currently gemified libraries and links to their docs. + +> "For the rest of us" this means libraries development extracted into separate GitHub repositories, and they are just packaged with main Ruby before release. It means you can do issue/PR to any of them independently, without going through more tough development process of the core Ruby. + +A few changes to mention, though: + +* BasicSocket#recv and BasicSocket#recv_nonblock returns `nil` instead of an empty string on closed connections. BaicSocket#recvmsg and BasicSocket#recvmsg_nonblock returns `nil` instead of an empty packet on closed connections. Discussion: Bug #19012 +* Name resolution such as Socket.getaddrinfo, Socket.getnameinfo, Addrinfo.getaddrinfo can now be interrupted. Discussion: Feature #19965 +* Random::Formatter#alphanumeric: `chars` keyword argument. Feature #18183: + ```ruby + require 'random/formatter' + # The default behavior: uses English alphabet + numbers + Random.alphanumeric + #=> "fhCshEkcGfCTO6Ny" + + # With the argument provided: + Random.alphanumeric(chars: ['a', 'b', 'c']) + #=> "cbacacbababccccc" + + # Note that the argument should be an array. + # So if you have a string of characters, you can do: + Random.alphanumeric(chars: 'abc'.chars) + #=> "abbaccaacbacbccc" + + # Any object is acceptable as an array element; the method + # would just use their `#to_s`; arrays would be flattened: + Random.alphanumeric(chars: [1, true, [2], Object.new]) + #=> "111true11211true2true#221" + + # An empty array just hangs forever: + Random.alphanumeric(chars: []) # never returns + ``` +* There were many amazing changes in Ruby's console IRB. See the article by IRB maintainer Stan Lo: [Unveiling the big leap in Ruby 3.3's IRB](https://railsatscale.com/2023-12-19-irb-for-ruby-3-3/). + +### Version updates[](#version-updates) + +#### Default gems[](#default-gems) + +* RubyGems 3.5.3 +* abbrev 0.1.2 +* base64 0.2.0 +* benchmark 0.3.0 +* bigdecimal 3.1.5 +* bundler 2.5.3 +* cgi 0.4.1 +* csv 3.2.8 +* date 3.3.4 +* delegate 0.3.1 +* drb 2.2.0 +* english 0.8.0 +* erb 4.0.3 +* error_highlight 0.6.0 +* etc 1.4.3 +* fcntl 1.1.0 +* fiddle 1.1.2 +* fileutils 1.7.2 +* find 0.2.0 +* getoptlong 0.2.1 +* io-console 0.7.1 +* io-nonblock 0.3.0 +* io-wait 0.3.1 +* ipaddr 1.2.6 +* irb 1.11.0 (Releases page, [blog post](https://railsatscale.com/2023-12-19-irb-for-ruby-3-3/)) +* json 2.7.1 +* logger 1.6.0 +* mutex_m 0.2.0 +* net-http 0.4.0 +* net-protocol 0.2.2 +* nkf 0.1.3 +* observer 0.1.2 +* open-uri 0.4.1 +* open3 0.2.1 +* openssl 3.2.0 +* optparse 0.4.0 +* ostruct 0.6.0 +* pathname 0.3.0 +* pp 0.5.0 +* prettyprint 0.2.0 +* pstore 0.1.3 +* psych 5.1.2 +* rdoc 6.6.2 +* readline 0.0.4 +* reline 0.4.1 +* resolv 0.3.0 +* rinda 0.2.0 +* securerandom 0.3.1 +* set 1.1.0 +* shellwords 0.2.0 +* singleton 0.2.0 +* stringio 3.1.0 +* strscan 3.0.7 +* syntax_suggest 2.0.0 +* syslog 0.1.2 +* tempfile 0.2.1 +* time 0.3.0 +* timeout 0.4.1 +* tmpdir 0.2.0 +* tsort 0.2.0 +* un 0.3.0 +* uri 0.13.0 +* weakref 0.1.3 +* win32ole 1.8.10 +* yaml 0.3.0 +* zlib 3.1.0 + +#### Bundled gems[](#bundled-gems) + +* minitest 5.20.0 +* rake 13.1.0 +* test-unit 3.6.1 +* rexml 3.2.6 +* rss 0.3.0 +* net-ftp 0.3.3 +* net-imap 0.4.9 +* net-smtp 0.4.0 +* rbs 3.4.0 +* typeprof 0.21.9 +* debug 1.9.1 + +### Standard library content changes[](#standard-library-content-changes) + +#### New libraries[](#new-libraries) + +* **prism (nee YARP) is added.** It is a new Ruby code parser, developed by Kevin Newton, which intends to become _the_ Ruby parser, shared by all implementations (not only CRuby/MRI, but also TruffleRuby, JRuby, and others) and tools that need to parser Ruby code (like Sorbet or Rubocop). It doesn't replace CRuby's Ruby parser, at least for now, but can be used to parse Ruby quickly and produce robust, easy to use AST. + * **Documentation:** Prism (it isn't very well rendered in the standard library docs, so the [official site](https://ruby.github.io/prism/) is recommended); + * **Note:** You can run Ruby with Prism as its main parser with `--parser=prism`, but it is only for experimentation and debugging for now. + +#### Removals[](#removals) + +* `readline` extension is removed. It was a standard library written in C to wrap [GNU Readline](https://tiswww.case.edu/php/chet/readline/rltop.html), used to implement interactive consoles like IRB. Ruby includes pure-Ruby replacement called reline [since 2.7](2.7.html#new-libraries), and now `require 'readline'` will just require it and make an alias `Readline = Reline`. Though, if the readline-ext gem is installed explicitly, `require 'readline'` would require it. Discussion: Feature #19616. + ```ruby + require 'readline' + + Readline + # Ruby 3.2: + # => Readline -- a separate library/constant + # Ruby 3.3: + # => Reline -- Readline is just an alias + + Readline.method(:readline) + # Ruby 3.2: + # => # -- a C-defined method with no location/signature extracted + # Ruby 3.3: + # => #/lib/ruby/3.3.0+0/forwardable.rb:231> + ``` + +#### Default gems that became bundled[](#default-gems-that-became-bundled) + +_This means that if your dependencies are managed by Bundler and your code depend on `racc`, it should be added to a `Gemfile`._ + +* racc 1.7.3 + +#### Gems that are warned to become bundled in the next version[](#gems-that-are-warned-to-become-bundled-in-the-next-version) + +These gems wouldn't in a Bundler-managed environment unless explicitly added to `Gemfile` since the next version of Ruby. For now, requiring them in such environment would produce a warning. Discussion: Feature #19351 (initial proposal to promote many gems, which then was deemed problematic), Feature #19776 (the warning proposal) + +* abbrev +* base64 +* bigdecimal +* csv +* drb +* getoptlong +* mutex_m +* nkf +* observer +* racc +* resolv-replace +* rinda +* syslog diff --git a/Gemfile b/Gemfile index 331868b..b026109 100644 --- a/Gemfile +++ b/Gemfile @@ -3,4 +3,5 @@ source 'https://rubygems.org' gem 'rake' gem 'memoist' gem 'github-pages', group: :jekyll_plugins -gem 'kramdown' \ No newline at end of file +gem 'kramdown' +gem 'webrick' diff --git a/Gemfile.lock b/Gemfile.lock index 37806f1..e90a481 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,63 +1,53 @@ GEM remote: https://rubygems.org/ specs: - activesupport (6.0.4.4) + activesupport (7.1.2) + base64 + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2, >= 2.2.2) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + minitest (>= 5.1) + mutex_m + tzinfo (~> 2.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) + base64 (0.2.0) + bigdecimal (3.1.5) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) - commonmarker (0.17.13) - ruby-enum (~> 0.5) - concurrent-ruby (1.1.9) - dnsruby (1.61.9) - simpleidn (~> 0.1) + commonmarker (0.23.10) + concurrent-ruby (1.2.2) + connection_pool (2.4.1) + dnsruby (1.70.0) + simpleidn (~> 0.2.1) + drb (2.2.0) + ruby2_keywords em-websocket (0.5.3) eventmachine (>= 0.12.9) http_parser.rb (~> 0) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) eventmachine (1.2.7) - execjs (2.8.1) - faraday (1.9.3) - faraday-em_http (~> 1.0) - faraday-em_synchrony (~> 1.0) - faraday-excon (~> 1.1) - faraday-httpclient (~> 1.0) - faraday-multipart (~> 1.0) - faraday-net_http (~> 1.0) - faraday-net_http_persistent (~> 1.0) - faraday-patron (~> 1.0) - faraday-rack (~> 1.0) - faraday-retry (~> 1.0) + execjs (2.9.1) + faraday (2.7.12) + base64 + faraday-net_http (>= 2.0, < 3.1) ruby2_keywords (>= 0.0.4) - faraday-em_http (1.0.0) - faraday-em_synchrony (1.0.0) - faraday-excon (1.1.0) - faraday-httpclient (1.0.1) - faraday-multipart (1.0.3) - multipart-post (>= 1.2, < 3) - faraday-net_http (1.0.1) - faraday-net_http_persistent (1.2.0) - faraday-patron (1.0.0) - faraday-rack (1.0.0) - faraday-retry (1.0.3) - ffi (1.15.5) + faraday-net_http (3.0.2) + ffi (1.16.3) forwardable-extended (2.6.0) gemoji (3.0.1) - github-pages (223) + github-pages (228) github-pages-health-check (= 1.17.9) - jekyll (= 3.9.0) + jekyll (= 3.9.3) jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) - jekyll-commonmark-ghpages (= 0.1.6) + jekyll-commonmark-ghpages (= 0.4.0) jekyll-default-layout (= 0.1.4) jekyll-feed (= 0.15.1) jekyll-gist (= 1.5.0) @@ -71,7 +61,7 @@ GEM jekyll-relative-links (= 0.6.1) jekyll-remote-theme (= 0.4.3) jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.7.1) + jekyll-seo-tag (= 2.8.0) jekyll-sitemap (= 1.4.0) jekyll-swiss (= 1.0.0) jekyll-theme-architect (= 0.2.0) @@ -89,12 +79,12 @@ GEM jekyll-theme-time-machine (= 0.2.0) jekyll-titles-from-headings (= 0.5.3) jemoji (= 0.12.0) - kramdown (= 2.3.1) + kramdown (= 2.3.2) kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.3) + liquid (= 4.0.4) mercenary (~> 0.3) minima (= 2.5.1) - nokogiri (>= 1.12.5, < 2.0) + nokogiri (>= 1.13.6, < 2.0) rouge (= 3.26.0) terminal-table (~> 1.4) github-pages-health-check (1.17.9) @@ -103,17 +93,17 @@ GEM octokit (~> 4.0) public_suffix (>= 3.0, < 5.0) typhoeus (~> 1.3) - html-pipeline (2.14.0) + html-pipeline (2.14.3) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.8.0) - i18n (0.9.5) + i18n (1.14.1) concurrent-ruby (~> 1.0) - jekyll (3.9.0) + jekyll (3.9.3) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) - i18n (~> 0.7) + i18n (>= 0.7, < 2) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) @@ -127,13 +117,13 @@ GEM jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) - jekyll-commonmark (1.3.1) - commonmarker (~> 0.14) - jekyll (>= 3.7, < 5.0) - jekyll-commonmark-ghpages (0.1.6) - commonmarker (~> 0.17.6) - jekyll-commonmark (~> 1.2) - rouge (>= 2.0, < 4.0) + jekyll-commonmark (1.4.0) + commonmarker (~> 0.22) + jekyll-commonmark-ghpages (0.4.0) + commonmarker (~> 0.23.7) + jekyll (~> 3.9.0) + jekyll-commonmark (~> 1.4.0) + rouge (>= 2.0, < 5.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) jekyll-feed (0.15.1) @@ -164,7 +154,7 @@ GEM rubyzip (>= 1.3.0, < 3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.7.1) + jekyll-seo-tag (2.8.0) jekyll (>= 3.8, < 5.0) jekyll-sitemap (1.4.0) jekyll (>= 3.7, < 5.0) @@ -217,43 +207,41 @@ GEM gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) - kramdown (2.3.1) + kramdown (2.3.2) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - liquid (4.0.3) - listen (3.7.1) + liquid (4.0.4) + listen (3.8.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) memoist (0.16.2) mercenary (0.3.6) - mini_portile2 (2.8.1) + mini_portile2 (2.8.5) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) - minitest (5.15.0) - multipart-post (2.1.1) - nokogiri (1.14.0) - mini_portile2 (~> 2.8.0) + minitest (5.20.0) + mutex_m (0.2.0) + nokogiri (1.15.5) + mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.14.0-x86_64-linux) + nokogiri (1.15.5-x86_64-linux) racc (~> 1.4) - octokit (4.22.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) pathutil (0.16.2) forwardable-extended (~> 2.6) - public_suffix (4.0.6) - racc (1.6.2) + public_suffix (4.0.7) + racc (1.7.3) rake (13.0.6) - rb-fsevent (0.11.0) + rb-fsevent (0.11.2) rb-inotify (0.10.1) ffi (~> 1.0) - rexml (3.2.5) + rexml (3.2.6) rouge (3.26.0) - ruby-enum (0.9.0) - i18n ruby2_keywords (0.0.5) rubyzip (2.3.2) safe_yaml (1.0.5) @@ -262,23 +250,22 @@ GEM sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.8.2) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) + faraday (>= 0.17.3, < 3) simpleidn (0.2.1) unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - thread_safe (0.3.6) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) - tzinfo (1.2.9) - thread_safe (~> 0.1) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) unf (0.1.4) unf_ext - unf_ext (0.0.8) + unf_ext (0.0.9.1) unicode-display_width (1.8.0) - zeitwerk (2.5.3) + webrick (1.8.1) PLATFORMS ruby @@ -289,6 +276,7 @@ DEPENDENCIES kramdown memoist rake + webrick BUNDLED WITH 2.2.0 diff --git a/History.md b/History.md index 2836ab7..f191942 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,11 @@ (Only outstanding content changes listed, the texts and code are constantly updated and fixed thanks to awesome contributors.) +## 2023-12-25 + +* **[3.3](3.3.html)** changelog added. Right on time this year, thanks to the "[Advent of the Changelog](https://zverok.space/blog/2023-12-07-advent-of-changelog-week1.html)" +* [Ruby Evolution](evolution.html) updated accordingly. + ## 2023-02-04 * **[3.2](3.2.html)** changelog added. "Better late than never," war year edition! diff --git a/Rakefile b/Rakefile index 39c8cea..843699d 100644 --- a/Rakefile +++ b/Rakefile @@ -20,7 +20,7 @@ rule /^(\d+\.\d+|evolution)\.md$/ => ->(s) { "_src/#{s}" } do |t| File.write(to, Render.(from)) end -VERSIONS = [*('2.4'..'2.7'), *('3.0'..'3.2')] +VERSIONS = [*('2.4'..'2.7'), *('3.0'..'3.3')] desc 'Convert file contents from source to target (prettify)' task contents: ['evolution', *VERSIONS].map(&'%s.md'.method(:%)) diff --git a/_data/book.yml b/_data/book.yml index a7a5510..ba8b872 100644 --- a/_data/book.yml +++ b/_data/book.yml @@ -94,6 +94,122 @@ chapters: path: "/evolution.html#freezing" - title: 'Appendix: Covered Ruby versions release dates' path: "/evolution.html#appendix-covered-ruby-versions-release-dates" +- title: Ruby 3.3 + path: "/3.3.html" + is_version: true + published_at: '2023-12-25' + description: | +

Highlights:

+ +
    +
  • it will become anonymous block argument in 3.4
  • +
  • Module#set_temporary_name
  • +
  • ObjectSpace::WeakKeyMap
  • +
  • Range#overlap?
  • +
  • Fiber#kill
  • +
+ +

Read more »

+ children: + - title: Highlights + path: "/3.3.html#highlights" + - title: Language changes + path: "/3.3.html#language-changes" + children: + - title: Standalone it in blocks will become anonymous argument in + Ruby 3.4 + path: "/3.3.html#standalone-it-in-blocks-will-become-anonymous-argument-in-ruby-34" + - title: Core classes and modules + path: "/3.3.html#core-classes-and-modules" + children: + - title: "Kernel#lambda raises when passed Proc instance" + path: "/3.3.html#kernellambda-raises-when-passed-proc-instance" + - title: "Proc#dup and #clone call #initialize_dup + and #initialize_copy" + path: "/3.3.html#procdup-and-clone-call-initialize_dup-and-initialize_copy" + - title: "Module#set_temporary_name" + path: "/3.3.html#moduleset_temporary_name" + - title: "Refinement#refined_class is renamed to Refinement#target" + path: "/3.3.html#refinementrefined_class-is-renamed-to-refinementtarget" + - title: Strings and regexps + path: "/3.3.html#strings-and-regexps" + children: + - title: "String#bytesplice: new arguments to select a portion + of the replacement string" + path: "/3.3.html#stringbytesplice-new-arguments-to-select-a-portion-of-the-replacement-string" + - title: "MatchData#named_captures: symbolize_names: + argument" + path: "/3.3.html#matchdatanamed_captures-symbolize_names-argument" + - title: "Time.new with string argument became stricter" + path: "/3.3.html#timenew-with-string-argument-became-stricter" + - title: "Array#pack and String#unpack: raise ArgumentError + for unknown directives" + path: "/3.3.html#arraypack-and-stringunpack-raise-argumenterror-for-unknown-directives" + - title: Enumerables and collections + path: "/3.3.html#enumerables-and-collections" + children: + - title: "Set#merge accepts multiple arguments" + path: "/3.3.html#setmerge-accepts-multiple-arguments" + - title: "ObjectSpace::WeakKeyMap" + path: "/3.3.html#objectspaceweakkeymap" + - title: "ObjectSpace::WeakMap#delete" + path: "/3.3.html#objectspaceweakmapdelete" + - title: "Thread::Queue#freeze and SizedQueue#freeze + raise TypeError" + path: "/3.3.html#threadqueuefreeze-and-sizedqueuefreeze-raise-typeerror" + - title: "Range" + path: "/3.3.html#range" + children: + - title: "#reverse_each" + path: "/3.3.html#reverse_each" + - title: "#overlap?" + path: "/3.3.html#overlap" + - title: Filesystem and IO + path: "/3.3.html#filesystem-and-io" + children: + - title: "Dir.for_fd and Dir.fchdir" + path: "/3.3.html#dirfor_fd-and-dirfchdir" + - title: "Dir#chdir" + path: "/3.3.html#dirchdir" + - title: Deprecate subprocess creation with method dedicated to files + path: "/3.3.html#deprecate-subprocess-creation-with-method-dedicated-to-files" + - title: "NoMethodError: change of rendering logic" + path: "/3.3.html#nomethoderror-change-of-rendering-logic" + - title: "Fiber#kill" + path: "/3.3.html#fiberkill" + - title: Internals + path: "/3.3.html#internals" + children: + - title: 'New Warning category: :performance' + path: "/3.3.html#new-warning-category-performance" + - title: "Process.warmup" + path: "/3.3.html#processwarmup" + - title: "Process::Status#& and #>> are + deprecated" + path: "/3.3.html#processstatus--and--are-deprecated" + - title: "TracePoint supports :rescue event" + path: "/3.3.html#tracepoint-supports-rescue-event" + - title: Standard library + path: "/3.3.html#standard-library" + children: + - title: Version updates + path: "/3.3.html#version-updates" + children: + - title: Default gems + path: "/3.3.html#default-gems" + - title: Bundled gems + path: "/3.3.html#bundled-gems" + - title: Standard library content changes + path: "/3.3.html#standard-library-content-changes" + children: + - title: New libraries + path: "/3.3.html#new-libraries" + - title: Removals + path: "/3.3.html#removals" + - title: Default gems that became bundled + path: "/3.3.html#default-gems-that-became-bundled" + - title: Gems that are warned to become bundled in the next version + path: "/3.3.html#gems-that-are-warned-to-become-bundled-in-the-next-version" - title: Ruby 3.2 path: "/3.2.html" is_version: true @@ -258,6 +374,9 @@ chapters: path: "/3.2.html#bundled-gems" - title: Standard library content changes path: "/3.2.html#standard-library-content-changes" + children: + - title: New libraries + path: "/3.2.html#new-libraries" - title: Ruby 3.1 path: "/3.1.html" is_version: true diff --git a/_layouts/default.html b/_layouts/default.html index 005cea1..22d44cd 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -7,7 +7,7 @@ - {{ page.title }} - {{ site.title }} + {{ page.title }}{% if page.title != site.title %} - {{ site.title }}{% endif %} diff --git a/_src/2.4.md b/_src/2.4.md index 7387403..e83ea5c 100644 --- a/_src/2.4.md +++ b/_src/2.4.md @@ -189,6 +189,7 @@ New single-method module was introduced, meant to be overridden in order to cont ``` * In Ruby 2.7, new methods [were added](2.7.html#warning-and-) to `Warning` module allowing control over per-category warning suppression; * In Ruby 3.0, category support [was improved](3.0.html#warningwarn-category-keyword-argument). + * [3.3](3.3.html#new-warning-category-performance): added new warning category: `:performance`. ### `Object#clone(freeze: false)` diff --git a/_src/2.7.md b/_src/2.7.md index 017696f..1057195 100644 --- a/_src/2.7.md +++ b/_src/2.7.md @@ -121,7 +121,9 @@ In block without explicitly specified parameters, variables `_1` through `_9` ca # %w[test me].each { _1.each_char { p _1 } } # ^~ ``` -* **Follow-up:** Warning on attempt to assign to numbered parameter [became errors in 3.0](3.0.html#other-changes). +* **Follow-ups:** + * [3.0](3.0.html#other-changes): Warning on attempt to assign to numbered parameter became errors. + * [3.3](3.3.html#standalone-it-in-blocks-will-become-anonymous-argument-in-ruby-34): a warning introduced indicating that in Ruby 3.4, `it` would become an alternative anonymous block parameter (only one, same as `_1`). There is no plan to deprecate numbered parameters. ### Beginless range @@ -799,6 +801,7 @@ Like `Array#union` and `#difference`, [added](2.6.html#arrayunion-and-arraydiffe map[1] # => nil -- value successfully collected, even if key was not GC-able ``` +* **Follow-ups:** [3.3](3.3.html#objectspaceweakkeymap): A new class `ObjectSpace::WeakKeyMap` introduced, more suitable for common use-cases of a "weak mapping." It only has garbage-collectable keys. ### `Fiber#raise` @@ -1028,7 +1031,7 @@ Allows to emit/suppress separate categories of warnings. ``` * **Notes:** * The only existing categories currently are `:deprecated` (covers all deprecations) and `:experimental` (as of 2.7, covers only pattern matching) - * Note that turning of `:deprecated` warning will also mute the warning of features which was deprecated explicitly in your code, for example with [Module#deprecate_constant](https://ruby-doc.org/core-2.7.0/Module.html#method-i-deprecate_constant) + * Note that turning off `:deprecated` warning will also mute the warning of features which was deprecated explicitly in your code, for example with [Module#deprecate_constant](https://ruby-doc.org/core-2.7.0/Module.html#method-i-deprecate_constant) ```ruby class HTTP NOT_FOUND = Exception.new @@ -1041,7 +1044,9 @@ Allows to emit/suppress separate categories of warnings. # ...no warning issued... ``` * Another way to turn on and off separate categories of warnings is passing `-W:(no-)` flag to ruby interpreter, e.g. `-W:no-experimental` means "no warnings when using experimental features". -* **Follow-up:** In Ruby 3.0, it was [allowed for user code](3.0.html#warningwarn-category-keyword-argument) to specify warning category, and intercept it. +* **Follow-ups:** + * [3.0](3.0.html#warningwarn-category-keyword-argument): user code is allowed to specify warning category, and intercept it; + * [3.3](3.3.html#new-warning-category-performance): added new warning category: `:performance`. ## Standard library diff --git a/_src/3.0.md b/_src/3.0.md index 690e474..1137115 100644 --- a/_src/3.0.md +++ b/_src/3.0.md @@ -105,6 +105,7 @@ Just a leftover from the separation of keyword arguments. logged_get('Logging', 'https://example.com', headers: {content_type: 'json'}) ``` * **Notes:** + * The adjustment was considered important enough to be backported to 2.7 branch; * "all arguments splat" `...` should be the last statement in the argument list (both on a declaration and a call) * on a method declaration, arguments before `...` can only be positional (not keyword) arguments, and can't have default values (it would be `SyntaxError`); * on a method call, arguments passed before `...` can't be keyword arguments (it would be `SyntaxError`); @@ -960,6 +961,7 @@ Just fixes an inconsistency introduced by optimization many versions ago. processed.lambda? # => false processed.call # => "nil", it is really still not a lambda ``` +* **Follow-ups:** [3.3](3.3.html#kernellambda-raises-when-passed-proc-instance): The warning was turned into an exception. #### `Proc#==` and `#eql?` diff --git a/_src/3.2.md b/_src/3.2.md index a1db17f..1e06089 100644 --- a/_src/3.2.md +++ b/_src/3.2.md @@ -1,6 +1,6 @@ --- title: Ruby 3.2 changes -prev: / +prev: 3.3 next: 3.1 description: Ruby 3.2 full and annotated changelog --- @@ -249,12 +249,12 @@ A few edge cases after [big keyword argument separation](3.0.html#keyword-argume ### Removals * Constants: - * `Fixnum` and `Bignum` (deprecated since unification into `Integer` in [2.4](/2.4.html#fixnum-and-bignum-are-unified-into-integer)) - * `Random::DEFAULT` (deprecated in favor of per-Ractor random generator since [3.0](/3.0.html#randomdefault-behavior-change)) + * `Fixnum` and `Bignum` (deprecated since unification into `Integer` in [2.4](2.4.html#fixnum-and-bignum-are-unified-into-integer)) + * `Random::DEFAULT` (deprecated in favor of per-Ractor random generator since [3.0](3.0.html#randomdefault-behavior-change)) * Methods: * `Dir.exists?`, `File.exists?` (deprecated since 2.1 as a general rule of having "bare verb" as a method name) - * `Object#=~` (deprecated since [2.6](/2.6.html#minor-changes)) - * `Object#taint`, `#untaint`, `#tainted?`, `#trust`, `#untrust`, `#untrusted?` (deprecated together with a general concept of "safety" since [2.7](/2.7.html#safe-and-taint-concepts-are-deprecated-in-general)) + * `Object#=~` (deprecated since [2.6](2.6.html#minor-changes)) + * `Object#taint`, `#untaint`, `#tainted?`, `#trust`, `#untrust`, `#untrusted?` (deprecated together with a general concept of "safety" since [2.7](2.7.html#safe-and-taint-concepts-are-deprecated-in-general)) ## Core classes and modules @@ -504,20 +504,7 @@ Returns list of refinements the module defines. * **Discussion:** [Feature #12737](https://bugs.ruby-lang.org/issues/12737) * **Documentation:** [Refinement#refined_class](https://docs.ruby-lang.org/en/3.2/Refinement.html#method-i-refined_class) * **Code:** See example above that demonstrates usage of `#refined_class` together with `Module#refinements`. -* **Note:** The name of the method implies only classes can be refined, which is not true; modules can be refined also, and `refined_class` will promptly return them: - ```ruby - module BetterEnum - refine Enumerable do - def each2(&block) = each_slice(2, &block) - end - end - - BetterEnum.refinements[0].refined_class #=> Enumerable - - using BetterEnum - (1..6).each2.to_a #=> [[1, 2], [3, 4], [5, 6]] - ``` - Currently [discussed](https://bugs.ruby-lang.org/issues/19366). +* **Follow-ups:** [3.3](3.3.html#refinementrefined_class-is-renamed-to-refinementtarget): Renamed to `#target`, because not only classes can be refined, modules too. #### `Module.used_refinements` @@ -622,7 +609,9 @@ Several method were added that operate on multibyte strings at byte-offset level str.byteslice(1..3) #=> "\xB2і" -- works, even if the slice is mid-character str.bytesplice(1..3, '...') # offset 1 does not land on character boundary (IndexError) ``` -* **Note:** After 3.2 release, `bytesplice` behavior [had changed](https://bugs.ruby-lang.org/issues/19314#note-8) to return `self` instead of replacement string. +* **Note:** + * After 3.2 release, `bytesplice` behavior [had changed](https://bugs.ruby-lang.org/issues/19314#note-8) to return `self` instead of replacement string. + * [3.3](3.3.html#stringbytesplice-new-arguments-to-select-a-portion-of-the-replacement-string): parameters added to `bytesplice` to allow partial copy of the buffer. #### `String#dedup` as an alias for `-"string"` @@ -753,6 +742,7 @@ The new protocol for `Time.new` is introduced, that parses Time from string. #=> 2023-01-29 00:00:00 +0200 ``` * `Time.new('2023')` works, too, but it is a feature that worked before (force-conversion of singular year argument to integer), see [Bug #19293](https://bugs.ruby-lang.org/issues/19293). It will probably be deprecated, but can't be quickly removed due to backward compatibility. +* **Follow-ups:**: [3.3](3.3.html#timenew-with-string-argument-became-stricter): `Time.new` became stricting, accepting only fully-specified date-time. ### `Struct` and `Data` @@ -891,7 +881,8 @@ A new class for containing value objects: it is somewhat similar to `Struct` (an res #=> # ``` - * `#with` method in Ruby 3.2 is naive and just copies all old and new attributes to the new instance, without invoking any custom initialization methods. In the next version, though, it [expected to call `#initialize`](https://bugs.ruby-lang.org/issues/19259): +* **Follow-ups:** + * `#with` method in Ruby 3.2.0 was naive and just copies all old and new attributes to the new instance, without invoking any custom initialization methods. It was fixed to [call `#initialize`](https://bugs.ruby-lang.org/issues/19259) in 3.2.2: ```ruby Point = Data.define(:x, :y) do def initialize(x:, y:) = super(x: x.to_i, y: y.to_i) @@ -1127,7 +1118,8 @@ Previously a part of standard library, Set (a collection of unique elements) was t.tag #=> 'test' t.dup.tag #=> nil ``` - This is a [bug](https://bugs.ruby-lang.org/issues/19362) and will probably [change](https://github.com/ruby/ruby/pull/7178) in Ruby 3.2.2. + This is a [bug](https://bugs.ruby-lang.org/issues/19362). +* **Follow-ups:** [3.3](3.3.html#procdup-and-clone-call-initialize_dup-and-initialize_copy): `#dup` properly invokes `#initialize_dup`. #### `Proc#parameters`: new keyword argument `lambda: true/false` @@ -1162,7 +1154,7 @@ Previously a part of standard library, Set (a collection of unique elements) was #### `Method#public?`, `#protected?`, and `#private?` are removed -Predicates to check method visibility added in Ruby 3.1 were reverted. +Predicates to check method visibility [added in Ruby 3.1](3.1.html#methodunboundmethod-public-private-protected) were reverted. * **Reason:** The new feature implementation have led to several bugs with `Method` class behavior; while investigating the root cause for those bugs, Matz have decided that method's visibility is not its inherent property, but rather a property of the module/object that owns the method, and as such, is already present in form of `Module#{private,public,protected}_instance_methods` and `Object#{private,public,protected}_methods` * **Discussion:** [Feature #11689#note-24](https://bugs.ruby-lang.org/issues/11689#note-24) @@ -1778,7 +1770,7 @@ A few changes to mention, though: * [net-http](https://github.com/ruby/net-http) 0.3.2 * [net-protocol](https://github.com/ruby/net-protocol) 0.2.1 * [nkf](https://github.com/ruby/nkf) 0.1.2 -* [open](https://github.com/ruby/open)-uri 0.3.0 +* [open-uri](https://github.com/ruby/open-uri) 0.3.0 * [open3](https://github.com/ruby/open3) 0.1.2 * [openssl](https://github.com/ruby/openssl) 3.1.0 * [optparse](https://github.com/ruby/optparse) 0.3.1 @@ -1812,7 +1804,7 @@ A few changes to mention, though: #### Bundled gems -* [minitest](https://github.com/ruby/minitest) 5.16.3 +* [minitest](https://github.com/minitest/minitest) 5.16.3 * [power_assert](https://github.com/ruby/power_assert) 2.0.3 * [test-unit](https://github.com/ruby/test-unit) 3.5.7 * [net-ftp](https://github.com/ruby/net-ftp) 0.2.0 @@ -1825,6 +1817,8 @@ A few changes to mention, though: ### Standard library content changes +#### New libraries + * [syntax_suggest](https://github.com/ruby/syntax_suggest) (formerly `dead_end`) gem added. It provides helpful error messages for wrong syntax, trying to guess the place of the error. For example, assuming this `test.rb`: ```ruby def foo diff --git a/_src/3.3.md b/_src/3.3.md new file mode 100644 index 0000000..73249cf --- /dev/null +++ b/_src/3.3.md @@ -0,0 +1,970 @@ +--- +title: Ruby 3.3 changes +prev: / +next: 3.2 +description: Ruby 3.3 full and annotated changelog +--- + +# Ruby 3.3 + +* **Released at:** Dec 25, 2023 ([NEWS.md](TODO) file) +* **Status (as of <>):** 3.3.0 is recently released +* **This document first published:** Dec 25, 2023 +* **Last change to this document:** <> + + + +**🇺🇦 🇺🇦 Before you start reading the changelog: A full-scale Russian invasion into my home country continues, for the second in a row. The only reason I am alive and able to work on the changelog is Armed Force of Ukraine, and international support with weaponry, funds and information. I got into the Army in March, and spent the summer in the frontlines. Now I have moved to another army position which lives me time to work for the Ruby community. You can [read my recent text](https://zverok.space/blog/2023-11-17-not-a-rubyconf.html) (that supposed to be a RubyConf talk) as an appeal to the community. Please [spread information, lobby our cause and donate](https://war.ukraine.ua/).🇺🇦 🇺🇦** + +> **Note:** As already explained in [Introduction](README.md), this site is dedicated to changes in the **language**, not the **implementation**, therefore the list below lacks mentions of lots of important _internal_ changes related to performance optimizations, parser, and JIT that happened in 3.3 (which is, on the other hand, somewhat lighter on the "small quality of life improvement" changes). The changes aren't covered not because they are not important, just because this site's goals are different. See [the official release notes](https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/) that cover those significant internal changes. + +## Highlights + +* [`it` will become anonymous block argument in 3.4](#standalone-it-in-blocks-will-become-anonymous-argument-in-ruby-34) +* [`Module#set_temporary_name`](#moduleset_temporary_name) +* [`ObjectSpace::WeakKeyMap`](#objectspaceweakkeymap) +* [`Range#overlap?`](#overlap) +* [`Fiber#kill`](#fiberkill) + +## Language changes + +### Standalone `it` in blocks will become anonymous argument in Ruby 3.4 + +In Ruby 3.3, it will just warn to prepare for a change. + +* **Reason:** Numeric designation for anonymous bloc arguments (`_1`, `_2`, and so on) were considered ugly by many people, so after years of discussion, the `it` keyword is to be introduced on the next Ruby version; for now, it just warns _in places where it would be considered an anonymous block argument_. +* **Discussion:** [Feature #18980](https://bugs.ruby-lang.org/issues/18980) +* **Code:** In the code below, where Ruby 3.3 currently produces a warning, Ruby 3.4 would treat `it` as an anonymous block argument; where Ruby 3.3 doesn't produce a warning, Ruby 3.4 would treat `it` as a local variable name or a method call (and would look for such names available in the scope). + ```ruby + # The cases that are warned: + # ------------------------- + # warning: `it` calls without arguments will refer to the first block param in Ruby 3.4; use it() or self.it + + (1..3).map { it } # inside a block without explicit parameters + (1..3).map { it; _1 } # ...even if numbered parameters are used, too + def it; end + (1..3).map { it } # even if a method with name `it` exists in the scope + + # The cases that are not warned: + # ----------------------------- + + it # not inside a block + (1..3).map { |x| it } # inside a block with named parameters + (1..3).map { || it } # ...even if they are empty + (1..3).map { it() } # with parentheses + (1..3).map { it {} } # with a block attached + (1..3).map { it = 5; it } # if a local variable with the same name is created in the block + it = 5 + (1..3).map { it } # if a local variable with the same name is in the scope + ``` +* **Notes:** The new feature isn't expected to conflict with RSpec's [`it`](https://rspec.info/documentation/3.12/rspec-core/RSpec/Core/ExampleGroup.html#it-class_method), as calling that without any block attached, or at least a description for the future example, is useless. + +## Core classes and modules + +### `Kernel#lambda` raises when passed `Proc` instance + +* **Reason:** `lambda`'s goal is to create a lambda from provided literal block; in Ruby, it is impossible to change the "lambdiness" of the block once it is created. But `lambda(&proc_instance)` never notified users of that, which was confusing. +* **Discussion:** [Feature #19777](https://bugs.ruby-lang.org/issues/19777) +* **Documentation:** [Kernel#lambda](https://docs.ruby-lang.org/en/master/Kernel.html#method-i-lambda) _(no specific details are provided, though)_ +* **Code:** + ```ruby + # Intended usage: + l = lambda { |a, b| a + b } + l.lambda? #=> true + l.parameters #=> [[:req, :a], [:req, :b]] + + # Unintended usage: + p = proc { |a, b| a + b } + + # In Ruby 3.2 and below, it worked, but the produced value wasn't lambda: + l = lambda(&p) + l.parameters #=> [[:opt, :a], [:opt, :b]] + l.lambda? #=> false + l.object_id == p.object_id #=> true, it is just the same proc + + # Ruby 3.3: + l = lambda(&p) + # in `lambda': the lambda method requires a literal block (ArgumentError) + + # Despite the message about a "literal block," the method + # works (though has no meaningful effect) with lambda-like Proc objects + other_lambda = lambda { |a, b| a + b } + lambda(&other_lambda) #=> works + lambda(&:to_s) #=> works + lambda(&method(:puts)) #=> works + ``` +* **Notes:** The discussion was once [started](https://bugs.ruby-lang.org/issues/15973) from the proposal to make `lambda` change "lambiness" of a passed block, but it raises multiple issues (changing the block semantics mid-program is just one of them). In general, `lambda` as a _method_ is considered legacy, inferior to the `-> { }` lambda literal syntax, exactly due to problems like this: it looks like a regular method that receives a block, and therefore should be able to accept _any_ block, but in fact it is "special" method. So in 3.0, there was a warning about `lambda(&proc_instance)`, and since 3.3, the warning finally turned into an error. + +### `Proc#dup` and `#clone` call `#initialize_dup` and `#initialize_copy` + +* **Reason:** A fix for a small inconsistency created [in 3.2](3.2.html#procdup-returns-an-instance-of-subclass): Since that version, `#dup` and `#clone` on an object inherited from the `Proc`, rightfully produced an instance of the inherited class. But despite `Object`'s `#dup` and `#clone` methods docs claiming that corresponding copying constructors would be called on object cloning/duplication, it was not true for `Proc`. +* **Discussion:** [Feature #19362](https://bugs.ruby-lang.org/issues/19362) +* **Documentation:** — (Adheres to the behavior described for [Object#dup](https://docs.ruby-lang.org/en/master/Object.html#method-i-dup) and [#clone](https://docs.ruby-lang.org/en/master/Kernel.html#method-i-clone)) +* **Code:** + ```ruby + # The examples would work the same way with + # #dup/#initialize_dup and #clone/#initialize_copy + + class TaggedProc < Proc + attr_reader :tag + + def initialize(tag) + super() + @tag = tag + end + + def initialize_dup(other) + @tag = other.tag + super + end + end + + proc = TaggedProc.new('admin') { } + + proc.tag #=> 'admin' + proc.dup.tag + # Ruby 3.1: + # undefined method `tag' for # -- #dup didn't preserve the class + # Ruby 3.2: + # => nil -- the class is preserved, yet the duplication didn't went through #initialize_dup + # Ruby 3.3: + # => "admin" + ``` +* **Notes:** Inheriting from core classes is an advanced technique, and most of the times there are simple ways to achieve same goals (like wrapper objects containing a `Proc` and an additional info). + +### `Module#set_temporary_name` + +Allows to assign a string to be rendered as class/module's `#name`, without assigning the class/module to a constant. + +* **Reason:** The feature is useful to provide reasonable representation for dynamically auto-generated classes without assigning them to constants (which pollutes the global namespace and might conflict with existing constants) or redefining `Class#name` (which might break other code and not always respected in the output). +* **Discussion:** [Feature #19521](https://bugs.ruby-lang.org/issues/19521) +* **Documentation:** [Module#set_temporary_name](https://docs.ruby-lang.org/en/master/Module.html#method-i-set_temporary_name) +* **Code:** + ```ruby + dynamic_class = Class.new do + def foo; end + end + + dynamic_class.name #=> nil + + # For dynamic classes, representation of related values is frequently unreadable: + dynamic_class #=> # + instance = dynamic_class.new #=> #<#:0x0...> + instance.method(:foo) #=> ##foo() ...> + + dynamic_class::Nested = Module.new + dynamic_class::Nested #=> #::Nested + + # After assigning the temporary name, representation becomes more convenient: + dynamic_class.set_temporary_name("MyDSLClass(with description)") + + dynamic_class #=> MyDSLClass(with description) + instance #=> # + instance.method(:foo) #=> # + + # Note that module constant names are assigned at the moment of their creation, + # and don't change when the temporary name is assigned: + dynamic_class::OtherNested = Module.new + + dynamic_class::Nested #=> #::Nested + dynamic_class::OtherNested #=> MyDSLClass(with description)::OtherNested + + # Assigning names that correspond to constant name rules is prohibited: + dynamic_class.set_temporary_name("MyClass") + # `set_temporary_name': the temporary name must not be a constant path to avoid confusion (ArgumentError) + dynamic_class.set_temporary_name("MyClass::NestedName") + # `set_temporary_name': the temporary name must not be a constant path to avoid confusion (ArgumentError) + + # When the module with a temporary name is put into a constant, + # it receives a permanent name, which can't be changed anymore + C = dynamic_class + + # It affects all associated values (including modules) + + dynamic_class #=> C + instance #=> # + instance.method(:foo) #=> # + dynamic_class::Nested #=> C::Nested + dynamic_class::OtherNested #=> C::OtherNested + + dynamic_class.set_temporary_name("Can I have it back?") + # `set_temporary_name': can't change permanent name (RuntimeError) + + # `nil` can be used to cleanup a temporary name: + other_class = Class.new + other_class.set_temporary_name("another one") + other_class #=> another one + other_class.set_temporary_name(nil) + other_class #=> # + ``` +* **Notes:** Any phrase that used as a temporary name would be used verbatim; this might create very confusing `#inspect` results and error messages; so it is advised to use strings somehow implying that the name belong to a module. Imagine we wrap into classes with temporary names RSpec-style examples, and then there is a typo in the body of such example: + ```ruby + it "works as a calculator" do + expec(2+2).to eq 4 + end + # If we assign just the example description as a temp.name, the + # error would look like this: + # + # undefined method `expec' for an instance of works as a calculator + # ^^^^^^^^^^^^^^^^^^^^^ + # + # ...which is confusing. So it is probably better to construct a + # module-like temporary name, to have: + # + # undefined method `expec' for an instance of MyFramework::Example("works as a calculator") + # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ``` + +### `Refinement#refined_class` is renamed to `Refinement#target` + +Just a renaming of the unfortunately named new method that [emerged in Ruby 3.2](3.2.html#refinementrefined_class). + +* **Discussion:** [Feature #19714](https://bugs.ruby-lang.org/issues/19714) +* **Documentation:** [Refinement#target](https://docs.ruby-lang.org/en/master/Refinement.html#method-i-target) + +### Strings and regexps + +#### `String#bytesplice`: new arguments to select a portion of the replacement string + +The low-level string manipulation method now allows to provide a coordinates of the part of the replacement string to be used. + +* **Reason:** The new "byte-oriented" methods [were introduced](https://rubyreferences.github.io/rubychanges/3.2.html#byte-oriented-methods) in Ruby 3.2 to support low-level programming like text editors or network protocol implementations. In those use cases, the necessity of copying of a small part of one string into the middle of another is frequent, and producing intermediate strings (by first slicing the necessary part) is costly. +* **Discussion:** [Feature #19314](https://bugs.ruby-lang.org/issues/19314) +* **Documentation:** [String#bytesplice](https://docs.ruby-lang.org/en/master/String.html#method-i-bytesplice) +* **Code:** + ```ruby + # Base usage + buf1 = "Слава Україні!" + # ^^^^^^^ - bytes 11-24 + buf2 = "Шана Героям" + # ^^^^^^ - bytes 9-20 + + buf1.bytesplice(11..24, buf2, 9..20) + #=> "Слава Героям!" + buf1 + #=> "Слава Героям!" -- The receiver is modified + + # Or, alternatively, with (start, length) pairs + buf1 = "Слава Україні!" + buf1.bytesplice(11, 14, buf2, 9, 12) + #=> "Слава Героям!" + + # Two forms can't be mixed: + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, 9, 12) + # `bytesplice': wrong number of arguments (given 4, expected 2, 3, or 5) (ArgumentError) + + # Index can't be in the middle of the Unicode character: + buf1.bytesplice(11..23, buf2, 9..20) + # ^ + # `bytesplice': offset 24 does not land on character boundary (IndexError) + buf1.bytesplice(11..24, buf2, 9..19) + # ^ + # `bytesplice': offset 20 does not land on character boundary (IndexError) + + # Semi-open ranges work: + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, 9..) + #=> "Слава Героям!" + + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, ...8) + #=> "Слава Шана!" + + # Empty ranges lead to inserting empty strings: + buf1 = "Слава Україні!" + buf1.bytesplice(11..24, buf2, 9...8) + #=> "Слава !" + ``` + +#### `MatchData#named_captures`: `symbolize_names:` argument + +* **Discussion:** [Feature #19591](https://bugs.ruby-lang.org/issues/19591) +* **Documentation:** [MatchData#named_captures](https://docs.ruby-lang.org/en/master/MatchData.html#method-i-named_captures) +* **Code:** + ```ruby + m = "2023-12-25".match(/(?\d{4})-(?\d{2})-(?\d{2})/) + m.named_captures + #=> {"year"=>"2023", "month"=>"12", "day"=>"25"} + m.named_captures(symbolize_names: true) + #=> {:year=>"2023", :month=>"12", :day=>"25"} + ``` +* **Notes:** While `symbolize_names:` might looks somewhat strange (usually we talk about hash _keys_), it is done for consistency with Ruby standard library's [`JSON.parse`](https://docs.ruby-lang.org/en/master/JSON.html#module-JSON-label-Output+Options) signature, which inherited the terminology from the JSON specification. + +### `Time.new` with string argument became stricter + +The method now requires fully-specified date-time string. + +* **Discussion:** [Bug #19293](https://bugs.ruby-lang.org/issues/19293) +* **Documentation:** [Time#new](https://docs.ruby-lang.org/en/master/Time.html#method-c-new) +* **Code:** + ```ruby + Time.new('2023-12-20') + # Ruby 3.2: #=> 2023-12-20 00:00:00 +0200 + # Ruby 3.3: in `initialize': no time information (ArgumentError) + + Time.new('2023-12') + # Ruby 3.2: #=> 2023-12-01 00:00:00 +0200 + # Ruby 3.3: in `initialize': no time information (ArgumentError) + + # Singular year is still works: + Time.new('2023') + #=> 2023-01-01 00:00:00 +0200 + + # ...because it is documented behavior of Time.new to accept + # strings that are numeric and treat them as numbers: + Time.new('2023', '12', '20') + #=> 2023-12-20 00:00:00 +0200 + ``` + +### `Array#pack` and `String#unpack`: raise `ArgumentError` for unknown directives + +* **Discussion:** [Bug #19150](https://bugs.ruby-lang.org/issues/19150) +* **Documentation:** [doc/packed_data.rdoc](https://docs.ruby-lang.org/en/master/packed_data_rdoc.html) +* **Code:** + ```ruby + [1, 2, 3].pack('r*') + # Ruby 3.1: + # => "", no warning + # Ruby 3.2: + # => "", warning: unknown pack directive 'r' in 'r*' + # Ruby 3.3: + # in `pack': unknown pack directive 'r' in 'r*' (ArgumentError) + + "\x01\x02\x03".unpack("r*") + # Ruby 3.1: + # => [], no warning + # Ruby 3.2: + # => [], warning: unknown unpack directive 'r' in 'r*' + # Ruby 3.3: + # in `unpack': unknown pack directive 'r' in 'r*' (ArgumentError) + ``` + +### Enumerables and collections + +#### `Set#merge` accepts multiple arguments + +* **Documentation:** [Set#merge](https://docs.ruby-lang.org/en/master/Set.html#method-i-merge) +* **Code:** + ```ruby + Set[1, 2, 3].merge(Set[3, 4, 5], Set[:a, :b, :c]) + #=> # + ``` +* **Notes:** The method's signature (seen in docs) has a rare clause `**nil`. It means "don't accept something that looks like keyword arguments." As `#merge` accept any list of enumerables, this protects from accidentally passing a hash believing it would be keyword arguments with some meaning: + ```ruby + Set[1, 2, 3].merge(Set[3, 4, 5], reorder: false) + # ^^^^^^^^^^^^^^ + # Without **nil, this would be treated implicitly as Hash, while looking like keyword arguments + # But actually, it produces + # no keywords accepted (ArgumentError) + + # When you do mean to merge data from hash, use parentheses to make it explicit + # (its #each would be used to produce set items): + Set[1, 2, 3].merge(Set[3, 4, 5], {some: 'data'}) + #=> # + ``` + +#### `ObjectSpace::WeakKeyMap` + +A new "weak map" concept implementation. Unlike `ObjectSpace::WeakMap`, it compares keys by equality (`WeakMap` compares by identity), and only references to keys are weak (garbage-collectible). + +* **Reason:** The idea of a new class grew out of increased usage of `ObjectSpace::WeakMap` (which was once considered internal). In many other languages, concept of "weak map" implies only key references are weak: this allows to use it as a generic "holder of some additional information related to a set of objects while they are alive," or just a weak set of objects (using them as keys and `true` as values): caches, deduplication sets, etc. +* **Discussion:** [Feature #18498](https://bugs.ruby-lang.org/issues/18498) +* **Documentation:** [ObjectSpace::WeakKeyMap](https://docs.ruby-lang.org/en/master/ObjectSpace/WeakKeyMap.html) +* **Code:** + ```ruby + map = ObjectSpace::WeekMap.new + + key = "foo" + map[key] = true + map["foo"] #=> true -- compares by equality, even if two strings are different objects + + # "Just return the equal key" API, always returns the key's object + map.getkey("foo") #=> "foo" + map.getkey("foo").object_id == key.object_id #=> true + + key = nil + GC.start + + map["foo"] #=> nil -- the key was garbage-collected, so the pair was removed + + # One of the possible usages: a lightweight uniqueness cache for + # many small objects: + class Money < Data.define(:amount, :currency) + def self.new(...) + value = super(...) + @cache ||= ObjectSpace::WeakKeyMap.new + if (existing = @cache.getkey(value)) + existing + else + @cache[value] = true + end + end + end + + m1 = Money.new(10, 'USD') + m2 = Money.new(10, 'USD') + m1.object_id #=> 60 + m2.object_id #=> 60 + # Same values, it is the same object, so there wouldn't be a huge memory + # penalty when thousands of similar values are created. + + # No references to "10 USD" object left + m1 = nil + m2 = nil + GC.start + m3 = Money.new(10, 'USD') + m3.object_id #=> 80 + # The unused values got garbage-collected, so the cache wouldn't just grow forever + ``` +* **Notes:** The class interface is significantly leaner than `WeakMap`'s, and doesn't provide any kind of iteration methods (which is very hard to implement and use correctly with weakly-referenced objects), so the new class is more like a black box with associations than a collection. + +#### `ObjectSpace::WeakMap#delete` + +* **Reason:** `WeakMap` is frequently used to have a loose list of objects that will need some processing at some point of program execution if they are still alive/used (that's why `WeekMap` and not `Array`/`Hash` is chosen in those cases). But it is possible that the code author wants to process objects conditionally, and to remove those which don't need processing anymore—even if they are still alive. `WeekMap` quacks like kind-of simple `Hash`, yet previously provided no way to delete keys. +* **Discussion:** [Feature #19561](https://bugs.ruby-lang.org/issues/19561) +* **Documentation:** [ObjectSpace::WeakMap#delete](https://docs.ruby-lang.org/en/master/ObjectSpace/WeakMap.html#method-i-delete) +* **Code:** + ```ruby + files_to_close = ObjectSpace::WeakMap.new + file1 = File.new('README.md') + file2 = File.new('NEWS.md') + + files_to_close[file1] = true + files_to_close[file2] = true + + files_to_close.delete(file1) #=> true + + # Attempt to delete non-existing key: + files_to_close.delete(file1) #=> nil + # An optional block can be provided in case the key doesn't exist: + files_to_close.delete(file1) { puts "Already removed"; 0 } + # Prints "Already removed", returns `0` + + # The block wouldn't be called if the deletion was effectful: + files_to_close.delete(file2) { puts "Already removed"; 0 } + # Prints nothing, returns true + ``` + +#### `Thread::Queue#freeze` and `SizedQueue#freeze` raise `TypeError` + +* **Reason:** The discussion was started with a bug report about `Queue` not respecting `#freeze` in any way (`#push` and `#pop` were still working after `#freeze` call). It was then decided that allowing to freeze a queue like any other collection (leaving it immutable) would have questionable semantics. As `Queue` is meant to be an inter-thread communication utility, freezing a queue while some thread waits for it would either leave this thread hanging, or would require `#freeze`'s functionality to extend for communication with dependent threads. Neither is a good option, so the behavior of the method was changed to communicate that queue freezing doesn't make sense. +* **Discussion:** [Bug #17146](https://bugs.ruby-lang.org/issues/17146) +* **Documentation:** [Thread::Queue#freeze](https://docs.ruby-lang.org/en/master/Thread/Queue.html#method-i-freeze) and [Thread::SizedQueue#freeze](https://docs.ruby-lang.org/en/master/Thread/SizedQueue.html#method-i-freeze) + +### `Range` + +#### `#reverse_each` + +Specialized `Range#reverse_each` method is implemented. + +* **Reason:** Previously, `Range` didn't have a specialized `#reverse_each` method, so calling it invoked a generic `Enumerable#reverse_each`. The latter works by converting the object to array, and then enumerating this array. In case of a `Range` this can be inefficient (producing large arrays) or impossible (when only upper bound of the range is defined). It also went into infinite loop with endless ranges, trying to enumerate it all to convert into array, while the range can say beforehand that it would be impossible. +* **Discussion:** [Feature #18515](https://bugs.ruby-lang.org/issues/18515) +* **Documentation:** [Range#reverse_each](https://docs.ruby-lang.org/en/master/Range.html#method-i-reverse_each) +* **Code:** + ```ruby + # Efficient implementation for integers: + + (1..2**100).reverse_each.take(3) + # Ruby 3.2: hangs on my machine, trying to produce an array + # Ruby 3.3: #=> [1267650600228229401496703205376, 1267650600228229401496703205375, 1267650600228229401496703205374] + # (returns immediately) + + (...5).reverse_each.take(3) + # Ruby 3.2: can't iterate from NilClass (TypeError) + # Ruby 3.3: #=> [5, 4, 3] + + # Explicit error for endless ranges: + + (1...).reverse_each + # Ruby 3.2: hangs forever, trying to produce an array + # Ruby 3.3: `reverse_each': can't iterate from NilClass (TypeError) + + # The latter change affects any type of range beginning: + ('a'...).reverse_each + # Ruby 3.2: hangs forever, trying to produce an array + # Ruby 3.3: `reverse_each': can't iterate from NilClass (TypeError) + ``` +* **Notes:** Other than raising `TypeError` for endless ranges (which works with any type of range beginning), the specialized behavior is only implemented for `Integer`. A possibility of a generalization was [discussed](https://bugs.ruby-lang.org/issues/18515#note-4) by using object's `#pred` method (opposite to `#succ`, which the range uses to iterate forward), but the scope of this change would be bigger, as currently only `Integer` implements such method. It is possible that the adjustments would be made in the future versions. + +#### `#overlap?` + +Checks for overlapping of two ranges. + +* **Discussion:** [Feature #19839](https://bugs.ruby-lang.org/issues/19839) +* **Documentation:** [Range#overlap?](https://docs.ruby-lang.org/en/master/Range.html#method-i-overlap-3F) +* **Code:** + ```ruby + (1..3).overlap?(2..5) #=> true + (1..3).overlap?(4..5) #=> false + (..3).overlap?(3..) #=> true + + (1...3).overlap?(3..5) + #=> false, the first range doesn't include 3 + (1..3).overlap?(3...3) + #=> false, the second range is empty (note it has an exclusive end) + + (1..3).overlap?('a'..'c') + #=> false, ranges are incompatible (but not an exception) + (1..3).overlap?(1) + # `overlap?': wrong argument type Integer (expected Range) (TypeError) + ``` +* **Notes:** As documentation points out, the _technically empty_ `(...-Float::INFINITY)` range (nothing can be lower than `Float::INFINITY`, and it is not included) still considered overlapping with itself by this method: + ```ruby + (...-Float::INFINITY).overlap?(...-Float::INFINITY) #=> true + # Same with other "nothing could be smaller" ranges: + (..."").overlap?(..."") #=> true + ``` + (Though, with Ruby's dynamic nature, one _technically can_ define an object that will report itself to be smaller than an empty string, and therefore belong to a range... Making it non-empty.) + +### Filesystem and IO + +#### `Dir.for_fd` and `Dir.fchdir` + +Two methods to accept an integer file descriptor as an argument: `for_fd` creates a `Dir` object from it; `fchdir` changes the current directory to one specified by a descriptor. + +* **Reason:** New methods allow to use UNIX file descriptors if they are returned from a C-level code or obtained from OS. +* **Discussion:** [Feature #19347](https://bugs.ruby-lang.org/issues/19347) +* **Documentation:** [Dir.for_fd](https://docs.ruby-lang.org/en/master/Dir.html#method-c-for_fd), [Dir.fchdir](https://docs.ruby-lang.org/en/master/Dir.html#method-c-fchdir) +* **Code:** + ```ruby + fileno = Dir.new('doc/').fileno + # In reality, this #fileno might come from other library + + dir = Dir.for_fd(fileno) + #=> # -- no readable path representation + dir.path #=> nil + dir.to_a + #=> ["forwardable.rd.ja", "packed_data.rdoc", "marshal.rdoc", "format_specifications.rdoc", .... + # It was performed in the Ruby's core folder, and lists the doc/ contents + + # Attempt to use a bogus fileno will result in error: + Dir.for_fd(0) + # `for_fd': Not a directory - fdopendir (Errno::ENOTDIR) + + # Same with fileno that doesn't designate a directory: + Dir.for_fd(Dir.new('README.md').fileno) + # in `initialize': Not a directory @ dir_initialize - README.md (Errno::ENOTDIR) + + # Same logic works for .fchdir + Dir.fchdir(fileno) #=> 0 + Dir.pwd + #=> "/home/zverok/projects/ruby/doc" -- the current path have changed successfully + + # A block form of fchdir is available, like for a regular .chdir: + Dir.fchdir(Dir.new('NEWS').fileno) do |*args| + p args #=> [] -- no arguments are passed into the block + p Dir.pwd #=> "/home/zverok/projects/ruby/doc/NEWS" + 'return value' + end #=> "return value" + Dir.pwd #=> "/home/zverok/projects/ruby/doc" -- back to the path before the block + ``` +* **Notes:** + * The functionality is only supported on POSIX platforms; + * The initial [ticket](https://bugs.ruby-lang.org/issues/19347) only proposed to find a way to be able to change a current directory to one specified by a descriptor (i.e., what eventually became `.fchdir`), but during the discussion a need were discovered for a generic instantiation of a `Dir` instance from the descriptor (what became `from_fd`), as well as a generic way to change the current directory to one specified by `Dir` instance ([`#chdir`](#dirchdir), which is not related to descriptors but is generically useful). + +#### `Dir#chdir` + +An instance method version of [Dir.chdir](https://docs.ruby-lang.org/en/master/Dir.html#method-c-chdir): changes the current working directory to one specified by the `Dir` instance. + +* **Discussion:** [Feature #19347](https://bugs.ruby-lang.org/issues/19347) +* **Documentation:** [Dir#chdir](https://docs.ruby-lang.org/en/master/Dir.html#method-i-chdir) +* **Code:** + ```ruby + Dir.pwd #=> "/home/zverok/projects/ruby" + dir = Dir.new('doc') + dir.chdir #=> nil + Dir.pwd #=> "/home/zverok/projects/ruby/doc" + + # The block form works, too: + Dir.new('NEWS').chdir do |*args| + p args #=> [] -- no arguments are passed into the block + Dir.pwd #=> "/home/zverok/projects/ruby/doc/NEWS" + 'return value' + end #=> "return value" + Dir.pwd #=> "/home/zverok/projects/ruby/doc" + ``` + +#### Deprecate subprocess creation with method dedicated to files + +* **Reason:** Methods that are dedicated for opening/reading a file by name historically supported the special syntax of the argument: if it started with pipe character `|`, the subprocess was created and could've been used to communicate with an external command. The functionality is still explained in [Ruby 3.2 docs](https://docs.ruby-lang.org/en/3.2/Kernel.html#method-i-open). It, though, created a security vulnerability: even when the program's author didn't rely on that behavior, the malicious string could've been passed by the attacker instead of an innocent filename. +* **Discussion:** [Feature #19630](https://bugs.ruby-lang.org/issues/19630) +* **Affected methods:** + * [Kernel#open](https://docs.ruby-lang.org/en/master/Kernel.html#method-i-open) + * [IO.binread](https://docs.ruby-lang.org/en/master/IO.html#method-c-binread) + * [IO.foreach](https://docs.ruby-lang.org/en/master/IO.html#method-c-foreach) + * [IO.readlines](https://docs.ruby-lang.org/en/master/IO.html#method-c-readlines) + * [IO.read](https://docs.ruby-lang.org/en/master/IO.html#method-c-read) + * [IO.write](https://docs.ruby-lang.org/en/master/IO.html#method-c-write) + * [URI.open](https://docs.ruby-lang.org/en/master/URI.html#method-c-open) ([open-uri](https://github.com/ruby/open-uri) standard library) +* **Code:** + ```ruby + IO.read('| ls') + #=> contents of the current folder + + Warning[:deprecated] = true # Or pass -w command-line option + IO.read('| ls') + # warning: Calling Kernel#open with a leading '|' is deprecated and will be removed in Ruby 4.0; use IO.popen instead + #=> contents of the current folder + ``` +* **Notes:** + * The documentation for the corresponding methods was adjusted accordingly. Compare the documentation for `Kernel#open` from [3.2](https://docs.ruby-lang.org/en/3.2/Kernel.html#method-i-open) (explains and showcases the `|` trick) and [3.3](https://docs.ruby-lang.org/en/master/Kernel.html#method-i-open) (just mentions that there is a vulnerability to command injection attack). + * As advised by the warning, [IO.popen](https://docs.ruby-lang.org/en/master/IO.html#method-c-popen) is a specialized method when communicating with an external process is desired functionality: + ```ruby + IO.popen('ls') + #=> contents of the current folder + ``` + * As the impact of the change might be big, note that target version for removal is set to **4.0**. To the best of my knowledge, there are no set date for major version yet. + +### `NoMethodError`: change of rendering logic + +`NoMethodError` doesn't use target object's `#inspect` in its message, and renders "instance of ClassName" instead. + +* **Reason:** While the `#inspect` of the object which failed to respond might be convenient in the error's output, it also might be extremely inefficient and confusing when the object is large and doesn't have `#inspect` redefined to something sensible. It is impossible to require all user objects to redefine `#inspect`, and even if it is redefined, it might be short yet inefficient; so the lesser of evils was chosen and exception's message became more efficient even if less informative. +* **Documentation:** [NoMethodError](https://docs.ruby-lang.org/en/master/NoMethodError.html) +* **Code:** + ```ruby + "hello".to_ary + # Ruby 3.2: undefined method `to_ary' for "hello":String (NoMethodError) + # Ruby 3.3: undefined method `to_ary' for an instance of String (NoMethodError) + + # But also, for some complicated data structure: + ([{name: 'User 1', role: 'admin'}] * 100).to_josn # typo + # Ruby 3.2: undefined method `to_josn' for [{:name=>"User 1", :role=>"admin"}, {:name=>"User 1", :role=>"admin"}, ... + # ....10 lines of console output.... + # ..., {:name=>"User 1", :role=>"admin"}]:Array (NoMethodError) + # + # Ruby 3.3: undefined method `to_josn' for an instance of Array (NoMethodError) + ``` + +### `Fiber#kill` + +Terminates the Fiber by sending an exception inside it. + +* **Reason:** The method is intended to be used to fibers that represent processes that need to be told explicitly to finalize themselves (invoking any `ensure` operations and cleanups that are necessary). If such fiber just abandoned and collected by a GC, it wouldn't invoke fiber's `ensure`, and therefore the resources wouldn't be cleaned; so there was need for a way to do this explicitly. +* **Discussion:** [Bug #595](https://bugs.ruby-lang.org/issues/595) +* **Documentation:** [Fiber#kill](https://docs.ruby-lang.org/en/master/Fiber.html#method-i-kill) +* **Code:** + ```ruby + f = Fiber.new do + (1..).each { Fiber.yield _1 } + ensure + puts "Closing myself" + end + #=> # + + f.resume #=> 1 + f.resume #=> 2 + f #=> # + f.kill + # Prints: "Closing myself" + f #=> # + f.resume + # `resume': attempt to resume a terminated fiber (FiberError) + + # Semi-realistic usage example: + + reader = Fiber.new do + conn = SomeConnection.open(**params) + while conn.open? + Fiber.yield conn.read + end + ensure + conn.close + end + + headers = reader.resume # reads something from the connection + body_line1 = reader.resume # reads some more + # Now, if we want to explicitly stop reading and be sure that the connection + # is closed, we might do this: + reader.kill # invokes #ensure + ``` +* **Notes:** + * The exception sent to Fiber is _uncatchable_ (so no `rescue Exception` will notice it), so it can't be said that it has some _class_; the only usage of the fact that it is raised through exception mechanism is invoking `ensure` block; + * The fibers that was invoking the killed one with `resume` or `transfer`, receives `nil` from that call; + ```ruby + f1 = Fiber.new { + # Instead of yielding something back, the fiber kills itself + Fiber.current.kill + } + + f2 = Fiber.new { + result = f1.transfer + p(result:) + } + + f2.resume + # prints: {:result => nil} + ``` + * Only fibers belonging to the same thread can be killed. + +### Internals + +#### New `Warning` category: `:performance` + +A new warning category was introduced for a code that is correct but is known to produces a performance problems. One new such warning was added for objects with too many "shape" variations. + +* **Discussion:** [Feature #19538](https://bugs.ruby-lang.org/issues/19538) +* **Documentation:** [Warning#[category]](https://docs.ruby-lang.org/en/master/Warning.html#method-c-5B-5D) +* **Code:** Here is an example of the new warning in play: + ```ruby + class C + def initialize(i) + instance_variable_set("@var_#{i}", i**2) + end + end + + Warning[:performance] = true # or pass `-W:performance` command-line argument + + (1..10).map { C.new(_1) } + # warning: Maximum shapes variations (8) reached by C, instance variables accesses will be slower. + ``` + The example is artificial, but it shows the principle: when we have more than 8 instances of the _same_ class, but with different _list of instance variables_ (shape), we might have a performance problem. This means, for example, that a frequently-used class that has many methods with a memoization idiom (`@var ||= value` on the first access) would create the same problem, unless all of them would be initialized in the `initialize`, making all instances having the same shape: + ```ruby + class C + # 9 different getters that create an instance varaible + # on the first access. + def var1 = @var1 ||= rand + def var2 = @var2 ||= rand + def var3 = @var3 ||= rand + def var4 = @var4 ||= rand + def var5 = @var5 ||= rand + def var6 = @var6 ||= rand + def var7 = @var7 ||= rand + def var8 = @var8 ||= rand + def var9 = @var9 ||= rand + end + + Warning[:performance] = true + # Invoking different getters on different instances of the same class makes + # them have different set of instance variables. + (1..9).map { C.new.send("var#{_1}") } + # warning: Maximum shapes variations (8) reached by C, instance variables accesses will be slower. + + # But if we add this to initialize: + class C + def initialize + @var1, @var2, @var3, @var4, @var5, @var5, @var6, @var7, @var8, @var9 = nil + end + end + + (1..9).map { C.new.send("var#{_1}") } + # no warning. All objects have the same list of instance vars = the same shape + ``` +* **Notes:** + * The warning category should be turned on explicitly by providing `-W:performance` CLI option or `Warning[:performance] = true` from the program. +* **Additional reading:** [Performance impact of the memoization idiom on modern Ruby](https://railsatscale.com/2023-10-24-memoization-pattern-and-object-shapes/) by Ruby core team member Jean Boussier. + +#### `Process.warmup` + +A method to call when a long-running application finalized its loading, and before the regular work is started. + +* **Discussion:** [Feature #18885](https://bugs.ruby-lang.org/issues/18885) +* **Documentation:** [Process.warmup](https://docs.ruby-lang.org/en/master/Process.html#method-c-warmup) +* **Notes:** Hardly something can be explained or showcased here better than the justification discussion linked above and the method docs are doing it. + +#### `Process::Status#&` and `#>>` are deprecated + +* **Reason:** These methods have been treating `Process::Status` as a very thin wrapper around an integer value of the return status of the process; which is unreasonable for supporting Ruby in more varying environments. +* **Discussion:** [Bug #19868](https://bugs.ruby-lang.org/issues/19868) +* **Documentation:** [Process::Status#&](https://docs.ruby-lang.org/en/master/Process/Status.html#method-i-26), [#>>](https://docs.ruby-lang.org/en/master/Process/Status.html#method-i-3E-3E) + +#### `TracePoint` supports `:rescue` event + +Allows to trace when some exception was `rescue`'d in the code of interest. + +* **Discussion:** [Feature #19572](https://bugs.ruby-lang.org/issues/19572) +* **Documentation:** [TracePoint#Events](https://docs.ruby-lang.org/en/master/TracePoint.html#class-TracePoint-label-Events) +* **Code:** + ```ruby + TracePoint.trace(:rescue) do |tp| + puts "Exception rescued: #{tp.raised_exception.inspect} at #{tp.path}:#{tp.lineno}" + end + + begin + raise "foo" + rescue => e + end + # Prints: "Exception rescued: # at example.rb:7 + ``` +* **Notes:** The event-specific attribute for the event is the same as for `:raise`: [#raised_exception](https://docs.ruby-lang.org/en/master/TracePoint.html#method-i-raised_exception). + +## Standard library + +Since Ruby 3.1 release, most of the standard library is extracted to either _default_ or _bundled_ gems; their development happens in separate repositories, and changelogs are either maintained there, or absent altogether. Either way, their changes aren't mentioned in the combined Ruby changelog, and I'll not be trying to follow all of them. + +> **[stdgems.org](https://stdgems.org/)** project has a nice explanations of default and bundled gems concepts, as well as a list of currently gemified libraries and links to their docs. + +> "For the rest of us" this means libraries development extracted into separate GitHub repositories, and they are just packaged with main Ruby before release. It means you can do issue/PR to any of them independently, without going through more tough development process of the core Ruby. + +A few changes to mention, though: + +* [BasicSocket#recv](https://docs.ruby-lang.org/en/master/BasicSocket.html#method-i-recv) and [BasicSocket#recv_nonblock](https://docs.ruby-lang.org/en/master/BasicSocket.html#method-i-recv_nonblock) returns `nil` instead of an empty string on closed connections. [BaicSocket#recvmsg](https://docs.ruby-lang.org/en/master/BasicSocket.html#method-i-recvmsg) and [BasicSocket#recvmsg_nonblock](https://docs.ruby-lang.org/en/master/BasicSocket.html#method-i-recvmsg_nonblock) returns `nil` instead of an empty packet on closed connections. Discussion: [Bug #19012](https://bugs.ruby-lang.org/issues/19012) +* Name resolution such as [Socket.getaddrinfo](https://docs.ruby-lang.org/en/master/Socket.html#method-c-getaddrinfo), [Socket.getnameinfo](https://docs.ruby-lang.org/en/master/Socket.html#method-c-getnameinfo), [Addrinfo.getaddrinfo](https://docs.ruby-lang.org/en/master/Addrinfo.html#method-c-getaddrinfo) can now be interrupted. Discussion: [Feature #19965](https://bugs.ruby-lang.org/issues/19965) +* [Random::Formatter#alphanumeric](https://docs.ruby-lang.org/en/master/Random/Formatter.html#method-i-alphanumeric): `chars` keyword argument. [Feature #18183](https://bugs.ruby-lang.org/issues/18183): + ```ruby + require 'random/formatter' + # The default behavior: uses English alphabet + numbers + Random.alphanumeric + #=> "fhCshEkcGfCTO6Ny" + + # With the argument provided: + Random.alphanumeric(chars: ['a', 'b', 'c']) + #=> "cbacacbababccccc" + + # Note that the argument should be an array. + # So if you have a string of characters, you can do: + Random.alphanumeric(chars: 'abc'.chars) + #=> "abbaccaacbacbccc" + + # Any object is acceptable as an array element; the method + # would just use their `#to_s`; arrays would be flattened: + Random.alphanumeric(chars: [1, true, [2], Object.new]) + #=> "111true11211true2true#221" + + # An empty array just hangs forever: + Random.alphanumeric(chars: []) # never returns + ``` +* There were many amazing changes in Ruby's console IRB. See the article by IRB maintainer Stan Lo: [Unveiling the big leap in Ruby 3.3's IRB](https://railsatscale.com/2023-12-19-irb-for-ruby-3-3/). + +### Version updates + +#### Default gems + +* [RubyGems](https://github.com/rubygems/RubyGems) [3.5.3](https://github.com/rubygems/rubygems/blob/v3.5.3/CHANGELOG.md) +* [abbrev](https://github.com/ruby/abbrev) 0.1.2 +* [base64](https://github.com/ruby/base64) 0.2.0 +* [benchmark](https://github.com/ruby/benchmark) 0.3.0 +* [bigdecimal](https://github.com/ruby/bigdecimal) [3.1.5](https://github.com/ruby/bigdecimal/blob/v3.1.5/CHANGES.md) +* [bundler](https://github.com/rubygems/rubygems/tree/master/bundler) [2.5.3](https://github.com/rubygems/rubygems/blob/v3.5.3/bundler/CHANGELOG.md) +* [cgi](https://github.com/ruby/cgi) 0.4.1 +* [csv](https://github.com/ruby/csv) [3.2.8](https://github.com/ruby/csv/blob/v3.2.8/NEWS.md) +* [date](https://github.com/ruby/date) 3.3.4 +* [delegate](https://github.com/ruby/delegate) 0.3.1 +* [drb](https://github.com/ruby/drb) 2.2.0 +* [english](https://github.com/ruby/english) 0.8.0 +* [erb](https://github.com/ruby/erb) 4.0.3 +* [error_highlight](https://github.com/ruby/error_highlight) 0.6.0 +* [etc](https://github.com/ruby/etc) 1.4.3 +* [fcntl](https://github.com/ruby/fcntl) 1.1.0 +* [fiddle](https://github.com/ruby/fiddle) 1.1.2 +* [fileutils](https://github.com/ruby/fileutils) 1.7.2 +* [find](https://github.com/ruby/find) 0.2.0 +* [getoptlong](https://github.com/ruby/getoptlong) 0.2.1 +* [io-console](https://github.com/ruby/io-console) 0.7.1 +* [io-nonblock](https://github.com/ruby/io-nonblock) 0.3.0 +* [io-wait](https://github.com/ruby/io-wait) 0.3.1 +* [ipaddr](https://github.com/ruby/ipaddr) 1.2.6 +* [irb](https://github.com/ruby/irb) 1.11.0 ([Releases](https://github.com/ruby/irb/releases) page, [blog post](https://railsatscale.com/2023-12-19-irb-for-ruby-3-3/)) +* [json](https://github.com/flori/json) [2.7.1](https://github.com/flori/json/blob/v2.7.1/CHANGES.md) +* [logger](https://github.com/ruby/logger) 1.6.0 +* [mutex](https://github.com/ruby/mutex)_m 0.2.0 +* [net-http](https://github.com/ruby/net-http) 0.4.0 +* [net-protocol](https://github.com/ruby/net-protocol) 0.2.2 +* [nkf](https://github.com/ruby/nkf) 0.1.3 +* [observer](https://github.com/ruby/observer) 0.1.2 +* [open-uri](https://github.com/ruby/open-uri) 0.4.1 +* [open3](https://github.com/ruby/open3) 0.2.1 +* [openssl](https://github.com/ruby/openssl) 3.2.0 +* [optparse](https://github.com/ruby/optparse) 0.4.0 +* [ostruct](https://github.com/ruby/ostruct) 0.6.0 +* [pathname](https://github.com/ruby/pathname) 0.3.0 +* [pp](https://github.com/ruby/pp) 0.5.0 +* [prettyprint](https://github.com/ruby/prettyprint) 0.2.0 +* [pstore](https://github.com/ruby/pstore) 0.1.3 +* [psych](https://github.com/ruby/psych) 5.1.2 +* [rdoc](https://github.com/ruby/rdoc) 6.6.2 +* [readline](https://github.com/ruby/readline) 0.0.4 +* [reline](https://github.com/ruby/reline) 0.4.1 +* [resolv](https://github.com/ruby/resolv) 0.3.0 +* [rinda](https://github.com/ruby/rinda) 0.2.0 +* [securerandom](https://github.com/ruby/securerandom) 0.3.1 +* [set](https://github.com/ruby/set) 1.1.0 +* [shellwords](https://github.com/ruby/shellwords) 0.2.0 +* [singleton](https://github.com/ruby/singleton) 0.2.0 +* [stringio](https://github.com/ruby/stringio) 3.1.0 +* [strscan](https://github.com/ruby/strscan) 3.0.7 +* [syntax_suggest](https://github.com/ruby/syntax_suggest) 2.0.0 +* [syslog](https://github.com/ruby/syslog) 0.1.2 +* [tempfile](https://github.com/ruby/tempfile) 0.2.1 +* [time](https://github.com/ruby/time) 0.3.0 +* [timeout](https://github.com/ruby/timeout) 0.4.1 +* [tmpdir](https://github.com/ruby/tmpdir) 0.2.0 +* [tsort](https://github.com/ruby/tsort) 0.2.0 +* [un](https://github.com/ruby/un) 0.3.0 +* [uri](https://github.com/ruby/uri) 0.13.0 +* [weakref](https://github.com/ruby/weakref) 0.1.3 +* [win32ole](https://github.com/ruby/win32ole) 1.8.10 +* [yaml](https://github.com/ruby/yaml) 0.3.0 +* [zlib](https://github.com/ruby/zlib) 3.1.0 + +#### Bundled gems + +* [minitest](https://github.com/minitest/minitest) [5.20.0](https://github.com/minitest/minitest/blob/v5.20.0/History.rdoc) +* [rake](https://github.com/ruby/rake) 13.1.0 +* [test-unit](https://github.com/ruby/test-unit) 3.6.1 +* [rexml](https://github.com/ruby/rexml) 3.2.6 +* [rss](https://github.com/ruby/rss) 0.3.0 +* [net-ftp](https://github.com/ruby/net-ftp) 0.3.3 +* [net-imap](https://github.com/ruby/net-imap) 0.4.9 +* [net-smtp](https://github.com/ruby/net-smtp) 0.4.0 +* [rbs](https://github.com/ruby/rbs) [3.4.0](https://github.com/ruby/rbs/blob/v3.4.0/CHANGELOG.md) +* [typeprof](https://github.com/ruby/typeprof) 0.21.9 +* [debug](https://github.com/ruby/debug) [1.9.1](https://github.com/ruby/debug/releases) + +### Standard library content changes + +#### New libraries + +* **[prism](https://github.com/ruby/prism) (nee YARP) is added.** It is a new Ruby code parser, developed by Kevin Newton, which intends to become _the_ Ruby parser, shared by all implementations (not only CRuby/MRI, but also TruffleRuby, JRuby, and others) and tools that need to parser Ruby code (like Sorbet or Rubocop). It doesn't replace CRuby's Ruby parser, at least for now, but can be used to parse Ruby quickly and produce robust, easy to use AST. + * **Documentation:** [Prism](https://docs.ruby-lang.org/en/master/Prism.html) (it isn't very well rendered in the standard library docs, so the [official site](https://ruby.github.io/prism/) is recommended); + * **Note:** You can run Ruby with Prism as its main parser with `--parser=prism`, but it is only for experimentation and debugging for now. + +#### Removals + +* `readline` extension is removed. It was a standard library written in C to wrap [GNU Readline](https://tiswww.case.edu/php/chet/readline/rltop.html), used to implement interactive consoles like IRB. Ruby includes pure-Ruby replacement called [reline](https://github.com/ruby/reline) [since 2.7](2.7.html#new-libraries), and now `require 'readline'` will just require it and make an alias `Readline = Reline`. Though, if the [readline-ext](https://github.com/ruby/readline-ext) gem is installed explicitly, `require 'readline'` would require it. Discussion: [Feature #19616](https://bugs.ruby-lang.org/issues/19616). + ```ruby + require 'readline' + + Readline + # Ruby 3.2: + # => Readline -- a separate library/constant + # Ruby 3.3: + # => Reline -- Readline is just an alias + + Readline.method(:readline) + # Ruby 3.2: + # => # -- a C-defined method with no location/signature extracted + # Ruby 3.3: + # => #/lib/ruby/3.3.0+0/forwardable.rb:231> + ``` + +#### Default gems that became bundled + +_This means that if your dependencies are managed by Bundler and your code depend on `racc`, it should be added to a `Gemfile`._ + +* [racc](https://github.com/ruby/racc) 1.7.3 + +#### Gems that are warned to become bundled in the next version + +These gems wouldn't in a Bundler-managed environment unless explicitly added to `Gemfile` since the next version of Ruby. For now, requiring them in such environment would produce a warning. Discussion: [Feature #19351](https://bugs.ruby-lang.org/issues/19351) (initial proposal to promote many gems, which then was deemed problematic), [Feature #19776](https://bugs.ruby-lang.org/issues/19776) (the warning proposal) + +* [abbrev](https://github.com/ruby/abbrev) +* [base64](https://github.com/ruby/base64) +* [bigdecimal](https://github.com/ruby/bigdecimal) +* [csv](https://github.com/ruby/csv) +* [drb](https://github.com/ruby/drb) +* [getoptlong](https://github.com/ruby/getoptlong) +* [mutex_m](https://github.com/ruby/mutex_m) +* [nkf](https://github.com/ruby/nkf) +* [observer](https://github.com/ruby/observer) +* [racc](https://github.com/ruby/racc) +* [resolv-replace](https://github.com/ruby/resolv-replace) +* [rinda](https://github.com/ruby/rinda) +* [syslog](https://github.com/ruby/syslog) diff --git a/_src/evolution.md b/_src/evolution.md index eade9d1..8ba4638 100644 --- a/_src/evolution.md +++ b/_src/evolution.md @@ -12,7 +12,7 @@ image: images/evolution.png 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). +It is part of a bigger [Ruby Changes](https://rubyreferences.github.io/rubychanges/) 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. @@ -119,7 +119,7 @@ As Ruby is highly object-oriented language, most of the changes can be associate ```ruby {a: 1, b: 2} => a: ``` -* [3.2](3.2.html#pattern-matching) Deconstruction added to core and standard library objects: `MatchData`: [#deconstruct](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct) and [#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct_keys)), [Time#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Time.html#method-i-deconstruct_keys), [Date#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Date.html#method-i-deconstruct_keys), [DateTime#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/DateTime.html#method-i-deconstruct_keys): +* [3.2](3.2.html#pattern-matching) Deconstruction added to core and standard library objects: [MatchData#deconstruct](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct) and [#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/MatchData.html#method-i-deconstruct_keys), [Time#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Time.html#method-i-deconstruct_keys), [Date#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/Date.html#method-i-deconstruct_keys), [DateTime#deconstruct_keys](https://docs.ruby-lang.org/en/3.2/DateTime.html#method-i-deconstruct_keys): ```ruby 'Ruby 3.2.0'.match(/Ruby (\d)\.(\d)\.(\d)/) => major, minor, patch major #=> "3" @@ -266,6 +266,8 @@ end class HTTP end ``` +* [3.3](3.3.md#moduleset_temporary_name) **[Module#set_temporary_name](https://docs.ruby-lang.org/en/master/Module.html#method-i-set_temporary_name)** to set a human-readable name for a module without assigning it to a constant. + ## `Comparable` @@ -511,6 +516,9 @@ Included in many classes to implement comparison methods. Once class defines a m Regexp.new('foo', 'im') #=> /foo/im ``` * [3.2](3.2.md#regexp-redos-vulnerability-prevention) ReDoS vulnerability prevention: [Regexp.timeout](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-timeout), [Regexp.timeout=](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-timeout-3D), [Regexp.new](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-new) (`timeout:` keyword argument), [Regexp.linear_time?](https://docs.ruby-lang.org/en/3.2/Regexp.html#method-c-linear_time-3F). +* [3.3](3.3.md#stringbytesplice-new-arguments-to-select-a-portion-of-the-replacement-string) [String#bytesplice](https://docs.ruby-lang.org/en/master/String.html#method-i-bytesplice): additional arguments to select a portion of the inserted string. +* [3.3](3.3.md#matchdatanamed_captures-symbolize_names-argument) [MatchData#named_captures](https://docs.ruby-lang.org/en/master/MatchData.html#method-i-named_captures): `symbolize_names:` argument. + ## `Struct` @@ -581,6 +590,7 @@ Included in many classes to implement comparison methods. Once class defines a m * **2.0** `Time#to_s` now returns US-ASCII encoding instead of BINARY. * [2.7](2.7.md#inspect-includes-subseconds) `Time#inspect` includes subseconds ([Time#inspect](https://ruby-doc.org/core-2.7.0/Time.html#method-i-inspect)) * [3.1](3.1.md#strftime-supports--0000-offset) `#strftime` supports `-00:00` offset ([Time#strftime](https://docs.ruby-lang.org/en/3.1/Time.html#method-i-strftime)) +* [3.3](3.3.md#timenew-with-string-argument-became-stricter) Core classes and modules: `Time.new` with string argument became stricter ([Time#new](https://docs.ruby-lang.org/en/master/Time.html#method-c-new)) --> ## Enumerables, collections, and iteration @@ -698,6 +708,9 @@ Included in many classes to implement comparison methods. Once class defines a m ``` * [2.6](2.6.md#rangecover-accepts-range-argument) [#cover?](https://docs.ruby-lang.org/en/2.6.0/Range.html#method-i-cover-3F) accepts range argument * [2.7](2.7.md#beginless-range) **Beginless range: `(...100)`** +* [3.3](3.3.md#reverse_each) [Range#reverse_each](https://docs.ruby-lang.org/en/master/Range.html#method-i-reverse_each) (specialized form of `Enumerable#reverse_each`) +* [3.3](3.3.md#overlap) [Range#overlap?](https://docs.ruby-lang.org/en/master/Range.html#method-i-overlap-3F) + ### Warnings @@ -892,6 +916,8 @@ This section covers exception raising/handling behavior changes, as well as chan * [2.5](2.5.md#warn-uplevel-keyword-argument) [Kernel#warn](https://ruby-doc.org/core-2.6/Kernel.html#method-i-warn): `uplevel:` keyword argument allows to tune which line to specify in warning message as a source of warning * [2.7](2.7.md#warning-and-) [Warning::[]](https://ruby-doc.org/core-2.7.0/Warning.html#method-c-5B-5D) and [Warning::[]=](https://ruby-doc.org/core-2.7.0/Warning.html#method-c-5B-5D-3D) 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](3.0.md#warningwarn-category-keyword-argument) User code allowed to specify category of its warnings with [Kernel#warn](https://docs.ruby-lang.org/en/3.0.0/Kernel.html#method-i-warn) and intercept the warning category [Warning#warn](https://docs.ruby-lang.org/en/3.0.0/Warning.html#method-i-warn) with `category:` keyword argument; the list of categories is still closed. +* [3.3](3.3.md#new-warning-category-performance) New `Warning` [category](https://docs.ruby-lang.org/en/master/Warning.html#method-c-5B-5D): `:performance`. + ## Concurrency and parallelism @@ -915,9 +941,12 @@ This section covers exception raising/handling behavior changes, as well as chan * **2.1** [.clock_gettime](https://docs.ruby-lang.org/en/2.1.0/Process.html#method-c-clock_gettime) and [.clock_getres](https://docs.ruby-lang.org/en/2.1.0/Process.html#method-c-clock_getres) * [2.5](2.5.md#processlast_status-as-an-alias-of-) [Process.last_status](https://ruby-doc.org/core-2.5.0/Process.html#method-c-last_status) as an alias of `$?` * [3.1](3.1.md#process_fork) [Process._fork](https://docs.ruby-lang.org/en/3.1/Process.html#method-c-_fork) +* [3.3](3.3.md#processwarmup) [Process.warmup](https://docs.ruby-lang.org/en/master/Process.html#method-c-warmup) + ### `Fiber` @@ -931,6 +960,8 @@ This section covers exception raising/handling behavior changes, as well as chan * [3.2](3.2.md#fiberschedulerio_select) [#io_select](https://docs.ruby-lang.org/en/3.2/Fiber/Scheduler.html#method-i-io_select) hook added * [3.0](3.0.md#fiberbacktrace--backtrace_locations) [#backtrace](https://docs.ruby-lang.org/en/3.0.0/Fiber.html#method-i-backtrace) and [#backtrace_locations](https://docs.ruby-lang.org/en/3.0.0/Fiber.html#method-i-backtrace_locations) * [3.2](3.2.md#fiber-storage) **Storage concept**: [.[]](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-c-5B-5D), [.[]=](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-c-5B-5D-3D), [#storage](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-i-storage), and [#storage=](https://docs.ruby-lang.org/en/3.2/Fiber.html#method-i-storage-3D) +* [3.3](3.3.md#fiberkill) [Fiber#kill](https://docs.ruby-lang.org/en/master/Fiber.html#method-i-kill) + ## `Comparable`[](#comparable) @@ -511,6 +516,9 @@ Included in many classes to implement comparison methods. Once class defines a m Regexp.new('foo', 'im') #=> /foo/im ``` * [3.2](3.2.md#regexp-redos-vulnerability-prevention) ReDoS vulnerability prevention: Regexp.timeout, Regexp.timeout=, Regexp.new (`timeout:` keyword argument), Regexp.linear_time?. +* [3.3](3.3.md#stringbytesplice-new-arguments-to-select-a-portion-of-the-replacement-string) String#bytesplice: additional arguments to select a portion of the inserted string. +* [3.3](3.3.md#matchdatanamed_captures-symbolize_names-argument) MatchData#named_captures: `symbolize_names:` argument. + ## `Struct`[](#struct) @@ -581,6 +590,7 @@ Included in many classes to implement comparison methods. Once class defines a m * **2.0** `Time#to_s` now returns US-ASCII encoding instead of BINARY. * [2.7](2.7.md#inspect-includes-subseconds) `Time#inspect` includes subseconds (Time#inspect) * [3.1](3.1.md#strftime-supports--0000-offset) `#strftime` supports `-00:00` offset (Time#strftime) +* [3.3](3.3.md#timenew-with-string-argument-became-stricter) Core classes and modules: `Time.new` with string argument became stricter (Time#new) --> ## Enumerables, collections, and iteration[](#enumerables-collections-and-iteration) @@ -698,6 +708,9 @@ Included in many classes to implement comparison methods. Once class defines a m ``` * [2.6](2.6.md#rangecover-accepts-range-argument) #cover? accepts range argument * [2.7](2.7.md#beginless-range) **Beginless range: `(...100)`** +* [3.3](3.3.md#reverse_each) Range#reverse_each (specialized form of `Enumerable#reverse_each`) +* [3.3](3.3.md#overlap) Range#overlap? + ### Warnings[](#warnings) @@ -892,6 +916,8 @@ This section covers exception raising/handling behavior changes, as well as chan * [2.5](2.5.md#warn-uplevel-keyword-argument) Kernel#warn: `uplevel:` keyword argument allows to tune which line to specify in warning message as a source of warning * [2.7](2.7.md#warning-and-) 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](3.0.md#warningwarn-category-keyword-argument) 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. +* [3.3](3.3.md#new-warning-category-performance) New `Warning` category: `:performance`. + ## Concurrency and parallelism[](#concurrency-and-parallelism) @@ -915,9 +941,12 @@ This section covers exception raising/handling behavior changes, as well as chan * **2.1** .clock_gettime and .clock_getres * [2.5](2.5.md#processlast_status-as-an-alias-of-) Process.last_status as an alias of `$?` * [3.1](3.1.md#process_fork) Process._fork +* [3.3](3.3.md#processwarmup) Process.warmup + ### `Fiber`[](#fiber) @@ -931,6 +960,8 @@ This section covers exception raising/handling behavior changes, as well as chan * [3.2](3.2.md#fiberschedulerio_select) #io_select hook added * [3.0](3.0.md#fiberbacktrace--backtrace_locations) #backtrace and #backtrace_locations * [3.2](3.2.md#fiber-storage) **Storage concept**: .[], .[]=, #storage, and #storage= +* [3.3](3.3.md#fiberkill) Fiber#kill +