macro_rules! export_string_getter {
(
$(#[$meta:meta])*
$slot:path,
$name:ident($handle:ident: $handle_ty:ty $(, $arg:ident: $arg_ty:ty)* $(,)?),
$body:block
) => { ... };
}Expand description
Declare an entry point that hands out an owned string.
$body runs inside catch and evaluates to
Result<String, FfiError>. Everything around it is the same in every such entry
point: clear the slot, reject a null handle, move the string into a CString,
report invalid_output if it holds an interior NUL,
record a failure and return null.
The handle parameter’s name is taken from the call site so that $body can project
through it — { Ok((*doc).inner.title()) }. The handle is *const, since producing a
string does not modify the document.
The null-handle message is "document is null", fixed here so the family reports it
identically — the same reason invalid_output’s wording
is fixed.
unparser_shared::export_string_getter!(
/// The report's line at `index`, or a failure when it is out of range.
///
/// # Safety
///
/// - `doc` must be a valid handle.
/// - The returned string must be freed with `demo2_free_string`.
LAST_ERROR,
demo2_line(doc: Demo2Document, index: c_int),
{
match unsafe { (&(*doc).inner.lines).get(index as usize) } {
Some(line) => Ok(line.clone()),
None => Err((unparser_shared::kind::OTHER, format!("no line {index}"))),
}
}
);
let doc = Box::into_raw(Box::new(Demo2Document {
inner: Report { lines: vec!["first".to_string()] },
}));
let line = unsafe { demo2_line(doc, 0) };
assert_eq!(unsafe { CStr::from_ptr(line) }.to_str().unwrap(), "first");
unsafe { demo2_free_string(line) };
assert!(unsafe { demo2_line(doc, 9) }.is_null());
assert_eq!(demo2_last_error_kind(), unparser_shared::kind::OTHER);
unsafe { demo2_free_document(doc) };