C headers are full of enum definitions. To the C compiler, these are just integers. To the programmer, they have semantic meaning. When binding these functions to Perl, passing magic numbers (like 0, 1, 2) makes your code unreadable. Affix solves this by allowing you to define Enums that generate Perl constants and return dualvars.

The Recipe

Let's wrap a hypothetical state machine library.

use v5.40;
use Affix qw[:all];
use Affix::Build;

# 1. Compile C
my $c = Affix::Build->new();
$c->add( \<<~'END', lang => 'c' );
    typedef enum {
        STATE_IDLE     = 0,
        STATE_RUNNING,
        STATE_PAUSED,
        STATE_ERROR    = 99,
        // Bitmasks
        FLAG_READ      = 1 << 0,  // 1
        FLAG_WRITE     = 1 << 1,  // 2
        FLAG_RDWR      = FLAG_READ | FLAG_WRITE // 3
    } MachineState;

    int set_state(MachineState s) {
        return s; // Returns the new state
    }
END
my $lib = $c->link;

# 2. Define the Enum
# We can use:
#   'NAME'            -> Auto-increments
#   [ NAME => Value ] -> Explicit value
#   [ NAME => 'Expr'] -> C-style expression strings
typedef State => Enum [
    [ STATE_IDLE => 0 ],                           # 0
    'STATE_RUNNING',                               # Auto-set to 1
    'STATE_PAUSED',                                # Auto-set to 2
    [ STATE_ERROR => 99 ],                         # Manually set to 99
    [ FLAG_READ   => '1 << 0' ],                   # Calculated value is 1
    [ FLAG_WRITE  => '1 << 1' ],                   # Calculated value is 2
    [ FLAG_RDWR   => 'FLAG_READ | FLAG_WRITE' ]    # Calculated value is 3
];

# 3. Bind
# We use the typedef alias '@State'
affix $lib, 'set_state', [ State() ] => State();

# 4. Use Constants (Input)
# typedef installs these constants into your package.
set_state( STATE_RUNNING() );
set_state( FLAG_RDWR() );

# 5. Use Dualvars (Output)
# The return value behaves like a number AND a string.
my $current = set_state( STATE_PAUSED() );
if ( $current == 2 ) {
    say 'Numeric check passed.';
}
if ( $current eq 'STATE_PAUSED' ) {
    say 'String check passed.';
}

# Debugging is free
say sprintf 'Current State: %s (%d)', $current, $current;    # Prints "Current State: STATE_PAUSED (2)"

How It Works

Kitchen Reminders