xg15's comment still makes sense if you replace "copy all ..." with "slide the remaining elements down by the number of removed elements" to match the mutative behavior.
However, a bigger problem is concurrent modification: What happens if your reject! predicate has a side effect which modifies the array? Ideally you'd receive a "ConcurrentModificationException" or the like.
No, before sliding elements, you set a local variable "dirty" to true. You iterate over all elements and keep the ones that are not rejected by moving them at the end of the current max index. When you code finishes normally, dirty is set to false.
You have an "ensure" block (finally) protecting the code which handles the cases where dirty is true (moving all the remaining elements after the current max). Then you truncate the array.
iirc, Ruby's array doesn't expose the distinction between length and capacity, but internally it may decide to amortize the cost of such copies. For example, if reject only removes a small % of elements, it may choose not to shrink capacity.
In a multithreaded context, even that isn't perfect; another thread can observe the array in an inconsistent state while you're mutating it. I wonder what Ruby does here?
If you're in a multithreaded context, then you need to handle locking of shared mutable memory; Ruby doesn't fix that for you (but it does make it pretty easy with stdlib Mutex and block syntax). MRI happens to make many basic operations thread-safe because of the GIL, but you shouldn't get into the habit of relying on that naively because it will stop working as you expect as soon as you start thinking of complex operations like reject! as 'just another atomic operation'.
Here's an example of how you'd manage the contention yourself:
my_array = [1, 2, 3]
my_array_lock = Mutex.new
Thread.new do
my_array_lock.synchronize do
my_array.reject! { |x| x % 2 == 0 }
end
end
Thread.new do
my_array_lock.synchronize do
my_array.push(4)
end
end
Thread.new do
my_array_lock.synchronize do
# Because we've serialised our operations on the array, there's no chance
# that we read the elements in an incorrect order
my_array.each do |x|
puts x
end
end
end
Either implementation will be a poor choice for some use cases (hence the bug reports about both).