DEV Community

Cover image for Have you used Rust (or Go, or anything else) and just felt relieved by how they treat errors?

Have you used Rust (or Go, or anything else) and just felt relieved by how they treat errors?

Daniel Reis on March 31, 2025

Alright, I need to vent. I've reflecting about engineering recently I swear, exception handling in PHP feels like duct taping over a leaking pipe. ...
Collapse
 
lucasayabe profile image
lucas-ayabe

at this point you should just return a tuple (like Go and Python do), there's no value to have a class that is a giant DTO with some predicate methods.

I really like how Result makes error handling better by using the power of monads to do the trick, but this implementation isn't a monad, and its bad.

You don't need the isOk attribute since it let you introduce invariants on your code, what happens if you have an error and isOk is true? I know that the custom constructors ensure that this won't happen, but it wasn't needed have this flaw there to have the extra care to prevent after.

It would've be better if you checked if there's a value in error or in value inside the isOk method to have this info, and even better than that if you made the Result an abstract class and implemented Ok and Err as subclasses since you would need to only one attribute instead of 2 making any ambiguity in the code non-existent.

Having an unwrap method makes so that this code is as bad as using exceptions, if not worse, since you can just write an if ($result->isOk()) ignore the else, and never handle the error, being literally the same as one exception, with the added drawback that at least try catch lets you catch the exceptions by type, here the errors are strings so you would need to have a lot of constants to be checked inside an switch/match to handle different types of errors if you ever compose result values (it can be fixed by using enums as errors, but with try catch you already has this out-the-box).

A better implementation would be a fold method instead of an unwrap, ex:

abstract class Result
{
    public static function ok($value): self
    {
        return new Ok($value);
    }

    public static function err($value): self
    {
        return new Err($value);
    }

    abstract function isOk(): bool;

    public function isErr(): bool
    {
        return !this->isOk();
    }

    abstract function fold(callable $ok, callable $err): mixed;
}

class Ok extends Result
{
    public function __construct(private mixed $value) {}

    public function isOk(): bool
    {
        return true;
    }

    public function fold(callable $ok, callable $err): mixed
    {
        return $ok($this->value);
    }
}

class Err extends Result
{
    public function __construct(private mixed $error) {}

    public function isOk(): bool
    {
        return false;
    }

    public function fold(callable $ok, callable $err): mixed
    {
        return $err($this->error);
    }
}

function div(int $x, int $y): Result
{
    if ($y === 0) {
        return Result::err("Cannot divide by 0");
    }

    return Result::ok($x / $y);
}

div(1, 0)->match(
    err: fn (string $message) => print($message),
    ok: fn ($value) => print($value)
);
Enter fullscreen mode Exit fullscreen mode

This actually emulates how Rust and the functional approach requires you to handle the error, you can't get the value from inside the Result without doing pattern matching (in this case emulated through a method).

Collapse
 
xwero profile image
david duymelinck

I was confused by div(1, 0)->match( I think this should be div(1, 0)->fold(.

Collapse
 
lucasayabe profile image
lucas-ayabe

oh, yeah, you're right, i wrote this from head, and didn't noticed the typo

Collapse
 
xwero profile image
david duymelinck • Edited

With great power comes great responsibility.
If thrown exceptions go lost in the application, it is most of the times a deliberate action by a developer. Php is not the problem.

I think the Result class is fine if you choose the monads route. Another option is to use multiple return types.

class validator 
{
    public function validate() : true|InvalidFields
    {
        // return true or add one or more messages to the InvalidFields object.
    }
}
Enter fullscreen mode Exit fullscreen mode

I would use those options if the warnings are going to picked up by the application.

The power of choosing names for the exceptions makes them more understandable. And that also makes it clear for developers why the exception is thrown.
Only having a RuntimeException when something goes wrong is not enough.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Personally, I quite like how zig does errors

Collapse
 
plutonium239 profile image
plutonium239

The "errors are values" paradigm grew most with go iirc, I'm sure it's not the first language to do so but definitely one of the biggest

Collapse
 
phil_coder_fbe725ee08b389 profile image
Phil Coder

I'm old enough to remember when PHP was hot and ruled the web and a lot of app dev for a while. I also remember moving a major company off PHP (for one system) to Go. I'm curious as to why anyone hangs around on PHP these days.

When I came out of college it was the cheapest way to get going. Now you have all this free and open language tooling that is far safer. Why add to the massive amount of bolting on that has to be done to make it better? I'd say it's time to let PHP and Java die. This is from someone who was once like a PHP guru.

Collapse
 
luqmanshaban profile image
Luqman Shaban • Edited

For all the languages I’ve tried out, Go, by far has the best error handling

Collapse
 
shricodev profile image
Shrijal Acharya

+1

Collapse
 
zaunere profile image
hz

Very much in favor of a new engine for PHP.

Collapse
 
peixotons profile image
Gabriel Peixoto

Really awesome Daniel