Skip to main content

diffsol_c/
error_c.rs

1use std::cell::RefCell;
2use std::ffi::CString;
3use std::os::raw::c_char;
4use std::ptr;
5
6struct LastError {
7    message: CString,
8    file: CString,
9    line: u32,
10}
11
12thread_local! {
13    static LAST_ERROR: RefCell<Option<LastError>> = const { RefCell::new(None) };
14}
15
16fn cstring_from_str(value: &str) -> CString {
17    if value.as_bytes().contains(&0) {
18        let mut bytes = value.as_bytes().to_vec();
19        for byte in &mut bytes {
20            if *byte == 0 {
21                *byte = b'?';
22            }
23        }
24        CString::new(bytes).unwrap_or_else(|_| CString::new("error").unwrap())
25    } else {
26        CString::new(value).unwrap_or_else(|_| CString::new("error").unwrap())
27    }
28}
29
30pub(crate) fn set_last_error(message: &str, file: &'static str, line: u32) {
31    let message = cstring_from_str(message);
32    let file = cstring_from_str(file);
33    LAST_ERROR.with(|slot| {
34        *slot.borrow_mut() = Some(LastError {
35            message,
36            file,
37            line,
38        });
39    });
40}
41
42pub(crate) fn clear_last_error() {
43    LAST_ERROR.with(|slot| {
44        *slot.borrow_mut() = None;
45    });
46}
47
48/// Return whether thread-local error state is currently set.
49///
50/// # Safety
51/// This function is safe to call from C. It relies on thread-local state managed
52/// by this library and does not dereference any caller-provided pointers.
53#[unsafe(no_mangle)]
54pub unsafe extern "C" fn diffsol_error_code() -> i32 {
55    LAST_ERROR.with(|slot| if slot.borrow().is_some() { 1 } else { 0 })
56}
57
58/// Return the last error message for the current thread, if any.
59///
60/// # Safety
61/// The returned pointer is borrowed from thread-local storage owned by this
62/// library and must not be freed or mutated by the caller.
63#[unsafe(no_mangle)]
64pub unsafe extern "C" fn diffsol_error() -> *const c_char {
65    LAST_ERROR.with(|slot| {
66        slot.borrow()
67            .as_ref()
68            .map(|err| err.message.as_ptr())
69            .unwrap_or(ptr::null())
70    })
71}
72
73/// Return the last error message for the current thread, if any.
74///
75/// # Safety
76/// The returned pointer is borrowed from thread-local storage owned by this
77/// library and must not be freed or mutated by the caller.
78#[unsafe(no_mangle)]
79pub unsafe extern "C" fn diffsol_last_error_message() -> *const c_char {
80    LAST_ERROR.with(|slot| {
81        slot.borrow()
82            .as_ref()
83            .map(|err| err.message.as_ptr())
84            .unwrap_or(ptr::null())
85    })
86}
87
88/// Return the source file associated with the last error for the current thread.
89///
90/// # Safety
91/// The returned pointer is borrowed from thread-local storage owned by this
92/// library and must not be freed or mutated by the caller.
93#[unsafe(no_mangle)]
94pub unsafe extern "C" fn diffsol_last_error_file() -> *const c_char {
95    LAST_ERROR.with(|slot| {
96        slot.borrow()
97            .as_ref()
98            .map(|err| err.file.as_ptr())
99            .unwrap_or(ptr::null())
100    })
101}
102
103/// Return the source line associated with the last error for the current thread.
104///
105/// # Safety
106/// This function is safe to call from C. It does not dereference any
107/// caller-provided pointers.
108#[unsafe(no_mangle)]
109pub unsafe extern "C" fn diffsol_last_error_line() -> u32 {
110    LAST_ERROR.with(|slot| slot.borrow().as_ref().map(|err| err.line).unwrap_or(0))
111}
112
113/// Clear the last error for the current thread.
114///
115/// # Safety
116/// This function is safe to call from C. It only mutates thread-local state
117/// owned by this library.
118#[unsafe(no_mangle)]
119pub unsafe extern "C" fn diffsol_clear_last_error() {
120    clear_last_error();
121}