[ruby-core:122900] [Ruby Bug#21529] Deprecate the /o modifier and warn against using it

Issue #21529 has been reported by jpcamara (JP Camara). ---------------------------------------- Bug #21529: Deprecate the /o modifier and warn against using it https://bugs.ruby-lang.org/issues/21529 * Author: jpcamara (JP Camara) * Status: Open * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I recently ran into a bug in some code because it was using the /o modifier as an optimization, not realizing it created a permanent, immutable value after the first time it gets evaluated. I dug into how the modifier works in CRuby and the history of it here: https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html. The feature seems like a total footgun with almost no upside. If I run a benchmark between a local regex, and a regex cached by /o, there is no real difference. ```rb require "benchmark" def letters "A-Za-z" end words = %w[the quick brown fox jumped over the lazy dog] Benchmark.bm do |bm| bm.report("without /o:") do regex = /\A[A-Za-z]+\z/ words.each do |word| word.match(regex) end end bm.report("with /o: ") do words.each do |word| word.match(/\A[#{letters}]+\z/o) end end end ``` Most of the time I found that "without /o" actually came out ahead. ``` user system total real without /o: 0.000019 0.000003 0.000022 ( 0.000014) with /o: 0.000020 0.000001 0.000021 ( 0.000020) ``` I'd like to deprecate the feature and update the docs to warn against using it. I'd be happy to submit a PR doing that. Thanks! -- https://bugs.ruby-lang.org/

Issue #21529 has been updated by jpcamara (JP Camara). Byroot brought to my attention that my example doesn’t make a lot of sense because it doesn’t interpolate anything. I’m hard pressed to find an example to compare with dynamic interpolation, since I think that the core issue with /o is that dynamic interpolation doesn’t work the way anyone would ever expect. But here’s an example that precompiles a regex, which I think is the only comparison that is apples to apples, at its core (no pun intended) ```ruby require "benchmark" def letters "A-Za-z" end words = %w[the quick brown fox jumped over the lazy dog] PRECOMPILED = /\A[#{letters}]+\z/.freeze Benchmark.bm do |bm| bm.report("without /o:") do words.each do |word| word.match(PRECOMPILED) end end bm.report("with /o: ") do words.each do |word| word.match(/\A[#{letters}]+\z/o) end end end ``` The performance is the same again, but at least the example is slightly more relevant. ---------------------------------------- Bug #21529: Deprecate the /o modifier and warn against using it https://bugs.ruby-lang.org/issues/21529#change-114204 * Author: jpcamara (JP Camara) * Status: Open * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I recently ran into a bug in some code because it was using the /o modifier as an optimization, not realizing it created a permanent, immutable value after the first time it gets evaluated. I dug into how the modifier works in CRuby and the history of it here: https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html. The feature seems like a total footgun with almost no upside. If I run a benchmark between a local regex, and a regex cached by /o, there is no real difference. ```rb require "benchmark" def letters "A-Za-z" end words = %w[the quick brown fox jumped over the lazy dog] Benchmark.bm do |bm| bm.report("without /o:") do regex = /\A[A-Za-z]+\z/ words.each do |word| word.match(regex) end end bm.report("with /o: ") do words.each do |word| word.match(/\A[#{letters}]+\z/o) end end end ``` Most of the time I found that "without /o" actually came out ahead. ``` user system total real without /o: 0.000019 0.000003 0.000022 ( 0.000014) with /o: 0.000020 0.000001 0.000021 ( 0.000020) ``` I'd like to deprecate the feature and update the docs to warn against using it. I'd be happy to submit a PR doing that. Thanks! -- https://bugs.ruby-lang.org/

Issue #21529 has been updated by byroot (Jean Boussier). My point was that `/o` is only "optimized" compared to the same interpolation but uncached, so: ```ruby require "benchmark" def letters "A-Za-z" end words = %w[the quick brown fox jumped over the lazy dog] Benchmark.bm do |bm| bm.report("without /o:") do words.each do |word| word.match(/\A[#{letters}]+\z/) end end bm.report("with /o: ") do words.each do |word| word.match(/\A[#{letters}]+\z/o) end end end ``` But yes, in the overwhelming majority of cases, you are much better to explicitly only performance the interpolation once and store the resulting regexp in a constant. `/o` is definitely a footgun, but also a very rare construct. In my opinion improving the documentation is more than welcome, but I'm not convinced deprecating is worth it, as I assume the problems come from people seeing it in the docs and misunderstanding the docs. ---------------------------------------- Bug #21529: Deprecate the /o modifier and warn against using it https://bugs.ruby-lang.org/issues/21529#change-114205 * Author: jpcamara (JP Camara) * Status: Open * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I recently ran into a bug in some code because it was using the /o modifier as an optimization, not realizing it created a permanent, immutable value after the first time it gets evaluated. I dug into how the modifier works in CRuby and the history of it here: https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html. The feature seems like a total footgun with almost no upside. If I run a benchmark between a local regex, and a regex cached by /o, there is no real difference. ```rb require "benchmark" def letters "A-Za-z" end words = %w[the quick brown fox jumped over the lazy dog] Benchmark.bm do |bm| bm.report("without /o:") do regex = /\A[A-Za-z]+\z/ words.each do |word| word.match(regex) end end bm.report("with /o: ") do words.each do |word| word.match(/\A[#{letters}]+\z/o) end end end ``` Most of the time I found that "without /o" actually came out ahead. ``` user system total real without /o: 0.000019 0.000003 0.000022 ( 0.000014) with /o: 0.000020 0.000001 0.000021 ( 0.000020) ``` I'd like to deprecate the feature and update the docs to warn against using it. I'd be happy to submit a PR doing that. Thanks! -- https://bugs.ruby-lang.org/

Issue #21529 has been updated by jpcamara (JP Camara). Yea I hear ya. So should I just submit a PR with my suggestions for the docs and close this? ---------------------------------------- Bug #21529: Deprecate the /o modifier and warn against using it https://bugs.ruby-lang.org/issues/21529#change-114206 * Author: jpcamara (JP Camara) * Status: Open * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I recently ran into a bug in some code because it was using the /o modifier as an optimization, not realizing it created a permanent, immutable value after the first time it gets evaluated. I dug into how the modifier works in CRuby and the history of it here: https://jpcamara.com/2025/08/02/the-o-in-ruby-regex.html. The feature seems like a total footgun with almost no upside. If I run a benchmark between a local regex, and a regex cached by /o, there is no real difference. ```rb require "benchmark" def letters "A-Za-z" end words = %w[the quick brown fox jumped over the lazy dog] Benchmark.bm do |bm| bm.report("without /o:") do regex = /\A[A-Za-z]+\z/ words.each do |word| word.match(regex) end end bm.report("with /o: ") do words.each do |word| word.match(/\A[#{letters}]+\z/o) end end end ``` Most of the time I found that "without /o" actually came out ahead. ``` user system total real without /o: 0.000019 0.000003 0.000022 ( 0.000014) with /o: 0.000020 0.000001 0.000021 ( 0.000020) ``` I'd like to deprecate the feature and update the docs to warn against using it. I'd be happy to submit a PR doing that. Thanks! -- https://bugs.ruby-lang.org/
participants (2)
-
byroot (Jean Boussier)
-
jpcamara (JP Camara)