Export owned objects
C receives an exported class as an opaque handle. The generated header names its destroy function and ownership rules.
pub enum CalcError:
Overflow
pub class Counter:
value: i64
def __del__(self) -> ():
drop(print(self.value))
pub export def create(value: i64) -> Result[Counter, AllocError] = "counter_create":
Ok(Counter(value))
pub export def read(owner: &Counter) -> i64 = "counter_read":
owner.value
pub export def increment(owner: &mut Counter) -> Result[(), CalcError] = "counter_increment":
result = checked:
owner.value + 1
owner.value = result.map_error(def(error: OverflowError[i64]) -> CalcError: CalcError.Overflow)?
Ok(())
pub export def finish(owner: Counter) -> () = "counter_finish":
drop(owner)
import counter
def main() -> Result[(), AllocError | IoError | counter.CalcError]:
mut owner = counter.create(42)?
counter.increment(&mut owner)?
print(counter.read(&owner))?
print("ready")?
counter.finish(owner)
Ok(())
43
ready
43
Build counter.plenty with --shared-library --library-name counter to get a C
factory returning a status and writing a counter_Counter * output on success.
A class returned to C must be declared as Result[Class, AllocError], because
allocating its handle can fail. The library allocates the handle before running
the function, so a failure has no other effects. Release the output exactly once
with counter_Counter_destroy.
counter_read borrows a const counter_Counter *; counter_increment borrows a
counter_Counter * exclusively. Neither transfers ownership.
counter_finish consumes its handle. A C caller must not use or destroy that
handle again; a Plenty caller gets a moved-value error if it tries to reuse
owner. A consuming function keeps this rule even if it returns an error.
Plenty consumers get automatic cleanup through the generated counter.plentyi.
Keep the originating library loaded for every owner’s lifetime.
The lifecycle trace uses drop(print(...)) deliberately: cleanup cannot return
an error, and a failed diagnostic write must not interrupt cleanup. For output
that must succeed, provide an explicit fallible operation and propagate its
result before leaving the scope.
The fallible increment maps arithmetic overflow to the payload-free
CalcError.Overflow at the ABI boundary. It leaves the object unchanged on
failure; callers must handle its status before continuing.