What's the point of having NULL? (explanation)

People often ask or bring up the point of having functions returning NULL instead of nil. This is something I feel everyone should understand.

[HR][/HR]
[HR][/HR]
What’s the point of NULL?

The reason NULL exists is because internally you can’t change the object type into something else. You can however change the object’s meta table.

Here’s an example situation:

[lua]
local ent = Entity(5)

timer.Simple(1, function()
ent:Remove()

timer.Simple(1, function() 
	ent:DoSomething() -- ????????
end)

end)
[/lua]

You can’t turn ent into nil here. It doesn’t work.

[lua]
function ENTITY:Remove()
self = nil – ???
end
[/lua]

It would just make the upvalue self nil inside the remove function / it’s useless.

This is the cleanest way to do it IMO:

[lua]
function ENTITY:Remove()
setmetatable(self, getmetatable(NULL))
end
[/lua]

This will make ent still be the same type, but with a different metatable. So when you try to use the ent variable it will throw a lua error instead of crashing the game.

The NULL meta looks something like this:

[lua]
local NULL = {}

function NULL:__tostring()
return “NULL”
end

function NULL:IsValid()
return false
end

function NULL:__index(key)
error((“tried to index %q on a null value”):format(key), 2)
end

_G.NULL = setmetatable({}, NULL)
[/lua]

[HR][/HR]
[HR][/HR]
Can’t you return nil and NULL?

This is inconsistent. Now you have to make something like a global called IsValid that will check if ent is nil or NULL. cough
The cleanest way to do this is to make sure you always return NULL if something is not valid. This eliminates the extra need to check if it’s nil first.

[HR][/HR]
[HR][/HR]
What if you set nil’s metatable to NULL?

It would only work with functions that may return a valid or non valid entity. See the first example situation. You can’t turn ent into nil.

Let’s assume GetInvalidEntity returns nil here instead of NULL:

[lua]
local ent = GetInvalidEntity()
if ent then
print(“this never happens!”)
end
[/lua]

But what about this?

[lua]
local ent = GetValidEntity()
if ent then
ent:Remove()

timer.Simple(1, function()
	if ent then
		ent:DoSomething() -- ???????
	end
end)

end
[/lua]

If we set the NULL meta on ent in its Remove function it would still be the same type like the above situation.
Now you have a situation where “if ent then” could lie. You’ll end up using ent:IsValid() instead just to make sure, making it better to just return NULL instead of nil.

Just to say it for everyone who needed this: Thanks.

I’m enlightened with this new knowledge, I shall use NULL in my returns forever and ever.

This is golden, thanks!