    fn shutdown(&self) -> std::result::Result<(), {{ core_import }}::{{ error_type }}> {
        let Some(fp) = self.vtable.shutdown_fn else { return Ok(()); };
        let mut _out_error: *mut std::ffi::c_char = std::ptr::null_mut();
        // SAFETY: fp is valid; user_data validity is the caller's responsibility.
        let rc = unsafe { fp(self.user_data, &mut _out_error) };
        if rc != 0 {
            let msg = if _out_error.is_null() { format!("shutdown returned {}", rc) } else {
                // SAFETY: _out_error is a callee-allocated NUL-terminated string.
                let msg = unsafe { std::ffi::CStr::from_ptr(_out_error) }.to_string_lossy().into_owned();
                if let Some(free_fn) = self.vtable.free_string {
                    // SAFETY: free_fn is the vtable-provided destructor for callback strings.
                    unsafe { free_fn(_out_error) };
                }
                msg
            };
            return Err({{ plugin_error_expr | safe }});
        }
        Ok(())
    }
}
