JLWInterop carriers

The complete list of public objects appears in the JLWInterop API reference.

Ownership and layout

The package defines fixed-layout types for passing values across a C ABI. Borrowed values do not own their underlying storage; the caller must keep the storage alive for the duration of the call. CArray, CString, CStrArray, and CDict each state their ownership in a leading :owned/:borrowed type parameter. The types are isbits when their element types are, work with juliac --trim, and cross a @ccallable boundary without allocating a Julia object.

JuliaLibWrapping targets recognize these types structurally, by name and field layout. Using JLWInterop keeps those layouts consistent across libraries.

JLWStatus — in-band error reporting

A library that needs to report errors to its caller (rather than abort the process) returns either a JLWStatus directly, or a struct that contains a JLWStatus field. code == 0 is success; any non-zero value is an error code the library defines. message is a fixed-size UTF-8 buffer, null-terminated within the buffer.

The buffer is inline and fixed-size (JLW_MESSAGE_BYTES bytes) on purpose: a Cstring or Ptr{UInt8} would force a decision about who allocates and frees the message, which has no good answer under juliac --trim. The price is a bounded message length; the benefit is that constructing a status performs no heap allocation.

Construct values with the helpers:

using JLWInterop

Base.@ccallable function safe_sqrt(x::Float64)::JLWStatus
    x < 0 && return jlw_error(1, "negative input")
    return jlw_ok()
end

See Manual error status handling for authoring the protocol and Generated Python bindings for its Python mapping.

CArray{owned,T,N} — N-D numeric buffer (column-major)

CArray{owned,T,N} is (dims::NTuple{N,Int64}, data::Ptr{T}) in column-major order. Targets may map this layout to native array types.

CArray ownership contract

Ownership is the leading type parameter, :owned or :borrowed, so the two flavors are two distinct types with identical layout.

  • CArray{:borrowed,T,N} wraps memory the caller owns. The caller keeps it alive and makes it writable before mutation; the consumer never releases it. CArray{:borrowed}(A::DenseArray) aliases A's own storage (pointer(A)) without copying; other array types are refused, since only DenseArray guarantees the contiguous column-major layout the carrier promises. Pointer constructors build carriers of either ownership for callers who vouch for the layout themselves.
  • CArray{:owned,T,N} holds a Julia allocation. CArray{:owned}(A) Libc.mallocs a dense column-major copy of A; the consumer releases data exactly once.

There is no ownership-defaulting constructor, and any parameter other than :owned or :borrowed is rejected, so an ownership is never guessed.

The one- and two-dimensional aliases are:

const CVector{owned,T} = CArray{owned,T,1}
const CMatrix{owned,T} = CArray{owned,T,2}

CArray{owned,T,N} <: AbstractArray{T,N} with linear indexing. It supports standard array operations; mutate only writable storage.

Python target

A borrowed return becomes a zero-copy numpy view: the façade calls as_numpy() and hands the view back, because the storage stays the caller's. An owning return is copied into a fresh numpy array and the Julia allocation is released in a finally. The generated classes follow: a borrowed class gets from_numpy and as_numpy and no free(); an owning class gets as_numpy and an idempotent free(), and no from_numpy.

A hand-written entrypoint returning an owning CArray without @export_release_entrypoints is demoted at build time to a TODO re-export naming the macro to add — the same rule as CString, CStrArray, and CDict. An @api function in that position fails the build instead. An owning CArray argument is likewise left to a human: it would transfer a Julia allocation into the library, which numpy cannot supply. The same holds for owning CString, CStrArray, and CDict arguments.

For N ≥ 2, the generated from_numpy helper requires a Fortran-contiguous array. Convert row-major input with np.asfortranarray.

C target

The two ownerships mangle to two distinct typedefs (CVector_owned_Float64, CVector_borrowed_Float64), so whether to free a returned buffer is visible in the signature.

CString{owned} — length-prefixed UTF-8

CString{owned} is (length::Int64, data::Ptr{UInt8}). It is length-prefixed, so it permits embedded NUL bytes.

CString{owned} <: AbstractString; call String(s) to copy the bytes into a Julia String.

CString ownership contract

