Errors¶
A Rust function that returns Result<T, E> throws in Kotlin. E must be an exported error type.
Error enums¶
#[derive(Debug, thiserror::Error, uniffi::Error)]
pub enum ArithmeticError {
#[error("Integer overflow on {a} + {b}")]
IntegerOverflow { a: u64, b: u64 },
#[error("Division by zero")]
DivisionByZero,
}
#[uniffi::export]
pub fn add(a: u64, b: u64) -> Result<u64, ArithmeticError> {
a.checked_add(b).ok_or(ArithmeticError::IntegerOverflow { a, b })
}
sealed class ArithmeticException : kotlin.Exception() {
class IntegerOverflow(val a: ULong, val b: ULong) : ArithmeticException()
class DivisionByZero : ArithmeticException()
}
try {
add(ULong.MAX_VALUE, 1u)
} catch (e: ArithmeticException.IntegerOverflow) {
println("${e.a} + ${e.b} overflows")
}
- An error type is always a
sealed classextendingkotlin.Exception, with one subclass per variant. - A trailing
Errorin the name becomesException:ArithmeticErrorturns intoArithmeticException. This applies to error enums and to objects used as errors. - For variants with fields,
messagelists the fields (a=1, b=2).
Flat errors¶
If only the error message matters, mark the enum flat_error. The variants lose their fields and
carry the Rust Display output as message:
#[derive(Debug, thiserror::Error, uniffi::Error)]
#[uniffi(flat_error)]
pub enum NetworkError {
#[error("timed out after {0:?}")]
Timeout(std::time::Duration),
#[error("connection refused")]
Refused,
}
sealed class NetworkException(message: String) : kotlin.Exception(message) {
class Timeout(message: String) : NetworkException(message)
class Refused(message: String) : NetworkException(message)
}
This is useful when a variant holds something that can't cross the FFI.
Objects as errors¶
An object can be an error too. This is handy for wrapping
anyhow::Error and keeping methods on it:
#[derive(Debug, thiserror::Error, uniffi::Object)]
#[error("{e:?}")]
pub struct RichError {
e: anyhow::Error,
}
#[uniffi::export]
impl RichError {
fn chain(&self) -> Vec<String> {
self.e.chain().map(ToString::to_string).collect()
}
}
#[uniffi::export]
fn oops() -> Result<(), Arc<RichError>> { /* ... */ }
The generated class extends kotlin.Exception, so you can catch it and still call chain():
try {
oops()
} catch (e: RichException) {
e.chain()
}
@Throws and Java callers¶
Throwing functions and methods are annotated with @Throws(SomeException::class). On the JVM this
puts a throws clause into the class file, so Java callers can catch the exception. Throwing
suspend functions also declare CancellationException.
Panics and unexpected errors¶
A Rust panic does not crash the process. It surfaces in Kotlin as InternalException with the panic
message. The class lives in uniffi.runtime, and the bindings' package has a typealias for it. The same exception is thrown for other failures that are not part of the
declared error type.