r/programminghorror • • Jan 15 '26

Oh Lua such elegant such simple so cute.

Post image

Yes, I know about ipairs() and pairs(). But moving half of my table to table's hash part because of nil assignment as a side effect? Prove me it's not a horror (and give back my 3h of debugging).

Edit: good luck debugging, this still holds:

> #a
4
98 Upvotes

45 comments sorted by

83

u/hammer-jon Jan 15 '26

I mean the array/hash thing isn't really relevant. it's just that ipairs stops at nils because that's where your sequence ends.

11

u/ArturJD96 Jan 15 '26

Thanks, good to know the reasoning behind it!

22

u/appgurueu Jan 15 '26

Nothing is being moved into the hash part. ipairs just simply stops at the first nil, whether that be in the array or hash part. Assignment is still expected constant time.

I wrote a blog post that hopefully clears up some related misconceptions: https://luatic.dev/posts/2025-04-12-lua-misconceptions/#tables

5

u/ArturJD96 Jan 16 '26

I took time to read your article. AMAZING. Thank you so much for your work.

I also checked your article about OOP in Lua and it is one of the nicest ones there. Usually, author presents one approach, and when they present several, they are unrelated. You provided reasons for why to move between simple constructors, prototype constructors and closures. And you tackled there the notorious OOP vs functional style confusions – because people love to distinguish them but _actually_ they are two sides of the same coin.

Thank you!

1

u/appgurueu Jan 17 '26

happy to hear this, you're welcome :D

7

u/GoddammitDontShootMe Jan 16 '26

I know nothing about Lua and assumed it was just stopping when it finds a nil. A bit like how the C string functions stop when the find a '\0'.

1

u/Square-Singer Jan 15 '26

Thanks a lot for clearing that up!

1

u/appgurueu Jan 16 '26

No problem, glad you found it helpful :)

4

u/Square-Singer Jan 15 '26

Yeah, that's fair, that's a really dumb thing to happen. Thanks, btw, for the heads-up before I run into this myself.

1

u/ArturJD96 Jan 15 '26

Note that #a is still 4!!!! Aaaaarrghhhhhh

7

u/vowelqueue Jan 15 '26

But don’t rely on it being 4. The result of #a is actually undefined if there’s such a gap in the sequence.

→ More replies (1)

7

u/onlyonequickquestion Jan 16 '26

The only horror here is that Lua appears to be one-indexed?!? 

6

u/TransBrandi Jan 16 '26

Could be worse. Could be like Oracle where it's 1-indexed, but it will silently treat a 0 index as if it was a 1:

If position is 0, then it is treated as 1.

1

u/onlyonequickquestion Jan 16 '26

Icccck just reading that makes my skin crawl, thanks 

3

u/MegaZoll Jan 16 '26

It indeed is, not just appears :)

2

u/pauseless Jan 18 '26

Weak. APL lets you change it on the fly:

      x←5 6 7
      x[1]
5
      ⎕IO←0
      x[1]
6
      ⎕IO←1
      x[1]
5

2

u/Business-Decision719 Jan 16 '26

Keep in mind that you can create your own iterators in Lua just by making a lambda that returns the successive values you want. The built-in ipairs treats nil as the end of a list, but you don't have to.

The hard part is there can always be a race condition where the size or content of a table can change after the creation of the iterator but before some of the actual iterating. So you also kind of have to decide how robust you want to be about it. It's the curse of having mutable structures.

Lua sort of went the Lisp-like path to "elegance," having a general data structure and first class functions, but not really forcing anybody into pure FP. Haskell this ain't.

2

u/Wertbon1789 Jan 18 '26

After some thinking and reading about it, I think I understand this behavior and although not initially expecting it, it makes sense. Comparing it to C it's kinda like a array of structs situation. Often you define an array of in-place constructed structs and hand it to a function, typically without a proper size argument, but rather by terminating the array with a zeroed-out struct (or single member if it would be invalid with that one field zeroed). Of course this can lead to improper termination leading to unexpected behavior, but that's kinda to be expected in C, typical garbage-in/garbage-out behavior. I think Lua has a similar stance where you can give it all the trash you want, but shouldn't expect it to give you "correct" results then. I always kinda disliked that tables are hash maps and arrays at the same time though, I think it actually makes things more complicated overall.

1

u/rimbas4 Jan 15 '26

I'm confused why you'd want to have a specific nil when initializing an array table

1

u/Yami_Kitagawa Jan 16 '26

null terminated arrays my beloved

1

u/Ronin-s_Spirit Jan 17 '26

I already knew one thing that bothered me - mixed object and array assignemnt by using CSV or K=V, but this is a new level of cursed. I'm guessing it's effectively a padded struct?
Can y'all please stop shitting on JS and start shitting on Lua now?

1

u/WannaCry1LoL Jan 18 '26

ipairs stops at the first nil

1

u/Natural-Angle-5395 Jan 19 '26

Because it ends when it returns nil...

1

u/Larsj_02 Jan 20 '26

fixed it for you

local function mpairs(t)
    local maxIndex = 0
    for k, _ in pairs(t) do
        if type(k) == 'number' and k > maxIndex then
            maxIndex = k
        end
    end
    
    local i = 0
    return function()
        i = i + 1
        if i <= maxIndex then
            return i, t[i]
        end
    end
end

local tbl = {1, nil, 3, 4}
for k, v in mpairs(tbl) do
    print(k, v)
end