Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Errors

In Javascript, errors are thrown when an error condition is found.

When calling code which can throw, it is good practice to wrap that code in a try/catch block:

try {
    const result = divide(42, 0); // throws
} catch (e: any) {
    // do something with the error.
}

In other languages, e.g. Java or Swift, the method that can throw must declare it on the method signature. e.g.

In Java:

float divide(float top, float bottom) throws MathException {}

while in Swift:

func divide(top: Float, bottom: Float) throws -> Float {}

In Rust, instead of throwing with try/catch, a method returns a Result enum.

#![allow(unused)]
fn main() {
#[derive(uniffi::Error)]
pub enum MathError {
    DivideByZero,
    NumberOverflow,
}

#[uniffi::export]
fn divide(top: f64, bottom: f64) -> Result<f64, MathError> {
    if bottom == 0.0 {
        Err(MathError::DivideByZero)
    } else {
        Ok(top / bottom)
    }
}
}

Enums as Errors

Notice that MathError is not itself a special kind of object. In idiomatic Rust, this is usually an enum.

uniffi-bindgen-react-native converts these types of enums-as-errors in to JS Errors. Due to a limitation in babel, subclasses of Error do not evaluate instanceof as expected. For this reason, each variant has its own instanceOf static method.

try {
    divide(x, y);
} catch (e: any) {
    if (MathError.instanceOf(e)) {
        e instanceof Error; // true
        e instanceof MathError; // false
    }

    if (MathError.DivideByZero.instanceOf(e)) {
        // handle divide by zero
    }
}

Such enums as errors, without properties also have a companion _Tags enum.

Using a switch on the error’s tag property is a convenient way of handling all cases:

try {
    divide(x, y);
} catch (e: any) {
    if (MathError.instanceOf(e)) {
        switch (e.tag) {
            case MathError_Tags.DivideByZero: {
                // handle divide by zero
                break;
            }
            case MathError_Tahs.NumberOverflow: {
                // handle overflow
                break;
            }
        }
    }
}

Enums with properties as Errors

Enums-as-errors may also have properties. These are exactly the same as other enums with properties, except they subclass Error.

e.g.

#![allow(unused)]
fn main() {
enum MyRequestError {
    UrlParsing(String),
    Timeout { timeout: u32 },
    ConnectionLost,
}

#[uniffi::export]
fn make_request() -> Result<String, MyRequestError> {
    // dummy implmentation.
    return Err(MyRequestError::ConnectionLost);
}
}

In typescript:

try {
    makeRequest();
} catch (e: any) {
    if (MyRequestError.instanceOf(e)) {
        switch (e.tag) {
            case MyRequestError_Tags.UrlParsing: {
                console.error(`Url is bad ${e.inner[0]}!`);
                break;
            }
            case MyRequestError_Tags.Timeout: {
                const { timeout } = e.inner;
                console.error(`Timeout after ${timeout} seconds!`);
                break;
            }
            case MyRequestError_Tags.ConnectionLost {
                console.error(`Connection lost!`);
                break;
            }
        }
    }
}

Flat errors

A common pattern in Rust is to convert enum properties to a message. Uniffi calls these error enums flat_errors.

In this example, a MyError::InvalidDataError has no properties but gets the message "Invalid data", ParseError converts its properties in to a message, and JSONError takes any serde_json::Error to make a JSONError, which then gets converted to a string.

In this case, the conversion is being managed by the thiserror crate’s macros.

#![allow(unused)]
fn main() {
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum MyError {
    // A message from a variant with no properties
    #[error("Invalid data")]
    InvalidDataError,

    // A message from a variant with named properties
    #[error("Parse error at line {line}, column {col}")]
    ParseError { line: usize, col: usize },

    // A message from an JSON error, converted into a MyError
    #[error("JSON Error: {0}")]
    JSONError(#[from] serde_json::Error),
}
}

Unlike flat enums, flat errors have a tag property and a companion MyError_Tags enum.

These can be handled in typescript like so:

try {
    // … do sometihng that throws
} catch (err: any) {
    if (MyError.instanceOf(err)) {
        switch (err.tag) {
            case MyError_Tags.InvalidDataError: {
                // e.message will be "MyError.InvalidDataError: Invalid data"
                break;
            }
            case MyError_Tags.ParseError: {
                // e.message will be paramterized, e.g.
                // "MyError.ParseError: Parse error at line 12, column 4"
                break;
            }
            case MyError_Tags.JSONError: {
                // e.message will be a wrapped serde_json error, e.g.
                // "MyError.JSONError: Expected , \" or \]"
                break;
            }
        }
    }
}

Objects as Errors

As you may have gathered, in Rust errors can be anything including objects. In the rare occasions this may be useful:

#![allow(unused)]
fn main() {
#[derive(uniffi::Object)]
pub struct MyErrorObject {
    e: String,
}

#[uniffi::export]
impl MyErrorObject {
    fn message_from_rust(&self) -> String {
        self.e.clone()
    }
}

#[uniffi::export]
fn throw_object(message: String) -> Result<(), MyErrorObject> {
    Err(MyErrorObject { e: message })
}
}

This is used in Typescript, the error itself is not the object.

try {
    throwObject("a message")
} catch (e: any) {
    if (MyErrorObject.instanceOf(e)) {
        // NOPE
    }
    if (MyErrorObject.hasInner(e)) {
        const error = MyErrorObject.getInner(e);
        MyErrorObject.instanceOf(error); // true
        console.error(error.messageFromRust())
    }
}

Rust Error is renamed as Exception in typescript

To avoid collisions with the ECMAScript standard Error, any Rust enums, objects and records called Error are renamed Exception.