Apologies in advance, this is going to be a long message. tldr: use the
warning gem and
Warning.process('', keyword_separation: :raise) to help debug keyword separation issues in Ruby 2.7.
I’m the main implementor of the ruby 2.7/3.0 keyword argument separation changes. My reading of this thread is that most people who posted appreciate the changes and would prefer if we do not make changes to ruby 2.7 or change the plan for ruby 3.0. This includes maintainers of very large applications, such as byroot (Shopify). I agree with them and recommend that we do not make changes to ruby 2.7 or change the plan for ruby 3.0.
As @matz is considered making things more backwards compatible in ruby 3.0, I created a patch that I think will restore most of the compatibility (https://bugs.ruby-lang.org/issues/16891), with the downside that most of the problems caused by positional hash to keyword argument converstion remain. I am not in favor of using the patch, but if we are to restore compatibility somewhat, I think it is the best way to do so. Some users who replied to this thread appeared to express favor of restoring some compatibility, such as Benoit_Daloze (though I don’t know his feelings regarding the patch).
Most of the difficulties people have with fixing keyword argument separation issues in 2.7 would be made easier by printing backtraces with warnings or raising errors instead of warnings in Ruby 2.7. This allows you to figure out places where keyword argument separation should be used (e.g. where
** should be added), when the warning message raised by Ruby is incorrect due to the use of delegation. The
warning gem now makes this easy. Based on @mame’s ideas, you can do the following with the
# To turn keyword argument separation warnings into RuntimeErrors
Warning.process('', keyword_separation: :raise)
# To print backtraces after keyword argument separation warnings
Warning.process('', keyword_separation: :backtrace)
# To turn keyword argument separation warnings into RuntimeErrors,
# but only for code in the application
Warning.process('/path/to/app', keyword_separation: :raise)
This should allow you to more easily determine the cause of the keyword argument separation issue, and once you determine the cause, it is generally simple to fix the issue. I think it’s easiest to use
keyword_separation: :raise, run the tests, and fix all failures, then repeat until there are no more failures. This is basically the same advantage as testing on the Ruby master branch, in terms of fixing keyword argument separation issues.
In regards to handling delegation in Ruby 2.7, if you do not need to be compatible with older versions of Ruby, just switch to using
*args, **kwargs in your delegation methods. If you want to be compatible with older versions of Ruby, or
*args, **kwargs causes a performance bottleneck, continue to use
*args for delegation and mark the methods with
There are a couple concerns raised about the use of
- It is ugly
- It will be going away at some point.
I don’t think item 1 is much of a concern, though I did prefer the original name I gave it (
pass_keywords). I like beautiful Ruby code as much as anyone, but sometimes compatibility requires code somewhat lacking in beauty. Almost all of my libraries support Ruby 1.9 to Ruby 2.7, so I am very used to dealing with compatibility issues.
ruby2_keywords was specifically designed with the goal to make the same code work in older Ruby versions, Ruby 2.7, and Ruby 3.x with the minimum of changes. It requires no changes to method internals and no duplicated method definitions. As far as I know, nobody has brought up technical issues with using
ruby2_keywords for handling delegation. JonRowe mentioned he would like
ruby2_keywords to be more permissive, but did not specify why the current
ruby2_keywords does not work for RSpec. I know Benoit_Daloze is against it as requires a minor performance decrease in some cases, but I think that is far outweighed by the performance increase in delegation cases.
In regards to 2, there is nothing requiring we (ruby-core) remove
ruby2_keywords. We will definitely be keeping it at least until Ruby 2.6 goes out of support. Beyond that, it is up to @matz.
ruby2_keywords has significant performance advantages over
*args, **kwargs delegation in CRuby and I personally believe
ruby2_keywords should be kept until there is an alternative to
ruby2_keywords that is at least as efficient in CRuby. Again, how long we keep it is up to @matz, there is not a determined time where we are planning to remove it.
Some other concerns raised in this thread:
doudou42 was against allowing non-Symbol keyword arguments and was relying on keyword argument splitting. The splitting was deprecated in 2.7 and removed in master and is something @matz never intended to support. Even if we allow positional hash to keyword argument conversion, I do not think we should continue to support splitting of hashes. Users should use separate arguments in such cases.
samsaffron was mostly concerned about the performance issues with delegation. These can be fixed by using
ruby2_keywords. He is hesitant to use
ruby2_keywords as he feels it will need to be removed later. As I mentioned, there is no need to remove it as long as people still find it useful (either for performance or compatibility). I have a lot of respect for samsaffron, but I disagree with him that we need support for an
Arguments-like object in Ruby.
ruby2_keywords is enough now, and hopefully by the time it isn’t enough, there will be a replacement that is at least as fast.
samsaffron also was concerned that we changed some deprecation warnings from only being issued in verbose mode to always being issued. It is my opinion that verbose mode deprecations are only useful as an early warning, to be followed by warning always before raising an exception or changing behavior.I do think it is problematic to have deprecation warnings only issued in verbose mode for many years, but only because it is an indication that the deprecation and removal should have happened much sooner.
matthewd was concerned in general about warnings being issued to users of command line Ruby applications. You can set
$VERBOSE = nil to silence all warnings, and that is probably a wise idea for all command line Ruby applications where you cannot control which Ruby version is used to run them.