use std::cell::RefCell;
use std::panic::{catch_unwind, AssertUnwindSafe};
thread_local! {
static LAST_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
}
pub fn set_last_error(msg: impl Into<String>) {
LAST_ERROR.with(|slot| *slot.borrow_mut() = Some(msg.into()));
}
pub fn take_last_error() -> Option<String> {
LAST_ERROR.with(|slot| slot.borrow_mut().take())
}
pub fn clear_last_error() {
LAST_ERROR.with(|slot| *slot.borrow_mut() = None);
}
#[inline]
pub fn ffi_guard<T>(default: T, body: impl FnOnce() -> T) -> T {
match catch_unwind(AssertUnwindSafe(body)) {
Ok(v) => v,
Err(payload) => {
let msg = payload
.downcast_ref::<&str>()
.map(|s| (*s).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "panic with non-string payload".to_string());
set_last_error(format!("alice-sdf FFI panic: {msg}"));
default
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn panic_becomes_default_and_message() {
clear_last_error();
let v = ffi_guard(-1i32, || -> i32 { panic!("boom {}", 42) });
assert_eq!(v, -1);
let msg = take_last_error().expect("message recorded");
assert!(msg.contains("boom 42"), "{msg}");
assert!(take_last_error().is_none(), "take clears the slot");
}
#[test]
fn success_leaves_slot_untouched() {
clear_last_error();
assert_eq!(ffi_guard(0, || 5), 5);
assert!(take_last_error().is_none());
}
}