Project

General

Profile

Feature #16806

Struct#initialize accepts keyword arguments too by default

Added by k0kubun (Takashi Kokubun) 3 months ago. Updated 3 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:98014]

Description

Proposal

Post = Struct.new(:id, :name)

# In addition to this,
Post.new(1, "hello") #=> #<struct Post id=1, name="hello">

# Let the following initialization also work
Post.new(id: 1, name: "hello") #=> #<struct Post id=1, name="hello">

Known incompatibility

  • Post.new(id: 1, name: "hello") will be #<struct Post id=1, name="hello"> instead of #<struct Post id={:id=>1, :name=>"hello"}, name=nil>
    • Struct initialization only using keyword arguments should be warned in Ruby 3.0. This feature should be introduced in Ruby 3.1 or later.

Edge cases

  • When keyword arguments and positional arguments are mixed: Post.new(1, name: "hello")
    • This should continue to work like Ruby 2: #<struct Post id=1, name={:name="hello"}>
  • Only keywords are given but they include an invalid member: Post.new(foo: "bar")
    • ArgumentError (unknown keywords: foo)
  • When keyword_init is used
    • nil: default behavior. Positional arguments given use positional init. Keyword arguments without positional arguments treated as positional in 3.0 with warning, and treated as keyword init in Ruby 3.1.
    • true: Require keyword init, disallow positional init.
    • false: Treat keywords as positional hash.

Use cases


Related issues

Related to Ruby master - Feature #11925: Struct construction with kwargsClosedActions
#1

Updated by k0kubun (Takashi Kokubun) 3 months ago

#2

Updated by k0kubun (Takashi Kokubun) 3 months ago

  • Description updated (diff)

Updated by jeremyevans0 (Jeremy Evans) 3 months ago

I'm OK with the basic idea of allowing keyword init for Structs by default. However, because this changes behavior, I think we should keep existing behavior and warn in Ruby 3.0 and not make this change until Ruby 3.1. I think this change should only affect cases where keywords are passed without positional arguments during Struct initialization.

For Post.new(foo: "bar"), this should still be treated as keyword init, even though foo is not a member of Post. In Ruby 3.0, that would warn and use {:foo=>"bar"} as the first member of Post. In Ruby 3.1, that would raise ArgumentError, since foo is not a member of Post and the keywords will be treated as named members.

For Post.new(1, foo: "bar"), Post.new(1, name: "hello"), Post.new(1, id: 1): I think these should be treated the same as Ruby 2. Any non-keyword argument should treat keywords as a positional hash argument. I think allowing mixing of the arguments would result in confusion, brings up additional edge cases, and there is no practical use case for it.

Do we want to support Post = Struct.new(:id, :name, keyword_init: false) to disallow keyword initialization and always treat keywords as a positional hash? I think we should as that is the intent of the code. Basically, keyword_init would allow 3 values:

  • nil: default behavior. Positional arguments given use positional init. Keyword arguments without positional arguments treated as positional in 3.0 with warning, and treated as keyword init in Ruby 3.1.
  • true: Require keyword init, disallow positional init.
  • false: Treat keywords as positional hash.

Updated by k0kubun (Takashi Kokubun) 3 months ago

  • Description updated (diff)

I agreed with your ideas and reflected them to the description.

Also available in: Atom PDF