There are some very good solutions above in this thread. I personally would just use the Union{Stuff,Nothing}
and initialize the array with nothing
. Just pretend that nothing
is undef
.
s = Vector{Union{Stuff,Nothing}}(nothing, B)
Now Iâm going to actually answer your question about âhow to undef a vector elementâ. Let me preface this by saying that this is a very, very bad idea. Any code that uses the below deserves a fiery death, and I am not liable for your computer exploding.
OK, now that youâve been warned. Letâs start with just creating a small array and populating it, leaving one element one defined so we can inspect it later.
julia> s = Vector{Stuff}(undef, 4)
4-element Vector{Stuff}:
#undef
#undef
#undef
#undef
julia> s[1] = Stuff(1, [], [])
Stuff(1, Float64[], String[])
julia> s[2] = Stuff(2, [3.0], ["a"])
Stuff(2, [3.0], ["a"])
julia> s[3] = Stuff(3, [4.0, 5.0], ["a", "b"])
Stuff(3, [4.0, 5.0], ["a", "b"])
julia> s
4-element Vector{Stuff}:
Stuff(1, Float64[], String[])
Stuff(2, [3.0], ["a"])
Stuff(3, [4.0, 5.0], ["a", "b"])
#undef
The first part of this is to understand what is the memory layout behind the Vector
s
. We can ask Julia for a pointer, and it will happily oblige. We can try to load information from the pointer.
julia> pointer(s)
Ptr{Stuff} @0x00007f4b688bfce0
julia> unsafe_load(ptr)
Stuff(1, Float64[], String[])
julia> unsafe_load(ptr, 2)
Stuff(2, [3.0], ["a"])
julia> unsafe_load(ptr, 3)
Stuff(3, [4.0, 5.0], ["a", "b"])
Since we can recover the information from the pointer, we have shown that the pointer is sufficient to retrieve information about objects in the array. I did not load index 4 from the pointer. If you do, you will likely find that Julia will crash. Thatâs why the function is called unsafe_load
. This is the first evidence that we are in dangerous territory. Turn back now!
So you are still reading? Fine. Weâll continue. First, letâs examine the pointer values by seeing how far they are apart.
julia> pointer(s, 2) - pointer(s, 1)
0x0000000000000018
julia> pointer(s, 3) - pointer(s, 2)
0x0000000000000018
julia> pointer(s, 4) - pointer(s, 3)
0x0000000000000018
julia> pointer(s, 4) - pointer(s, 3) |> Int
24
We see that the pointers are spaced 24 bytes apart. Where does that come from? Letâs take a closer look at Stuff
.
julia> sizeof(Stuff)
24
julia> fieldoffset(Stuff, 1)
0x0000000000000000
julia> fieldoffset(Stuff, 2)
0x0000000000000008
julia> fieldoffset(Stuff, 3)
0x0000000000000010
We see that the pointer spacing matches the sizeof(Stuff)
and the fields are each offset by 8 bytes. Iâm going to wildly speculate that what is being stored is an Int64
followed by two pointers.
julia> const RawStuff = Tuple{Int, Ptr{Nothing}, Ptr{Nothing}}
Tuple{Int64, Ptr{Nothing}, Ptr{Nothing}}
julia> sizeof(RawStuff)
24
Now I will cast the pointer and try to load RawStuff
.
julia> ptr = Ptr{RawStuff}(ptr)
Ptr{Tuple{Int64, Ptr{Nothing}, Ptr{Nothing}}} @0x00007f4b688bfce0
julia> unsafe_load(ptr, 1)
(1, Ptr{Nothing} @0x00007f4b69541590, Ptr{Nothing} @0x00007f4b695963e0)
julia> unsafe_load(ptr, 2)
(2, Ptr{Nothing} @0x00007f4b6a2a4d10, Ptr{Nothing} @0x00007f4b6a2a4d50)
julia> unsafe_load(ptr, 3)
(3, Ptr{Nothing} @0x00007f4b6a5109c0, Ptr{Nothing} @0x00007f4b6a2b76d0)
julia> unsafe_load(ptr, 4)
(0, Ptr{Nothing} @0x0000000000000000, Ptr{Nothing} @0x0000000000000000)
julia> C_NULL
Ptr{Nothing} @0x0000000000000000
Iâve loaded the forbidden 4th element! Itâs a Int64(0)
followed by two null pointers (e.g. C_NULL
). Now we know what undef
actually looks like for Stuff
.
Letâs try to clear out the memory now, first using the conventional method to let the garbage collector properly know it can reclaim memory.
julia> const emptystuff = Stuff(0, [], [])
Stuff(0, Float64[], String[])
julia> s[1] = emptystuff
Stuff(0, Float64[], String[])
julia> s[2] = emptystuff
Stuff(0, Float64[], String[])
julia> s[3] = emptystuff
Stuff(0, Float64[], String[])
julia> s
4-element Vector{Stuff}:
Stuff(0, Float64[], String[])
Stuff(0, Float64[], String[])
Stuff(0, Float64[], String[])
#undef
julia> unsafe_load(ptr, 1)
(0, Ptr{Nothing} @0x00007f4b6981ce10, Ptr{Nothing} @0x00007f4b69511810)
julia> unsafe_load(ptr, 2)
(0, Ptr{Nothing} @0x00007f4b6981ce10, Ptr{Nothing} @0x00007f4b69511810)
julia> unsafe_load(ptr, 3)
(0, Ptr{Nothing} @0x00007f4b6981ce10, Ptr{Nothing} @0x00007f4b69511810)
julia> unsafe_load(ptr, 4)
(0, Ptr{Nothing} @0x0000000000000000, Ptr{Nothing} @0x0000000000000000)
We see that the first three entries are all identical, but they are distinct from the 4th element that represents undef
.
julia> const undefstuff = (0, C_NULL, C_NULL)
(0, Ptr{Nothing} @0x0000000000000000, Ptr{Nothing} @0x0000000000000000)
julia> undefstuff::RawStuff # type assertion
(0, Ptr{Nothing} @0x0000000000000000, Ptr{Nothing} @0x0000000000000000)
julia> unsafe_store!(ptr, undefstuff, 1)
Ptr{Tuple{Int64, Ptr{Nothing}, Ptr{Nothing}}} @0x00007f4b688bfce0
julia> s
4-element Vector{Stuff}:
#undef
Stuff(0, Float64[], String[])
Stuff(0, Float64[], String[])
#undef
Tada! The first the element is now undef
. Letâs do the second and third element.
julia> unsafe_store!(ptr, undefstuff, 2)
Ptr{Tuple{Int64, Ptr{Nothing}, Ptr{Nothing}}} @0x00007f4b688bfce0
julia> unsafe_store!(ptr, undefstuff, 3)
Ptr{Tuple{Int64, Ptr{Nothing}, Ptr{Nothing}}} @0x00007f4b688bfce0
julia> s
4-element Vector{Stuff}:
#undef
#undef
#undef
#undef
Now s
has returned to its prestine undef
state. Doing what I just did is a bad idea!! We use a bunch of unsafe
methods to do it. The details of how this works may change at any point. Do not do this.