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.
require"benchmark"defletters"A-Za-z"endwords=%w[the quick brown fox jumped over the lazy dog]Benchmark.bmdo|bm|bm.report("without /o:")doregex=/\A[A-Za-z]+\z/words.eachdo|word|word.match(regex)endendbm.report("with /o: ")dowords.eachdo|word|word.match(/\A[#{letters}]+\z/o)endendend
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.
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)
require"benchmark"defletters"A-Za-z"endwords=%w[the quick brown fox jumped over the lazy dog]PRECOMPILED=/\A[#{letters}]+\z/.freezeBenchmark.bmdo|bm|bm.report("without /o:")dowords.eachdo|word|word.match(PRECOMPILED)endendbm.report("with /o: ")dowords.eachdo|word|word.match(/\A[#{letters}]+\z/o)endendend
The performance is the same again, but at least the example is slightly more relevant.
My point was that /o is only "optimized" compared to the same interpolation but uncached, so:
require"benchmark"defletters"A-Za-z"endwords=%w[the quick brown fox jumped over the lazy dog]Benchmark.bmdo|bm|bm.report("without /o:")dowords.eachdo|word|word.match(/\A[#{letters}]+\z/)endendbm.report("with /o: ")dowords.eachdo|word|word.match(/\A[#{letters}]+\z/o)endendend
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.
Sorry, /o is a traditional option. Even though Perl is not popular anymore, we are not going to deprecate it. If you (or someone) is going to update the document for modern audience, I'd be very happy.