PHPackages                             helloimalemur/rustify-php - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. helloimalemur/rustify-php

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

helloimalemur/rustify-php
=========================

Rust-like Option and Result types for PHP

v0.0.4(5mo ago)015PHPPHP &gt;=8.0

Since Dec 7Pushed 5mo agoCompare

[ Source](https://github.com/helloimalemur/rustify-php)[ Packagist](https://packagist.org/packages/helloimalemur/rustify-php)[ RSS](/packages/helloimalemur-rustify-php/feed)WikiDiscussions main Synced 1mo ago

READMEChangelogDependencies (1)Versions (5)Used By (0)

rustify-php
===========

[](#rustify-php)

Rust-like `Option` and `Result` types for PHP 8+.

Fully compatible with typical PHP code

You can: • store Option or Result in arrays • return them from any function • use them as method return types • wrap exceptions with them • integrate with your Rust backend patterns (structural symmetry)

Option and Result behave like normal objects but with Rust semantics.

Install
-------

[](#install)

```
composer require helloimalemur/rustify-php
```

Usage
-----

[](#usage)

```
use Rustify\Json;
use function Rustify\{ok, err, some, none};

$raw = '{"name":"James"}';

$res = Json::decodeObject($raw);

if ($res->isErr()) {
    $err = $res->unwrapErr();
    // handle error
} else {
    $data = $res->unwrap();
    // handle data
}
```

### Construct Options

[](#construct-options)

```
use Rustify\Option;
use function Rustify\{some, none};

$optA = some("value");
$optB = none();

if ($optA->isSome()) {
    $v = $optA->unwrap();
}
```

### Functional transforms:

[](#functional-transforms)

```
$nameOpt = some("James")
    ->map(fn($name) => strtoupper($name))
    ->andThen(fn($name) => strlen($name) > 0 ? some($name) : none());

$final = $nameOpt->unwrapOr("Unknown");
```

### Construct Results

[](#construct-results)

```
use Rustify\Result;
use function Rustify\{ok, err};

function open_file(string $path): Result
{
    return is_readable($path)
        ? ok(file_get_contents($path))
        : err("File not readable");
}

$res = open_file("config.json");

if ($res->isOk()) {
    $content = $res->unwrap();
} else {
    $error = $res->unwrapErr();
}
```

### Chaining:

[](#chaining)

```
$parsed = open_file("config.json")
    ->andThen(fn($s) => ok(json_decode($s, true)))
    ->map(fn($arr) => $arr["name"] ?? "none")
    ->unwrapOr("missing");
```

### Eager vs lazy fallbacks (`orElseValue` vs `orElse`)

[](#eager-vs-lazy-fallbacks-orelsevalue-vs-orelse)

There are two ways to provide a fallback for `Option` and `Result`:

- Eager: provide another value right away using `orElseValue(...)`.
- Lazy: provide a closure that will be called only if needed using `orElse(...)`.

Option example:

```
use function Rustify\{some, none, option_or_else_value};

$a = some('A');
$b = some('B');

// Eager: returns $a because it's Some
$x = $a->orElseValue($b);         // Some('A')

// Lazy: closure will NOT be called because $a is Some
$y = $a->orElse(fn() => some('C')); // Some('A')

// When None, eager uses provided value; lazy executes the closure
$n = none();
$x2 = $n->orElseValue($b);          // Some('B')
$y2 = $n->orElse(fn() => some('C')); // Some('C')

// The same using the helper function:
$x3 = option_or_else_value($a, $b); // Some('A')
$x4 = option_or_else_value($n, $b); // Some('B')
```

Result example (note: the lazy version receives the error):

```
use function Rustify\{ok, err, result_or_else_value};

$r1 = ok(10);
$r2 = err('network');

// Eager: value provided upfront
$a = $r1->orElseValue(ok(0)); // Ok(10)
$b = $r2->orElseValue(ok(0)); // Ok(0)

// The same using the helper function:
$a2 = result_or_else_value($r1, ok(0)); // Ok(10)
$b2 = result_or_else_value($r2, ok(0)); // Ok(0)

// Lazy: closure gets the error when called
$c = $r1->orElse(fn(string $e) => ok(0));     // Ok(10), closure not called
$d = $r2->orElse(fn(string $e) => ok(strlen($e))); // Ok(7)

// Likewise for unwrap defaults:
$n = $r2->unwrapOrElse(fn(string $e) => strlen($e)); // 7
```

### Using Option and Result for API validation

[](#using-option-and-result-for-api-validation)

```
function validateUser(array $body): Result
{
    if (!isset($body["email"]) || !is_string($body["email"])) {
    return err("Invalid email");
}
    if (!isset($body["name"]) || !is_string($body["name"])) {
    return err("Invalid name");
}

    return ok([
        "name" => $body["name"],
        "email" => $body["email"],
    ]);
}
```

### Using helpers (if\_some, if\_ok)

[](#using-helpers-if_some-if_ok)

```
use function Rustify\{if_some, if_ok};

$maybeToken = some("abc123");

if_some($maybeToken, function ($token) {
    error_log("Token = $token");
});

$result = ok(42);

if_ok($result, function ($v) {
    echo "Result: $v";
});
```

### Match-like helpers (option\_match, result\_match)

[](#match-like-helpers-option_match-result_match)

```
use function Rustify\{option_match, result_match};

$opt = none();

$msg = option_match(
    $opt,
    fn($v) => "Some: $v",
    fn()   => "None"
);

$res = err("bad input");

$message = result_match(
    $res,
    fn($v) => "OK: $v",
    fn($e) => "ERR: $e"
);
```

Catching and logging errors with Result/Option
----------------------------------------------

[](#catching-and-logging-errors-with-resultoption)

You can keep exceptions out of your core flow by returning `Result`/`Option` and logging at the boundary (controller/CLI/job). Here are a few idioms you can mix and match:

### Log errors without changing the value using `ifErr`

[](#log-errors-without-changing-the-value-using-iferr)

```
use Rustify\Result;
use function Rustify\{ok, err};

function doWork(): Result
{
    // ... return ok($value) or err($reason)
}

$res = doWork();

$res->ifErr(function ($e) {
    // $e can be a string, array, or an Exception/Throwable you put there
    error_log('[doWork] failed: ' . (is_string($e) ? $e : (is_object($e) ? $e->getMessage() : json_encode($e))));
});

// Continue with a default if needed
$value = $res->unwrapOr('default');
```

### Transform or annotate the error while logging using `mapErr`

[](#transform-or-annotate-the-error-while-logging-using-maperr)

```
$res = doWork()
    ->mapErr(function ($e) {
        error_log('[doWork] error: ' . (is_string($e) ? $e : (is_object($e) ? $e->getMessage() : json_encode($e))));
        // Optionally normalize to a domain error type/value
        return is_string($e) ? $e : 'internal_error';
    });
```

### Recover from an error while logging using `orElse`

[](#recover-from-an-error-while-logging-using-orelse)

```
use function Rustify\{ok};

$safe = doWork()
    ->orElse(function ($e) {
        error_log('[doWork] recovered: ' . (is_string($e) ? $e : (is_object($e) ? $e->getMessage() : json_encode($e))));
        return ok('fallback'); // provide a fallback Ok value
    })
    ->unwrap(); // safe because we've recovered
```

### One‑liner logging with defaults using `unwrapOrElse`

[](#oneliner-logging-with-defaults-using-unwraporelse)

```
$value = doWork()->unwrapOrElse(function ($e) {
    error_log('[doWork] defaulted: ' . (is_string($e) ? $e : (is_object($e) ? $e->getMessage() : json_encode($e))));
    return 'default';
});
```

### Catch exceptions once, convert to `Result`, then log via the patterns above

[](#catch-exceptions-once-convert-to-result-then-log-via-the-patterns-above)

```
use Rustify\Result;
use function Rustify\{ok, err};

function parseJsonSafe(string $raw): Result
{
    try {
        $data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
        return ok($data);
    } catch (\Throwable $e) {
        return err($e); // store the Throwable in Err
    }
}

$res = parseJsonSafe($input);
$data = $res->unwrapOrElse(function ($e) {
    // $e is the Throwable we stored
    error_log('[parseJsonSafe] ' . $e->getMessage());
    return [];
});
```

### Optional values: log when a value is missing using the `ifNone` pattern

[](#optional-values-log-when-a-value-is-missing-using-the-ifnone-pattern)

```
use function Rustify\{some, none};

/** @return Rustify\Option */
function maybeEnv(string $key)
{
    $v = getenv($key);
    return $v === false ? none() : some($v);
}

$opt = maybeEnv('API_TOKEN');
$opt->ifNone(fn() => error_log('[env] API_TOKEN is not set'));
$token = $opt->unwrapOr('');
```

- Prefer logging at the application boundary (controllers, handlers, CLI commands) rather than deep inside pure functions. This keeps core code testable and composable.
- Use `mapErr` when you need to add context as the error bubbles up, `orElse` when you can safely recover with a fallback, and `unwrapOrElse` for concise defaulting with logging.

WHY!?
=====

[](#why)

Null is not a value. It is an absence of information. It carries zero context about what and why something is, where it came from or what state the system is in.

Option and Result turn absence into structured flow, explicit alternate paths, recoverable and predictable outcomes. They convey intent, preserve context, and shorten debugging time.

### Option represents an intentional “maybe”:

[](#option-represents-an-intentional-maybe)

```
Some(value) → a value is present
None → a value is intentionally absent

```

### Result represents the outcome of an operation:

[](#result-represents-the-outcome-of-an-operation)

```
Ok(value) → the operation succeeded
Err(error) → the operation failed, with a meaningful reason

```

### By using Option and Result, the purpose of a function becomes explicit.

[](#by-using-option-and-result-the-purpose-of-a-function-becomes-explicit)

#### They eliminate ambiguity, prevent silent failures, and preserve information that null would throw away.

[](#they-eliminate-ambiguity-prevent-silent-failures-and-preserve-information-that-null-would-throw-away)

```
it’s practical
it reduces bugs
it simplifies reasoning
it improves interfaces
it prevents hours of debugging
it’s how you write reliable APIs

```

### This is not “Functional Programming nonsense”, this is industry standard.

[](#this-is-not-functional-programming-nonsense-this-is-industry-standard)

```
Swift has Optional
Kotlin has Nullable with smart handling
Rust has Option and Result
Haskell, Elm, OCaml all use Maybe/Result
TypeScript uses | undefined but companies implement Option for safety

```

### Null introduces silent ambiguity

[](#null-introduces-silent-ambiguity)

Null can mean any number of different things, but the language gives you no way to distinguish them. It could signal an error, missing data, invalid input, or an uninitialized value—yet all of these collapse into the same opaque null.

Example:

```
function findUser(int $id) {
    if ($id === 1) return ['id'=>1, 'name'=>'James'];
    return null;
}
```

What does null mean??? user not found?.. DB error?.. invalid input?.. developer forgot a return statement?.. exception swallowed somewhere?..

In production debugging, you cannot tell which one happened.

null destroys signal clarity.

### Null forces defensive code everywhere

[](#null-forces-defensive-code-everywhere)

code you’ve written a thousand times:

```
$user = findUser($id);
    if ($user === null) {
    // handle maybe-error maybe-not-error?
}
```

Your brain has to constantly do “Is null okay here, or is null an error?” This cognitive cost multiplies across the codebase.

### Null often fails far away from where the real problem happened

[](#null-often-fails-far-away-from-where-the-real-problem-happened)

Consider: `Fatal error: Call to a member function foo() on null`. The real problem happened in a function 3 layers earlier, but the crash happened way later when dereferencing was attempted.

Option and Result prevent this entirely because they force handling.

### Null causes production bugs that are extremely hard to trace

[](#null-causes-production-bugs-that-are-extremely-hard-to-trace)

```
$total = $invoice['amount'] + $invoice['tax'];
```

If `$invoice` was null, you get a warning or `TypeError`. But logs don’t tell why it was null. No context survives and you lose the root cause.

Result fixes this by carrying failure information forward:

```
return err("Invoice not found");
```

You cannot lose the root cause.

### Option makes “maybe” explicit

[](#option-makes-maybe-explicit)

When something is optional, the type should say so, not the comments, not the mental model, not tribal knowledge.

Example:

```
function maybeGetEmail(User $u): ?string

// vs

function maybeGetEmail(User $u): Option
```

### Which one communicates meaning?

[](#which-one-communicates-meaning)

```
?string → maybe string, maybe null, maybe error, figure it out yourself
Option → explicitly either Some(string) or None, handle both

```

### Result replaces exceptions and null-return error codes with structured information

[](#result-replaces-exceptions-and-null-return-error-codes-with-structured-information)

Exceptions in hot paths suck. null for errors is painful. Result gives a middle ground:

$result = doThing();

```
if ($result->isErr()) {
    return $result->unwrapErr();
}
```

#### This is the same reason Go developers use error returns:

[](#this-is-the-same-reason-go-developers-use-error-returns)

```
explicit
linear
predictable
no hidden control flow

```

### Result makes success and failure equally obvious

[](#result-makes-success-and-failure-equally-obvious)

```
return err("Invalid request");
return ok($user);
```

Both outcomes are visible. No ambiguity. No surprises.

### Developers get better autocomplete and static analysis

[](#developers-get-better-autocomplete-and-static-analysis)

PHPStan/Psalm can reason about:

```
Option
Result
```

But they cannot reason about null except “maybe null, maybe not.”

### So with Option/Result:

[](#so-with-optionresult)

```
fewer runtime errors
more pre-runtime catches
more confident refactoring

```

###  Health Score

30

—

LowBetter than 64% of packages

Maintenance71

Regular maintenance activity

Popularity6

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity33

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

4

Last Release

162d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6727f5a444ef352bf727f90b92865c156b2e4c8b0ea503abb17fd43446ae887e?d=identicon)[helloimalemur](/maintainers/helloimalemur)

---

Top Contributors

[![helloimalemur](https://avatars.githubusercontent.com/u/37273704?v=4)](https://github.com/helloimalemur "helloimalemur (11 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/helloimalemur-rustify-php/health.svg)

```
[![Health](https://phpackages.com/badges/helloimalemur-rustify-php/health.svg)](https://phpackages.com/packages/helloimalemur-rustify-php)
```

###  Alternatives

[fale/isbn

ISBN library

88524.8k6](/packages/fale-isbn)[roomify/bat

A booking and availability management library

156146.0k](/packages/roomify-bat)[thathoff/kirby-git-content

Plugin to track changes to content in a git repository.

15343.7k](/packages/thathoff-kirby-git-content)[tuupola/base32

Base32 encoder and decoder for arbitrary data

14552.2k6](/packages/tuupola-base32)[dmamontov/asynctask-7

AsyncTask enables proper and easy use of the thread. This class allows to perform background operations and publish results on the thread without having to manipulate threads and/or handlers.

1313.1k](/packages/dmamontov-asynctask-7)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
