Skip to main content

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}