Expand description
Zero-copy RESP2 and RESP3 protocol parser and serializer.
resp-rs provides high-performance parsing and serialization for the
Redis Serialization Protocol
(RESP), supporting both RESP2 and RESP3.
§Features
- Zero-copy parsing using
bytes::Bytes– parsing a bulk string is an O(1) slice operation, not a copy - RESP2 and RESP3 support with separate
resp2::Frameandresp3::Frametypes - Streaming parser (
resp2::Parser/resp3::Parser) for incremental data – handles partial reads and pipelining - Serialization via
resp2::frame_to_bytesandresp3::frame_to_bytes - Minimal dependencies – only
bytesandthiserror - No async runtime required – pure sync parsing that works in any context
§Modules
| Module | Description |
|---|---|
resp2 | RESP2 types, resp2::parse_frame, resp2::frame_to_bytes, resp2::Parser |
resp3 | RESP3 types, resp3::parse_frame, resp3::frame_to_bytes, resp3::Parser, resp3::parse_streaming_sequence |
§Quick Start
§Parsing a RESP2 command
use bytes::Bytes;
use resp_rs::resp2::{self, Frame};
// A Redis SET command on the wire
let data = Bytes::from("*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n");
let (frame, remaining) = resp2::parse_frame(data).unwrap();
assert_eq!(frame, Frame::Array(Some(vec![
Frame::BulkString(Some(Bytes::from("SET"))),
Frame::BulkString(Some(Bytes::from("key"))),
Frame::BulkString(Some(Bytes::from("value"))),
])));
assert!(remaining.is_empty());§Parsing RESP3 types
RESP3 adds null, booleans, doubles, maps, sets, and more:
use bytes::Bytes;
use resp_rs::resp3::{self, Frame};
// Simple string
let (frame, _) = resp3::parse_frame(Bytes::from("+OK\r\n")).unwrap();
assert_eq!(frame, Frame::SimpleString(Bytes::from("OK")));
// Null
let (frame, _) = resp3::parse_frame(Bytes::from("_\r\n")).unwrap();
assert_eq!(frame, Frame::Null);
// Boolean
let (frame, _) = resp3::parse_frame(Bytes::from("#t\r\n")).unwrap();
assert_eq!(frame, Frame::Boolean(true));
// Double
let (frame, _) = resp3::parse_frame(Bytes::from(",3.14\r\n")).unwrap();
assert_eq!(frame, Frame::Double(3.14));
// Integer
let (frame, _) = resp3::parse_frame(Bytes::from(":-42\r\n")).unwrap();
assert_eq!(frame, Frame::Integer(-42));
// Bulk string
let (frame, _) = resp3::parse_frame(Bytes::from("$5\r\nhello\r\n")).unwrap();
assert_eq!(frame, Frame::BulkString(Some(Bytes::from("hello"))));
// Null bulk string
let (frame, _) = resp3::parse_frame(Bytes::from("$-1\r\n")).unwrap();
assert_eq!(frame, Frame::BulkString(None));
// Array
let (frame, _) = resp3::parse_frame(Bytes::from("*2\r\n:1\r\n:2\r\n")).unwrap();
assert_eq!(frame, Frame::Array(Some(vec![Frame::Integer(1), Frame::Integer(2)])));
// Map
let data = Bytes::from("%2\r\n+name\r\n$5\r\nAlice\r\n+age\r\n:30\r\n");
let (frame, _) = resp3::parse_frame(data).unwrap();
assert_eq!(frame, Frame::Map(vec![
(Frame::SimpleString(Bytes::from("name")), Frame::BulkString(Some(Bytes::from("Alice")))),
(Frame::SimpleString(Bytes::from("age")), Frame::Integer(30)),
]));
// Set
let (frame, _) = resp3::parse_frame(Bytes::from("~3\r\n:1\r\n:2\r\n:3\r\n")).unwrap();
assert_eq!(frame, Frame::Set(vec![Frame::Integer(1), Frame::Integer(2), Frame::Integer(3)]));
// Big number
let (frame, _) = resp3::parse_frame(Bytes::from("(12345678901234567890\r\n")).unwrap();
assert_eq!(frame, Frame::BigNumber(Bytes::from("12345678901234567890")));
// Verbatim string
let (frame, _) = resp3::parse_frame(Bytes::from("=15\r\ntxt:hello world\r\n")).unwrap();
assert_eq!(frame, Frame::VerbatimString(Bytes::from("txt"), Bytes::from("hello world")));
// Blob error
let (frame, _) = resp3::parse_frame(Bytes::from("!5\r\nOOPS!\r\n")).unwrap();
assert_eq!(frame, Frame::BlobError(Bytes::from("OOPS!")));
// Error
let (frame, _) = resp3::parse_frame(Bytes::from("-ERR unknown\r\n")).unwrap();
assert_eq!(frame, Frame::Error(Bytes::from("ERR unknown")));§Serialization
Convert any resp2::Frame or resp3::Frame back to wire format:
use bytes::Bytes;
use resp_rs::resp2::{Frame, frame_to_bytes};
let frame = Frame::Array(Some(vec![
Frame::BulkString(Some(Bytes::from("GET"))),
Frame::BulkString(Some(Bytes::from("mykey"))),
]));
let wire = frame_to_bytes(&frame);
assert_eq!(wire, Bytes::from("*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n"));Roundtrip is guaranteed: parse_frame(frame_to_bytes(&frame)) == Ok((frame, empty)).
§Streaming parser
The resp2::Parser and resp3::Parser types buffer incremental data
and yield frames as they become complete – ideal for reading from TCP sockets.
use bytes::Bytes;
use resp_rs::resp3::{Parser, Frame};
let mut parser = Parser::new();
// Simulate receiving data in chunks (e.g., from TCP)
parser.feed(Bytes::from("+HEL"));
assert!(parser.next_frame().unwrap().is_none()); // not enough data yet
parser.feed(Bytes::from("LO\r\n:42\r\n"));
// Now we have two complete frames buffered
let frame1 = parser.next_frame().unwrap().unwrap();
assert_eq!(frame1, Frame::SimpleString(Bytes::from("HELLO")));
let frame2 = parser.next_frame().unwrap().unwrap();
assert_eq!(frame2, Frame::Integer(42));
assert!(parser.next_frame().unwrap().is_none()); // buffer exhausted§Pipelined commands
Multiple frames in a single buffer parse naturally in sequence:
use bytes::Bytes;
use resp_rs::resp2::{self, Frame};
let wire = Bytes::from("+OK\r\n$5\r\nhello\r\n:42\r\n");
let (f1, rest) = resp2::parse_frame(wire).unwrap();
assert_eq!(f1, Frame::SimpleString(Bytes::from("OK")));
let (f2, rest) = resp2::parse_frame(rest).unwrap();
assert_eq!(f2, Frame::BulkString(Some(Bytes::from("hello"))));
let (f3, rest) = resp2::parse_frame(rest).unwrap();
assert_eq!(f3, Frame::Integer(42));
assert!(rest.is_empty());§RESP3 streaming sequences
RESP3 supports chunked/streaming data. Use resp3::parse_streaming_sequence
to accumulate chunks into a complete frame:
use bytes::Bytes;
use resp_rs::resp3::{self, Frame};
// Streaming string: $?\r\n followed by chunks, terminated by ;0\r\n
let data = Bytes::from("$?\r\n;5\r\nHello\r\n;6\r\n World\r\n;0\r\n\r\n");
let (frame, _) = resp3::parse_streaming_sequence(data).unwrap();
if let Frame::StreamedString(chunks) = frame {
assert_eq!(chunks.len(), 2);
assert_eq!(chunks[0], Bytes::from("Hello"));
assert_eq!(chunks[1], Bytes::from(" World"));
}§Performance
The parser uses offset-based internal parsing to minimize allocations. Bulk string parsing is an O(1) slice into the input buffer, not a copy.
§parse_frame vs Parser
resp2::parse_frame and resp3::parse_frame parse directly from a bytes::Bytes
buffer with no overhead. The resp2::Parser and resp3::Parser wrappers add
incremental buffering for TCP streams, but have roughly 2x overhead per frame due to
internal BytesMut split/unsplit operations.
If you already have a complete buffer (e.g., a full response read from a socket),
call parse_frame directly in a loop rather than going through Parser:
use bytes::Bytes;
use resp_rs::resp2;
let wire = Bytes::from("+OK\r\n:42\r\n$5\r\nhello\r\n");
let mut input = wire;
while !input.is_empty() {
match resp2::parse_frame(input) {
Ok((frame, rest)) => {
// process frame
input = rest;
}
Err(resp_rs::ParseError::Incomplete) => break, // need more data
Err(e) => panic!("parse error: {e}"),
}
}Use Parser when data arrives incrementally (e.g., reading from a TCP socket in
chunks) and you need to buffer partial frames across reads.
§RESP2 vs RESP3
RESP2 parsing is roughly 3x faster than RESP3 for simple types due to RESP2’s smaller
type tag match (5 variants vs 16+). The gap narrows for collection-heavy workloads
where per-element parsing dominates. If your application only needs RESP2, prefer the
resp2 module for best performance.
§Representative timings
Measured on Apple M4 (single core, criterion):
| Operation | RESP2 | RESP3 |
|---|---|---|
| Simple string | 12 ns | 39 ns |
| Bulk string | 13 ns | 39 ns |
| Integer | 25 ns | 47 ns |
| 3-element array | 43 ns | 82 ns |
| 100-element array | 822 ns | 2.0 us |
| 5-frame pipeline (direct) | 107 ns | 129 ns |
| 5-frame pipeline (Parser) | 242 ns | 286 ns |
| Streaming string (2 chunks) | – | 124 ns |
| Streaming array (5 elements) | – | 226 ns |
Run cargo bench to reproduce on your hardware.
§Error Handling
All parsing functions return Result<_, ParseError>. The ParseError::Incomplete
variant signals that more data is needed (not a protocol error), while other
variants indicate malformed input.
use bytes::Bytes;
use resp_rs::{ParseError, resp2};
// Not enough data
assert_eq!(resp2::parse_frame(Bytes::from("$5\r\nhel")), Err(ParseError::Incomplete));
// Unknown type tag
assert_eq!(resp2::parse_frame(Bytes::from("X\r\n")), Err(ParseError::InvalidTag(b'X')));
// Empty input
assert_eq!(resp2::parse_frame(Bytes::new()), Err(ParseError::Incomplete));Modules§
Enums§
- Parse
Error - Errors that can occur during RESP parsing.