Marwes/combine parser design, zero-copy parsing, and partial input
A parser combinator library for Rust
At a glance
- What is it?
- Marwes/combine is an MIT-licensed Rust parser combinator library for arbitrary streams, zero-copy parsing over in-memory data, and parsers that can pause until more input arrives. It suits developers who want composable grammar code while accepting LL(1)-first parsing and less precise default error positions.
- Who is it for?
- Adopt combine when a Rust parser needs composable combinators, access to borrowed input, or resumable network input without committing to a generated grammar. Do not choose it solely for precise default error offsets or easily enabled backtracking, since these require explicit design choices.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why combine targets composable Rust grammars
combine solves the problem of building recursive parsers without writing a separate parser generator or implementing low-level input handling for every grammar rule. It is aimed at Rust developers creating format, protocol, query, template, or language parsers. The basic abstraction is a parser combinator: a function that accepts existing parsers and returns a new parser. For example, the README explains that `many` accepts a parser and applies it zero or more times.
This approach keeps grammar definitions in ordinary Rust code. A parser can therefore be assembled from reusable units, named in local variables, passed through functions, and given explicit output types. That matters when the parser is part of a larger application rather than a generated standalone tool. combine follows Parsec as its inspiration, but exposes that model through Rust traits, iterators, streams, and collection types.
The intended users are not limited to people implementing conventional programming languages. The repository links Rust projects that use combine for GraphQL, TOML editing, diff formats, Redis protocol handling, JavaScript, JPEG metadata, query parsing, and template languages. That list does not prove suitability for every one of those workloads, but it does show that the library is aimed at both complete parsers and lexer-like components. combine-language is also a separate crate for language lexing and parsing, which helps keep the core package focused on general parser composition.
LL(1) composition and explicit lookahead trade-offs
combine parsers are LL(1) by default, following the design of Parsec. In practical terms, the parser normally needs to distinguish the next production using bounded lookahead. This is a meaningful constraint when alternatives can share a prefix. A grammar that begins with similar tokens may require refactoring, a more explicit lookahead construct, or another parsing approach.
The library provides `attempt` as an opt-in mechanism for arbitrary lookahead. That is a significant design choice because it makes the more permissive behavior deliberate rather than an implicit property of every combinator. It can help with grammars that cannot remain LL(1), but the README does not give a performance comparison between the default path and `attempt`, nor does it document a retry budget. Operators should treat unrestricted backtracking as a grammar-level cost that needs examination rather than assuming it is free.
This makes combine more constrained than a parser generator that exposes left recursion or grammar analysis directly. It also differs from runtime-parsing libraries built around a different error model or input abstraction. For straightforward, deterministic grammars, LL(1) composition can keep parser definitions compact. For deeply ambiguous inputs, the burden shifts to the developer, who must decide where `attempt` belongs and what behavior should occur when several branches consume input before failing.
Input flow across slices, readers, iterators, and custom streams
combine is designed to parse more than a single in-memory string. The README states that built-in streams cover `&[u8]`, `str`, iterators, and `Read` instances. Applications can also implement the relevant traits to define a custom stream. That abstraction is the core data flow: a parser receives a value representing the current stream position, consumes input as combinators succeed, and returns either a parsed value or an error together with remaining input.
The basic example makes that return shape visible. `parse` returns a `Result` whose successful value contains both the parser output and the unconsumed input. This is more useful than exposing only the parsed value when the language has no terminator or when the parser intentionally stops after one item. It also lets callers decide whether leftover input is acceptable.
The in-memory path can avoid copying. The README points to the `range` module for parsers specialized in zero-copy parsing, while the `ZeroCopy` topic is reinforced by the package description in `Cargo.toml`. Borrowing is useful when tokens must remain references into the original buffer or when copying large slices would add avoidable allocation and memory traffic. However, the repository overview does not quantify that benefit or claim universal zero-copy behavior across every stream. The distinction is important: zero-copy support is a capability for suitable inputs and parsers, not a blanket promise for all grammar actions.
A first parser built from many1 and sep_by
The clearest first use is the README's word parser. It parses one or more letters into a `String`, separates those words with whitespace, collects them into a `Vec`, and maps the result to the last word. The type annotations are necessary because `sep_by` can collect into any type implementing `Default + Extend`, so Rust needs help inferring both the outer `Vec` and inner `String`.
This example demonstrates three distinct layers. `many1(letter())` defines repetition. `sep_by(word, space())` adds separators and collection. `map` changes the successful parsed collection into the value required by the application. The remaining input is retained, so the exclamation mark is not silently discarded.
The following is the complete example shown in the README:
extern crate combine;
use combine::{many1, Parser, sep_by};
use combine::parser::char::{letter, space};
let word = many1(letter());
let mut parser = sep_by(word, space())
.map(|mut words: Vec<String>| words.pop());
let result = parser.parse("Pick up that word!");
assert_eq!(result, Ok((Some("word".to_string()), "!")));After this parses the input, the successful output contains `Some("word".to_string())`, and the remaining input is `"!"`. The repository does not give a dedicated installation command. Its README directs users to crates.io and links to the versioned documentation on docs.rs, while the crate metadata identifies the package as `combine` with version `4.6.9`. The wiki contains the longer tutorial and explanations of internal behavior.
Resumable parsing for incomplete socket data
Partial parsing allows a parser to stop when the current stream does not contain enough data and resume later without losing its progress. The README identifies sockets as a principal use case: data can arrive across multiple reads, and the parser does not have to be reconstructed from the beginning each time. If another fragment is required, the parser can wait and then continue at the same logical point.
This is more than ordinary success-or-failure parsing over a complete `&str`. It is useful for framed protocols and incremental decoders where the consumer needs to distinguish incomplete input from malformed input. The repository includes `examples/async.rs`, and its feature list points to that example as the place to inspect the asynchronous pattern. `Cargo.toml` also lists optional dependencies for several generations of Tokio and futures, plus `tokio-util`, `bytes`, and the older `bytes 0.5` package. Those optional packages indicate that compatibility paths exist, but the truncated metadata does not state which feature names activate each dependency.
The operational boundary is equally important. A paused parser does not remove the need to retain the unconsumed input or the parser state while more bytes arrive. The README does not document timeout policy, maximum parser-state size, cancellation behavior, or how a transport failure is represented. Those details must be established by the surrounding application rather than assumed from combinator composition.
Error offsets, licensing, releases, and upkeep
combine's default error locations are its clearest documented rough edge. Streams over `str` and slices do not carry additional position information. Instead, the implementation compares buffer pointers to determine stream ordering, which can produce positions that are difficult to interpret. The README gives two remedies: call `translate_position` on a `PointerOffset`, or wrap the stream with `State`. This means an application that presents line and column diagnostics to users needs to account for position reporting separately from parsing.
The project is published under the MIT license. That identifier and the repository's `LICENSE` file establish the license terms to review before distribution or integration. They do not remove the need to read the actual license text and comply with its notice and other conditions.
The package adheres to semantic versioning. Version `4.6.9` is listed as the current repository version, with a release on 2026-10-15, following `v4.6.8` from 2026-08-25. The last push was on 2026-10-15, so the repository has recent development activity. The presence of `CHANGELOG.md`, `release.toml`, and continuous integration configuration indicates some release and validation infrastructure, although the repository does not specify its upgrade policy or compatibility guarantees across all feature combinations. Because optional dependencies span multiple Tokio and bytes generations, upgrades deserve feature-specific checks rather than an assumption that the default parser use is the whole surface.
Editorial conclusion
Adopt combine when a Rust parser needs composable combinators, access to borrowed input, or resumable network input without committing to a generated grammar. Do not choose it solely for precise default error offsets or easily enabled backtracking, since these require explicit design choices. Before adoption, verify the parser's lookahead requirements and test the `State` or `translate_position` error-location path against the errors your users will see.
Frequently asked questions
Does combine support zero-copy parsing?
Yes, when parsing in-memory data through suitable combinators. The README points to the `range` module for parsers specialized in zero-copy parsing, but it does not claim that every stream or parser action is zero-copy.
Can combine parse data received in chunks?
Yes. The documented partial-parsing feature can stop when more input is needed and resume from the same point later. The repository includes `examples/async.rs` for the asynchronous pattern.
Why does combine produce hard-to-read error positions?
Streams over `str` and slices rely on buffer-pointer comparison rather than carrying extra position information. The README recommends calling `translate_position` on a `PointerOffset` or wrapping the stream with `State`.
What kinds of input can a combine parser consume?
Built-in support covers `&[u8]`, `str`, iterators, and `Read` instances. Developers can also implement the required traits when none of the built-in stream types fit an application.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/marwes-combine)Was this useful?