lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Fri, May 17, 2019 at 05:14:14PM +0200, Jim wrote:
> when looking at Lua's C API i wondered why many of the API functions
> do not return a result where that would make sense and just return
> "void" (i. e. nothing), for example:
> 
> lua_call()
> lua_close()
> lua_concat()
> lua_copy()
> lua_createtable()
> ...
> lua_len()
> ...
> lua_newtable()
> ...
> lua_pop()
> lua_push*()
> ...
> 
> here it might be useful to indicate whether the call succceeded by returning
> a result (probably an int). this does not even break compatibility since that
> new return value can be easily ignored and/or casted away via (void) where
> it is not wanted/needed.

Most of those throw errors and only return to the immediate caller on
success, mirroring their Lua counter parts.
 
> in the case of the lua_to*() functions i would suggest to also return a success
> result (an int) and return the requested value via an output pointer parameter
> provided by the caller, for example:
> 
> int lua_toboolean ( lua_State * L, int index, int * result ) ;
> 
> int lua_tointeger ( lua_State * L, int index, lua_Integer * result ) ;
> ^^^ indicates whether the call succeeded, i. e. if the value at the
> given stack index
> could get converted to an lua_Integer, the result is copied into the
> output argument
> pointer provided by the caller.

This depends on use cases. Whether it's more convenient to signal an error
through the return value or a separate flag depends on the situation.
Imagine you're writing a simple integer arithmetic library that handles
overflow. The obvious way to do this is similar to the GCC and clang
builtin:

  bool __builtin_add_overflow(type1 x, type2 y, type3 *sum)
  bool __builtin_mul_overflow(type1 x, type2 y, type3 *prod);

Those are great if you're just performing a single operation. But what you
need to accumulate the result of 3 add operations and a multiplication?

   int a, b, c, d, e;
   if (__builtin_add_overflow(1234, 4321, &c))
     return ERANGE;
   if (__builtin_add_overflow(9999, &c, &d))
     return ERANGE;
   if (__builtin_add_overflow(8888, &d, &e))
     return ERANGE;
   if (__builtin_mul_overflow(7777, &e, &result))
     return ERANGE;
   return 0;

All those conditionals obscure the math--that's not good code. We could fix
it slighty like:

   bool overflow = 0;
   overflow |= __builtin_add_overflow(1234, 4321, &c);
   overflow |= __builtin_add_overflow(9999, &c, &d);
   overflow |= __builtin_add_overflow(8888, &d, &e);
   overflow |= __builtin_mul_overflow(7777, &e, &result);
   return (overflow)? ERANGE : 0;

But even better might be something like:

   bool overflow = 0;
   c = __xbuiltin_add_overflow(1234, 4321, &overflow);
   d = __xbuiltin_add_overflow(9999, &c, &overflow);
   e = __xbuiltin_add_overflow(8888, &d, &overflow);
   *result = __xbuiltin_mul_overflow(7777, &e, &overflow);
   return (overflow)? ERANGE : 0;

If the function names were shorter we might return some evaluations directly
into the function calls of others.

The latter method is how the assembly code usually works, where the result
of an operation is placed into the destination register and a flag is set in
a separate, specialized register on overflow. It's also closer to how you
might organize the code in a higher-level language like Lua. And not
coincidentally, I think, it's also how lua_tointegerx works.

While the Lua C API has some unpleasant artifacts from incremental
improvements, it's actually much nicer than you seem to give it credit for.
And in my experience much nicer thanh the vast majority of APIs I've run
across.
 
> this way it is possible to tell if the lua_tointeger() call succeeded
> and the given
> (by index) stack position contained a zero or if the call failed.
> that is clearly an advantage IMO.
> 
> further such functions could be added, eg
> 
> int lua_tounsigned ( lua_State * L, int index, lua_Unsigned * result ) ;
> 
> for lua_Unsigned or
> 
> int lua_convert2integer ( lua_State * L, int index, lua_Integer * result ) ;
> 
> that tries to convert the value at the given stack position to an
> integer an so on.
> they should be named differently to preserve compatibility and the original
> API functions could be implemented in terms of the new ones.

See above.
 
<snip>
> BTW:
> 
> in the case of
> 
> int luaL_argerror (lua_State *L, int arg, const char *extramsg) ;
> 
> it would be nice if this could be changed to work akin to
> 
> int luaL_error ( lua_State * L, const char * fmt, ... ) ;
> 
> (which it calls anyway). this would enable the caller to not only pass
> one string
> but use a fmt and supplied values to create more helpful argument
> error messages.

I can't disagree about the inconvenience but this is the sort of change
where the benefit is slight but the backwards incompatibility very painful.
I normally either write a helper routine or use the pattern found in the Lua
code itself: luaL_argerror(L, arg, lua_pushfstring(L, fmt, x, y));