COOLJAPAN
← All posts

OxiXML 0.1.2 Released — True oxrdf API Parity and Zero-Allocation Iteration

OxiXML 0.1.2 closes every documented API-parity divergence from oxrdf/oxttl/oxrdfxml/oxjsonld: borrowed-ref iteration, impl Into<String> builders, Send JSON-LD parsers, and a JsonLdSerializer that finally emits its @context/@graph envelope. Deliberately breaking vs 0.1.1, Pure Rust, 0 external crates by default.

release oxixml rdf turtle json-ld xml pure-rust

oxixml-model’s own documentation has claimed since 0.1.0 that code written against oxrdf ports over by changing only the use path. Until today, that claim was not true.

Today we released OxiXML 0.1.2 — a deliberately breaking release that closes every documented gap between oxixml-model’s public API and its oxrdf / oxttl / oxrdfxml / oxjsonld counterparts, checked signature-by-signature against oxrdf 0.3.3, oxttl 0.2.3, oxrdfxml 0.2.3, oxjsonld 0.2.5, oxrdfio 0.2.5, and spargebra 0.4.6.

No C, no C++, no Fortran — still the same zero-external-crates-by-default Pure-Rust workspace introduced at 0.1.0 and hardened at 0.1.1. 0.1.2 breaks compatibility with 0.1.1 on purpose and ships no compatibility shims: 0.1.1 is superseded, and 0.1.2 is the version to depend on. It compiles to a single static binary, targets WASM, and runs everywhere.

Why OxiXML 0.1.2 is a game changer

At 0.1.1, oxixml-model looked like oxrdf on the surface and diverged the moment real code ran against it:

OxiXML 0.1.2 ends all of that:

Technical Deep Dive: closing the gap signature by signature

  1. Interning is what makes the borrow free. oxixml-model’s string table already existed at 0.1.1; 0.1.2 changes what leaves it. Graph/Dataset iteration, every pattern query, and the vocabulary constants now decode straight out of that table as *Ref<'_> types — a borrow into the interned store, not a fresh Triple/Quad/NamedNode built per item.
  2. One trait, IntoOxString, unifies every &str constructor. NamedNode::new, BlankNode::new, Variable::new, Literal::new_simple_literal/new_typed_literal, and the language-tagged constructors used to require Into<OxString> — and OxString is OxStr<'static>, so any &str that didn’t happen to live for 'static was rejected outright, which is the single most common thing a port from oxrdf’s impl Into<String> would hit. IntoOxString copies a borrowed &str and moves an owned OxString/String without a new allocation, so the zero-copy paths this crate already had keep working exactly as before.
  3. The serializer tier follows the model tier. rdf/oxixml-turtle, -rdfxml, -jsonld, -trix, and -io’s serialize_triple/serialize_quad — blocking, async, and low-level — now take impl Into<TripleRef<'a>>/impl Into<QuadRef<'a>> instead of &Triple/&Quad, so a graph parsed, filtered, and re-serialized through the same borrowed loop never clones a term between read and write. Existing &owned call sites keep compiling unchanged.
  4. What’s still deliberately different from oxrdf — and now says so. Three gaps remain, documented in oxixml-model’s own crate docs instead of left for a reader to discover: Term additionally implements PartialEq against NamedNode/BlankNode/Literal, which makes term == x.into() ambiguous and needs term == Term::from(x); owned accessors return OxString rather than String; and oxrdf’s deprecated is_plain/LiteralRef::destruct aren’t provided.

Getting Started

cargo add oxixml-model oxixml-turtle
use oxixml_model::{Graph, vocab::rdf};
use oxixml_turtle::TurtleParser;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let file = r#"@prefix schema: <http://schema.org/> .
<http://example.com/alice> a schema:Person ;
    schema:name "Alice" ."#;

    // Parse into an owned Graph.
    let mut graph = Graph::new();
    for triple in TurtleParser::new().for_reader(file.as_bytes()) {
        graph.insert(&triple?);
    }

    // New in 0.1.2: iterating a borrowed &Graph yields TripleRef<'_>,
    // not an owned Triple — no clone per item, matching oxrdf.
    for triple in &graph {
        if triple.predicate == rdf::TYPE {
            println!("{} is a {}", triple.subject, triple.object);
        }
    }

    // Need to keep one past the graph's lifetime? Ask for it explicitly.
    let first_owned = graph.iter().next().map(|t| t.into_owned());
    assert!(first_owned.is_some());
    Ok(())
}

Or through the facade, with only the features you need:

[dependencies]
oxixml = { version = "0.1.2", features = ["turtle"] }

What’s New in 0.1.2

Breaking

Fixed

Full itemized detail is in the CHANGELOG.

Tips

This is the foundation

OxiXML depends on nothing else in the ecosystem, so any project can adopt it without a dependency cycle. The ecosystem-wide migration wave that landed the same day as this release already put OxiRS, OxiEphemeris, TensorLogic, and OxiFY — alongside OxiGeo, OxiMedia, Legalis-RS, and the rest of the seventeen COOLJAPAN projects that used to pull quick-xml or an Oxigraph crate directly — on top of OxiXML tier by tier. 0.1.2 is what turns the next hop, from 0.1.1’s owned-iterator shape to this release’s borrowed one, into a routine version bump instead of a rewrite.

Repository: https://github.com/cool-japan/oxixml

Star the repo if you want an RDF stack that is drop-in compatible with oxrdf in fact, not just in the crate docs’ claim — zero-allocation iteration included.

The era of a migration guide that breaks on the first borrowed loop is over. Pure Rust XML and RDF — complete, conformant, and sovereign — is here.

KitaSan at COOLJAPAN OÜ August 10, 2026

↑ Back to all posts