qtty-ffi
C-compatible FFI bindings for qtty physical quantities and unit conversions.
What this crate provides
- Stable ABI:
#[repr(C)]/#[repr(u32)]types with explicit discriminants for every unit. - Generated header:
build.rsrunscbindgen, producinginclude/qtty_ffi.hfor C/C++ consumers. - CSV-driven registry: every unit lives in
units.csv; edit one file and the Rust code plus header stay in sync (details inunits.csv.md). - Rust helpers: conversion traits, helper fns, and the
impl_unit_ffi!macro to adapt your own quantity wrappers.
Building & header generation
# Build cdylib/staticlib/rlib targets
# Generated header checked into git:
# qtty-ffi/include/qtty_ffi.h
build.rs re-runs whenever units.csv, src/, or cbindgen.toml change. It emits:
UnitIdenum variants/discriminants and the lookup tables behindqtty_unit_*APIs.- The runtime registry that powers conversions.
- The C header described above.
Unit definitions
units.csv rows look like:
discriminant,dimension,name,symbol,ratio
10011,Length,Meter,m,1.0
21000,Time,Minute,min,60.0
30001,Angle,Degree,deg,0.017453292519943295
- Discriminants follow a DSSCC scheme (
D = dimension,SS = system/category,CC = counter). - Dimension must be one of the
DimensionIdvariants (Length,Time,Angle,Mass,Power). - Ratio is the scale factor relative to the canonical unit (meters, seconds, radians, grams, watts).
The CSV becomes the ABI contract: review diffs to see exactly which units were added or changed.
ABI surface
Types
typedef struct qtty_quantity_t;
typedef enum DimensionId;
UnitId is #[repr(u32)] and contains every row from units.csv. Layouts (16-byte qtty_quantity_t, 4-byte enums) are part of the ABI contract.
Status codes
| Constant | Value | Meaning |
|---|---|---|
QTTY_OK |
0 | Success |
QTTY_ERR_UNKNOWN_UNIT |
-1 | Invalid unit id |
QTTY_ERR_INCOMPATIBLE_DIM |
-2 | Dimension mismatch |
QTTY_ERR_NULL_OUT |
-3 | Null output pointer |
QTTY_ERR_INVALID_VALUE |
-4 | Reserved for future validation |
Functions
bool ;
int32_t ;
int32_t ;
const char* ;
int32_t ;
int32_t ;
int32_t ;
uint32_t ; // currently 1
Usage from C/C++
int
Usage from Rust
use ;
use ;
// Built-in conversions via From/TryFrom
let ffi: QttyQuantity = new.into;
let km: Kilometers = ffi.try_into.unwrap;
assert!;
// Custom wrapper (must expose new()/value())
;
impl_unit_ffi!;
let ffi_again: QttyQuantity = new.into;
Helper functions like meters_into_ffi, try_into_hours, etc. are available for ergonomic wrappers.
ABI stability & thread safety
- Existing
UnitId/DimensionIddiscriminants, status codes, type layouts, and exported signatures will not change. - New variants/functions may be added in minor versions (callers should handle unknown ids defensively).
- All functions are thread-safe and never panic; errors flow through the status codes above.
- Special
f64values (NaN,±INF) are passed through untouched.
License
Same license as the parent workspace (AGPL-3.0). See ../LICENSE.