Vdir client library, written in Rust.
This library manages vCard (.vcf) and iCalendar (.ics) files inside Vdir filesystems. It is composed of 2 feature-gated layers:
- Low-level I/O-free coroutines: these
no_std-compatible state machines contain the whole Vdir logic and can be used anywhere - Mid-level std client: a standard, blocking Vdir client built on
std::fs
- I/O-free coroutines:
no_stdstate machines; no filesystem calls, no async runtime, nostdrequired, drive against any blocking, async, or fuzz harness. - Standard, blocking client (requires
clientfeature) backed bystd::fs. - Collections: create, delete, list, rename and update directories of items, including the optional
displayname,descriptionandcolormetadata markers. - Items: store, get, list, locate, copy, move and delete vCard and iCalendar files, with atomic writes via a temporary file then rename.
- Optional parsing (
parserfeature): decode item bytes into calcard vCard or iCalendar values.
Tip
I/O Vdir is written in Rust and uses cargo features. The default feature set is declared in Cargo.toml or on docs.rs.
Every coroutine implements the VdirCoroutine trait. Its resume(arg: Option<VdirReply>) method returns a VdirCoroutineState<Yield, Return> with two variants:
Yielded(Y): intermediate.Yis the per-coroutine yield type; every io-vdir coroutine picks the standardVdirYield, whose variants areWantsRandom { len },WantsDirCreate,WantsDirRead,WantsDirExists,WantsDirRemove,WantsFileCreate,WantsFileRead,WantsFileExists,WantsFileRemove,WantsRename,WantsCopy. The driver services the request and feeds back the matchingVdirReplyvariant on the nextresume.Complete(R): terminal. By conventionR = Result<Output, Error>carrying the operation's final value.
No features required: works in #![no_std], no filesystem calls, no async runtime. You own the loop and the syscalls; the library only computes the operations to perform and consumes their results.
Drive a multi-step command (store an item) against a blocking caller (the same shape works under async or in-memory replay):
use std::{collections::hash_map::RandomState, fs, hash::{BuildHasher, Hasher}};
use io_vdir::{coroutine::*, item::{store::*, types::ItemKind}, path::VdirPath};
let collection = VdirPath::new("/path/to/vdir/contacts");
let bytes = b"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Alice\r\nEND:VCARD\r\n".to_vec();
let opts = VdirItemStoreOptions::default();
let mut coroutine = VdirItemStore::new(collection, None, ItemKind::Vcard, bytes, opts);
let mut arg: Option<VdirReply> = None;
let out = loop {
match coroutine.resume(arg.take()) {
VdirCoroutineState::Complete(Ok(out)) => break out,
VdirCoroutineState::Complete(Err(err)) => panic!("{err}"),
VdirCoroutineState::Yielded(VdirYield::WantsRandom { len }) => {
// Real code fills via the OS RNG; this xorshift64* keeps the
// example dependency-free.
let mut out = vec![0u8; len];
let mut state = RandomState::new().build_hasher().finish();
for byte in &mut out {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
*byte = state as u8;
}
arg = Some(VdirReply::Random(out));
}
VdirCoroutineState::Yielded(VdirYield::WantsFileCreate(files)) => {
for (path, bytes) in files {
fs::write(path.as_str(), &bytes).unwrap();
}
arg = Some(VdirReply::FileCreate);
}
VdirCoroutineState::Yielded(VdirYield::WantsRename(pairs)) => {
for (from, to) in pairs {
fs::rename(from.as_str(), to.as_str()).unwrap();
}
arg = Some(VdirReply::Rename);
}
VdirCoroutineState::Yielded(other) => unreachable!("VdirItemStore yielded {other:?}"),
}
};
println!("stored {} at {}", out.id, out.path);Enable the client feature (on by default). VdirClient::new(root) wraps a filesystem root and exposes one method per coroutine; the resume loop is driven for you via VdirClient::run and std::fs.
[dependencies]
io-vdir = "0.0.3" # client is enabled by defaultuse io_vdir::{client::VdirClient, collection::types::Collection, item::types::ItemKind};
let client = VdirClient::new("/path/to/vdir");
let contacts = Collection::from_path("/path/to/vdir/contacts");
client.create_collection(contacts).unwrap();
let bytes = b"BEGIN:VCARD\r\nVERSION:4.0\r\nFN:Alice\r\nEND:VCARD\r\n".to_vec();
let (id, path) = client
.store_item("/path/to/vdir/contacts", None, ItemKind::Vcard, bytes)
.unwrap();
println!("stored {id} at {path}");See complete examples at ./examples.
Have a look at real-world projects built on top of this library:
- io-addressbook: Set of I/O-free Rust coroutines to manage contacts.
- io-calendar: Set of I/O-free Rust coroutines to manage calendars.
- Cardamum: CLI to manage contacts.
- Calendula: CLI to manage calendars.
This project is licensed under either of:
at your option.
This project is developed with AI assistance. This section documents how, so users and downstream packagers can make informed decisions.
-
Tools: Claude Code (Anthropic), Opus 4.7, invoked locally with a persistent project-scoped memory and a small set of repo-specific rules.
-
Used for: Refactors, mechanical multi-file edits, boilerplate (feature gates, error enums, derive macros, trait impls), test scaffolding, doc polish, exploratory design conversations.
-
Not used for: Engineering, critical code, git manipulation (commit, merge, rebase…), real-world tests.
-
Verification: Every AI-assisted change is read, compiled, tested, and formatted before commit (
nix develop --command cargo check / cargo test / cargo fmt). Behavioural correctness is verified against the relevant RFC or upstream spec, not assumed from the model output. Tests are never adjusted to fit AI-generated code; the code is adjusted to fit correct behaviour. -
Limitations: AI models occasionally produce code that compiles and passes tests but is subtly wrong: off-by-one errors, missed edge cases, plausible but nonexistent APIs, stale RFC references. The verification workflow catches most of this; it does not catch all of it. Bug reports are welcome and taken seriously.
-
Last reviewed: 31/05/2026
- Chat on Matrix
- News on Mastodon or RSS
- Mail at pimalaya.org@posteo.net
Special thanks to the NLnet foundation and the European Commission that have been financially supporting the project for years:
- 2022 → 2023: NGI Assure
- 2023 → 2024: NGI Zero Entrust
- 2024 → 2026: NGI Zero Core
- 2027 in preparation…
If you appreciate the project, feel free to donate using one of the following providers:
