Error handling across the ABI
A Julia throw inside a --trim-compiled @ccallable does not become a Python exception — at best it aborts the host process. JuliaLibWrapping defines a small convention for surfacing errors across the C ABI so that generated wrappers can translate them into native exceptions.
The JLWStatus convention
The canonical status struct is defined in the JLWInterop subdir package and has the following shape:
struct JLWStatus
code::Int32 # 0 == success; non-zero == error
message::NTuple{256, UInt8} # UTF-8, null-terminated
endA library opts in by either
- returning a
JLWStatusdirectly, or - returning a struct that contains a
JLWStatusfield (any field name).
Either form is recognized by the Python backend.
The fixed 256-byte message buffer is deliberate: a Cstring or Ptr{UInt8} would raise an ownership question (who frees it, when?) that has no good answer under juliac --trim. An inline buffer keeps JLWStatus an isbits type with no heap allocation, at the cost of a bounded message length.
Authoring a library
Add JLWInterop to your library's project, then construct status values with the provided helpers:
using JLWInterop
struct ResultStruct
status::JLWStatus
value::Float64
end
Base.@ccallable function compute(x::Float64)::ResultStruct
x < 0 && return ResultStruct(jlw_error(1, "negative input"), 0.0)
return ResultStruct(jlw_ok(), x * 2)
endjlw_ok() and jlw_error(code, msg) perform no heap allocation, so they are safe in --trim builds.
What the Python wrapper does
For each entrypoint whose return type carries a JLWStatus (directly or via an embedded field), the generated wrapper checks status.code and raises a JLWError (a RuntimeError subclass, carrying .code and .message) when it is non-zero:
from mylib import compute, JLWError
try:
result = compute(-1.0)
except JLWError as e:
print(e.code, e.message) # 1, "negative input"On success the full return struct is handed back unchanged, including the status field (so callers can still inspect it explicitly if they prefer).
C backend
The C header generator does not currently auto-emit a status-check macro; C callers should check result.status.code themselves and read the message with e.g. printf("%.256s\n", result.status.message). A JLW_CHECK-style helper macro is a possible follow-up.
Recognition is structural
The Python emitter matches on struct name ("JLWStatus") plus field shape (code::Int32 followed by a 256-byte tuple message). Authors who copy and paste a compatible definition — rather than depending on JLWInterop — still get the same wrapper behavior. The canonical package merely keeps the definition from drifting across libraries.