csv-async

Async CSV reading and writing for Rust that mirrors the sync csv crate's API for tokio and async-std runtimes.

Library
Cargo
v1.3.1
51stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
33/100Needs Attention
Development Activity0
Maintenance20
Community40
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
67/100Good
Architecture76
Code Quality80
Innovation48
Learning Curve65

csv-async is a Rust crate for reading and writing CSV data in asynchronous contexts. It closely mirrors the API of the widely-used csv crate (from which it shares the underlying csv-core parsing engine), so developers already familiar with synchronous CSV handling in Rust can adopt it with minimal relearning — the main difference is create_... factory methods on builders instead of from_..., and .await on record iteration.

The crate is runtime-agnostic by design: by default it builds on futures::io::AsyncRead/AsyncWrite, which works with async-std and any other futures-compatible executor, while an optional tokio feature switches the reader/writer to tokio::io::AsyncRead/AsyncWrite and swaps the stream implementation to tokio-stream. With the with_serde feature (enabled by default), CSV rows can be deserialized directly into typed structs via serde, or serialized back out, without leaving the async execution context.

What You Get

  • AsyncReader/AsyncWriter for streaming CSV records without blocking the async runtime
  • AsyncDeserializer/AsyncSerializer for typed row (de)serialization via serde derive macros
  • A choice of backend: futures/async-std by default, or tokio via the tokio cargo feature
  • AsyncReaderBuilder/AsyncWriterBuilder for configuring delimiters, terminators, headers, trimming, and flexible-length rows
  • StringRecord and ByteRecord types for UTF-8-validated or raw-byte CSV rows

Common Use Cases

  • Streaming large CSV files from disk or network sockets without blocking the async executor
  • Filtering, transforming, or re-shaping CSV rows inside async web handlers or background tasks
  • Migrating an existing codebase from the synchronous csv crate to async I/O with minimal API churn
  • Deserializing CSV rows directly into typed Rust structs inside async ETL or ingestion pipelines

Under The Hood

Architecture csv-async layers async reader/writer/(de)serializer types on top of the same csv-core DFA-based parsing engine used by the synchronous csv crate, so byte-level CSV parsing semantics are shared rather than reimplemented. Builder types (AsyncReaderBuilder, AsyncWriterBuilder) configure a boxed csv_core::ReaderBuilder/state machine, then hand off to one of two backend implementations selected at compile time via cfg_if! on the tokio feature: src/async_readers/ardr_futures.rs and ades_futures.rs implement the default futures/async-std path, while ardr_tokio.rs and ades_tokio.rs provide tokio-native equivalents built on tokio::io::AsyncBufRead and exposed as a tokio_stream::Stream. StringRecord/ByteRecord in src/byte_record.rs and src/string_record.rs carry parsed field data across both backends, and src/error.rs centralizes a typed Error/ErrorKind hierarchy rather than using raw I/O errors. The trade-off of this cfg_if-branched design is some duplication between the futures and tokio code paths in exchange for avoiding a heavier async-trait abstraction layer.

Tech Stack The crate targets Rust 2021 edition (rust-version 1.61+) and depends on csv-core for parsing, cfg-if for feature-gated backend selection, and futures 0.3 for the default async I/O traits and stream types. The optional tokio feature (off by default) pulls in tokio 1.25 with the io-util feature plus tokio-stream, letting callers use tokio::fs::File instead of async_std::fs::File. The optional with_serde feature (on by default) adds serde, itoa, and ryu for numeric formatting during typed (de)serialization, plus bstr’s serde support. Dev-dependencies (async-std, chrono, indoc, and a second tokio build with fs/rt-multi-thread/macros) exist purely to exercise both backends in the test suite.

Code Quality The crate enforces #![deny(missing_docs)] at the crate root, requiring every public item to carry doc comments — roughly a fifth of the ~5,700 lines of Rust are doc comments, and src/error.rs (342 lines) defines a dedicated typed error hierarchy instead of surfacing raw io::Error. Integration tests in tests/read_records.rs and tests/read_serde.rs use a custom #[helpers::test] attribute macro that runs each test body against both the async-std and tokio backends from a single test definition, covering malformed rows, non-UTF-8 files, and serde round-tripping. CI runs the test suite across Linux, Windows, and macOS via GitHub Actions with codecov coverage reporting. A small amount of unsafe code is isolated to string_record.rs and explicitly called out in a source comment rather than scattered through the crate.

API Design The public API is deliberately shaped to match the synchronous csv crate field-for-field, differing mainly in create_... builder methods (versus from_...) and .await on iteration — this trades a small amount of naming novelty for a near-zero learning curve for anyone who already knows csv. Reader/writer/(de)serializer construction, configuration, and record access all follow the same builder-then-use pattern, and the crate’s own doc comments include full working examples for both the futures/async-std and tokio code paths.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search