Ruby 3.2.0 RC 1 發布

我們很高興宣佈 Ruby 3.2.0-rc1 發佈了。Ruby 3.2 新增許多新功能及效能提升。

基於 WASI 的 WebAssembly 支援

這是首次基於 WASI 支援 WebAssembly。使得 CRuby binary 可用於網頁瀏覽器、Serverless Edge 環境、與其他 WebAssembly/WASI 嵌入式環境. 目前已通過 basic 與 bootstrap 測試,但不包括 Thread API。

背景

WebAssembly (Wasm) 最初是為了在網頁瀏覽器中安全快速地執行程式。但其目標 - 在不同的環境上安全又有效率的執行程式,不僅是 web 應用程式,也是其他一般應用程式的目標。

WASI (The WebAssembly System Interface) 被設計用於此使用場景。 儘管應用程式需要與作業系統溝通,但 WebAssembly 卻是運行在沒有系統介面的虛擬機中。WASI 將其標準化了。

Ruby 中的 WebAssembly/WASI 支援透過這些專案,允許 Ruby 開發者可以開發在相容此功能的平台上執行的應用程式。

使用場景

此支援功能使得開發者可以在 WebAssembly 環境上使用 CRuby。 其中一個範例就是 TryRuby playground 的 CRuby 支援。現在您可以在您的網頁瀏覽器上嘗試原生的 CRuby。

技術特點

因為目前 WASI 和 WebAssembly 不斷地再改進與安全性理由,仍缺少一些功能來實現 Fiber、異常、和 GC。所以 CRuby 透過使用 Asyncify,一個在使用者空間的 binary 轉換技術,來彌補中間的差距。

並且,我們建置了 a VFS on top of WASI,讓我們可以很容易地將 Ruby 應用程式打包成單一 .wasm 檔案。簡化了 Ruby 應用程式的分發過程。

相關連結

Regexp 增強 ReDoS 防禦

眾所皆知 Regexp matching 所花的時間可能會非預期的久。如果您的程式使用效率可能較低的 Regexp 來比對不可信的輸入內容,攻擊者可能可以藉此來發動服務阻斷攻擊。(稱為 Regular expression DoS, or ReDoS)。

我們進行了兩項改進,可以顯著降低 ReDos 攻擊的影響。

改善 Regexp 比對演算法

從 Ruby 3.2 開始,透過使用 memoization 技術,Regexp 的比對演算法得到了很大的改進。

# 這個比對在 Ruby 3.1 需要花費 10 秒。 而在 Ruby 3.2 只需要花費 0.003 秒。

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

改進後的演算法使得大部分的 Regexp (我們實驗中的 90%) 可以在線性時間內完成。

(給預覽使用者:這個改善可能會花費與輸入長度成比例的記憶體。我們預期這不會有實際問題,因為這種記憶體分配通常都會延遲,而正常的 Regexp 最多可花費輸入長度 10 倍的記憶體。如果您在現實場景中使用 Regexp 進行比對時遇到記憶題不足的問題,請向我們回報。)

最初提案: https://bugs.ruby-lang.org/issues/19104

Regexp 逾時設定

上述的改善無法套用在某些 Regexp,像是包含進階功能 (例如:back-references 或是 look-around),或有大量固定重複次數。作為備案,我們在 Regexp 比對中導入了逾時設定。

Regexp.timeout = 1.0

/^a*b?a*()\1$/ =~ "a" * 50000 + "x"
#=> 1 秒後拋出 Regexp::TimeoutError

注意 Regexp.timeout 是全域設定。如果您想要為一些特定的 Regexps 使用不同的逾時設定,您可以在呼叫 Regexp.new 時使用 timeout keyword。

Regexp.timeout = 1.0

