user1452276
user1452276

Reputation: 91

lua indexing a nil value

I have been googling this issue to no avail so far so I was wondering if anyone might know the answer - when you index a nil value in lua, the script raises an error - attemp to index 'variableName' - a nil value - is it possible to instead capture that with may be a metamethod, do some processing and not error out? For example if variable 'num' is not defined and you say 'num = 2' you can set the __newindex metamethod to do some processing and you have both the variable name - 'num' and the value - '2' on the stack, but if you say 'num[2] = 3' and 'num' is not defined you error out - instead just like __newindex, I want to capture that event and get access to the name of the nil variable - 'num', the index - 2, and the set value - 3. Any help would be appreciated.

Upvotes: 1

Views: 6415

Answers (2)

Mike Corcoran
Mike Corcoran

Reputation: 14565

you can override the __newindex metamethod on the global table.

function OnNewIndex(tbl, key, value)
    if tbl[key] == nil then
        print(tostring(key).. " doesn't exist.")
    end
end

setmetatable(_G, { __newindex = OnNewIndex })

num = 15

however, by default I don't believe lua has this behavior. just doing something like:

num = 15

should just declare the variable 'num' as a global and assign the value 15 to it. are you using lua as part of some framework that has strict checking for this?

edit: an easy way to 'catch' this error without letting it bubble to the surface would be to wrap the assignment in a pcall like this...

pcall(function() num[2] = 15 end)

if you print this, you'll get the output:

false   test2.lua:18: attempt to index global 'num' (a nil value)

which at least gives you some indication of what happened, if you named the function, i guess you could potentially try to recover.

function TrySetValue()
    num[2] = 15
end

local result, msg = pcall(TrySetValue)
if (not result) then
    num = {}
end

result, msg = pcall(TrySetValue)
print(result, msg)
print(num[2])

seems like an awful lot of work when you could probably just fix the code up front.

Upvotes: 1

Mud
Mud

Reputation: 28991

First, it's easier to help if you state the problem you're trying to solve. Are you trying to prevent uninitialized globals from being used? You can do that with a metatable. Are you trying to make sure script errors never percolate up to your app? You can do that with pcall. Are you really only trying to catch this one specific usage of a nil global, and recover from it in some way? The more context you give, the easier it is for others to find a solution you might not have thought of.

As for the situation you described, where you want to catch num[index] where num is nil, you can't really detect that directly.

There are two parts to this:

  1. when the current value of num is read from the current environment table (aka _ENV or _G)
  2. when you attempt to index that value.

There's no way you can detect #2, because nil is not a table so you can't give it a metatable.

However, you can detect #1. Depending on your requirements you could use #1 to return a proxy object which will then allow you to detect #2, however this will mean no global will ever be nil, so logic like this won't work:

--- initialize globalFoo if it doesn't exists
if not globalFoo then
    globalFoo = CreateGlobalFoo()
    ....

Of course you could always do something like name your proxy object NIL, so you could write:

if globalFoo == NIL then -- globalFoo is not initilalize
    globalFoo = CreateGlobalFoo()

Example of a proxy object that works like that:

local nil_proxy_mt = {
    __index    = function(t,k)   print('Attempted to index nil!') end,
    __newindex = function(t,k,v) print('Attempted to index nil!') end,
}

local NIL = setmetatable({}, nil_proxy_mt)

setmetatable (_G, { __index = function(t,k) return NIL end })

With this in place, if you tried either of these:

foo = num[2]
num[2] = 15

You'll get "Attempted to index nil!" and the program continues running.

Upvotes: 1

Related Questions