Hare by Example: Errors

Errors are a special type in Hare that can be use in conjunction with match in order to properly handle, propagate, and assert errors. You may handle errors much like you can do with Rust with the ? or ! operators to propogate or assert no errors respectively. However, matching on errors is much more powerful.

use fmt;
use fs;
use io;
use errors;
use os;

// This function attempts to open a bad file we will know to cause an
// error and we propogate that error up with the ? operator since out
// function has fs::error as a possible return type.
fn open_bad_file() (void | fs::error) = {
	os::open("/tmp/bad-file")?;
};

export fn main() void = {
	// Use the ! to assert there should be no error, otherwise, the
	// programm will abort immediately.
	fmt::println("This should not fail")!;

	match(open_bad_file()) {
	case fs::error =>
		fmt::println("Yup, we know")!;
	case void =>
		fmt::println("Will not get here")!;
	};

	// If we try to open a file that does not exist we will get an
	// error based on the os::open() documentation which we can
	// match on each case to handle accordingly
	match (os::open("/tmp/does-not-exist.txt")) {

	// If the file exists, which it shouldn't then we'll get here
	case io::file =>
		fmt::println("The file exists to get a handle to it!")!;

	// Since the file doesn't exist we'll end up here
	case fs::error =>
		fmt::println("So sorry, something happened")!;
	};

	// We can still assign a local variable to the case results like
	// any other `match` and then use the file handle. However, this
	// can lead to awkway match nesting as we handle errors.
	match (os::open("./errors.ha")) {
	case let f: io::file =>
		fmt::println("Awkward nesting occurs.")!;
		// match (io::copy(os::stdout, f)) {
		// case let sz: size =>
		// 	fmt::printf("Wrote {} bytes to stdout", sz)!;
		// case io::error =>
		// 	fmt::fatal("Derp, we got an io error");
		// };

		// io::close(f)!;
	case fs::error =>
		fmt::fatal("It should not error");
	};

	// Instead of using the file directly within the case we can
	// `yield` the results back up to assign it to a variable to be
	// use later on.
	const f = match(os::open("./errors.ha")) {
	case let f: io::file =>
		yield f;
	case fs::error =>
		fmt::fatal("It should not error");
	};

	// Now we can use f from above outside the case branch to keep
	// going on and copy the file contents to stdout.
	match (io::copy(os::stdout, f)) {
	case let sz: size =>
		fmt::printf("Wrote {} bytes to stdout", sz)!;
	case io::error =>
		fmt::fatal("Derp, we got an io error");
	};

	io::close(f)!;
};
$ hare run errors.ha
4/4 tasks completed (100%)
This should not fail
Yup, we know
So sorry, something happened
Awkward nesting occurs.
use fmt;
use fs;
use io;
use errors;
use os;

// This function attempts to open a bad file we will know to cause an
// error and we propogate that error up with the ? operator since out
// function has fs::error as a possible return type.
fn open_bad_file() (void | fs::error) = {
        os::open("/tmp/bad-file")?;
};

export fn main() void = {
        // Use the ! to assert there should be no error, otherwise, the
        // programm will abort immediately.
        fmt::println("This should not fail")!;

        match(open_bad_file()) {
        case fs::error =>
                fmt::println("Yup, we know")!;
        case void =>
                fmt::println("Will not get here")!;
        };

        // If we try to open a file that does not exist we will get an
        // error based on the os::open() documentation which we can
        // match on each case to handle accordingly
        match (os::open("/tmp/does-not-exist.txt")) {

        // If the file exists, which it shouldn't then we'll get here
        case io::file =>
                fmt::println("The file exists to get a handle to it!")!;

        // Since the file doesn't exist we'll end up here
        case fs::error =>
                fmt::println("So sorry, something happened")!;
        };

        // We can still assign a local variable to the case results like
        // any other `match` and then use the file handle. However, this
        // can lead to awkway match nesting as we handle errors.
        match (os::open("./errors.ha")) {
        case let f: io::file =>
                fmt::println("Awkward nesting occurs.")!;
                // match (io::copy(os::stdout, f)) {
                // case let sz: size =>
                //      fmt::printf("Wrote {} bytes to stdout", sz)!;
                // case io::error =>
                //      fmt::fatal("Derp, we got an io error");
                // };

                // io::close(f)!;
        case fs::error =>
                fmt::fatal("It should not error");
        };

        // Instead of using the file directly within the case we can
        // `yield` the results back up to assign it to a variable to be
        // use later on.
        const f = match(os::open("./errors.ha")) {
        case let f: io::file =>
                yield f;
        case fs::error =>
                fmt::fatal("It should not error");
        };

        // Now we can use f from above outside the case branch to keep
        // going on and copy the file contents to stdout.
        match (io::copy(os::stdout, f)) {
        case let sz: size =>
                fmt::printf("Wrote {} bytes to stdout", sz)!;
        case io::error =>
                fmt::fatal("Derp, we got an io error");
        };

        io::close(f)!;
};
Wrote 2262 bytes to stdout