Вийшов Ruby 3.2.0

Опублікував naruse 25-12-2022
Переклав: Andrii Furmanets

Раді повідомити про вихід Ruby 3.2.0. Ruby 3.2 додає багато можливостей та покращень продуктивності.

Підтримка WebAssembly на основі WASI

Це початковий порт підтримки WebAssembly на основі WASI. Це дозволяє бінарному файлу CRuby бути доступним у веб-браузері, середовищі Serverless Edge або інших вбудовувачах WebAssembly/WASI. Зараз цей порт проходить базові та bootstrap тестові набори, що не використовують Thread API.

Передумови

WebAssembly (Wasm) спочатку було впроваджено для безпечного та швидкого запуску програм у веб-браузерах. Але його мета — ефективне виконання програм з безпекою у різних середовищах — довго бажана не лише для веб, але й загальними застосунками.

WASI (The WebAssembly System Interface) розроблено для таких випадків використання. Хоча такі застосунки потребують спілкування з операційними системами, WebAssembly працює на віртуальній машині, яка не мала системного інтерфейсу. WASI стандартизує це.

Підтримка WebAssembly/WASI в Ruby має на меті використати ці проекти. Це дозволяє розробникам Ruby писати застосунки, які працюють на таких обіцяних платформах.

Випадок використання

Ця підтримка заохочує розробників використовувати CRuby в середовищі WebAssembly. Приклад використання — підтримка CRuby в TryRuby playground. Тепер ви можете спробувати оригінальний CRuby у вашому веб-браузері.

Технічні моменти

Сьогоднішні WASI та WebAssembly самі по собі не мають деяких можливостей для реалізації Fiber, винятків та GC, оскільки вони все ще розвиваються, а також з міркувань безпеки. Тому CRuby заповнює прогалину, використовуючи Asyncify, який є технікою бінарного перетворення для контролю виконання в userland.

Крім того, ми побудували VFS поверх WASI, щоб ми могли легко упакувати застосунки Ruby в один .wasm файл. Це робить розповсюдження застосунків Ruby трохи простішим.

Пов’язані посилання

