Trailing Nil Parameters

lua-users home
wiki


[!] VersionNotice: The below code pertains to an older Lua version, Lua 4. It does not run as is under Lua 5.

In pure Lua, passing a nil argument is equivalent to omitting this argument. However, C-Functions called from Lua can detect the number of arguments given to them (including nils!). This leads to a lot of confusion and ugly code.

In my opinion, the lua standard libraries should be cleaned up to not use the number of arguments passed in determining how to behave.

strfind(s,exp) should lead to the same result as: strfind(s,exp,nil,nil)

then you can write a wrapper for any function by using the maximum number of arguments:

function mystrfind(a,b,c,d)
    -- do some extra handling here...
    return strfind(a,b,c,d)
end

Example (a mail of ReubenThomas on the lua-l list):

If I run the following script:

t = {}
tinsert(t, 3, "hello", 1)
print(t[3])

I get 1 when I expect hello.

[...] I was bitten by this because I was trying to execute a statement of the form:
tinsert(t, n, gsub(...))

and was getting the number of replacements rather than the result of the replacement inserted. Surely this is wrong?

Now that i am reading this again, i realize that this is the opposite of the trailing nils problem - lua functions ignore extra arguments, while c functions get them all (and tinsert happens to insert its LAST parameter) - Perhaps it would be better to title this page "Problems arising from the different treatment of arguments to lua-functions and c-functions" -- PeterPrade

However, later in the thread, when people were trying to fix the oddity with tinsert, they encountered the trailing nils problem - they finally had to realize that you cannot write a proper wrapper function for tinsert in lua. Let me quote ETs last message to that thread:

[...] Compatible (fixed) version would be:

function tinsert(t, ...)
  if arg.n == 1 then
    %tinsert(t, arg[1])
  elseif arg.n == 2 then
    %tinsert(t, arg[1], arg[2])
  else
    error("wronger number of args ("..arg.n+1..") for tinsert");
  end
end
But that still gives unexpected results for:

tinsert(t, foo()) --> may generate to tinsert(t,a,b)

or

tinsert(t, x, foo()) --> may generate to tinsert(t, x)

when foo does not return the expected number of results.

For a long time I advocate to make C-functions behave similar to Lua functions regarding argument handling (make missing args nil). IMHO tinsert has a particularly good example of what can happen...

Ciao, ET.
Another example is on the FileInputOutput page - ugly code, because readfrom(nil) results in an error (while readfrom() is perfectly legal):

function readfrom(f)
  local h, err
  if f then h, err = %readfrom(f)
  else      h, err = %readfrom()
  end
  ...

another example appeared on the mailing list:

> I just noticed that contrary to what you might expect, if you 
> pass "nil" as the fourth argument of strfind, it does a plain match. 
> Looking at the source, str_find indeed simply looks to see whether it
> has a fourth argument.

Sigh, another example of the arg handling ad hockery...

> This is rather annoying, as in some other cases where it's nice to be
> able to pass "nil" instead of omitting the argument.

You say it...

> It would not be contrary to the 4.0 manual to change this behaviour
>[...]
> Could this be done?

Sure, it _could_.  One could fix this place by converting the test
lua_gettop(L)>3  to  luaL_opt_int(L,3,0)==1,  or one could finally
start thinking about cleaning up argument handling in the C-API...

>[...] is there any way around this?

function strfind(a,b,c,d)
  c = c or 1  -- c has a similar problem...
  if d then
    return %strfind(a,b,c,d)
  else
    return %strfind(a,b,c)
  end
end

add more examples here...


RecentChanges · preferences
edit · history
Last edited July 7, 2007 8:23 pm GMT (diff)