[ruby-core:122303] [Ruby Bug#21375] Set[] does not call #initialize

Issue #21375 has been reported by Ethan (Ethan -). ---------------------------------------- Bug #21375: Set[] does not call #initialize https://bugs.ruby-lang.org/issues/21375 * Author: Ethan (Ethan -) * Status: Open * ruby -v: ruby 3.5.0dev (2025-05-26T17:42:35Z master 909a0daab6) +PRISM [x86_64-darwin22] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I have a subclass of Set that overrides #initialize. Following #21216, .new does call #initialize but .[] does not. ```ruby class MySet < Set def initialize(enum = nil) compare_by_identity super end end MySet.new.compare_by_identity? # => true MySet[].compare_by_identity? # => false ``` -- https://bugs.ruby-lang.org/

Issue #21375 has been updated by nobu (Nobuyoshi Nakada). I'm not sure if it is intentional or not. If unintentional, this patch may fix it. ```diff diff --git a/set.c b/set.c index 8676c62cd35..c41781c446f 100644 --- a/set.c +++ b/set.c @@ -409,13 +409,7 @@ static VALUE set_s_create(int argc, VALUE *argv, VALUE klass) { VALUE set = set_alloc_with_size(klass, argc); - set_table *table = RSET_TABLE(set); - int i; - - for (i=0; i < argc; i++) { - set_table_insert_wb(table, set, argv[i], NULL); - } - + rb_obj_call_init(set, argc, argv); return set; } ``` ---------------------------------------- Bug #21375: Set[] does not call #initialize https://bugs.ruby-lang.org/issues/21375#change-113447 * Author: Ethan (Ethan -) * Status: Open * ruby -v: ruby 3.5.0dev (2025-05-26T17:42:35Z master 909a0daab6) +PRISM [x86_64-darwin22] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I have a subclass of Set that overrides #initialize. Following #21216, .new does call #initialize but .[] does not. ```ruby class MySet < Set def initialize(enum = nil) compare_by_identity super end end MySet.new.compare_by_identity? # => true MySet[].compare_by_identity? # => false ``` -- https://bugs.ruby-lang.org/

Issue #21375 has been updated by jeremyevans0 (Jeremy Evans). nobu (Nobuyoshi Nakada) wrote in #note-1:
I'm not sure if it is intentional or not.
It was intentional when I developed core Set. `Set.[]` takes different arguments than `Set#initialize`. `Array.[]` does not call `Array#initialize`, and `Hash.[]` does not call `Hash#initialize`, so there is precedent for the core collection classes to operate this way. `Set.[]` was not documented as calling `Set#initialize` previously, and there were no tests or specs for it. Relying on `Set.[]` calling `Set#initialize` was relying on undefined behavior.
If unintentional, this patch may fix it.
```diff diff --git a/set.c b/set.c index 8676c62cd35..c41781c446f 100644 --- a/set.c +++ b/set.c @@ -409,13 +409,7 @@ static VALUE set_s_create(int argc, VALUE *argv, VALUE klass) { VALUE set = set_alloc_with_size(klass, argc); - set_table *table = RSET_TABLE(set); - int i; - - for (i=0; i < argc; i++) { - set_table_insert_wb(table, set, argv[i], NULL); - } - + rb_obj_call_init(set, argc, argv); return set; }
```
I'm not sure whether this is correct. `#initialize` arguments are enumerables of elements, `.[]` arguments are elements. I think if we wanted to call `#initialize`, we would have to allocate an array for the elements, and pass that, which would reduce performance. ---------------------------------------- Bug #21375: Set[] does not call #initialize https://bugs.ruby-lang.org/issues/21375#change-113453 * Author: Ethan (Ethan -) * Status: Open * ruby -v: ruby 3.5.0dev (2025-05-26T17:42:35Z master 909a0daab6) +PRISM [x86_64-darwin22] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I have a subclass of Set that overrides #initialize. Following #21216, .new does call #initialize but .[] does not. ```ruby class MySet < Set def initialize(enum = nil) compare_by_identity super end end MySet.new.compare_by_identity? # => true MySet[].compare_by_identity? # => false ``` -- https://bugs.ruby-lang.org/

Issue #21375 has been updated by Ethan (Ethan -). It seems like a regression to me. I mean, it broke my code - maybe I'm alone, I can't say whether other people override #initialize and expect Set[] to call it, but it seems like a reasonable assumption to make, particularly since it always has done that. I wasn't aware that Array[]/Hash[] don't call initialize and I find that counterintuitive, and would advocate against changing other classes/constructors to do that.
`Set.[]` was not documented as calling `Set#initialize` previously
My feeling is that the base expectation is that #initialize is called on new objects. I know constructors can allocate and do things without #initialize, but I think that is quite rare. Maybe I'm wrong, I just learned Array[] and Hash[] do that, but I've almost never seen it. I'll also note that the effects of not calling #initialize are easy to miss, since the set will still be a perfectly functional set, but will have lost whatever checks or changes are intended on initialization. At least in my case this was not straightforward to trace back to this change, it took me a while in the debugger to figure out that some of my sets were unexpectedly no longer compare_by_identity as my #initialize sets. Having figured that out it is of course trivial for me to override MySet.[], but I'd rather Set behave like it used to, especially if that would save other people having to debug subtle bugs and fix. ---------------------------------------- Bug #21375: Set[] does not call #initialize https://bugs.ruby-lang.org/issues/21375#change-113460 * Author: Ethan (Ethan -) * Status: Open * ruby -v: ruby 3.5.0dev (2025-05-26T17:42:35Z master 909a0daab6) +PRISM [x86_64-darwin22] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I have a subclass of Set that overrides #initialize. Following #21216, .new does call #initialize but .[] does not. ```ruby class MySet < Set def initialize(enum = nil) compare_by_identity super end end MySet.new.compare_by_identity? # => true MySet[].compare_by_identity? # => false ``` -- https://bugs.ruby-lang.org/

Issue #21375 has been updated by Eregon (Benoit Daloze). IMO this is unnecessarily breaking compatibility. If we don't want to support subclasses of Set properly as it used to work (and calling `initialize` is I think a totally reasonable assumption, especially for any library which used to be written in Ruby), then I think we should forbid subclassing Set entirely to make it clear. ---------------------------------------- Bug #21375: Set[] does not call #initialize https://bugs.ruby-lang.org/issues/21375#change-113509 * Author: Ethan (Ethan -) * Status: Open * ruby -v: ruby 3.5.0dev (2025-05-26T17:42:35Z master 909a0daab6) +PRISM [x86_64-darwin22] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I have a subclass of Set that overrides #initialize. Following #21216, .new does call #initialize but .[] does not. ```ruby class MySet < Set def initialize(enum = nil) compare_by_identity super end end MySet.new.compare_by_identity? # => true MySet[].compare_by_identity? # => false ``` -- https://bugs.ruby-lang.org/

Issue #21375 has been updated by Dan0042 (Daniel DeLorme). Ethan (Ethan -) wrote in #note-3:
My feeling is that the base expectation is that #initialize is called on new objects.
I agree. `Array[*args]` is equivalent to `Array.new.concat(*args)` and in that case there was no need to call #initialize because it's a no-op when there are no arguments. But the side-effect of that optimization is that `MyArray[*args]` does not call #initialize when it should, and I consider that a bug. It would be better to fix that bug in `Array` rather than introduce it in `Set` just to keep things "consistent". ---------------------------------------- Bug #21375: Set[] does not call #initialize https://bugs.ruby-lang.org/issues/21375#change-113794 * Author: Ethan (Ethan -) * Status: Open * ruby -v: ruby 3.5.0dev (2025-05-26T17:42:35Z master 909a0daab6) +PRISM [x86_64-darwin22] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I have a subclass of Set that overrides #initialize. Following #21216, .new does call #initialize but .[] does not. ```ruby class MySet < Set def initialize(enum = nil) compare_by_identity super end end MySet.new.compare_by_identity? # => true MySet[].compare_by_identity? # => false ``` -- https://bugs.ruby-lang.org/

Issue #21375 has been updated by knu (Akinori MUSHA). I'm leaning toward making Set subclass-friendly again. That's how I've always wanted Set to be (unlike Array and Hash), and the feedback shows that there are real users and use cases that share and rely on this concept, so we shouldn't easily compromise that core value. https://bugs.ruby-lang.org/issues/21396#note-8 ---------------------------------------- Bug #21375: Set[] does not call #initialize https://bugs.ruby-lang.org/issues/21375#change-113962 * Author: Ethan (Ethan -) * Status: Open * ruby -v: ruby 3.5.0dev (2025-05-26T17:42:35Z master 909a0daab6) +PRISM [x86_64-darwin22] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- I have a subclass of Set that overrides #initialize. Following #21216, .new does call #initialize but .[] does not. ```ruby class MySet < Set def initialize(enum = nil) compare_by_identity super end end MySet.new.compare_by_identity? # => true MySet[].compare_by_identity? # => false ``` -- https://bugs.ruby-lang.org/
participants (6)
-
Dan0042 (Daniel DeLorme)
-
Eregon (Benoit Daloze)
-
Ethan (Ethan -)
-
jeremyevans0 (Jeremy Evans)
-
knu (Akinori MUSHA)
-
nobu (Nobuyoshi Nakada)