Library architecture¶
Overview¶
The library is an hourglass: a Rust core, a C ABI in ffi.rs, and
thin wrappers (PyO3, Julia ccall, C++ RAII). Every language talks
to the same rkr_* surface; none reimplements the parser.
Python (PyO3) Julia (ccall) C++ (RAII header)
\ | /
\ | /
+------- C FFI (ffi.rs) -----+
|
Rust core library
types | parser | writer | iterators
Core types (types.rs)¶
FrameHeader9-line header metadata (cell, angles, atom counts, masses, spec_version, metadata). The
spec_versionfield (u32) records the CON spec version from the JSON metadata line. Themetadatafield (BTreeMap<String, serde_json::Value>) stores additional key-value pairs from the JSON, preserving unrecognized keys through round-trips.AtomDatumSingle atom data (symbol, coordinates, fixed flag, atom_id (original atom index before type-based reordering), optional velocity / force / charge / spin / magmom / displacement / spread fields).
ConFrameComplete frame (header + atom data / SoA columns for declared sections).
ConFrameBuilderBuilder pattern for constructing frames programmatically. Accepts an optional
metadatamap; the builder always setsspec_versiontoCON_SPEC_VERSION. Exposes typed setters for common keys likeenergy,frame_index,time, etc.
Symbol strings use Arc<str> to avoid per-atom string clones
within a type block and ensure thread-safety for parallel parsing.
Parser (parser.rs)¶
parse_line_of_n<T>Generic whitespace-separated value parser for header lines.
parse_line_of_n_f64fast-float2 specialized parser for the coordinate/velocity hot path.
parse_line_of_range_f64Flexible parser accepting between
minandmaxcolumns, padding from defaults. Used for atom lines where column 5 (atom_id) is optional.parse_frame_headerConsumes 9 header lines. Detects JSON metadata on line 2 (starts with
{) for spec v2+; non-JSON line 2 triggers legacy mode (spec_version = 1). Malformed JSON (starts with{but invalid) produces an error. Reserved metadata keys are checked against the spec schema.parse_single_frameHeader + coordinate blocks. Accepts 4-column atom lines (column 5 defaults to sequential index). In validate mode, coordinate labels, symbols, fixed flags, atom ids, cell geometry, atom counts, and masses are checked before the frame is accepted.
Section parsers (
parse_velocity_section,parse_force_section, and the scalar / 3-vector paths forenergies/charges/spins/magmoms/displacements/spreads) :: Optional blocks after coordinates. Spec-v2+ files declare sections in JSON metadata; files without asectionskey can still auto-detect velocities by blank separator. Declared sections must be present at their declared position. If JSON metadata setsvalidatetotrue, section parser paths verify component symbols, exact labels, fixed masks, and atom ids against the coordinate blocks before attaching section data.
Writer (writer.rs)¶
ConFrameWriter<W: Write>Generic buffered writer.
Line 2 is always serialized as a JSON object containing
con_spec_versionplus any entries fromframe.header.metadata. The originalprebox_header[1]value is overwritten. Managed keys (con_spec_versionandsections) are regenerated by the writer; whenvalidate=true, the writer emits an emptysectionsarray for coordinate-only frames.Writes header, coordinate blocks, then any declared optional sections present on the frame (
has_velocities,has_forces,has_energies,has_charges,has_spins,has_magmoms,has_displacements,has_spreads) insectionsorder.
Iterators (iterators.rs)¶
ConFrameIteratorLazy frame-by-frame parser with
next()andforward()(skip without parsing atom data).read_all_frames()Convenience function using memmap2 for large trajectory files. With the
parallelfeature, Rayon parse turns on automatically when the buffer is at least 48 KiB.read_all_frames_with_threads(path, n)Noneor0is that automatic policy,1is sequential,n >2= pins a Rayon pool (sequential fallback whenparallelis off).
parse_frames_parallel()/parse_frames_parallel_with_threads():: Rayon-based parallel parsing behind theparallelfeature gate.
FFI layer (ffi.rs)¶
Opaque handle pattern:
RKRConFrameOpaque Rust frame handle.
CFrame/CAtomTransparent C structs for direct data access.
Iterator lifecycle:
read_con_file_iterator->con_frame_iterator_next->rkr_frame_to_c_frame->free_c_frame->free_rkr_frame->free_con_frame_iterator.
Version and spec constants:
#define RKR_CON_SPEC_VERSION 2Compile-time spec version for
#ifguards.rkr_con_spec_version()Runtime library spec version query.
rkr_library_version()Returns library version string (e.g.
"0.5.2"). The pointer is static; do not free it.
Per-frame metadata accessors:
rkr_frame_spec_version(handle)Returns the spec version stored in a parsed frame’s header (the version the file was written with, not the library’s version).
rkr_frame_metadata_json(handle)Returns the full JSON metadata line as a heap-allocated C string. The caller must free with
rkr_free_string().rkr_frame_energy(handle),rkr_frame_time(handle), etc.Typed accessors for common metadata keys.
rkr_read_all_framesSame automatic policy as
read_all_framesfor the features the library was built with. The published C prefix enablesparallel.rkr_read_all_frames_n_threads(filename, num_frames, n_threads)0auto,1sequential,n_threads >2= a Rayon pool.rkr_has_parallel_support()1when Rayon is linked.RKRStatusPrefixed C-compatible status codes such as
RKR_STATUS_SUCCESS,RKR_STATUS_NULL_POINTER, andRKR_STATUS_BUFFER_TOO_SMALL.rkr_status_message(status)Returns a static human-readable message for every status value.
Builder FFI functions preserve the full atom payload. Existing boolean fixed helpers remain available, while
\*_fixed_maskvariants accept per-axis fixed flags. Force and velocity+force variants call the same RustConFrameBuilderpaths as native Rust.
C++ wrapper (readcon-core.hpp)¶
Header-only RAII wrappers with lazy caching:
ConFrameIteratorRange-based for loop support.
ConFrameCached accessors for cell, angles, atoms, headers, velocities, forces, and metadata helpers.
ConFrameWriterRAII file writer.
ConFrameBuilderOverloads for boolean fixed flags and
std::array<bool, 3>masks, with velocity, force, and velocity+force atom constructors.
Python wrapper (python.rs)¶
The PyO3 layer stores ConFrame.atoms as a live Python list and
ConFrame.metadata as a live Python dict. Writers validate metadata
as JSON-compatible values and convert the live atom list into a Rust
ConFrame at write time. read_first_frame(path) uses the Rust
first-frame reader; iter_con(path) exposes a Python iterator for
loop-oriented frame processing.
ASE conversion maps all-fixed atoms to FixAtoms and partial masks to
FixCartesian, preserving atom_id through a named ASE array.
Julia wrapper (wrapper.jl)¶
The Julia ccall layer exposes typed frame metadata fields populated
from C FFI accessors. write_con(path, frames; precision=6)
reconstructs opaque frame handles through the C builder API and writes
them through the Rust writer, preserving velocities, forces, per-axis
fixed masks, masses, atom ids, and JSON metadata.
C ABI install layout (cargo-c)¶
The [package.metadata.capi.*] keys in Cargo.toml drive
cargo-c. Running
cargo cinstall --prefix=$PREFIX --libdir=$PREFIX/lib --library-type cdylib
produces:
Asset |
Source |
Install path |
|---|---|---|
C header |
|
|
C++ header |
|
|
pkg-config file |
cargo-c / CMake / Meson (same name) |
|
cmake package |
|
|
Linux shared object |
|
|
macOS dylib |
|
|
Windows DLL + import lib |
|
|
The header is shipped pre-generated at include/readcon-core.h.
CMake (FetchContent / find_package), Meson (wrap / pkg.generate
filebase: readcon-core), and cargo-c (generation = false) all
install that file. None of them run cbindgen. The cxx source tarball
(scripts/package-cxx.sh → readcon-core-cxx-$VERSION.tar.gz on the
GitHub Release) is the FetchContent / wrapdb URL. The prebuilt C ABI
tarball (scripts/package-clib.sh →
readcon-core-clib-$VERSION-$target.tar.gz) is the Julia / Fortran /
pkg-config consumer path; attach it to an existing tag with
.github/workflows/c_lib_tarball.yml (workflow_dispatch tag).
Windows + chemfiles is not a clib asset. Maintainers only:
scripts/regen-capi-headers.sh.
The
generation = false flag in [package.metadata.capi.header] tells
cargo-c to copy it from the install assets list rather than running
cbindgen at install time. Optional metatensor C exports are gated
(READCON_CORE_HAS_METATENSOR); prefer include/readcon-metatensor.h
(metatensor.h first). Fat builds write
target/<profile>/readcon-metatensor.env for include/lib. Ownership:
tensor_block_into_raw_mts then mts_block_free only (bindings page).
Maintainers may draft a header with scripts/regen-capi-headers.sh.
That tool is not a consumer or CI dependency. CI compiles C/C++ against
the committed header and checks that the library exports the declared
rkr_* symbols.
The [package.metadata.capi.pkg_config] filename = "readcon-core"
override is load-bearing: without it cargo-c defaults the .pc filename
to the [lib] name (readcon_core), which downstream packagers check
for at the hyphenated name.
C ABI stability contract (rkr_*)¶
The published C surface is the rkr_* API in the shipped
include/readcon-core.h. Consumers compile against that header; they
do not run cbindgen. The C++ wrapper (include/readcon-core.hpp) is a
header-only RAII layer over the same symbols.
What is frozen in 0.14.x **¶
Symbol prefix
rkr_*and the opaque handle types (RKRConFrame,RKRConFrameWriter,RKRConFrameBuilder,CConFrameIterator).Ownership rules in the header comment: caller-owned handles,
free_rkr_frame_arrayvsfree_rkr_frame,rkr_free_stringfor heapchar*, process-staticrkr_library_version.Sentinel policy:
NaNfor absent floats,UINT64_MAXfor absent unsigned integers,NULLfor absent strings.Error policy: functions that allocate or validate return
NULLor a negativeRKRStatus; they do not abort the process. Builderrkr_frame_builder_buildreturnsNULLonMassMismatchand otherParseErrorcases.Feature-gated symbols (
RKR_STATUS_FEATURE_DISABLED) stay in the header; a lean build returns that status instead of hiding the symbol.Periodic-table helpers stay at Z=1..92 plus D/T. The C ABI does not grow a Z=118 table.
What is not a 1.0 freeze **¶
The crate, wheels, and Fortran package are 0.16.1. PyPI still reports
Development Status :: 4 - Beta. A 1.0.0 cut is the production
classifier and the SONAME/semver major bump. Until that tag:
Additive
rkr_*symbols may appear in a 0.14.z patch.Existing 0.14.x signatures, sentinels, and ownership do not change in a patch.
A breaking C change requires a 0.15+ minor (0.x semver) and a matching header/SONAME bump. Downstream packagers that ship
run_exportsshould pin anupper_boundofx.xfor that reason.
Drift check **¶
The committed include/readcon-core.h is the C ABI. CI (ci_cxx.yml,
Meson C/C++ examples, check_feature_matrix.sh) compiles against that
file and nm-checks the exported symbols. cbindgen is a maintainer
draft helper only.
Design rationale¶
The hourglass is the design: Rust core, rkr_* C ABI, wrappers for
C++, Python, Julia, and Fortran. The full CON payload (constraints,
optional section data, atom_id, JSON) is available without a Python
interpreter on the I/O path.
Also in-tree: chemfiles import/selection (land structures as CON), SoA +
DLPack export (optional CUDA), metatensor TensorBlock export, index_proj
for campaign screening (readcon-db), Cap’n Proto RPC. Lean builds return
RKR_STATUS_FEATURE_DISABLED for missing features. UTF-8 CON remains
authoritative on disk and in corpora.
Related work and roles: Frequently Asked Questions. Spec: The CON File Format Specification. Evolution: Format Evolution and Design Rationale. Measurements: Performance Benchmarks.