• Subject: Re: Suggestion: Deprecate "Attempt to index a nil value" error; instead, return nil or create table
• From: Coda Highland <chighland@...>
• Date: Fri, 28 Feb 2020 17:10:03 -0600

On Fri, Feb 28, 2020 at 3:59 PM Anton Jordaan <anton.jordaan@sylvean.com> wrote:
Lua is memory efficient for huge but sparse multi-dimensional arrays,
since nil values aren't stored in the tables.

However, reading and writing to such sparse multi-dimensional arrays can
be quite a hassle.  If I want to access the value of
t[a][b][c][d][e], I first have to check whether t[a] is a table, t[a][b]
is a table, t[a][b][c] is a table etc, otherwise Lua throws an error
"Attempt to index a nil value".

For example, to read the value at t[a][b][c][d][e], I cannot simply use:
v = t[a][b][c][d][e]
Instead, the code must look something like:
v = t and t[a] and t[a][b] and t[a][b][c] and t[a][b][c][d] and
t[a][b][c][d][e]

Or, to write a value to t[a][b][c][d][e], I cannot simply use:
t[a][b][c][d][e] = v

Instead, the code must look something like:

if not t then t = {[a] = {[b] = {[c] = {[d] = {[e] = v}}}}}
elseif not t[a] then t[a] = {[b] = {[c] = {[d] = {[e] = v}}}}
elseif not t[a][b] then t[a][b] = {[c] = {[d] = {[e] = v}}}
elseif not t[a][b][c] then t[a][b][c] = {[d] = {[e] = v}}
elseif not t[a][b][c][d] then t[a][b][c][d] = {[e] = v}
else t[a][b][c][d][e] = v
end

I suggest that it would be more useful -- and more consistent -- if the
"Attempt to index a nil value" error is deprecated and, instead, the
indexing of undefined variables does the following:

When reading: simply return nil.  Given that undefined variables and
non-existent table entries both return nil, I think it would be more
consistent if an attempt to index an undefined variable also simply
returns nil.  (The Lua FAQ states that: "In many languages, trying to
access a non-existent key in a list or dictionary causes an exception;
in Lua the result is simply nil. This is unambiguous because nil cannot
be usefully put into tables.")  If t is nil, then t[a] should also
simply be nil, as should t[a][b][c][d][e].

When writing: automatically assign a single-entry table to each
undefined variable, with the given index the sole entry.  For example,
if t is an undefined variable, then t[a] = v would be syntactic sugar
for t = {[a] = v}.  Similarly, if t[a][b] is already declared as a table
but t[a][b][c] is nil, then t[a][b][c][d][e] = v would mean t[a][b][c] =
{[d] = {[e] = v}}.

-- Anton

This comes up very, very frequently.

This is absolutely a non-starter for inclusion in stock Lua, but for your own programs there are a number of solutions that have been proposed and/or implemented.

The very simplest thing you can do is to implement something like Lodash's get and set methods:

function get(tbl, k, ...)
if tbl == nil or tbl[k] == nil then return nil end
if select('#', ...) == 0 then return tbl[k] end
return get(tbl[k], ...)
end

function set(tbl, k, maybeValue, ...)
if select('#', ...) == 0 then
-- this will throw if the top-level tbl is nil, which is the desired behavior
tbl[k] = maybeValue
return
end
if tbl[k] == nil then tbl[k] = {} end
set(tbl[k], maybeValue, ...)
end

Then t[a][b][c][d] can be expressed as `get(t, a, b, c, d)` and t[a][b][c][d] = e can be expressed as `set(t, a, b, c, d, e)`.

If you want the syntax you've given, then this will work for read-only:

debug.setmetatable(nil, { __index = function(t, k) return nil end })

After this, `(nil).a.b.c` will simply return nil. This may break existing code, so be judicious where you use it.

Automatically creating nested tables is quite a bit more controversial and I really do recommend the set() function above for that. I'm not sure if you can implement this in vanilla Lua without a patch.

If you really do want to modify Lua, http://lua-users.org/wiki/LuaPowerPatches has a "safe table navigation" patch for 5.2 that may give you some insight.