Learning XS - Prototyping
Over the past year, I’ve been self-studying XS and have now decided to share my learning journey through a series of blog posts. This fifth post introduces you to subroutine(method/function) prototypes in XS.
What are prototypes?
Prototypes in Perl are a way to define the expected argument types for a subroutine. They help Perl to check the number of arguments passed to a subroutine and can also influence how the arguments are passed.
In Perl, you can define a prototype for a method like this:
sub my_sub ($$) {
my ($arg1, $arg2) = @_;
# Do something with $arg1 and $arg2
}
This prototype indicates that 'my_sub' expects two scalar arguments. If you call 'my_sub' with a different number of arguments, Perl will throw an error. Here is a table of common prototypes in Perl:
Prototype | Meaning |
---|---|
'$' | Scalar value |
'@' | Array value (flattens list) |
'%' | Hash value (flattens list) |
'&' | Code block (subroutine reference) |
'*' | Typeglob |
';' | Separates mandatory and optional arguments |
'[$@%&*]' | Reference to scalar, array, hash, code, or typeglob |
'[]' | Optional argument (used in documentation, not in actual prototypes) |
XS also supports prototypes, allowing you to specify the expected argument types for XS methods. By defining prototypes in your XS code, you can guide Perl’s argument parsing and enforce calling conventions similar to those in pure Perl subroutines. This can help ensure that your XS functions are used correctly from Perl code and they can also be used to extend the functionality of the language itself.
In this post we are not going to start with a new example, instead we will continue back when our 'time' (well year) began with our Roman::Numeral module from https://blogs.perl.org/users/robert_acock/2025/06/learning-xs---overloading.html.
We are going to implement the following functionality in our module:
numeral my $n => 'XX';
Which will instantiate a Roman::Numeral object with the value of 'XX'(20) into the variable. It is the same as doing the following:
my $n = Roman::Numeral->new('XX');
To achieve this, we will use a prototype in our method definition. The prototype will ensure that the method is called with a scalar reference (our variable) and a scalar value (the Roman numeral string). This looks like the following in perl:
use parent qw/Exporter/;
our @EXPORT = qw/numeral/;
sub numeral (\[$]$) {
my ($var, $val) = @_;
${$var} = Roman::Numeral->new($val);
$var;
}
So onto our XS code, we will first need to create a new test file to test our new functionality. Create a file named '03-numeral.t' in the 't' directory of your Roman::Numeral module with the following code:
use Test::More;
use Roman::Numeral;
numeral my $num => 'XX';
is("$num", 'XX');
is($num->as_number, 20);
done_testing();
Now we can implement the XS code to support this prototype. Open the 'Roman/Numeral.xs' file and first enable prototype by modifying the PROTOTYPES line.
Next, we will define the 'numeral' function with the prototype. Add the following code to the end of the 'Roman/Numeral.xs' file:
SV *
numeral(self, num)
SV *self
SV *num
PROTOTYPE: \[$]$
CODE:
SV * ret = SvRV(self);
sv_setsv( ret, new_numeral("Roman::Numeral", normalise(num)));
RETVAL = self;
OUTPUT:
RETVAL
This code defines the function with a prototype that expects a scalar reference (the variable) and a scalar value (the Roman numeral string). The function retrieves the reference to the variable, creates a new Roman::Numeral object using the provided numeral string, and assigns it to the variable using sv_setsv which copies the value from one SV (scalar value) to another.. Finally, it returns the variable reference.
Before our test passes, we need to ensure our normalise function is exported, for now we will do this in perl in the future I will show you how to do the same in XS. Open the 'Roman/Numeral.pm' file and add the following lines after the version declaration:
use parent "Exporter";
our @EXPORT = qw/numeral/;
With that in place the new test should now pass and we have implemented a basic prototype in XS. In the future we will explore more prototypes but for now we will leave it here.
Have a nice evening and happy coding!
Leave a comment