# Why does arrayref throw?

**URL:** https://discourse.julialang.org/t/why-does-arrayref-throw/104283
**Category:** New to Julia
**Tags:** question
**Created:** [September 26, 2023, 9:13pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283 "2023-09-26T21:13:03Z")
**Posts on this page:** 20
**Page:** 3

<div class="post-metadata">

### Author: ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)
#### Post date: [September 30, 2023, 1:36pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/41 "2023-09-30T13:36:46Z")

</div>

Note that the layout of `Array` is about to change (although not in ways that will get rid of `undef`) [Add `Memory` type by oscardssmith · Pull Request #51319 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/51319)

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [September 30, 2023, 10:27pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/42 "2023-09-30T22:27:45Z")

</div>

> [@tmpo](#):
>
> And, asking for a default constructor for `T` in order to support `Vector{T}(n)` does not appear completely crazy.

It’s not crazy, but we just have a variety of methods to construct it that aren’t attached to the type itself.

> [@tmpo](#):
>
> For example, this is how C++ works, right?

I’d rather not initialize like how C++ does, users seem to agree that it’s [complicated](https://blog.tartanllama.xyz/initialization-is-bonkers/) and doesn’t even stop indeterminate values generally. The rule of thumb, very much emphasized in that blog, is to never rely on automatic things for initialization.

> [@tmpo](#):
>
> ```julia
> std::vector<int> a(10);
> std::vector<std::vector<int> > b(10);
> 
> ```
> 
> everything is fully initialized.

That second line would make a vector of 10 empty vectors, did you mean [`> b(10, a);`](https://www.dcs.bbk.ac.uk/~roger/cpp/week13.htm) to make a 10x10? That’s called the fill constructor, but unlike Julia’s `fill`, it _copies_ the value to each element. Either way, can be done with a comprehension in Julia.

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 1, 2023, 9:29am UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/43 "2023-10-01T09:29:33Z")

</div>

Thanks for pointing at `sizehint!`

> [@albheim](#):
>
> which does seem to suggest that there is a difference between capacity and size for some types, though I’m not sure which.
> 
> It makes a small difference for a normal Array at least, so probably works there.

Yeah, since the array is dynamic (can grow and shrink), it must distinguish capacity and size internally. Otherwise, for example, the code

```julia
a = Int[]
for i = 1:n
    push!(a, i)
end

```

would cause reallocation on every iteration, turning this into a quadratic time operation.

What I meant was for this to be part of the array interface, and user controllable. `sizehint!` does seem to do this, although it appears to make few promises.

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 1, 2023, 11:49am UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/44 "2023-10-01T11:49:24Z")

</div>

> [@Sukera](#):
>
> Additionally, I think it’d be quite confusing if I’d request a `Vector{T}` and get a `Vector{Union{Nothing, T}}` back instead (not to mention causing type instability across the board). Hence, the only safe way to get an actual object of type `T` means requiring the user to either provide a default, or require a function that constructs an object for every index.

Yeah, I don’t mean that `a = Vector{T}(n)` should give `a::Vector{Union{T,Nothing}}`.  
I mean that if you can’t use some mechanism to initialize at creation, then you can 1) construct a vector `a::Vector{Union{T,Nothing}}` initialized with `nothing`, 2) populate it with elements of type `T`, and 3) construct from `a` the vector `b::Vector{T}`. If the vector is never fully populated with elements of type `T`, or if this is done in a non-local manner, then maybe `Vector{Union{T,Nothing}}` (or some other type, like a dictionary or sparse vector) is a better type than `Vector{T}`.

A functional programming/type theory view is that code that produces data of some type is akin to a constructive proof that an element of that type exists (see [Curry–Howard correspondence - Wikipedia](https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence)). To me, this does not match Julia’s `Vector{T}` as every element in such a vector should be of type `T` - I feel Julia “fakes” this type.

It’s like

- Simple function f: Julia, I ready to do work! I can handle any vector of `T`’s as input!
- Julia: Excellent news f! We happen to have a lot to work today, could you please compute on this data for me?
- Simple function f: Sure can! I promise to do my best to complete as soon as possible!
- Simple function f: It is a vector of `T`’s, right?
- Julia: Yes, its totally a vector of `T`’s, I promise!
- Simple function f: \*goes to work\*  
…
- Simple function f: Julia, I found a nullpointer in the data!
- Julia: \*\*\*\*, no one will ever know, \*blows bomb\*

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [October 1, 2023, 12:03pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/45 "2023-10-01T12:03:52Z")

</div>

> [@tmpo](#):
>
> A functional programming/type theory view is that code that produces data of some type is akin to a constructive proof that an element of that type exists (see [Curry–Howard correspondence - Wikipedia](https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence)). To me, this does not match Julia’s `Vector{T}` as every element in such a vector should be of type `T` - I feel Julia “fakes” this type.

Julia doesn’t necessarily break Curry-Howard here. Constructing the `Vector{T}` itself works perfectly fine; it’s _retrieving_ an element in that which ultimately throws. That is, having a container object is not equivalent with having that container actually be filled with sensible/retrievable/initialized data.

If julia would not throw here, then yes, it’d break constructor assumptions, which is exactly what happens with `isbits` types that only allow specific bitpatterns to exist, and is why I brought up that as an example above/in the other thread. This isn’t really related to what happens in the case of mutable data though.

> [@tmpo](#):
>
> It’s like
> 
> - Simple function f: Miss Julia, I ready to do work! I can handle any vector of `T`’s as input!

Please don’t assign a gender to a programming language.

> **[Julia Community - Standards](https://julialang.org/community/standards/#be_respectful_and_inclusive)**
>
> The official website for the Julia Language. Julia is a language that is fast, dynamic, easy to use, and open source. Click here to learn more.

> In particular, do not sexualize the term “Julia” or any other aspects of the project. While “Julia” is a female name in many parts of the world, the programming language is not a person and does not have a gender.

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 1, 2023, 12:14pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/46 "2023-10-01T12:14:07Z")

</div>

> [@Sukera](#):
>
> Please don’t assign a gender to a programming language.

Sorry, edited.

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 1, 2023, 5:12pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/47 "2023-10-01T17:12:40Z")

</div>

> [@Benny](#):
>
> This would make an uninitialized array on the Julia level so the compiler still cannot assume all arrays are fully initialized.

Then couldn’t we just pass the initialization mechanism to the C level?

As I noted above, `_new_array_` indeed does perform zero-initialization, so I don’t see why more sophisticated initialization would be impossible.

> [@Benny](#):
>
> I get you’re trying to justify eliding that defined-reference check, but that is not going to go away even if you somehow guaranteed fully initialized arrays because checking pointers is part of Julia’s memory safety designs; a segfault isn’t even the worst thing that can happen, and errors are safer and more informative.

I don’t understand this. To me this appears inconsistent with

> [@tmpo](#):
>
> Also, that a vector of type `Vector{Int}` lives on the heap does not mean it can be “absent”. See for example
> 
> ```julia
> 
> julia> f(a::Vector{Int}) = @inbounds a[1]
> 
> f (generic function with 1 method)
> 
> julia> a = [1]
> 
> 1-element Vector{Int64}:
> 
> 1
> 
> julia> @code_llvm f(a)
> 
> ; @ REPL[19]:1 within `f`
> 
> ; Function Attrs: uwtable
> 
> define i64 @julia_f_464({}* noundef nonnull align 16 dereferenceable(40) %0) #0 {
> 
> top:
> 
> ; ┌ @ essentials.jl:13 within `getindex`
> 
> %1 = bitcast {}* %0 to i64**
> 
> %2 = load i64*, i64** %1, align 8
> 
> %3 = load i64, i64* %2, align 8
> 
> ; └
> 
> ret i64 %3
> 
> }
> 
> ```
> 
> There is no check to test if `a` is undefined or absent.

and with

> [@StefanKarpinski](#):
>
> The approach we went with was to allow creating an uninitialized array or object but then very carefully not make the undefined value first class—any access to it is an immediate error and you cannot make something undefined after it has been defined.

I would appreciate any reference to documentation or code that explains the memory safety design you speak of.

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [October 1, 2023, 10:37pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/48 "2023-10-01T22:37:36Z")

</div>

> [@tmpo](#):
>
> I don’t understand this. To me this appears inconsistent with
> 
> > [@tmpo](#):
> >
> > Also, that a vector of type `Vector{Int}`

`Vector{Int}`'s elements don’t have pointers to check, `Int` is an `isbits` type. No pointers, no dereferencing problems like segfaults or corruption. Granted, you still shouldn’t index it before proper initialization because it’s indeterminate, just interprets preexisting bits in memory.

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 1, 2023, 10:50pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/49 "2023-10-01T22:50:53Z")

</div>

I mean `f` uses `a::Vector{Int}` without check, and `Vector{Int}` is not an isbitstype type.

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [October 1, 2023, 10:54pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/50 "2023-10-01T22:54:04Z")

</div>

`Vector{Int}` and `Vector{Vector{Int}}` have different element types. See the difference between your first post

> [@tmpo](#):
>
> ```julia
> function test(a::Vector{Vector{Int}})
> return @inbounds a[1]
> end
> 
> ```

and this post:

> [@tmpo](#):
>
> `julia> f(a::Vector{Int}) = @inbounds a[1]`

`Int` is an `isbits` type so indexing an element with its type doesn’t require pointer checks.

> [@tmpo](#):
>
> I mean `f` uses `a::Vector{Int}`

A method itself doesn’t need to check arguments for nonexisting instances, the error would be thrown before even reaching the method.

```julia
julia> global b::Vector{Int}

julia> f(a::Vector{Int}) = @inbounds a[1]
f (generic function with 1 method)

julia> f(b)
ERROR: UndefVarError: b not defined
...

```

Same goes for `isbits` types, you don’t get random memory bits.

```julia
julia> let
         local c::Int
         c+1
       end
ERROR: UndefVarError: c not defined
...

```

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 1, 2023, 11:59pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/51 "2023-10-01T23:59:18Z")

</div>

> [@Benny](#):
>
> A method itself doesn’t need to check arguments for nonexisting instances, the error would be thrown before even reaching the method.

Yes, that’s my point!

Recall, I was replying to

> [@Benny](#):
>
> I get you’re trying to justify eliding that defined-reference check, but that is not going to go away even if you somehow guaranteed fully initialized arrays because checking pointers is part of Julia’s memory safety designs; a segfault isn’t even the worst thing that can happen, and errors are safer and more informative.

In my example, the reference to `a` is known to be valid, so no check is needed.  
If we always had fully initialized arrays, then wouldn’t the same reasoning apply for the vector elements? When accessing an element of `a::Vector{T}`, for reference type `T`, why would Julia check the reference?

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [October 2, 2023, 12:24am UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/52 "2023-10-02T00:24:55Z")

</div>

> [@tmpo](#):
>
> If we always had fully initialized arrays, then wouldn’t the same reasoning apply for the vector elements?

Correct logic, but we don’t have those. Variables can be determined to have defined or undefined references at compile-time, so the compiler may elide those checks (e.g. observe `foo() = @inbounds a[1]` when the global `a` is defined versus undefined). Container elements and struct fields cannot because methods that initialize the overall instance are written directly in Julia. The only way to accomplish what you’re aiming for is to sacrifice that feature by doing everything like:

> [@tmpo](#):
>
> Then couldn’t we just pass the initialization mechanism to the C level?

, which goes against the design of Julia.

---

<div class="post-metadata">

### Author: ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)
#### Post date: [October 2, 2023, 7:47pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/53 "2023-10-02T19:47:54Z")

</div>

Everyone agrees that it would be preferable if arrays and objects could be guaranteed to always come into existence fully constructed. But we also need to have optimally efficient ways to do all kinds of array or object constructions in the language. If there’s some construction that simply cannot be done efficiently, that’s not really acceptable. So far, the two proposed approaches to avoiding partial initialization are:

1. Require providing a default object for all reference types that are used in arrays.
2. Make the array construction API take an iterator to provide initial values.

I had a big comment written about this, but then I got to an example that made my points better than my wall of text, so here’s that example. Suppose you want to fill an array with Pascal’s triangle values. In Julia now you can do this:

```julia
function pascal(::Type{T}, n::Integer) where {T<:Real}
    A = Matrix{T}(undef, n, n)
    A[:, 1] .= one(T)
    A[1, :] .= one(T)
    for j = 2:n, i = 2:n
        A[i, j] = A[i-1, j] + A[i, j-1]
    end
    return A
end

```

When `T` is a value type like `Int` then there’s no issue. But there can be number types that aren’t value types, like `BigInt`. This example could also be generalized to allow vectors as elements and allow the caller to provide initial values for the first row/column that aren’t all ones, in which case the element type would be something more complex—and even more importantly something mutable (more later).

Let’s focus on the `T = BigInt` case. This is what the allocation looks like currently:

```julia
julia> A = Matrix{BigInt}(undef, 3, 3)
3×3 Matrix{BigInt}:
 #undef #undef #undef
 #undef #undef #undef
 #undef #undef #undef

```

Yes, those pesky undefs are there, but we didn’t have to allocate any BigInts. Suppose we did default initialization. For BigInts, zero is a good default value, so we can emulate this by replacing `Matix{T}(undef, n, n)` with `zeros(T, n, n)`. Let’s time them for `T = BigInt`, `n = 3`:

```julia
julia> @belapsed Matrix{BigInt}(undef, 3, 3)
1.5405811623246494e-8

julia> @belapsed zeros(BigInt, 3, 3)
3.876814516129032e-8

```

That’s a 2.5x slowdown for default initialization. Not great. Keep in mind that in the `pascal` function we’re just going to replace all these values anyway, so this 2.5x slowdown just to fill the initial array with something valid is wasted.

We should also note that in this case default initialization is using the same instance of `BigInt(0)` to fill the entire matrix. If we consider BigInts to be immutable that’s fine, but they do actually have a mutable API, not exposed by Base, but available through the `MutableArithmetics` package, which can be used to write faster code and which lets us expose this fact:

```julia
julia> using MutableArithmetics

julia> A = zeros(BigInt, 3, 3)
3×3 Matrix{BigInt}:
 0 0 0
 0 0 0
 0 0 0

julia> add!!(A[1], 123)
123

julia> A
3×3 Matrix{BigInt}:
 123 123 123
 123 123 123
 123 123 123

```

Oops! All the entires changed. Now, one may say that we shouldn’t be surprised here and that the issue is that BigInts should be treated as immutable unless you allocate one yourself and can be sure that it’s safe to mutate it. But the proposal is to use default filling for all reference types, not just ones like BigInt which are conceptually immutable. In fact, the original example was to use `Int[]` as the default value for `Vector{Int}`. In that case we’d have this:

```julia
julia> A = fill(Int[], 3, 3)
3×3 Matrix{Vector{Int64}}:
 [] [] []
 [] [] []
 [] [] []

julia> push!(A[1], 123)
1-element Vector{Int64}:
 123

julia> A
3×3 Matrix{Vector{Int64}}:
 [123] [123] [123]
 [123] [123] [123]
 [123] [123] [123]

```

And in this case you can’t use the defense that the elements aren’t supposed to be mutable because the whole point of arrays is that you can change their contents. So if we’re going to do default filling, then if we use the same default instance for the whole array we’ve got this pretty bad footgun.

What if we instead we used a newly constructed default value for each array slot? That would be equivalent to doing `[zero(T) for i=1:n, j=1:n]` in place of `Matrix{T}(undef, n, n)`. Let’s time that as well for `T = BigInt`, `n = 3`:

```julia
julia> @belapsed [zero(BigInt) for i=1:3, j=1:3]
1.7863372859025033e-7

```

Ah, now we get a 11x slowdown relative to `Matrix{T}(undef, n, n)` instead of just 2.5x. On the other hand, we don’t have the issue with contagious mutation:

```julia
julia> A = [zero(BigInt) for i=1:3, j=1:3]
3×3 Matrix{BigInt}:
 0 0 0
 0 0 0
 0 0 0

julia> add!!(A[1], 123)
123

julia> A
3×3 Matrix{BigInt}:
 123 0 0
   0 0 0
   0 0 0

```

What’s worse about this one is that, whereas the relative slowdown of `zeros(BigInt, n, n)` versus `Matrix{T}(undef, n, n)` stays around 2.5x slower even for larger matrices, `[zero(T) for i=1:n, j=1:n]` is gets relatively worse and worse the bigger `n` gets—for `n = 5` it’s already 23x slower; for `n = 100` it’s 89x slower.

So filling arrays with a default value is pretty bad from a performance perspective, at least in the case where you just need an array that you’re immediately going to replace the contents of. It’s bad enough that it really can’t be the only option—there needs to be something else.

The proposed alternative is using an iterator to generate initial values for an array. That’s fine when it works, but how would you implement the `pascal` function as an iterator? This is a serious question. There isn’t an elegant way to do it that I can tell because the initialization wants to look at the previously initialized values, which iteration won’t let you do. Assuming the iterator goes in column major order you could remember what you assigned in the previous column, but where do you store those values? You can’t look them up in the array you’re initializing because that array object doesn’t exist yet—you’re still constructing it. So you’d need to allocate an O(n) temporary storage buffer to remember the values you generated in the last column of the array.

Keep in mind that the Pascal triangle initialization is hardly the gnarliest initialization possible. It’s not too hard to come up with reasonable cases where either the best initialization order for an array is all over the place or each next element depends on the previously initialized elements in some arbitrarily complex way so that you’d need to keep a second copy of the entire array so far.

Regarding C++: you’ll note that [people have asked](https://stackoverflow.com/questions/96579/stl-vectors-with-uninitialized-storage) how to get around this exact limitation of `std::vector` for performance reasons. In C++, of course, you always have other options that allow uninitialiezed memory, which is what some answers there suggest. In Julia we wouldn’t have that option because unlike std::vector, which builds upon lower level C++ primitives like raw pointers, Julia’s Array is the primitive mutable collection time and there’s nothing lower to resort to. In other words, we can’t not have a way to do this efficiently.

---

<div class="post-metadata">

### Author: ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)
#### Post date: [October 2, 2023, 8:17pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/54 "2023-10-02T20:17:17Z")

</div>

> [@StefanKarpinski](#):
>
> There isn’t an elegant way to do it that I can tell because the initialization wants to look at the previously initialized values, which iteration won’t let you do. Assuming the iterator goes in column major order you could remember what you assigned in the previous column, but where do you store those values? You can’t look them up in the array you’re initializing because that array object doesn’t exist yet—you’re still constructing it. So you’d need to allocate an O(n) temporary storage buffer to remember the values you generated in the last column of the array.

This is sufficiently tricky that it took me a while to figure out how to do it. Here’s the code:

```julia
function pascal′(::Type{T}, n::Integer) where {T<:Real}
    x = one(T)
    v = zeros(T, n)
    [ begin
        if i == 1 && j != 1
            x = zero(T)
        end
        x = v[i] += x
      end for i = 1:n, j = 1:n ]
end

```

This works but it seems significantly less clear than the original, not to mention less efficeint because it needs a temporary vector `v` to keep track of what was generated in the previous column.

---

<div class="post-metadata">

### Author: ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)
#### Post date: [October 2, 2023, 8:38pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/55 "2023-10-02T20:38:52Z")

</div>

Oh, I should also mention that we’ve been focused here on arrays but partially initialized objects are also a place where undef happens. The canonical motivating example is a recursive type like this:

```julia
mutable struct X
    f::X
    function X()
        x = new() # x.f is undef here
        x.f = x
    end
end

```

If all objects have to be constructed fully initialized, how can you do this? Note that this isn’t entirely academic since this is basically the structure of a circular linked list. I should also note, however, that unlike arrays, the compiler can and does analyze on an individual field basis, whether each field can be undef or not, so if a type doesn’t ever construct partially initialized objects then the compiler will know that there’s no need for undef checks.

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 2, 2023, 11:58pm UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/56 "2023-10-02T23:58:25Z")

</div>

Wow, thanks for this very detailed post! Lots to unpack and to digest! (for a future day with more time)

I have a few direct reactions:

I like how the example illustrates that code written for, e.g. some numerical computation, should work well with both value types and reference types. I had not really though about this.

I fully agree that a default instance/value is a bad idea.

You compare the cost of initialization when going from “no initialization” to “initialization”, of course this can look bad (yes, I know “no initialization” requires “null initialization”, but still). Luckily, I think, initialization is usually not the dominant cost in a computation - and if it is, then that’s a pretty good sign you are using a dense structure when you should be using a sparse.

The Pascal example connects back to earlier discussion in this thread about incremental construction and `sizehint!`

```julia
function pascal(::Type{T}, n::Integer) where {T<:Real}
    A = T[]
    sizehint!(A, n*n)

    idx(i, j) = n * (j - 1) + i

    for j = 1:n
        for i = 1:n
            if i == 1 || j == 1
                push!(A, one(T))
            else
                push!(A, A[idx(i-1, j)] + A[idx(i, j-1)])
            end
        end
    end

    return reshape(A, (n,n))
end

```

Yes, your index-style implementation looks much better than this one.

---

<div class="post-metadata">

### Author: ![tmpo](https://avatars.discourse-cdn.com/v4/letter/t/ba9def/32.png) [@tmpo](https://discourse.julialang.org/u/tmpo)
#### Post date: [October 3, 2023, 7:51am UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/57 "2023-10-03T07:51:31Z")

</div>

> [@StefanKarpinski](#):
>
> But we also need to have optimally efficient ways to do all kinds of array or object constructions in the language. If there’s some construction that simply cannot be done efficiently, that’s not really acceptable.

I understand your point, and I very much appreciate the emphasis on performance in the design. However, this doesn’t appear to be an absolute. Even Julia have tradeoffs here (I believe).

For example, going back to the Pascal calculation. Assume you wanted to use high precision floats and opted for DoubeFloats.jl. Now, since `Double64` is not an isbitstype type, you have your “number objects” heap allocated [_this is false, see below_] – which is probably not ideal for performance in this example. And AFAICT, there is no way to tell Julia that a vector should store elements in-place, or to annotate a mutable struct (or other non-isbitstype struct) to be a value type instead of a reference type (which would cause it to be stored in-place in a vector).

This change, allocating the objects in-place, I think, would be a standard way to optimize the code in C++ (without changing algorithm). It could then be further tweaked to use a tiling iteration pattern to optimize the cache usage. I imagine (without having checked this) that this would give a good speedup.

This is not saying that “C++ is better than Julia”: C++ is complicated, Julia is simpler. Simplicity is also valuable. (IMHO, of course)

**EDIT:** My statement above about `Double64` being heap allocated is false. `Double64` actually an isbitstype type. I should have checked. Apologies. (However, the argument still holds for types that are not isbitstype, I believe.)

**EDIT:**

> [@Mason](#):
>
> Still not quite:
> 
> ```julia
> julia> struct Foo
> x::Union{Float64, Int}
> end
> 
> julia> isbitstype(Foo)
> false
> 
> julia> Base.allocatedinline(Foo)
> true
> 
> ```
> 
> unions of `isbits` types are also allowed to be stored inline

---

<div class="post-metadata">

### Author: ![Benny](https://avatars.discourse-cdn.com/v4/letter/b/49beb7/32.png) [@Benny](https://discourse.julialang.org/u/Benny)
#### Post date: [October 3, 2023, 8:47am UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/58 "2023-10-03T08:47:26Z")

</div>

> [@tmpo](#):
>
> Now, since `Float64` is not an isbitstype type

Is this a typo? `isbitstype(Float64) == true`.

> [@tmpo](#):
>
> And AFAICT, there is no way to tell Julia that a vector should store elements in-place…

Are there any `isbitstype` elements that aren’t stored in-place?

> [@tmpo](#):
>
> or to annotate a mutable struct (or other non-isbitstype struct) to be a value type instead of a reference type (which would cause it to be stored in-place in a vector).

Well, `mutable struct` instances are referenced to implement mutability, so they’re all reference types. As I understand it, `isbitstype`s are equivalent to value types, but the term formally refers to `Val` in Julia instead.

---

<div class="post-metadata">

### Author: ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)
#### Post date: [October 3, 2023, 8:52am UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/59 "2023-10-03T08:52:34Z")

</div>

> [@tmpo](#):
>
> allocating the objects in-place

Somewhat relevant, escape analysis, that’s being worked on for Julia, could enable some optimizations that you’d like:

[https://docs.julialang.org/en/v1.11-dev/devdocs/EscapeAnalysis/](https://docs.julialang.org/en/v1.11-dev/devdocs/EscapeAnalysis/)

> stack allocation of mutable objects

---

<div class="post-metadata">

### Author: ![Sukera](https://avatars.discourse-cdn.com/v4/letter/s/ce7236/32.png) [@Sukera](https://discourse.julialang.org/u/Sukera)
#### Post date: [October 3, 2023, 9:03am UTC](https://discourse.julialang.org/t/why-does-arrayref-throw/104283/60 "2023-10-03T09:03:53Z")

</div>

> [@tmpo](#):
>
> This change, allocating the objects in-place, I think, would be a standard way to optimize the code in C++ (without changing algorithm). It could then be further tweaked to use a tiling iteration pattern to optimize the cache usage. I imagine (without having checked this) that this would give a good speedup.

This change is not trivial to do. Consider this, which must continue to work:

```julia
mutable struct A
   el::Int
end

a = A(1)
b = [a]
b[1].el = 2
a.el == 2 # true

```

Evidently, trying to declare `b` to have its elements stored in-line doesn’t work with the non-moving GC we have today; `a` already exists before `b` is ever allocated, so either the in-line semantics of `b` breaks, the use of `a` in `b` copies (thus breaking the `==` test) or an error is thrown. Neither of these seem good.

That Julia stores mutable values as references is by design; Julia doesn’t have the reference semantics exposed to be manipulated/worked with by the user. There are undoubtly situations where treating a collection and the objects it contains as one entity for the purposes of memory management would be useful, but that’s not the semantics `Vector` (or mutable objects for that matter) has today.

That being said, if you do want to have those kinds of semantics, you can somewhat get to them using `Ref` (pointing it to an array like `Ref([a], 1)`), which must explicitly be indexed in order to retrieve the stored (mutable) object.

[Previous page](https://discourse.julialang.org/t/why-does-arrayref-throw/104283.md?page=2)

[Next page](https://discourse.julialang.org/t/why-does-arrayref-throw/104283.md?page=4)
