-
-
Notifications
You must be signed in to change notification settings - Fork 5.7k
Description
Implementing Base.resize! used to be sufficient for push! and append! to work on Julia 1.10 and prior.
Consider the following Vector wrapper.
struct MyVector <: AbstractVector{Int}
v::Vector{Int}
function MyVector(args...)
new(Vector{Int}(args...))
end
end
Base.size(mv::MyVector) = size(mv.v)
Base.getindex(v::MyVector, i::Int) = getindex(v.v, i)
Base.setindex!(mv::MyVector, v, i::Int) = mv.v[i] = vAttempting to use push! would suggest the implementation of resize!:
julia> v = MyVector(undef, 5)
5-element MyVector:
0
0
0
0
0
julia> push!(v, 1)
ERROR: MethodError: no method matching resize!(::MyVector, ::Int64)
Closest candidates are:
resize!(::BitVector, ::Integer)
@ Base bitarray.jl:814
resize!(::Vector, ::Integer)
@ Base array.jl:1312
Stacktrace:
[1] _append!(a::MyVector, ::Base.HasLength, iter::Tuple{Int64})
@ Base ./array.jl:1196
[2] append!(a::MyVector, iter::Tuple{Int64})
@ Base ./array.jl:1187
[3] push!(a::MyVector, iter::Int64)
@ Base ./array.jl:1188
[4] top-level scope
@ REPL[6]:1After implmenting resize! as follows,
Base.resize!(mv::MyVector, nl::Integer) = resize!(mv.v, nl)
this allows push! to work on Julia 1.10.
julia> v = MyVector(undef, 5)
5-element MyVector:
3
124051546992229
160
6
45253729152848
julia> push!(v, 6)
6-element MyVector:
3
124051546992229
160
6
45253729152848
6
julia> append!(v, [1,2,3])
9-element MyVector:
3
124051546992229
160
6
45253729152848
6
1
2
3On Julia 1.11-rc2, this no longer works:
julia> v = MyVector(undef, 5)
5-element MyVector:
15
9223372036854775807
16
0
-1
julia> push!(v, 6)
ERROR: MethodError: no method matching sizehint!(::MyVector, ::Int64)
The function `sizehint!` exists, but no method is defined for this combination of argument types.
Closest candidates are:
sizehint!(::BitSet, ::Integer; first, shrink)
@ Base bitset.jl:58
sizehint!(::BitVector, ::Integer)
@ Base bitarray.jl:809
sizehint!(::Dict{T}, ::Any; shrink) where T
@ Base dict.jl:193
...
Stacktrace:
[1] _append!(a::MyVector, ::Base.HasLength, iter::Tuple{Int64})
@ Base ./array.jl:1320
[2] append!(a::MyVector, iter::Tuple{Int64})
@ Base ./array.jl:1313
[3] push!(a::MyVector, iter::Int64)
@ Base ./array.jl:1314
[4] top-level scope
@ REPL[15]:1Following the suggestion, an implementation of sizehint! leads to StackOverflow error on Julia 1.11:
julia> Base.sizehint!(mv::MyVector, nl::Integer) = sizehint!(mv.v, nl)
julia> push!(v, 6)
ERROR: StackOverflowError:
Stacktrace:
[1] sizehint!(a::Vector{Int64}, sz::Int64; first::Bool, shrink::Bool)
@ Base ./array.jl:1480
[2] sizehint!
@ ./array.jl:1480 [inlined]
[3] sizehint!
@ ./REPL[16]:1 [inlined]
[4] sizehint!
@ ./array.jl:1519 [inlined]
[5] _append!
@ ./array.jl:1320 [inlined]
[6] append!
@ ./array.jl:1313 [inlined]
[7] push!(a::MyVector, iter::Int64)
@ Base ./array.jl:1314
[8] _append!
@ ./array.jl:1322 [inlined]
--- the above 3 lines are repeated 79981 more times ---
[239952] append!
@ ./array.jl:1313 [inlined]
julia> append!(v, [1,2,3])
ERROR: StackOverflowError:
Stacktrace:
[1] sizehint!(a::Vector{Int64}, sz::Int64; first::Bool, shrink::Bool)
@ Base ./array.jl:1480
[2] sizehint!
@ ./array.jl:1480 [inlined]
[3] sizehint!
@ ./REPL[16]:1 [inlined]
[4] sizehint!
@ ./array.jl:1519 [inlined]
[5] _append!
@ ./array.jl:1320 [inlined]
[6] append!
@ ./array.jl:1313 [inlined]
[7] push!(a::MyVector, iter::Int64)
@ Base ./array.jl:1314
[8] _append!
@ ./array.jl:1322 [inlined]
--- the above 3 lines are repeated 79981 more times ---
[239952] append!
@ ./array.jl:1313 [inlined]As of #51903, append!(a::AbstractVector, iter) depends on push!(a::AbstractVector, iter...). This is problematic because push!(a::AbstractVector, iter...) depends on append!(a::AbstractVector, iter) which leads to the above stack overflow condition.
Lines 1305 to 1329 in 786caaa
| append!(a::AbstractVector, iter) = _append!(a, IteratorSize(iter), iter) | |
| push!(a::AbstractVector, iter...) = append!(a, iter) | |
| append!(a::AbstractVector, iter...) = foldl(append!, iter, init=a) | |
| function _append!(a::AbstractVector, ::Union{HasLength,HasShape}, iter) | |
| @_terminates_locally_meta | |
| n = length(a) | |
| i = lastindex(a) | |
| resize!(a, n+Int(length(iter))::Int) | |
| for (i, item) in zip(i+1:lastindex(a), iter) | |
| if isa(a, Vector) # give better effects for builtin vectors | |
| @_safeindex a[i] = item | |
| else | |
| a[i] = item | |
| end | |
| end | |
| a | |
| end | |
| function _append!(a::AbstractVector, ::IteratorSize, iter) | |
| for item in iter | |
| push!(a, item) | |
| end | |
| a | |
| end |
The new implementation of append! suggests that we should modify the generic implementation of push! for AbstractVector:
import Base: push!
function push!(mv::AbstractVector, v)
new_length = length(mv) + 1
resize!(mv, new_length)
mv[new_length] = v
return mv
end
push!(a::AbstractVector, iter...) = append!(a, iter)Importantly, this new implementation of push! now only depends on the AbstractArray interface and an implementation of resize!.