לדלג לתוכן

פתרון קריאה חוזרת סינכרונית

הגרסה הבאה של ספריית סי עוצרת לפני הקריאה הבאה כאשר מתקבלת תוצאה שאינה אפס.

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 אינו מאריך את חיי האובייקט ולכן נדרש תכנון אחר.