owned is :owned or :borrowed; both types have the same layout.

  • CString{:borrowed} wraps a buffer the caller owns and keeps alive; the consumer never releases it.
  • CString{:owned}(::AbstractString) allocates a copy of the UTF-8 bytes. The consumer releases data once with jlw_free.

Python target

JuliaPython
AbstractStringstr

A borrowed class gets from_str, from_bytes, as_str, and as_bytes. An owning class gets the conversion methods and an idempotent free(), but no constructors. Owning returns are decoded and freed in a finally; owning arguments require a manual wrapper.

CStrArray{owned} — string arrays

CStrArray{owned} is (length::Int64, data::Ptr{CString{owned}}): a pointer to length CStrings with matching ownership. Converting it to Vector{String} copies the strings without freeing the source.

CStrArray ownership contract

owned is :owned or :borrowed, and the two are distinct types with the same layout.

  • CStrArray{:borrowed} wraps storage the caller owns and keeps alive; the consumer never releases it.
  • CStrArray{:owned} holds a Julia allocation, produced by CStrArray{:owned}(::AbstractVector{<:AbstractString}). The consumer releases it once with jlw_free_strings. There is no borrowing constructor: a Julia string vector has no length-prefixed layout to alias.

Python target

JuliaPython
Vector{String}list[str]

A borrowed class gets from_list and as_list; an owning class gets as_list and an idempotent free(). Owning returns are copied and freed in a finally.

CDict{owned,V} — string-keyed dictionaries

CDict{owned,V} is (length::Int64, keys::Ptr{CString{owned}}, values::Ptr{V}): two parallel arrays. Keys are CStrings with matching ownership; values use a type in CDICT_VALUE_TYPES. Converting to a Julia Dict copies without freeing the source.

CDict ownership contract

owned is :owned or :borrowed, and the two are distinct types with the same layout.

  • CDict{:borrowed,V} wraps storage the caller owns and keeps alive; the consumer never releases it.
  • CDict{:owned,V} holds two separate Julia allocations, produced by CDict{:owned}(::AbstractDict{<:AbstractString,V}). keys is released with jlw_free_strings and values with jlw_free, each exactly once. There is no borrowing constructor: a Dict's storage is neither length-prefixed strings nor a dense value array.

Python target

JuliaPython
Dict{String,V}dict[str, V]

A borrowed class gets from_dict and as_dict; an owning class gets as_dict and an idempotent free(). Owning returns are copied and freed in a finally.

COpt{T} — optional scalars

COpt represents Union{T,Nothing} with an Int32 discriminant and an inline value. It is allocation-free and has no ownership state.

Python target

JuliaPython
Union{T,Nothing} for scalar TT | None

The generated from_optional and as_optional helpers convert the value without a keepalive or release operation.

CNTuple{N,T} — tuple returns

A tuple return is carried by CNTuple, whose one field holds a tuple of the elements' own carriers. Ownership is per element: a CNTuple{2, Tuple{CVector{:owned, Float64}, Int64}} has one buffer to release and one scalar to read.

There is no borrowed form, because a tuple has no argument mapping.

Python target

JuliaPython
Tuple{T1,…,TN} for mapped Tituple

The generated façade converts each element by its own kind and releases the owning ones afterwards.

JLWInterop.CNTupleType
CNTuple{N,T}

C-ABI representation of an N-element tuple return. T is the tuple of the elements' own carriers, so each element keeps its own ownership: a CNTuple{2, Tuple{CVector{:owned,Float64}, Int64}} has one buffer to release and one scalar to read.

A tuple has no argument carrier, so there is no borrowed form.

source

Owning carrier returns

Libraries returning an owning CArray, CString, CStrArray, or CDict must emit the release entrypoints at module top level:

using JLWInterop

JLWInterop.@export_release_entrypoints

The macro exports jlw_free and jlw_free_strings. Binding targets use these functions to release buffers through the library that allocated them. Owning buffers must be released exactly once; borrowed buffers must not be released.

Without these entrypoints, a target cannot safely automate owning returns. The current Python target leaves affected functions for manual wrapping. A library whose carriers are all borrowed needs no release entrypoints at all.