Perl is great for glue; Rust is great for logic.
Historically, binding Rust to Perl required complex XS modules or specific crates like perl-xs. With Affix::Build, you can treat Rust just like C. By using standard ABI hooks (extern "C"), we can compile and run Rust code dynamically without ever leaving the Perl script.
The Recipe
Let's define a simple Rust library that operates on a Struct and handles strings.
use v5.40;
use Affix qw[:all];
use Affix::Build;
$|++;
# 1. Compile Rust
# Affix::Build detects the 'rs' extension and invokes `rustc`.
my $c = Affix::Build->new( name => 'rust_demo' );
# We define a struct to share between Rust and Perl
$c->add( \<<~'RUST', lang => 'rs' );
use std::ffi::CStr;
use std::os::raw::c_char;
// We must define the struct layout to match C
#[repr(C)]
pub struct Point {
x: i32,
y: i32,
}
// #[no_mangle] keeps the symbol name "add_points"
// extern "C" ensures the ABI uses standard registers
#[no_mangle]
pub extern "C" fn add_points(a: Point, b: Point) -> Point {
Point {
x: a.x + b.x,
y: a.y + b.y
}
}
// Example of reading a Perl string
// We accept a raw pointer (*const c_char)
#[no_mangle]
pub unsafe extern "C" fn hello_rust(name: *const c_char) {
if !name.is_null() {
let c_str = CStr::from_ptr(name);
if let Ok(s) = c_str.to_str() {
println!("Hello, {} from Rust!", s);
}
}
}
RUST
my $lib = $c->link;
# 2. Define Types
typedef Point => Struct [ x => Int, y => Int ];
# 3. Bind
# We treat Rust structs exactly like C structs.
affix $lib, 'add_points', [ Point(), Point() ] => Point();
affix $lib, 'hello_rust', [String] => Void;
# 4. Execute
my $p1 = { x => 10, y => 20 };
my $p2 = { x => 5, y => 5 };
# Pass by value
my $p3 = add_points( $p1, $p2 );
say "Result: $p3->{x}, $p3->{y}"; # 15, 25
# Pass string
hello_rust('Perl');
How It Works
-
1.
#[repr(C)]Rust creates its own memory layout for structs by default, which usually does not match C (or Affix). Adding this attribute forces the compiler to lay out the struct exactly like a C struct, ensuring Affix can write to fieldsxandycorrectly. -
2.
#[no_mangle]andextern "C"Just like C++, Rust mangles function names (e.g.,_ZN7example3add...) to support namespaces and generics.#[no_mangle]turns off name mangling, keeping the symbol asadd_points.extern "C"sets the calling convention to the system standard (cdecl/SystemV).
-
3. Pointers and Unsafe When accepting pointers from Perl (like the string in
hello_rust), Rust requires anunsafeblock to dereference them. Affix handles the memory management of the string buffer (ensuring it is NULL-terminated); Rust just reads it.
Kitchen Reminders
-
Panics If your Rust code panics (crashes), it may "unwind" the stack across the FFI boundary. This is undefined behavior and will likely abort the Perl interpreter immediately. Pro Tip: Use
std::panic::catch_unwindinside yourextern "C"functions to catch Rust errors and return a clean error code to Perl instead of crashing. -
Windows MinGW If you are using Strawberry Perl,
Affix::Buildautomatically handles the ABI mismatch between Rust (which defaults to MSVC on Windows) and Perl (which uses GCC/MinGW), ensuring the libraries link correctly.