with one click
rust-ffi
Instructions for using whenever's internal Rust FFI abstractions
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Instructions for using whenever's internal Rust FFI abstractions
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
| name | rust-ffi |
| description | Instructions for using whenever's internal Rust FFI abstractions |
pyo3_ffi, not pyo3The low-level pyo3_ffi module is used, not pyo3 directly.
This avoids overhead, complex abstractions, and gives full control over generated code.
The src/py/ module provides safe wrappers. Key types:
| Type | Purpose |
|---|---|
PyObj | Core wrapper around *mut PyObject. Has .extract() (Copy types), .extract_ref() (ref types), .type_(), .is_none() |
Owned<T> | RAII refcount wrapper. Use Owned::new() to take ownership, .borrow() for non-owning access |
HeapType<T> | A Python heap type that carries module state via .state() → &State |
PyType | A Python type object. .same_module() checks if two types belong to the same module |
PyReturn | Alias for PyResult<Owned<PyObj>> — the return type of Python-visible functions |
PyErrMarker | Sentinel indicating the Python error indicator is set |
ContextVarBool | Copy wrapper for a context variable. Has .get() -> PyResult<bool> |
Key helpers in src/py/:
raise_value_err(), raise_type_err(), raise_key_err() — raise Python exceptionswarn_with_class(cls, msg, stacklevel) — emit a Python warning. Takes PyObj, not a raw pointerhandle_kwargs(fname, kwargs, handler) — iterate kwargs with interned string matchinghandle_one_arg(fname, args) — extract exactly one positional arg, or raise TypeErrorhandle_opt_arg(fname, args) — extract zero or one positional arghandle_one_kwarg(fname, key, kwargs) — extract a single optional kwarg by keyfind_interned(value, handler) — match a PyObj against interned strings, returns Optionmatch_interned_str(name, value, handler) — like find_interned but raises on no matchgeneric_alloc(type_, data) — allocate a Python object with given dataPyAsciiStrBuilder::format() — build a Python string without intermediate Rust StringPyTuple::with_len() / .init_item() — safe tuple construction.to_py() via the ToPy trait — convert Rust values to Python objects.to_tuple() — convert a Python sequence to a tuple (prefer over seq_len+seq_getitem)import(module_name) — import a Python module (don't call PyImport_ImportModule directly)State (in src/pymodule/def.rs) is a large struct stored on the Python module. It holds:
HeapType<T> for each class (date_type, time_delta_type, etc.)exc_repeated, exc_skipped, etc.)warn_deprecation, warn_days_not_always_24h, etc.)ContextVarBools for suppressing warningsstr_years, str_hour, str_units, etc.)Access it via cls.state() from any HeapType<T>. Unpack needed fields at the top
of the function to avoid repeated state. access:
let &State {
date_type,
str_disambiguate,
exc_skipped,
time_delta_type,
..
} = cls.state();
When adding new fields to State, update three places: the struct definition,
the initialization in init_module, and Py_CLEAR in the traverse/clear functions.
Methods are registered in a static mut METHODS: &[PyMethodDef] array using macros:
method0! — no argsmethod1! — one positional argmethod_vararg! — variable positional argsmethod_kwargs! — positional args + keyword argsclassmethod1!, classmethod_kwargs! — class methodsThe function signatures must match the macro used. For method_kwargs!:
fn my_method(cls: HeapType<MyType>, slf: MyType, args: &[PyObj], kwargs: &mut IterKwargs) -> PyReturn
PyAsciiStrBuilder instead of format!() → to_py())i32/i64 over i128 when possibleu8::eq_ignore_ascii_case() instead of manual case checkspy_eq for comparisonsPositional argument handling:
// Exactly one required arg:
let arg = handle_one_arg("method_name", args)?;
// Zero or one optional arg:
let maybe_arg = handle_opt_arg("method_name", args)?;
Kwarg handling:
handle_kwargs("method_name", kwargs, |key, value, eq| {
if eq(key, str_some_kwarg) {
// parse value
} else {
return Ok(false); // unrecognized kwarg
}
Ok(true)
})
Single optional kwarg shortcut:
let relative_to = handle_one_kwarg("total", state.str_relative_to, kwargs)?;
Building deltas from kwargs (shift/add/subtract methods):
Use handle_delta_unit_kwargs() for full datetime units, or
handle_date_delta_unit_kwargs() for calendar-only units. These build typed
DeltaMonths/DeltaDays/TimeDelta directly from kwargs.
Structured since/until kwarg parsing:
let SinceUntilKwargs { units, round_mode, round_increment } =
SinceUntilKwargs::parse(fname, state, kwargs)?;
Unit sets: Use the appropriate bitfield type:
CalUnitSet — calendar units only (years, months, weeks, days)ExactUnitSet — exact units only (weeks through nanoseconds)DeltaUnitSet — all delta units (calendar + exact)Each has from_py() for parsing from Python, .iter(), .smallest(), .contains().
Interned string matching with custom errors:
Use find_interned + manual error message when you need a specific error format.
Use match_interned_str when the default error format is acceptable.
Error handling:
raise_value_err("msg")? for ValueError.ok_or_value_err("msg")? on Options — for domain errors with specific messages.ok_or_range_err()? on Options — for generic out-of-range errors (preferred)PyErrMarker() (with parens) as the sentinel in PyResult<T>Ord in Rust. Compare via .instant() for ordering.
Non-Copy (contains Arc<TimeZone>). Uses Arc::ptr_eq + content equality for timezone comparison.
DST-aware operations need ambiguity_for_local() resolution.Instant has Ord). Offset is an Offset scalar.DateTime in Rust) compares by local date+time. Has Ord.secs: DeltaSeconds + subsec: SubSecNanos. Use .total_nanos() -> i128.
Has .in_single_unit() and .in_exact_units() for unit decomposition.DeltaField<T> with i32::MAX as the UNSET sentinel.
DeltaField has custom Debug showing <unset> for sentinel values..ok_or_range_err() for out-of-range errors instead of custom messages.// SAFETY: comments for unsafe blocks per the Rust convention (exact casing matters).pub(crate) not pub for internal visibility.