Some tricks for prettier xs
XS has a reputation of being ugly and cumbersome, but in my experience, it doesn't have to be. Let's take for example this snippet from my
MODULE = Thread::Csp PACKAGE = Thread::Csp::Promise PREFIX = promise_ SV* promise_get(Promise* promise) bool promise_is_finished(Promise* promise) SV* promise_get_notifier(Promise* promise)
How did I write XS with so little code/boilerplate? By using XS the way it was originally intended: to glue Perl and C together, not to implement any behavior.
No CODEA lot of people seem to think you need a
CODEblock in your XS functions, but often you don't. For example
SV* promise_get(Promise* promise)is actually equivalent to
SV* promise_get(Promise* promise) CODE: RETVAL = promise_get(promise); OUTPUT: RETVALBy giving the `promise_get` function the right shape and name, I don't need to write any of that.
This doesn't only mean less code (which is always good), it also means that it's much easier to split a large amount of code into multiple files, as doing this in C is much easier than doing it in XS (e.g. DBI.xs is 5700 lines). This aids in making your project more maintainable.
No K&RThe second thing you may notice is that I'm declaring the types of the arguments in ANSI style (within the parentheses), not the common K&R style like:
SV* promise_get(promise) Promise* promise;The author of perlxs and perlxstut was clearly fond of K&R style, and everyone seems to have copied it from the documentation, but ANSI style is far more familiar to most people, and less repetitive. While the K&R style can do a few things ANSI style can't (e.g. with regards to custom conversions), it's very uncommon to need any of that.
TypemapsTo convert from arguments from Perl values to C values, and vice-versa for the return values, I used type maps.
TYPEMAP Promise* T_PROMISE INPUT T_PROMISE $var = sv_to_promise($arg) OUTPUT T_PROMISE $arg = promise_to_sv($var);
Using these templates, you don't need the XS or the individual functions to worry about type conversions for the most common argument types.
PrefixAll the XS packages in my module are defined with a prefix:
MODULE = Thread::Csp PACKAGE = Thread::Csp::Promise PREFIX = promise_That way I can namespace my C functions to all start with `promise_`, but on the Perl side
promise_getwill be a sub called
get(in the package
C_ARGSI still had one function where the default glue code didn't quite cut it, because that had a slurpy argument so I couldn't map arguments 1-on-1. Instead of using a `CODE` block here, I used the
C_ARGSto override only how the arguments are passed from Perl to C, without overriding any of the rest of the code generation. And then I defined a helper that turns the arguments on the stack into an array that is passed to the function.
Promise* thread_spawn(SV* class, SV* module, SV* function, ...) C_ARGS: slurp_arguments(1)