--- name: The no-XS rule --- ```{index} single: XS; not loadable single: XSLoader::load; under pperl single: dynamic loading not available in this perl ``` # The no-XS rule, and the shapes you will meet pperl cannot load a compiled extension. This is structural, not a setting: XS binds to perl5's C-level ABI, and pperl is a Rust reimplementation of Perl 5 that does not have one to bind to. An `XSLoader::load` under pperl fails with the message such modules are written to expect: ```text dynamic loading not available in this perl ``` What that means for an install depends entirely on how the distribution is put together. There are five shapes in the wild and they behave very differently, so it is worth knowing which one you are looking at. ## 1. Pure Perl Nothing to say. It installs and runs exactly as it does anywhere else. This is the large majority of CPAN. ```{index} single: XS; optional accelerator ``` ## 2. A Perl module with an optional XS accelerator The module is Perl; a *separate* distribution speeds it up if present. `Readonly` and `ReadonlyXS` are the classic pair. Install the Perl one and ignore the accelerator. The module probes for its companion, does not find it, and uses its own implementation - which is the documented, supported path, not a degraded mode. ```bash pperl --cpan Readonly # yes pperl --cpan ReadonlyXS # no, and not needed ``` This is the shape to prefer when you have a choice of dependency. The Perl module is the module, and XS is an optimisation. ```{index} single: XS; pure-Perl fallback ``` ## 3. One distribution, both halves, XS optional at build time The distribution ships C *and* Perl, and its `Makefile.PL` treats the compile as optional: if the compiler step fails or is declined, it installs the Perl half and carries on. At runtime the module notices the XS is absent and uses its own fallback. `Cookie::Baker` and `WWW::Form::UrlEncoded` are of this shape. They install cleanly under pperl. ```{index} single: XS; mandatory build single: PERL_ONLY ``` ## 4. One distribution, both halves, XS mandatory at build time Same idea, but the build compiles C unconditionally. The compile fails, the installer aborts, and with an ordinary installer the Perl half - which would have worked - never reaches disk. `HTTP::Parser::XS` is the worked example. pperl implements it natively now, so you will not hit it in practice - an installer sees 0.17 already present and never fetches the distribution at all. It is still the clearest illustration of the shape, because the failure is not where you would guess. Its runtime logic is impeccable: ```perl eval { require XSLoader; XSLoader::load(__PACKAGE__, $VERSION); $BACKEND = 'xs'; }; die $@ if $@ && $BACKEND =~ /\bxs\b/; # only if you FORCED xs if (not __PACKAGE__->can('parse_http_response')) { require HTTP::Parser::XS::PP; # automatic fallback $BACKEND = 'pp'; } ``` Given the files on disk, that falls back by itself, with no intervention. The problem is purely that `Makefile.PL` builds picohttpparser unconditionally, so the files never get there. Its `PERL_ONLY=1` switch selects the runtime backend and drives a test target; it does not make the build skip the C. :::{note} A native implementation is the escape hatch, and it is the one that was used here. But it only holds while pperl's version satisfies what dependents ask for: pperl reports the native version, so a dependency on `HTTP::Parser::XS >= 0.18` would send the installer after the distribution again and the abort would come back. ::: ```{index} single: --cpan; Perl part of a failed XS build ``` A shape-4 distribution pperl does *not* implement natively installs all the same, because pperl's `ExtUtils::MakeMaker` never compiles. Under pperl a `Makefile.PL` produces a Makefile for the Perl half only: no compiler, no perl header files and no `xsubpp` are looked for, so the build cannot fail at the C. Two things then decide whether the distribution is installed: - every module in it that names an XS loader is loaded once. One that falls back to Perl, as above, loads. One that is only the front of its compiled part does not: it is left out and named, and the rest of the distribution goes on; - the distribution's own tests run, as for any other module. ```text pperl: left out, because without the compiled part, which pperl cannot build, it does not load: Some/Dist/Fast.pm ``` A distribution built with `Module::Build` still attempts its compile and fails. For those, `--cpan` installs the Perl files the build had already copied into its `blib` directory, after the same load check, and says so: ```text pperl: Some-Dist-1.23-0: XS build failed; installed its 4 pure-Perl file(s) ``` That run ends with a failure status, because the distribution's tests never ran: test what was installed before you rely on it. ## 5. XS only No Perl implementation exists anywhere in the distribution. There is nothing to fall back to and nothing pperl can do with it. The load check leaves no module over, and the build stops: ```text pperl: left out, because without the compiled part, which pperl cannot build, it does not load: String/CRC32.pm pperl: not built: no module of this distribution loads without its compiled part ``` Nothing of the distribution is installed. ```{index} single: XS; alternatives single: Peta::FFI ``` ## When you actually need the speed The reason XS exists is performance, so it is fair to ask what replaces it. pperl's answer has three parts, and none of them is "write it in Perl and hope". - **Native modules.** About a hundred of the modules people reach for XS to get are already Rust inside the binary, at Rust speed, with no build step. See [what pperl already has](already-there). - **The JIT.** Hot loops are compiled, so the pure-Perl version of an algorithm is not automatically the slow version. See the [performance guide](../../guide/performance/index). - **`Peta::FFI`.** When you genuinely need a C library, call it directly rather than wrapping it in XS. See the [FFI primer](../../guide/graphics/ffi-graphics-primer). If you hit a module in shape 4 or 5 that you cannot work around, that is worth reporting: