C Outputs and Errors

An authored contract decides which C pointer parameters are visible Python arguments, returned values, or native-only status storage.

Return several C outputs

Use a named Return(...) slot for every native output pointer that should become part of the Python return value.

Create stats.c:

#include <stddef.h>

void stats_compute(size_t count, const double *values, double *mean, double *total) {
    double sum = 0.0;
    for (size_t index = 0; index < count; ++index) {
        sum += values[index];
    }
    *total = sum;
    *mean = count ? sum / (double)count : 0.0;
}

Create stats.pyi:

from prik.contracts import Arg, Float64, Return, Returns, bind, native_call

@bind("stats_compute")
@native_call([Arg(0).shape[0], Arg(0), Return("mean", 0), Return("total", 1)])
def summarize(values: Float64[:]) -> tuple[Returns["mean", Float64], Returns["total", Float64]]: ...
python3 -m prik --language c stats.pyi \
  --native-c-sources stats.c \
  --compiler cc \
  --out stats \
  --out-dir build
import sys

import numpy as np

sys.path.insert(0, "build")
import stats

mean, total = stats.summarize(np.array([1.0, 2.0, 3.0, 4.0], dtype=np.float64))
print(mean, total)
2.5 10.0

See Calls and Results for the complete shared contract vocabulary.

Hide native outputs and raise Python exceptions

Use Hidden(name, T) for C output storage that Python should not return. A common case is a status value and diagnostic message consumed by @raises.

Create checked.c:

#include <string.h>

void checked_sqrt(double value, double *root, int *status, char *message) {
    if (value < 0.0) {
        *status = -1;
        *root = 0.0;
        strcpy(message, "value must not be negative");
        return;
    }
    *status = 0;
    message[0] = '\0';
    *root = value == 4.0 ? 2.0 : value;
}

Create checked.pyi:

from prik.contracts import Arg, Float64, Hidden, Int32, Return, Returns, String, native_call, raises

@raises(status="status", message="message", success=0)
@native_call([Arg(0), Return("root", 0), Hidden("status", Int32), Hidden("message", String[64])])
def checked_sqrt(value: Float64) -> Returns["root", Float64]: ...
python3 -m prik --language c checked.pyi \
  --native-c-sources checked.c \
  --compiler cc \
  --out checked \
  --out-dir build
import sys

import numpy as np

sys.path.insert(0, "build")
import checked

print(checked.checked_sqrt(np.float64(4.0)))
try:
    checked.checked_sqrt(np.float64(-1.0))
except RuntimeError as error:
    print(error)
2.0
value must not be negative

The function returns only root; status and message produce a RuntimeError on failure. A hidden message needs a fixed capacity because PRIK allocates its native storage.

A caller-owned visible message buffer uses rank-zero NumPy storage:

@raises(status="status", message="message", success=0)
@native_call([Arg(0), Arg(1), Hidden("status", Int32)])
def checked(value: Float64, message: String[64][()]) -> None: ...

The caller can inspect the S64 array after the call or exception.

Next

Continue with Symbols, Headers, and Dependencies for larger native APIs and libraries.