generic_lang_api/abi.rs
1//! The raw C ABI shared between the generic interpreter and its plugins.
2//!
3//! Everything in this module is `#[repr(C)]` and mirrored in the generated
4//! `include/generic.h` for non-Rust plugins. Plugin authors normally
5//! use the safe wrapper in the crate root instead of these types directly.
6
7use core::ffi::c_void;
8use core::mem::MaybeUninit;
9
10/// Version of the plugin ABI described by this crate.
11///
12/// The host checks a module's [`ModuleDesc::abi_version`] before calling
13/// anything in it and refuses to load mismatching plugins.
14pub const GENERIC_PLUGIN_ABI_VERSION: u32 = 1;
15
16/// An opaque generic runtime value.
17///
18/// This is the host's 32-byte `Value` bit-copied - discriminant and payload
19/// included. Plugins must never inspect or fabricate its bytes; values are
20/// opaque handles to be passed back to host callbacks. Use
21/// [`HostApi::value_kind`] to ask what a value holds.
22#[repr(C)]
23#[derive(Clone, Copy)]
24pub struct GenericValue {
25 /// Opaque storage. The limbs are [`MaybeUninit`] because a host `Value`
26 /// does not initialize all 32 bytes - small enum variants leave the
27 /// rest unwritten - and bit-copying it in must not assert those bytes
28 /// are initialized (that would be undefined behavior). `u64` limbs give
29 /// the type the host `Value`'s 8-byte alignment; it renders as
30 /// `uint64_t opaque[4]` in C. Never inspect.
31 pub opaque: [MaybeUninit<u64>; 4],
32}
33
34impl core::fmt::Debug for GenericValue {
35 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
36 // The bytes are opaque - nothing meaningful to print.
37 f.debug_struct("GenericValue").finish_non_exhaustive()
38 }
39}
40
41/// A borrowed UTF-8 string. Not NUL-terminated.
42///
43/// Lifetime rules: an `FfiStr` returned by a host callback stays valid until
44/// the next *re-entering* callback (see the rooting contract); an `FfiStr`
45/// passed to a host callback only needs to be valid for that call.
46#[repr(C)]
47#[derive(Debug, Clone, Copy)]
48pub struct FfiStr {
49 /// Pointer to the first byte. Must be non-null in both directions - an
50 /// empty string is a non-null pointer with `len == 0` (e.g. C's `""`);
51 /// a null pointer is not a valid string value.
52 pub ptr: *const u8,
53 /// Length in bytes.
54 pub len: usize,
55}
56
57impl FfiStr {
58 /// A null-pointer `FfiStr` for initializing the out-parameter of a
59 /// bool-probe callback (`string_get` overwrites it on success). Not a
60 /// valid string value - see [`FfiStr::ptr`].
61 #[must_use]
62 pub const fn null() -> Self {
63 Self {
64 ptr: core::ptr::null(),
65 len: 0,
66 }
67 }
68}
69
70/// Discriminator for [`FfiReturn::status`].
71///
72/// On the wire the status is a plain `u32` (an arbitrary integer from a
73/// plugin must not become a Rust enum); decode with
74/// [`FfiStatus::from_u32`], encode with `as u32`.
75#[repr(u32)]
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum FfiStatus {
78 /// Success - `value` is the call's result.
79 Ok = 0,
80 /// `value` is the exception *instance*.
81 ///
82 /// From a host callback this is the exception generic code raised,
83 /// handed over with full identity: returning the same value (the safe
84 /// wrapper's `?` does) re-raises exactly that exception - class,
85 /// fields, and original stack trace intact. To throw a fresh
86 /// exception, create the instance with [`HostApi::exception_new`] and
87 /// return it under this status; a caught one can be examined with
88 /// [`HostApi::is_instance`] against a class from
89 /// [`HostApi::builtin_get`].
90 Exception = 1,
91 /// A fatal host runtime error passing through the plugin.
92 ///
93 /// Not an exception - it is uncatchable. A re-entering host
94 /// callback returns it when the interpreter hit a fatal error; the
95 /// plugin must forward it unchanged (the safe wrapper's `?` does), and
96 /// the host re-raises it as a fatal error when the plugin call
97 /// returns. `value` carries no meaning for this status.
98 Fatal = 99,
99}
100
101impl FfiStatus {
102 /// Decode a raw status. `None` means the value is not a valid status -
103 /// a protocol violation the host surfaces as a plugin bug (there is no
104 /// safe fallback: `value` must not be interpreted at all).
105 #[must_use]
106 pub const fn from_u32(status: u32) -> Option<Self> {
107 match status {
108 0 => Some(Self::Ok),
109 1 => Some(Self::Exception),
110 99 => Some(Self::Fatal),
111 _ => None,
112 }
113 }
114}
115
116/// Result of a plugin function or a re-entering host callback.
117///
118/// `value` is always present; `status` (a [`FfiStatus`] as `u32`) says
119/// what it is: the call's result, an exception instance to (re-)raise, or
120/// a meaningless placeholder accompanying a fatal pass-through error.
121#[repr(C)]
122#[derive(Debug, Clone, Copy)]
123pub struct FfiReturn {
124 /// A [`FfiStatus`] as `u32`: what `value` means.
125 pub status: u32,
126 /// The result, the exception instance, or a fatal-status placeholder.
127 pub value: GenericValue,
128}
129
130/// The signature every exported plugin function has.
131///
132/// `args` points at `nargs` contiguous values owned by the host; they stay
133/// valid (and GC-rooted) for the whole call.
134pub type PluginFn =
135 extern "C" fn(host: *const HostApi, args: *const GenericValue, nargs: usize) -> FfiReturn;
136
137/// Description of one exported plugin function.
138#[repr(C)]
139pub struct FunctionDesc {
140 /// Function name as seen from generic code.
141 pub name: FfiStr,
142 /// Accepted argument counts (the host checks arity before calling).
143 pub arities: *const u8,
144 /// Number of entries in `arities`.
145 pub arities_len: usize,
146 /// The function implementation; a null pointer is rejected at load.
147 /// The type is [`PluginFn`] spelled out inline - cbindgen only renders
148 /// a nullable C function pointer for an inline `Option<fn>`, not
149 /// through the alias.
150 pub fun: Option<
151 extern "C" fn(host: *const HostApi, args: *const GenericValue, nargs: usize) -> FfiReturn,
152 >,
153}
154
155/// Reports a held [`GenericValue`] to the host's mark phase during GC.
156///
157/// The host grays `value`; `ctx` is the `visit_ctx` passed to the enclosing
158/// [`PluginTraverseFn`]. Called only from within a [`PluginTraverseFn`], never
159/// directly.
160pub type PluginVisitFn = extern "C" fn(ctx: *mut c_void, value: GenericValue);
161
162/// Per-class GC traversal callback, declared on [`ClassDesc::traverse`].
163///
164/// Called by the host's GC during the mark phase, once per live plugin-backed
165/// instance. The plugin must call `visit(visit_ctx, v)` for every
166/// [`GenericValue`] its opaque struct references; failure to report a held
167/// value is a use-after-free bug (the collector is mark-and-sweep, so a value
168/// that is never reported is swept even though it is still reachable).
169///
170/// Returns `0` on success; a non-zero return is reserved and currently ignored.
171///
172/// # Safety
173///
174/// `opaque_ptr` is the pointer installed via `instance_set_opaque`, or null if
175/// `__init__` has not run yet - the plugin must handle null gracefully. The
176/// host already traces the instance's generic fields, so the plugin reports
177/// only the values held in its own opaque state. `visit` and `visit_ctx` are
178/// the host-provided marking function and its context; pass them through to
179/// `visit` unchanged.
180pub type PluginTraverseFn =
181 extern "C" fn(opaque_ptr: *mut c_void, visit: PluginVisitFn, visit_ctx: *mut c_void) -> i32;
182
183/// The signature of a plugin value creator.
184///
185/// Builds one module constant at import time, using the host callbacks to
186/// construct the value. May use re-entering callbacks. Returning
187/// [`FfiStatus::Exception`] makes the import fail with that exception.
188pub type PluginValueFn = extern "C" fn(host: *const HostApi) -> FfiReturn;
189
190/// Description of one exported plugin module value (a module constant,
191/// built once at import time).
192#[repr(C)]
193pub struct ValueDesc {
194 /// Value name as seen from generic code.
195 pub name: FfiStr,
196 /// The creator; a null pointer is rejected at load. This is
197 /// [`PluginValueFn`] spelled out inline - cbindgen only renders a
198 /// nullable C function pointer for an inline `Option<fn>`, not through
199 /// the alias.
200 pub fun: Option<extern "C" fn(host: *const HostApi) -> FfiReturn>,
201}
202
203/// The signature of a plugin method: like [`PluginFn`], but the receiver
204/// (`self`) arrives as a separate first value, not in `args`. `args`/`nargs`
205/// are the remaining arguments only.
206pub type PluginMethodFn = extern "C" fn(
207 host: *const HostApi,
208 receiver: GenericValue,
209 args: *const GenericValue,
210 nargs: usize,
211) -> FfiReturn;
212
213/// Description of one method of a plugin-defined class.
214#[repr(C)]
215pub struct MethodDesc {
216 /// Method name as seen from generic code (e.g. `"__init__"`, `"value"`).
217 pub name: FfiStr,
218 /// Accepted argument counts, **excluding** the receiver (the host checks
219 /// arity before calling). A method called as `obj.foo(a, b)` declares
220 /// `&[2]`; a receiver-only method declares `&[0]`.
221 pub arities: *const u8,
222 /// Number of entries in `arities`.
223 pub arities_len: usize,
224 /// The method implementation; a null pointer is rejected at load. This is
225 /// [`PluginMethodFn`] spelled out inline - cbindgen only renders a nullable
226 /// C function pointer for an inline `Option<fn>`, not through the alias.
227 pub fun: Option<
228 extern "C" fn(
229 host: *const HostApi,
230 receiver: GenericValue,
231 args: *const GenericValue,
232 nargs: usize,
233 ) -> FfiReturn,
234 >,
235}
236
237/// Description of a plugin-defined class; one entry per class in
238/// [`ModuleDesc::classes`].
239#[repr(C)]
240pub struct ClassDesc {
241 /// Class name as seen from generic code (e.g. `"Counter"`).
242 pub name: FfiStr,
243 /// Pointer to `methods_len` contiguous [`MethodDesc`] entries.
244 pub methods: *const MethodDesc,
245 /// Number of entries in `methods`.
246 pub methods_len: usize,
247 /// Destructor for the plugin's opaque per-instance state, called by the
248 /// host with the `*mut c_void` installed via `instance_set_opaque` when a
249 /// plugin-backed instance is garbage-collected. May be null if the plugin
250 /// manages the lifetime itself (rare).
251 pub drop: Option<extern "C" fn(opaque_ptr: *mut c_void)>,
252 /// GC traversal callback, called during the mark phase for each live
253 /// plugin-backed instance. May be null if the opaque struct holds no
254 /// [`GenericValue`]s. This is [`PluginTraverseFn`] spelled out inline -
255 /// cbindgen only renders a nullable C function pointer for an inline
256 /// `Option<fn>`, not through the alias.
257 pub traverse: Option<
258 extern "C" fn(opaque_ptr: *mut c_void, visit: PluginVisitFn, visit_ctx: *mut c_void) -> i32,
259 >,
260}
261
262/// Description of a plugin module; returned by `generic_plugin_init`, the
263/// one symbol every plugin must export:
264///
265/// ```c
266/// const ModuleDesc *generic_plugin_init(void);
267/// ```
268#[repr(C)]
269pub struct ModuleDesc {
270 /// ABI version the plugin was built against ([`GENERIC_PLUGIN_ABI_VERSION`]).
271 pub abi_version: u32,
272 /// Pointer to `functions_len` contiguous [`FunctionDesc`] entries.
273 pub functions: *const FunctionDesc,
274 /// Number of entries in `functions`.
275 pub functions_len: usize,
276 /// Pointer to `classes_len` contiguous [`ClassDesc`] entries.
277 pub classes: *const ClassDesc,
278 /// Number of entries in `classes`. May be 0 (function-only plugins).
279 pub classes_len: usize,
280 /// Pointer to `values_len` contiguous [`ValueDesc`] entries.
281 pub values: *const ValueDesc,
282 /// Number of entries in `values`. May be 0.
283 pub values_len: usize,
284}
285
286// SAFETY: sharing a descriptor only permits reading its fields (copying
287// pointer values), which is thread-safe; dereferencing the pointers is
288// `unsafe` and carries the following obligations at each use site:
289// - `functions` must point to `functions_len` contiguous, initialized
290// `FunctionDesc` entries, `classes` to `classes_len` contiguous,
291// initialized `ClassDesc` entries, and `values` to `values_len`
292// contiguous, initialized `ValueDesc` entries - all never mutated and
293// outliving every read; for descriptors returned by
294// `generic_plugin_init`, that means the lifetime of the loaded library.
295// - Within each `FunctionDesc`/`MethodDesc`, `name` must reference `name.len`
296// bytes of valid UTF-8, `arities` must reference `arities_len` initialized
297// bytes, and `fun` must be a function with the documented `PluginFn` ABI.
298// Within each `ClassDesc`, `name` is as above and `methods` points to
299// `methods_len` contiguous `MethodDesc` entries; `drop`/`traverse` are
300// either null or valid `extern "C"` functions - all under the same
301// immutability and lifetime requirements as above.
302// The impl is required so a descriptor can live in a `static` (statics must
303// be `Sync`); `export_module!` discharges all of the above by building the
304// tables from `const` data.
305unsafe impl Sync for ModuleDesc {}
306
307/// The host vtable handed to every plugin call.
308///
309/// `ctx` is an opaque pointer owned by the host; pass it as the first
310/// argument to every callback. Callbacks marked **re-entering** run generic
311/// bytecode, during which garbage collection may occur - see the rooting
312/// contract: across a re-entering callback, `root` every value still
313/// held and re-fetch any [`FfiStr`] afterward. All other callbacks never
314/// trigger collection.
315///
316/// Return conventions, decided solely by whether the payload forces an
317/// out-parameter:
318/// - A payload the caller must receive as something other than a
319/// [`GenericValue`] - a raw machine scalar (`bool`, `i64`, `f64`,
320/// `usize`) or a borrowed [`FfiStr`] - cannot ride in an [`FfiReturn`]
321/// (whose payload is a [`GenericValue`]), so it travels through an
322/// out-parameter and the callback returns a plain `bool` - `true` on
323/// success, `false` on the sole "wrong kind" failure. These carry no
324/// exception.
325/// - Everything else - payload is a [`GenericValue`], or there is no
326/// payload - returns [`FfiReturn`], carrying a real exception instance on
327/// failure whose class and message mirror what the equivalent generic
328/// operation would throw.
329/// - Infallible callbacks return their value directly.
330#[repr(C)]
331pub struct HostApi {
332 /// ABI version of the host ([`GENERIC_PLUGIN_ABI_VERSION`]).
333 pub abi_version: u32,
334 /// Opaque host context; pass it back as the first argument of every
335 /// callback. Never dereference it.
336 pub ctx: *mut c_void,
337
338 // --- inspect (never re-enter) ---
339 /// Kind of the value, as a [`ValueKind`](crate::ValueKind) code.
340 pub value_kind: extern "C" fn(ctx: *mut c_void, value: GenericValue) -> u32,
341 /// `false` if the value is not a bool.
342 pub bool_get: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut bool) -> bool,
343 /// Read an integer into `out`; `false` if the value is not an integer
344 /// or does not fit in an `i64` (big integers - fall back to
345 /// `value_display`).
346 pub int_get: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut i64) -> bool,
347 /// `false` if the value is not a float.
348 pub float_get: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut f64) -> bool,
349 /// Read the interned bytes of a string value into `out` (valid until
350 /// the next re-entering callback); `false` if the value is not a
351 /// string.
352 pub string_get: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut FfiStr) -> bool,
353 /// `false` if the value is not a list.
354 pub list_len: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut usize) -> bool,
355 /// The element at `index`. `TypeError` if the value is not a list;
356 /// `IndexError` if the index is out of bounds.
357 pub list_get: extern "C" fn(ctx: *mut c_void, value: GenericValue, index: usize) -> FfiReturn,
358 /// `false` if the value is not a tuple.
359 pub tuple_len: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut usize) -> bool,
360 /// The element at `index`. `TypeError` if the value is not a tuple;
361 /// `IndexError` if the index is out of bounds.
362 pub tuple_get: extern "C" fn(ctx: *mut c_void, value: GenericValue, index: usize) -> FfiReturn,
363 /// `false` if the value is not a dict.
364 pub dict_len: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut usize) -> bool,
365 /// `false` if the value is not a set.
366 pub set_len: extern "C" fn(ctx: *mut c_void, value: GenericValue, out: *mut usize) -> bool,
367 /// Look up a builtin global by name (exception classes like
368 /// `"TypeError"`, native classes, builtin functions). `NameError` if
369 /// absent; `TypeError` if the name is invalid UTF-8.
370 pub builtin_get: extern "C" fn(ctx: *mut c_void, name: FfiStr) -> FfiReturn,
371 /// Whether `value` is an instance of `of_class` or of a subclass of it
372 /// (a bool value on success) - the exact semantics of the `isinstance`
373 /// builtin, value-type proxy classes included. `TypeError` if
374 /// `of_class` is not a class.
375 pub is_instance:
376 extern "C" fn(ctx: *mut c_void, value: GenericValue, of_class: GenericValue) -> FfiReturn,
377 /// The class of an instance, as a class value (callable to construct
378 /// another instance of it - the analogue of `type(self)`). `TypeError` if
379 /// `value` is not an instance. Lets a plugin method reach its own class
380 /// from the receiver: e.g. to construct a result of the same class, or to
381 /// `is_instance`-check another argument before reading its opaque state.
382 pub class_of: extern "C" fn(ctx: *mut c_void, value: GenericValue) -> FfiReturn,
383
384 // --- attributes (never re-enter; generic fields are plain map entries) ---
385 /// A field of an instance; `AttributeError` if absent, `TypeError` if
386 /// the receiver is not an instance.
387 pub attr_get:
388 extern "C" fn(ctx: *mut c_void, receiver: GenericValue, name: FfiStr) -> FfiReturn,
389 /// Set a field on an instance; `TypeError` if the receiver is not an
390 /// instance (the ok value is nil).
391 pub attr_set: extern "C" fn(
392 ctx: *mut c_void,
393 receiver: GenericValue,
394 name: FfiStr,
395 value: GenericValue,
396 ) -> FfiReturn,
397 /// Whether an instance has a field (a bool value on success).
398 /// `TypeError` if the receiver is not an instance or the name is
399 /// invalid UTF-8.
400 pub attr_has:
401 extern "C" fn(ctx: *mut c_void, receiver: GenericValue, name: FfiStr) -> FfiReturn,
402
403 // --- construct (never re-enter) ---
404 /// A new nil value.
405 pub nil_new: extern "C" fn(ctx: *mut c_void) -> GenericValue,
406 /// A new bool value.
407 pub bool_new: extern "C" fn(ctx: *mut c_void, value: bool) -> GenericValue,
408 /// A new integer value.
409 pub int_new: extern "C" fn(ctx: *mut c_void, value: i64) -> GenericValue,
410 /// A new float value.
411 pub float_new: extern "C" fn(ctx: *mut c_void, value: f64) -> GenericValue,
412 /// Interns the given UTF-8 bytes into a string value; `ValueError` on
413 /// invalid UTF-8.
414 pub string_new: extern "C" fn(ctx: *mut c_void, value: FfiStr) -> FfiReturn,
415 /// A new, empty list.
416 pub list_new: extern "C" fn(ctx: *mut c_void) -> GenericValue,
417 /// Append to a list (the ok value is nil); `TypeError` if the target is
418 /// not a list.
419 pub list_push:
420 extern "C" fn(ctx: *mut c_void, list: GenericValue, item: GenericValue) -> FfiReturn,
421 /// Replace the element at an index (the ok value is nil). `TypeError`
422 /// if the target is not a list; `IndexError` if the index is out of
423 /// bounds.
424 pub list_set: extern "C" fn(
425 ctx: *mut c_void,
426 list: GenericValue,
427 index: usize,
428 value: GenericValue,
429 ) -> FfiReturn,
430 /// A new exception instance of `of_class` carrying `message`.
431 /// `TypeError` if `of_class` is not a class deriving from `Exception`
432 /// or the message is invalid UTF-8. Sets the message directly,
433 /// bypassing the class's `__init__` - exactly like the VM's own throw;
434 /// use `call_value` on the class for full construction semantics.
435 /// Return the instance under [`FfiStatus::Exception`] to throw it.
436 pub exception_new:
437 extern "C" fn(ctx: *mut c_void, of_class: GenericValue, message: FfiStr) -> FfiReturn,
438
439 // --- display (never re-enters) ---
440 /// The raw string representation of any value, as a new string value.
441 /// Does NOT honor a user class's `__str__` (use `value_str` for that),
442 /// which makes it safe to call anywhere, including error paths.
443 pub value_display: extern "C" fn(ctx: *mut c_void, value: GenericValue) -> GenericValue,
444
445 // --- re-entering (run generic bytecode; GC may occur) ---
446 /// Call a callable value (closure, native, class, …) with the given
447 /// arguments. Generic exceptions come back as a nonzero status.
448 pub call_value: extern "C" fn(
449 ctx: *mut c_void,
450 callee: GenericValue,
451 args: *const GenericValue,
452 nargs: usize,
453 ) -> FfiReturn,
454 /// Invoke a named method on a receiver.
455 pub invoke_method: extern "C" fn(
456 ctx: *mut c_void,
457 receiver: GenericValue,
458 name: FfiStr,
459 args: *const GenericValue,
460 nargs: usize,
461 ) -> FfiReturn,
462 /// String conversion honoring a user class's `__str__`.
463 pub value_str: extern "C" fn(ctx: *mut c_void, value: GenericValue) -> FfiReturn,
464 /// Look up a key (`KeyError` if absent); re-enters for `__hash__`/`__eq__`.
465 pub dict_get:
466 extern "C" fn(ctx: *mut c_void, dict: GenericValue, key: GenericValue) -> FfiReturn,
467 /// Insert or replace a key (the ok value is nil).
468 pub dict_set: extern "C" fn(
469 ctx: *mut c_void,
470 dict: GenericValue,
471 key: GenericValue,
472 value: GenericValue,
473 ) -> FfiReturn,
474 /// Whether a dict contains a key (the ok value is a bool).
475 pub dict_contains:
476 extern "C" fn(ctx: *mut c_void, dict: GenericValue, key: GenericValue) -> FfiReturn,
477 /// Add an item to a set (the ok value is nil).
478 pub set_add:
479 extern "C" fn(ctx: *mut c_void, set: GenericValue, item: GenericValue) -> FfiReturn,
480 /// Whether a set contains an item (the ok value is a bool).
481 pub set_contains:
482 extern "C" fn(ctx: *mut c_void, set: GenericValue, item: GenericValue) -> FfiReturn,
483 /// Truthiness honoring `__bool__` (the ok value is a bool).
484 pub value_truthy: extern "C" fn(ctx: *mut c_void, value: GenericValue) -> FfiReturn,
485 /// Equality honoring `__eq__` (the ok value is a bool).
486 pub value_equals:
487 extern "C" fn(ctx: *mut c_void, a: GenericValue, b: GenericValue) -> FfiReturn,
488 /// Hash honoring `__hash__` (the ok value is an integer).
489 pub value_hash: extern "C" fn(ctx: *mut c_void, value: GenericValue) -> FfiReturn,
490
491 // --- rooting (never re-enter) ---
492 /// Keep a value alive across re-entering callbacks. Roots are released
493 /// automatically when the plugin function returns; `unroot` releases
494 /// the `n` most recent roots early.
495 pub root: extern "C" fn(ctx: *mut c_void, value: GenericValue),
496 /// Release the `n` most recently rooted values. Releasing more roots
497 /// than were pushed corrupts interpreter state.
498 pub unroot: extern "C" fn(ctx: *mut c_void, n: usize),
499
500 // --- plugin instance state (never re-enter) ---
501 /// Install the plugin's opaque pointer on a plugin-backed instance
502 /// (typically from `__init__`, with the receiver as `self`). The class's
503 /// `drop`/`traverse` were declared on its [`ClassDesc`]; this only sets the
504 /// pointer. `TypeError` if `receiver` is not a plugin-backed instance.
505 ///
506 /// Overwriting an already-installed pointer leaks the previous one:
507 /// the host does not run `drop` on it, since it cannot know whether the plugin
508 /// still holds a copy elsewhere. To replace state, recover the old pointer
509 /// with `instance_get_opaque` and free it yourself first.
510 pub instance_set_opaque:
511 extern "C" fn(ctx: *mut c_void, receiver: GenericValue, ptr: *mut c_void) -> FfiReturn,
512 /// Recover the pointer installed by [`HostApi::instance_set_opaque`], or
513 /// null if none was installed (e.g. before `__init__` ran) or `receiver`
514 /// is not a plugin-backed instance. Never raises.
515 pub instance_get_opaque: extern "C" fn(ctx: *mut c_void, receiver: GenericValue) -> *mut c_void,
516}