Project

General

Profile

Actions

Bug #22021

closed

Array#delete_if may delete wrong object if array has been altered already

Bug #22021: Array#delete_if may delete wrong object if array has been altered already

Added by chucke (Tiago Cardoso) about 1 month ago. Updated about 1 month ago.

Status:
Rejected
Assignee:
-
Target version:
-
[ruby-core:125397]

Description

The simplest example I can come up with:

$ar = ar = [1, 2, 3, 4, 5]

def del(i)
  $ar.delete(i)
end

ar.delete_if { |e| e == 2 ? (del(e) && true) : false }

p ar #=> [1, 4, 5], and it should be [1, 3, 4, 5]

Updated by Eregon (Benoit Daloze) about 1 month ago Actions #1

  • Description updated (diff)

Updated by Eregon (Benoit Daloze) about 1 month ago ยท Edited Actions #2 [ruby-core:125405]

IMO it's not reasonable for Array#delete_if to do anything else than what it already does.
How can Array#delete_if know which element you deleted and whether it should delete or not?
The block returns true for the second 2nd block call, so it should delete the 2nd element after that block call (unless the Array#size < index when it's noop then).

Updated by Eregon (Benoit Daloze) about 1 month ago Actions #3 [ruby-core:125406]

  • Status changed from Open to Rejected

I think this is safe to reject, I think it's not feasible to change this.

One should avoid mutating the Array while iterating, otherwise this kind of behavior is expected.

Updated by chucke (Tiago Cardoso) about 1 month ago Actions #4 [ruby-core:125409]

I get the point of avoiding mutating while iterating, but the point of delete_if is to mutate the array. there's nothing in its name hinting at it being based on the index, that's what #delete_at is for, so that's an implementation detail. I didn't check the implementation yet, so I may be oversimplifying it, but I don't see why the element ref can't be kept for post-comparison (although, if based on the #each impl, which is known to skip elements on mutation, I can understand your judgement).

Updated by Eregon (Benoit Daloze) about 1 month ago Actions #5 [ruby-core:125415]

The only way to iterate an Array is to use an index from 0 to size, so it's always going to be based on index.

chucke (Tiago Cardoso) wrote in #note-4:

I don't see why the element ref can't be kept for post-comparison

What if you have [1, 2, 2, 2, 2, 3, 4, 5, 2], how would delete_if find out if one of the 2 was removed concurrently?
There could also be another Thread writing to the Array, so checking if the element is the same before yielding to the block and after is inherently brittle and unreliable.

Actions

Also available in: PDF Atom