פתרון קריאה חוזרת סינכרונית¶
הגרסה הבאה של ספריית סי עוצרת לפני הקריאה הבאה כאשר מתקבלת תוצאה שאינה אפס.
typedef int (*visitor)(void *, unsigned int);
int visit_range(unsigned int end, visitor callback, void *context) {
for (unsigned int i = 0; i < end; ++i)
if (callback(context, i) != 0) return -1;
return 0;
}
הקוד הבא הוא תוכנית מלאה המחוברת לספרייה הזאת. מדיניות הפאניקה נשארת מוגדרת, והשגיאה הרגילה נשמרת כאובייקט בבעלות ראסט.
use std::ffi::{c_int, c_uint, c_void};
use std::panic::{catch_unwind, AssertUnwindSafe};
unsafe extern "C" {
fn visit_range(end: c_uint,
callback: unsafe extern "C" fn(*mut c_void, c_uint) -> c_int,
context: *mut c_void) -> c_int;
}
struct State<F> { callback: F, error: Option<String> }
unsafe extern "C" fn call<F: FnMut(u32) -> Result<(), String>>(
raw: *mut c_void, value: c_uint) -> c_int {
let state = unsafe { &mut *raw.cast::<State<F>>() };
let result = catch_unwind(AssertUnwindSafe(|| (state.callback)(value)));
match result {
Ok(Ok(())) => 0,
Ok(Err(error)) => { state.error = Some(error); -1 }
Err(payload) => {
std::mem::forget(payload);
state.error = Some("callback panicked".into());
-1
}
}
}
fn visit<F: FnMut(u32) -> Result<(), String>>(end: u32, callback: F)
-> Result<(), String> {
let mut state = State { callback, error: None };
let status = unsafe {
visit_range(end, call::<F>, (&mut state as *mut State<F>).cast())
};
match state.error {
Some(error) => Err(error),
None if status == 0 => Ok(()),
None => Err("foreign failure".into()),
}
}
fn main() {
let mut seen = Vec::new();
let result = visit(5, |n| {
seen.push(n);
if n == 2 { Err("stop".into()) } else { Ok(()) }
});
assert_eq!(result, Err("stop".into()));
assert_eq!(seen, [0, 1, 2]);
}
הודעת השגיאה עוברת החוצה באמצעות העברת בעלות מ־state.error, לאחר שאין עוד קריאה זרה פעילה. המצביע אל context אינו נשמר בשום מקום. במקרה האסינכרוני הסדר הזה מתהפך: הפונקציה חוזרת, המצב המקומי מושמד, ורק אז מגיע callback עם כתובת שכבר אינה שייכת לו. שום cast אינו מאריך את חיי האובייקט ולכן נדרש תכנון אחר.