Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala's Option or Rust's Result error handling in C [closed]

I started writing C again after professionally doing Scala and Python for quite some time. After using Scala's "Option" and "Either" error handling patterns and recently tasted the Rust way I wanted something similar in C. So I came up with something like this:

typedef struct {
    int age;
    char* name;
} User;

typedef struct {
    char* error;
    User* value;
} UserResult;

UserResult get_user() {
    /* Some complicated user fetching process .. that fails */
    return (UserResult) { .error = "403 Unauthorized\n" };
}

int main(void) {
    UserResult res = get_user();
    if (res.error)
        handle_error(res.error);
    if (res.value)
        do_something(res.value);
    /* ... */
    return 0;
}

But this isn't really safe (we could access invalid pointers). How can I get something similar than the Scala or Rust way of handling errors in C ?

EDIT: corrected UserResult field name "value"

like image 713
lemour_a Avatar asked Apr 12 '20 21:04

lemour_a


People also ask

What is unwrap() in Rust?

To “unwrap” something in Rust is to say, “Give me the result of the computation, and if there was an error, panic and stop the program.” It would be better if we showed the code for unwrapping because it is so simple, but to do that, we will first need to explore the Option and Result types.

What does OK Do rust?

It is an enum with the variants, Ok(T) , representing success and containing a value, and Err(E) , representing error and containing an error value. Functions return Result whenever errors are expected and recoverable.


2 Answers

There are many ways to handle errors in existing C code in the wild. So many that it represents a non neglectable mental load to understand how to handle errors for various libraries, even more so when using several at the same time in a project.

Introducing a new way of handling errors could be beneficial, but on the flip side, it could be akin to this famous XKCD comic.

The Rust (and Scala, etc) way is indeed very powerful and has a few advantages over other older idioms.

However, the Result idiom is based on Sum Types, which are not natively supported by the C language. There is no guarantee that your UserResult is in a coherent state. user and error could be both null, or non-null.

This could be modified a bit, for example by using a union instead:

typedef struct{
    union {
        char* error;
        User user;
    }
    bool has_error;
} UserResult;

The has_error flag indicates which field is actually filled:

int main(void) {
    UserResult res = get_user();
    if (res.has_error)
        handle_error(res.error);
    else
        do_something(res.value);
    /* ... */
    return 0;
}

This is not perfect, as the C language does not enforce proper union access, instead invoking undefined behaviour when used incorrectly.

like image 136
SirDarius Avatar answered Sep 29 '22 02:09

SirDarius


The problem is even with UserResult model in C the onus is still on the programmer to remember to do null-checks, that is, compiler is not enforcing it, so even though we declared UserResult nothing prevents us to mistakenly write

int main(void) {
  UserResult res = get_user();
  do_something(res.value); // Runtime error: oops I forgot to null-check and unfortunately compiler is still happy
  return 0;
}

whilst with Scala's Option programmer is forced by the compiler to deal with the situation, for example,

case class User(age: Int, name: String)
case class UserResult(value: Option[User], error: Option[String])

def get_user(): UserResult = {
  /* Some complicated user fetching process .. that fails */
  UserResult(value = None, error = Some("403 Unauthorized"))
}

def do_something(user: User) = ???

object Main extends App {
  val res = get_user()
  do_something(res.value) // Compile-time error: oops I forgot to "null-check" but fortunately the compiler is unhappy
}
like image 22
Mario Galic Avatar answered Sep 29 '22 01:09

Mario Galic