YJIT готовий до виробництва

  • YJIT більше не експериментальний
    • Протестовано на виробничих навантаженнях більше року та доведено, що він досить стабільний.
  • YJIT тепер підтримує як x86-64, так і arm64/aarch64 процесори на Linux, MacOS, BSD та інших платформах UNIX.
    • Цей реліз приносить підтримку для Apple M1/M2, AWS Graviton, Raspberry Pi 4 та інших.
  • Збірка YJIT тепер потребує Rust 1.58.0+. [Feature #18481]
    • Щоб забезпечити збірку CRuby з YJIT, будь ласка, встановіть rustc >= 1.58.0 перед запуском скрипта ./configure.
    • Будь ласка, зверніться до команди YJIT, якщо у вас виникнуть проблеми.
  • Реліз YJIT 3.2 швидший за 3.1 і має приблизно в 1/3 менше накладних витрат пам’яті.
    • Загалом YJIT на 41% швидший (геометричне середнє) за інтерпретатор Ruby на yjit-bench.
    • Фізична пам’ять для JIT коду виділяється ліниво. На відміну від Ruby 3.1, RSS процесу Ruby мінімізовано, оскільки віртуальні сторінки пам’яті, виділені --yjit-exec-mem-size, не будуть відображені на фізичні сторінки пам’яті, поки вони не будуть фактично використані JIT кодом.
    • Впроваджено Code GC, який звільняє всі сторінки коду, коли споживання пам’яті JIT кодом досягає --yjit-exec-mem-size.
    • RubyVM::YJIT.runtime_stats повертає метрики Code GC на додаток до існуючих ключів inline_code_size та outlined_code_size: code_gc_count, live_page_count, freed_page_count та freed_code_size.
  • Більшість статистики, створеної RubyVM::YJIT.runtime_stats, тепер доступна в release збірках.
    • Просто запустіть ruby з --yjit-stats, щоб обчислити та вивести статистику (спричиняє деякі накладні витрати під час виконання).
  • YJIT тепер оптимізовано для використання переваг форм об’єктів. [Feature #18776]
  • Використовуйте переваги дрібнозернистої інвалідації констант, щоб інвалідувати менше коду при визначенні нових констант. [Feature #18589]
  • Типове значення --yjit-exec-mem-size змінено на 64 (MiB).
  • Типове значення --yjit-call-threshold змінено на 30.

Покращення Regexp проти ReDoS

Відомо, що зіставлення Regexp може займати неочікувано довго. Якщо ваш код намагається зіставити можливо неефективний Regexp з ненадійним вводом, зловмисник може використати це для ефективної Denial of Service (так звана Regular expression DoS, або ReDoS).

Ми впровадили два покращення, які значно пом’якшують ReDoS.

Покращений алгоритм зіставлення Regexp

Починаючи з Ruby 3.2, алгоритм зіставлення Regexp значно покращено за допомогою техніки мемоїзації.

# Це зіставлення займає 10 сек. у Ruby 3.1, і 0.003 сек. у Ruby 3.2

/^a*b?a*$/ =~ "a" * 50000 + "x"

Покращений алгоритм зіставлення дозволяє більшості зіставлень Regexp (близько 90% у наших експериментах) завершуватися за лінійний час.

Ця оптимізація може споживати пам’ять, пропорційну довжині вводу для кожного зіставлення. Ми не очікуємо виникнення практичних проблем, оскільки це виділення пам’яті зазвичай відкладене, і нормальне зіставлення Regexp повинно споживати максимум у 10 разів більше пам’яті, ніж довжина вводу. Якщо у вас закінчується пам’ять під час зіставлення Regexps у реальному застосунку, будь ласка, повідомте про це.

Оригінальна пропозиція: https://bugs.ruby-lang.org/issues/19104

Таймаут Regexp

Оптимізацію вище не можна застосувати до деяких видів регулярних виразів, таких як ті, що включають розширені можливості (наприклад, зворотні посилання або look-around), або з великою фіксованою кількістю повторень. Як запасна міра, також впроваджено функцію таймауту для зіставлень Regexp.

Regexp.timeout = 1.0

/^a*b?a*()\1$/ =~ "a" * 50000 + "x"
#=> Regexp::TimeoutError викликається через одну секунду

Зверніть увагу, що Regexp.timeout є глобальною конфігурацією. Якщо ви хочете використовувати різні налаштування таймауту для деяких спеціальних Regexps, ви можете використати ключове слово timeout для Regexp.new.

Regexp.timeout = 1.0

# Цей regexp не має таймауту
long_time_re = Regexp.new('^a*b?a*()\1$', timeout: Float::INFINITY)

long_time_re =~ "a" * 50000 + "x" # ніколи не переривається

Оригінальна пропозиція: https://bugs.ruby-lang.org/issues/17837.

Інші помітні нові можливості

SyntaxSuggest

  • Функція syntax_suggest (раніше dead_end) інтегрована в Ruby. Це допомагає вам знайти позицію помилок, таких як відсутні або зайві end, щоб швидше повернутися на правильний шлях, наприклад, у наступному прикладі:

    Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?
    
      1  class Dog
    > 2    defbark
    > 3    end
      4  end
    

    [Feature #18159]

ErrorHighlight

  • Тепер він вказує на відповідні аргументи для TypeError та ArgumentError
test.rb:2:in `+': nil can't be coerced into Integer (TypeError)

sum = ary[0] + ary[1]
               ^^^^^^

Мова

  • Анонімні rest та keyword rest аргументи тепер можна передавати як аргументи, замість того, щоб просто використовувати їх у параметрах методів. [Feature #18351]

      def foo(*)
        bar(*)
      end
      def baz(**)
        quux(**)
      end
    
  • Proc, який приймає один позиційний аргумент та ключові слова, більше не буде автоматично розпаковуватися. [Bug #18633]

    proc{|a, **k| a}.call([1, 2])
    # Ruby 3.1 та раніше
    # => 1
    # Ruby 3.2 та пізніше
    # => [1, 2]
    
  • Порядок оцінки присвоєння констант для констант, встановлених на явних об’єктах, зроблено узгодженим з порядком оцінки присвоєння одного атрибута. З цим кодом:

      foo::BAR = baz
    

    foo тепер викликається перед baz. Аналогічно, для множинних присвоєнь констант використовується порядок оцінки зліва направо. З цим кодом:

        foo1::BAR1, foo2::BAR2 = baz1, baz2
    

    Тепер використовується наступний порядок оцінки:

    1. foo1
    2. foo2
    3. baz1
    4. baz2

    [Bug #15928]

  • Паттерн find більше не є експериментальним. [Feature #18585]

  • Методи, які приймають rest параметр (як *args) та бажають делегувати ключові аргументи через foo(*args), тепер повинні бути позначені ruby2_keywords (якщо це ще не зроблено). Іншими словами, всі методи, які бажають делегувати ключові аргументи через *args, тепер повинні бути позначені ruby2_keywords, без винятків. Це полегшить перехід до інших способів делегації, коли бібліотека може потребувати Ruby 3+. Раніше прапорець ruby2_keywords зберігався, якщо метод, що приймає, приймав *args, але це була помилка та неузгодженість. Хороша техніка для пошуку потенційно відсутніх ruby2_keywords — запустити тестовий набір, знайти останній метод, який повинен отримувати ключові аргументи для кожного місця, де тестовий набір не проходить, і використати puts nil, caller, nil там. Потім перевірте, що кожен метод/блок у ланцюжку викликів, який повинен делегувати ключові слова, правильно позначений ruby2_keywords. [Bug #18625] [Bug #16466]

      def target(**kw)
      end
    
      # Випадково працювало без ruby2_keywords у Ruby 2.7-3.1, ruby2_keywords
      # потрібен у 3.2+. Так само, як (*args, **kwargs) або (...) були б потрібні на
      # обох #foo та #bar при міграції від ruby2_keywords.
      ruby2_keywords def bar(*args)
        target(*args)
      end
    
      ruby2_keywords def foo(*args)
        bar(*args)
      end
    
      foo(k: 1)
    

Покращення продуктивності

MJIT

  • Компілятор MJIT реалізовано в Ruby як ruby_vm/mjit/compiler.
  • Компілятор MJIT виконується під форкованим процесом замість виконання в нативному потоці, званому MJIT worker. [Feature #18968]
    • В результаті Microsoft Visual Studio (MSWIN) більше не підтримується.
  • MinGW більше не підтримується. [Feature #18824]
  • Перейменовано --mjit-min-calls на --mjit-call-threshold.
  • Змінено типове значення --mjit-max-cache назад з 10000 на 100.

PubGrub

  • Bundler 2.4 тепер використовує резолвер PubGrub замість Molinillo.

    • PubGrub — це алгоритм вирішення наступного покоління, використовуваний менеджером пакетів pub для мови програмування Dart.
    • Ви можете отримати інший результат резолюції після цієї зміни. Будь ласка, повідомте про такі випадки до RubyGems/Bundler issues
  • RubyGems все ще використовує резолвер Molinillo в Ruby 3.2. Ми плануємо замінити його на PubGrub у майбутньому.

Інші помітні зміни з версії 3.1

  • Data
    • Новий основний клас для представлення простого незмінного об’єкта значення. Клас схожий на Struct і частково ділить реалізацію, але має більш мінімальний та суворий API. [Feature #16122]

        Measure = Data.define(:amount, :unit)
        distance = Measure.new(100, 'km')            #=> #<data Measure amount=100, unit="km">
        weight = Measure.new(amount: 50, unit: 'kg') #=> #<data Measure amount=50, unit="kg">
        weight.with(amount: 40)                      #=> #<data Measure amount=40, unit="kg">
        weight.amount                                #=> 50
        weight.amount = 40                           #=> NoMethodError: undefined method `amount='
      
  • Hash
    • Hash#shift тепер завжди повертає nil, якщо хеш порожній, замість повернення значення за замовчуванням або виклику default proc. [Bug #16908]
  • MatchData
  • Module
  • Proc
    • Proc#dup повертає екземпляр підкласу. [Bug #17545]
    • Proc#parameters тепер приймає ключове слово lambda. [Feature #15357]
  • Refinement
  • RubyVM::AbstractSyntaxTree
    • Додано опцію error_tolerant для parse, parse_file та of. [Feature #19013] З цією опцією
      1. SyntaxError пригнічується
      2. AST повертається для недійсного вводу
      3. end доповнюється, коли парсер досягає кінця вводу, але end недостатньо
      4. end розглядається як ключове слово на основі відступу
        # Без опції error_tolerant
        root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY)
        def m
          a = 10
          if
        end
        RUBY
        # => <internal:ast>:33:in `parse': syntax error, unexpected `end' (SyntaxError)
      
        # З опцією error_tolerant
        root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true)
        def m
          a = 10
          if
        end
        RUBY
        p root # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-4:3>
      
        # `end` розглядається як ключове слово на основі відступу
        root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true)
        module Z
          class Foo
            foo.
          end
      
          def bar
          end
        end
        RUBY
        p root.children[-1].children[-1].children[-1].children[-2..-1]
        # => [#<RubyVM::AbstractSyntaxTree::Node:CLASS@2:2-4:5>, #<RubyVM::AbstractSyntaxTree::Node:DEFN@6:2-7:5>]
      
    • Додано опцію keep_tokens для parse, parse_file та of. [Feature #19070]

        root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true)
        root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
        root.tokens.map{_1[2]}.join # => "x = 1 + 2"
      
  • Set
    • Set тепер доступний як вбудований клас без необхідності require "set". [Feature #16989] Зараз він автоматично завантажується через константу Set або виклик Enumerable#to_set.
  • String
    • Додано String#byteindex та String#byterindex. [Feature #13110]
    • Оновлено Unicode до версії 15.0.0 та Emoji версії 15.0. [Feature #18639] (також застосовується до Regexp)
    • Додано String#bytesplice. [Feature #18598]
  • Struct
    • Клас Struct також може ініціалізуватися ключовими аргументами без keyword_init: true на Struct.new [Feature #16806]

        Post = Struct.new(:id, :name)
        Post.new(1, "hello") #=> #<struct Post id=1, name="hello">
        # Починаючи з Ruby 3.2, наступний код також працює без keyword_init: true.
        Post.new(id: 1, name: "hello") #=> #<struct Post id=1, name="hello">
      

Проблеми сумісності

Примітка: Виключаючи виправлення помилок функцій.

Видалені константи

Наступні застарілі константи видалено.

Видалені методи

Наступні застарілі методи видалено.

Проблеми сумісності стандартної бібліотеки

Більше не bundle сторонніх джерел

  • Ми більше не bundle сторонні джерела, такі як libyaml, libffi.

    • Джерело libyaml видалено з psych. Вам може знадобитися встановити libyaml-dev на платформі Ubuntu/Debian. Назва пакета відрізняється для кожної платформи.

    • Bundled джерело libffi також видалено з fiddle

  • Psych та fiddle підтримували статичні збірки з конкретними версіями джерел libyaml та libffi. Ви можете зібрати psych з libyaml-0.2.5 так:

      $ ./configure --with-libyaml-source-dir=/path/to/libyaml-0.2.5
    

    І ви можете зібрати fiddle з libffi-3.4.4 так:

      $ ./configure --with-libffi-source-dir=/path/to/libffi-3.4.4
    

    [Feature #18571]

Оновлення C API

Оновлені C API

Наступні API оновлено.

  • Оновлення PRNG
    • rb_random_interface_t оновлено та версіоновано. Бібліотеки розширень, які використовують цей інтерфейс та зібрані для старіших версій. Також потрібно визначити функцію init_int32.

Видалені C API

Наступні застарілі API видалено.

  • Змінна rb_cData.
  • Функції “taintedness” та “trustedness”. [Feature #16131]

Оновлення стандартної бібліотеки

  • Bundler

    • Додано підтримку –ext=rust до bundle gem для створення простих gems з розширеннями Rust. [GH-rubygems-6149]
    • Пришвидшено клонування git репозиторіїв [GH-rubygems-4475]
  • RubyGems

    • Додано підтримку mswin для cargo builder. [GH-rubygems-6167]
  • ERB

    • ERB::Util.html_escape зроблено швидшим за CGI.escapeHTML.
      • Він більше не виділяє об’єкт String, коли немає символів, які потрібно екранувати.
      • Він пропускає виклик методу #to_s, коли аргумент вже є String.
      • ERB::Escape.html_escape додано як псевдонім для ERB::Util.html_escape, який не був monkey-patched Rails.
  • IRB

    • Додано команди інтеграції debug.gem: debug, break, catch, next, delete, step, continue, finish, backtrace, info
    • Додано більше команд та можливостей, подібних до Pry.
      • Додано edit та show_cmds (як help у Pry).
      • ls приймає опцію -g або -G для фільтрації виводу.
      • show_source є псевдонімом від $ та приймає нецитовані входи.
      • whereami є псевдонімом від @.
  • Оновлено наступні default gems.

    • RubyGems 3.4.1
    • abbrev 0.1.1
    • benchmark 0.2.1
    • bigdecimal 3.1.3
    • bundler 2.4.1
    • cgi 0.3.6
    • csv 3.2.6
    • date 3.3.3
    • delegate 0.3.0
    • did_you_mean 1.6.3
    • digest 3.1.1
    • drb 2.1.1
    • english 0.7.2
    • erb 4.0.2
    • error_highlight 0.5.1
    • etc 1.4.2
    • fcntl 1.0.2
    • fiddle 1.1.1
    • fileutils 1.7.0
    • forwardable 1.3.3
    • getoptlong 0.2.0
    • io-console 0.6.0
    • io-nonblock 0.2.0
    • io-wait 0.3.0
    • ipaddr 1.2.5
    • irb 1.6.2
    • json 2.6.3
    • logger 1.5.3
    • mutex_m 0.1.2
    • net-http 0.3.2
    • net-protocol 0.2.1
    • nkf 0.1.2
    • open-uri 0.3.0
    • open3 0.1.2
    • openssl 3.1.0
    • optparse 0.3.1
    • ostruct 0.5.5
    • pathname 0.2.1
    • pp 0.4.0
    • pstore 0.1.2
    • psych 5.0.1
    • racc 1.6.2
    • rdoc 6.5.0
    • readline-ext 0.1.5
    • reline 0.3.2
    • resolv 0.2.2
    • resolv-replace 0.1.1
    • securerandom 0.2.2
    • set 1.0.3
    • stringio 3.0.4
    • strscan 3.0.5
    • syntax_suggest 1.0.2
    • syslog 0.1.1
    • tempfile 0.1.3
    • time 0.2.1
    • timeout 0.3.1
    • tmpdir 0.1.3
    • tsort 0.1.1
    • un 0.2.1
    • uri 0.12.0
    • weakref 0.1.2
    • win32ole 1.8.9
    • yaml 0.2.1
    • zlib 3.0.0
  • Оновлено наступні bundled gems.

    • minitest 5.16.3
    • power_assert 2.0.3
    • test-unit 3.5.7
    • net-ftp 0.2.0
    • net-imap 0.3.3
    • net-pop 0.1.2
    • net-smtp 0.3.3
    • rbs 2.8.2
    • typeprof 0.21.3
    • debug 1.7.1

Див. GitHub releases, такі як GitHub Releases of logger, або changelog для деталей default gems або bundled gems.

Див. NEWS або журнали комітів для подробиць.

Унаслідок цих змін 3048 файлів змінено, 218253 вставок(+), 131067 видалень(-) з часу Ruby 3.1.0!

З Різдвом, зі святами, і приємного програмування з Ruby 3.2!

Завантаження

  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.tar.gz

    SIZE: 20440715
    SHA1: fb4ab2ceba8bf6a5b9bc7bf7cac945cc94f94c2b
    SHA256: daaa78e1360b2783f98deeceb677ad900f3a36c0ffa6e2b6b19090be77abc272
    SHA512: 94203051d20475b95a66660016721a0457d7ea57656a9f16cdd4264d8aa6c4cd8ea2fab659082611bfbd7b00ebbcf0391e883e2ebf384e4fab91869e0a877d35
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.tar.xz

    SIZE: 15058364
    SHA1: bcdae07183d66fd902cb7bf995545a472d2fefea
    SHA256: d2f4577306e6dd932259693233141e5c3ec13622c95b75996541b8d5b68b28b4
    SHA512: 733ecc6709470ee16916deeece9af1c76220ae95d17b2681116aff7f381d99bc3124b1b11b1c2336b2b29e468e91b90f158d5ae5fca810c6cf32a0b6234ae08e
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0.zip

    SIZE: 24583271
    SHA1: 581ec7b9289c2a85abf4f41c93993ecaa5cf43a5
    SHA256: cca9ddbc958431ff77f61948cb67afa569f01f99c9389d2bbedfa92986c9ef09
    SHA512: b7d2753825cc0667e8bb391fc7ec59a53c3db5fa314e38eee74b6511890b585ac7515baa2ddac09e2c6b6c42b9221c82e040af5b39c73e980fbd3b1bc622c99d
    

Що таке Ruby

Ruby уперше розробив Matz (Yukihiro Matsumoto) у 1993 році, і зараз вона розвивається як Open Source. Вона працює на багатьох платформах і використовується по всьому світу, особливо для веброзробки.

Останні новини

Вийшов Ruby 4.0.0

Ми раді повідомити про випуск Ruby 4.0.0. Ruby 4.0 представляє “Ruby Box” та “ZJIT”, а також додає багато покращень.

Опублікував naruse 25-12-2025

Новий вигляд документації Ruby

Слідом за ре-дизайном ruby-lang.org, ми маємо більше новин, щоб відсвяткувати 30-річчя Ruby: docs.ruby-lang.org має повністю новий вигляд завдяки Aliki — новій темі за замовчуванням для...

Опублікував Stan Lo 23-12-2025

Більше новин...