Ruby 3.2.0 Preview 3 發布

我們很高興宣佈 Ruby 3.2.0-preview3 發佈了. 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

其他值得注意的新功能

不再綑綁第三方原始碼

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

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

    • 綑綁的 libffi 原始碼也從 fiddle 中被移除

語言功能

  • 除了作為方法參數,匿名不定長度參數現在也可以傳遞為其他方法的參數。 [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

  • 支援 arm64 / aarch64 架構的 UNIX 平台。
  • 建置 YJIT 時需要 Rust 1.58.1+ 。 [Feature #18481]

Other notable changes since 3.1

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

相容性問題

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

被移除的常數

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

被移除的方法

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

Stdlib 相容性問題

  • Psych 不再綑綁 libyaml 原始碼. 使用者需要透過套件管理系統自行安裝 libyaml 函式庫。 [Feature #18571]

C API 更新

Updated C APIs

以下是更新的 APIs。

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

被移除的 C APIs

下列廢棄的 APIs 已被移除。

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

標準函式庫更新

  • SyntaxSuggest

    • 被稱為 dead_endsyntax_suggest 以整合進 Ruby。 [Feature #18159]
  • ErrorHighlight

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

sum = ary[0] + ary[1]
               ^^^^^^
  • 更新了以下預設 gems。
    • RubyGems 3.4.0.dev
    • bigdecimal 3.1.2
    • bundler 2.4.0.dev
    • cgi 0.3.2
    • date 3.2.3
    • error_highlight 0.4.0
    • etc 1.4.0
    • io-console 0.5.11
    • io-nonblock 0.1.1
    • io-wait 0.3.0.pre
    • ipaddr 1.2.4
    • json 2.6.2
    • logger 1.5.1
    • net-http 0.2.2
    • net-protocol 0.1.3
    • ostruct 0.5.5
    • psych 5.0.0.dev
    • reline 0.3.1
    • securerandom 0.2.0
    • set 1.0.3
    • stringio 3.0.3
    • syntax_suggest 0.0.1
    • timeout 0.3.0
  • 更新了以下 bundled gems。
    • minitest 5.16.3
    • net-imap 0.2.3
    • rbs 2.6.0
    • typeprof 0.21.3
    • debug 1.6.2
  • 以下預設 gems 現在成為了 bundled gems。

參見 NEWScommit logs 來了解更多。

自 Ruby 3.1.0 以來,計 2719 檔案變更, 191269 行新增 (+), 120315 行刪減 (-)

下載

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

    SIZE: 20086542
    SHA1: dafca8116d36ceaa32482ab38359768de8c3ae5e
    SHA256: c041d1488e62730d3a10dbe7cf7a3b3e4268dc867ec20ec991e7d16146640487
    SHA512: 860634d95e4b9c48f18d38146dfbdc3c389666d45454248a4ccdfc3a5d3cd0c71c73533aabf359558117de9add1472af228d8eaec989c9336b1a3a6f03f1ae88
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-preview3.tar.xz

    SIZE: 14799804
    SHA1: c94e2add05502cb5c39afffc995b7c8f000f7df0
    SHA256: d3f5619de544240d92a5d03aa289e71bd1103379622c523a0e80ed029a74b3bb
    SHA512: c1864e2e07c3711eaa17d0f85dfbcc6e0682b077782bb1c155315af45139ae66dc4567c73682d326975b0f472111eb0a70f949811cb54bed0b3a816ed6ac34df
    
  • https://cache.ruby-lang.org/pub/ruby/3.2/ruby-3.2.0-preview3.zip

    SIZE: 24426893
    SHA1: 346c051c4be7ab8d0b551fd2ff8169785697db62
    SHA256: cf49aa70e7ebd8abebffd5e49cd3bd92e5b9f3782d587cc7ed88c98dd5f17069
    SHA512: 4f22b5ea91be17ef5f68cf0acb1e3a226dcc549ad71cc9b40e623220087c4065ca9bea942710f668e5c94ca0323da8d2ccd565f95a9085c1a0e38e9c0543b22f
    

Ruby 是什麼

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