# This regexp has no timeout
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
    > 4    end
      5  end
    

    [Feature #18159]

ErrorHighlight

  • 現在會指向 TypeError 和 ArgumentError 相關的參數。
test.rb:2:in `+': nil can't be coerced into Integer (TypeError)

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

語言功能

  • 除了作為方法參數,匿名不定長度參數現在也可以傳遞為其他方法的參數。 [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 pattern 不再是實驗性功能。 [Feature #18585]

  • 使用不定長度參數 (例如 *args) 的方法,如果同時希望可以作為 keyword 參數傳遞給 foo(*args)。必須標記為 ruby2_keywords (若還未標記)。 換句話說,希望作為接收 keyword 參數的其他方法都毫無例外地必須標記為 ruby2_keywords。若某個函式庫需要使用 Ruby 3+,這會是一個較為容易的過渡升級方法。 在此之前,當接受方法取得 *args時會保留 ruby2_keywords 標記,但這是一個錯誤且行為不一致。 對於找到可能缺少 ruby2_keywords 標記的好方法是執行測試,在測試失敗的地方,找到最後一個接收 keyword 參數的方法,在哪裡使用 puts nil, caller, nil,並檢查每一個在呼叫鏈上的方法/區塊,是否都被正確地標記為 ruby2_keywords。[Bug #18625] [Bug #16466]

      def target(**kw)
      end
    
      # 意外地 Ruby 2.7-3.1 在沒有 ruby2_keywords 的情況下可以成功
      # 執行,但在 3.2+ 卻是必需的。若需移除 ruby2_keywords,
      # #foo 和 #bar 需要將參數改成 (*args, **kwargs) 或 (...)
      ruby2_keywords def bar(*args)
        target(*args)
      end
    
      ruby2_keywords def foo(*args)
        bar(*args)
      end
    
      foo(k: 1)
    

效能提升

YJIT

  • YJIT 現在支援 x86-64 和 arm64/aarch64 架構的 Linux、MacOS、BSD 和其他 UNIX 平台。
    • 此發佈支援 Mac M1/M2、AWS Graviton 和 Raspberry Pi 4 ARM64 處理器.
  • 建置 YJIT 時需要 Rust 1.58.1+ 。 [Feature #18481]
    • 為了確保使用 YJIT 建置 CRuby,請安裝 rustc >= 1.58.0 並在執行 ./configure 時加入 --enable-yjit
    • 若執行時遇到任何問題請聯絡 YJIT 團隊。
  • JIT 程式碼的物理記憶體是延遲分配的。與 Ruby 3.1 不同,Ruby process 的 RSS 被最小化,因為由 --yjit-exec-mem-size 分配的虛擬記憶體分頁在被 JIT 程式碼實際使用之前不會被映射到物理記憶體分頁。
  • 導入 Code GC,當 JIT 程式碼使用的記憶體達到 --yjit-exec-mem-size 時釋放所有 code pages。
    • RubyVM::YJIT.runtime_stats 除了現有的 inline_code_sizeoutlined_code_size,還會回傳 Code GC 指標 code_gc_countlive_page_countfreed_page_count、和 freed_code_size
  • 大部分由 RubyVM::YJIT.runtime_stats 產生的統計資料可以在建置發佈時使用。
    • 只需要使用 --yjit-stats 執行 ruby 計算統計資料 (會產生一些運行開銷)。
  • YJIT 現在經過最佳化可以使用 Object Shapes。[Feature #18776]
  • 利用更細粒度的常數失效來在定義新常數時減少失效的程式碼。 [Feature #18589]

MJIT

  • MJIT 編譯器在 Ruby 已重新實作為標準函式庫 mjit
  • MJIT 編譯器在 forked Ruby process 下執行,而不是在名為 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 resolver 而不是 Molinillo
    • PubGrub 是 Dart 程式語言的 pub 套件管理器使用的下一代求解演算法。
    • 這個變更可能會導致您得到不同的解析結果。請回報問題至 RubyGems/Bundler issues
  • 在 Ruby 3.2,RubyGems 仍然使用 Molinillo resolver。我們計畫未來用 PubGrub 取代。

自 3.1 以來其他值得注意的變更

  • Hash
    • 當 hash 為空時,Hash#shift 現在總是回傳 nil,取代以往回傳預設值或呼叫預設的 proc。 [Bug #16908]
  • MatchData
  • Module
  • Proc
  • Refinement
  • RubyVM::AbstractSyntaxTree
    • parseparse_fileof 新增 error_tolerant 選項。[[Feature #19013]]
  • Set
    • Set 現在可以直接使用,不再需要先 require "set"。 [Feature #16989] 目前是透過 Set 常數或呼叫 Enumerable#to_set 來自動載入。
  • String
    • 已新增 String#byteindex 和 String#byterindex。 [Feature #13110]
    • 更新 Unicode 至 Version 15.0.0 和 Emoji Version 15.0。 [Feature #18639] (也適用於 Regexp)
    • 已新增 String#bytesplice。 [Feature #18598]
  • Struct
    • Struct.new 不需要傳入 keyword_init: true 也可以透過 keyword 參數初始化。 [Feature #16806]

相容性問題

注意:不包含功能問題的修正。

被移除的常數

下列廢棄的常數已被移除。

被移除的方法

下列廢棄的方法已被移除。

Stdlib 相容性問題

不再綑綁第三方原始碼

  • 我們不再綑綁第三方原始碼像是 libyaml, libffi

    • psych 中的 libyaml 原始碼已經被移除。您可能需要在 Ubuntu/Debian 平台上安裝 libyaml-dev。 每個平台上的套件名稱有所不同。

    • fiddle 中綑綁的 libffi 原始碼也已經移除。

  • Psych 和 fiddle 支援指定 libyaml 和 libffi 原始碼版本來靜態建置。您可以使用 libyaml-0.2.5 建置 psych 像是:

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

    您也能使用 libffi-3.4.4 建置 fiddle,像是:

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

    [Feature #18571]

C API 更新

更新的 C APIs

下列是已更新的 APIs。

  • PRNG 更新 rb_random_interface_t 更新版本。 使用此舊版介面建置的擴展函式庫還需要定義 init_int32 函式。

被移除的 C APIs

下列廢棄的 APIs 已被移除。

  • rb_cData 變數。
  • “taintedness” 和 “trustedness” 函式. [Feature #16131]

標準函式庫更新

  • 更新了以下的預設 gem。
    • RubyGems 3.4.0.dev
    • benchmark 0.2.1
    • bigdecimal 3.1.3
    • bundler 2.4.0.dev
    • cgi 0.3.6
    • date 3.3.0
    • delegate 0.3.0
    • did_you_mean 1.6.2
    • digest 3.1.1
    • drb 2.1.1
    • erb 4.0.2
    • error_highlight 0.5.1
    • etc 1.4.1
    • fcntl 1.0.2
    • fiddle 1.1.1
    • fileutils 1.7.0
    • forwardable 1.3.3
    • getoptlong 0.2.0
    • io-console 0.5.11
    • io-nonblock 0.2.0
    • io-wait 0.3.0.pre
    • ipaddr 1.2.5
    • irb 1.5.1
    • json 2.6.2
    • logger 1.5.2
    • mutex_m 0.1.2
    • net-http 0.3.1
    • net-protocol 0.2.0
    • nkf 0.1.2
    • open-uri 0.3.0
    • openssl 3.1.0.pre
    • optparse 0.3.0
    • ostruct 0.5.5
    • pathname 0.2.1
    • pp 0.4.0
    • pstore 0.1.2
    • psych 5.0.0
    • racc 1.6.1
    • rdoc 6.5.0
    • reline 0.3.1
    • resolv 0.2.2
    • securerandom 0.2.1
    • set 1.0.3
    • stringio 3.0.3
    • syntax_suggest 1.0.1
    • timeout 0.3.1
    • tmpdir 0.1.3
    • tsort 0.1.1
    • un 0.2.1
    • uri 0.12.0
    • win32ole 1.8.9
    • zlib 3.0.0
  • 更新了以下的 bundled gem。
    • minitest 5.16.3
    • power_assert 2.0.2
    • test-unit 3.5.5
    • net-ftp 0.2.0
    • net-imap 0.3.1
    • net-pop 0.1.2
    • net-smtp 0.3.3
    • rbs 2.8.1
    • typeprof 0.21.3
    • debug 1.7.0

參見 NEWScommit logs 來了解更多。

自 Ruby 3.1.0 以來,計 2846 檔案變更, 203950 行新增 (+), 127153 行刪減 (-)

下載

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

    SIZE: 20253652
    SHA1: 9b45af61ef1ae3c21ab88d7c9e30b80060116ac3
    SHA256: 3bb9760c1ac1b66416aaa4899809f6ccd010e57038eaaeca19a383fd56275dac
    SHA512: 798157d785ebae94cb128d3c134fa35e0e90c654972e531cb6562823042f3fb68a270226f7b1cf0c42572ef2b1488a1a3e44f88389ad2a6f9ca4b280a2a8e759
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-rc1.tar.xz

    SIZE: 14934012
    SHA1: 5576e304786d466410f27a345dc1cb66f2c773f6
    SHA256: 0d45b3af14e84337882a2021235a091ae5dcfc0baaf31dccc479b71d96dd07bc
    SHA512: d38fcb1e09eb9984f3b2347e65ae7406129c2578d068a25d33b5b4f021ec3b567a9abe56c2acbec6d07a3c2b4bc7b485dbd330cbfbb3a96350f60a2bb94d016e
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-rc1.zip

    SIZE: 24473024
    SHA1: 8fdc85363ce61e0b8f04da36e709d49028d04a75
    SHA256: 7ff32473be108534548e401aaa9092c37a27f73323ea4091c33901c714c87ee5
    SHA512: 07adf6a9c89fdcf420e7b131f40f2b1f4aca036aa6f28539ade26ca552f84a75e0698f77a8b774d2ea52b8c756c4982ef319bda5afa786c081a31dd9873c5ef7
    

Ruby 是什麼

Ruby 最初由 Matz(Yukihiro Matsumoto)於 1993 年開發的開源軟體。可以在許多平台上執行。使用者來自世界各地,特別活躍於網路開發領域。