#varint #zig-zag

no-std tiny-varint

A no_std compatible VarInt encoding/decoding Rust library

2 unstable releases

0.2.0 Apr 7, 2025
0.1.0 Mar 25, 2025

#191 in Embedded development

Download history 88/week @ 2025-03-21 22/week @ 2025-03-28 115/week @ 2025-04-04 26/week @ 2025-04-11 4/week @ 2025-04-18

145 downloads per month

MIT/Apache

69KB
1K SLoC

tiny-varint

crates.io Rust Version License no_std

A dependency-free, high-performance variable-length integer (VarInt) encoding/decoding Rust library, fully compatible with no_std environments.

Features

  • Depends only on zigzag-rs: Supports #![no_std] environments
  • Full Type Support: Supports all Rust native integer types
  • High-performance Implementation: Optimized critical paths for resource-constrained scenarios
  • Iterator-based Interface: No dynamic memory allocation required, suitable for embedded systems
  • Zigzag Encoding: Efficiently handles signed integers
  • Type-aware VarintValue: Unified type for mixed data serialization
  • Rich Error Handling: Detailed error types and messages

Installation

[dependencies]
tiny-varint = "0.2.0"

Feature Overview

Core Functions

Feature Function Name Description
Generic Encoding encode<T: VarInt>() Encodes any integer type to varint
Generic Decoding decode<T: VarInt>() Decodes a varint to any integer type
ZigZag Encoding encode_zigzag() Encodes signed integers using zigzag
ZigZag Decoding decode_zigzag() Decodes zigzag-encoded signed integers
Batch Processing VarIntEncoder/VarIntDecoder Batch encodes/decodes integer arrays
Iterator-based Encoding bytes_of() Iterator-based encoding method
Iterator-based Decoding values_from() Iterator-based decoding method
Unified Value Type VarintValue Type-aware encoding for mixed integer types

Direct Usage with encode/decode

The simplest way to use tiny-varint is with the generic encode and decode functions:

use tiny_varint::{encode, decode};

// Encode an unsigned integer
let mut buffer = [0u8; 10];
let bytes_written = encode(42u64, &mut buffer).unwrap();
println!("Encoded 42 using {} bytes", bytes_written);

// Decode it back
let (decoded_value, bytes_read) = decode::<u64>(&buffer[..bytes_written]).unwrap();
assert_eq!(decoded_value, 42u64);

// Works with any integer type - even signed integers
let bytes_written = encode(-123i32, &mut buffer).unwrap();
let (decoded_value, bytes_read) = decode::<i32>(&buffer[..bytes_written]).unwrap();
assert_eq!(decoded_value, -123i32);

Using Encoders/Decoders

use tiny_varint::{VarIntEncoder, VarIntDecoder};

// Encoder example
let mut buffer = [0u8; 100];
let mut encoder = VarIntEncoder::new(&mut buffer);

// Write multiple values
encoder.write(123u64)?;
encoder.write(456u32)?;
encoder.write_zigzag(-789i32)?;

// Get encoded bytes
let bytes_written = encoder.position();

// Decoder example
let mut decoder = VarIntDecoder::new(&buffer[..bytes_written]);

// Read values
let v1 = decoder.read::<u64>()?; // 123
let v2 = decoder.read::<u32>()?; // 456
let v3 = decoder.read_zigzag::<i32>()?; // -789

Using Iterator-based Methods

use tiny_varint::{bytes_of, values_from};

// Encoding iterator - No buffer pre-allocation needed
let value = 16384u64;
for byte in bytes_of(value) {
    // Process each byte individually
    println!("{:02X}", byte);
}

// Decoding iterator - Decode from any byte source
let buffer = [0x80, 0x80, 0x01]; // Encoded value 16384
for result in values_from::<u64>(&buffer) {
    match result {
        Ok(value) => println!("Decoded value: {}", value),
        Err(e) => println!("Decoding error: {:?}", e),
    }
}

Using VarintValue for Mixed Types

The VarintValue enum allows working with different integer types through a unified interface:

use tiny_varint::{VarintValue, varint};

// Create values of different types
let values = [
    varint!(u32: 42),
    varint!(i16: -100),
    varint!(u64: 1000000)
];

// Serialize mixed-type values
let mut buffer = [0u8; 100];
let mut pos = 0;

for value in &values {
    let bytes_written = value.to_bytes(&mut buffer[pos..]).unwrap();
    pos += bytes_written;
}

// Deserialize values while preserving their original types
let mut read_pos = 0;
let mut index = 0;

while read_pos < pos {
    let (value, bytes_read) = VarintValue::from_bytes(&buffer[read_pos..]).unwrap();
    println!("Value {}: {:?}", index, value);
    read_pos += bytes_read;
    index += 1;
}

// Each value maintains its original type
assert!(matches!(values[0], VarintValue::U32(_)));
assert!(matches!(values[1], VarintValue::I16(_)));
assert!(matches!(values[2], VarintValue::U64(_)));

Performance Optimizations

tiny-varint has been optimized for various use cases:

  • Avoid Boundary Checks: Pre-validate buffer sizes to reduce in-loop checks
  • Inline Critical Functions: Critical paths marked with #[inline(always)]
  • Optimized Size Calculation: Use lookup-based approach to quickly determine encoding size
  • Batch Processing Optimization: Batch operations avoid multiple boundary checks
  • Iterator-based Design: Directly operate on provided buffers without copying data

Example Code

Basic Encoding/Decoding

use tiny_varint::{encode, decode};

// Encode a single u64 value
let mut buffer = [0u8; 10];
let bytes_written = encode(123u64, &mut buffer)?;
println!("Encoded using {} bytes", bytes_written);

// Decode a single u64 value
let (value, bytes_read) = decode::<u64>(&buffer)?;
println!("Decoded value: {}, used {} bytes", value, bytes_read);

// Works with any integer type
let bytes_written = encode(42i32, &mut buffer)?;
let (value, bytes_read) = decode::<i32>(&buffer)?;
assert_eq!(value, 42i32);

ZigZag Encoding Signed Integers

use tiny_varint::{encode_zigzag, decode_zigzag};

// Encode an i32 value
let mut buffer = [0u8; 10];
let bytes_written = encode_zigzag(-123i32, &mut buffer)?;

// Decode back to i32
let (value, bytes_read) = decode_zigzag::<i32>(&buffer)?;
assert_eq!(value, -123);

Batch Processing

use tiny_varint::{VarIntEncoder, VarIntDecoder};

// Encode multiple values
let values = [1u64, 10, 100, 1000, 10000];
let mut buffer = [0u8; 50];
let mut encoder = VarIntEncoder::new(&mut buffer);
let bytes_written = encoder.write_batch(&values)?;

// Decode multiple values
let mut decoded = [0u64; 5];
let mut decoder = VarIntDecoder::new(&buffer[..bytes_written]);
decoder.read_batch(&mut decoded)?;
assert_eq!(values, decoded);

Type-aware Protocol Messages

use tiny_varint::{VarintValue, varint};

// Create a simple protocol message
struct Message {
    id: VarintValue,
    temperature: VarintValue,
    data_points: Vec<VarintValue>,
}

let msg = Message {
    id: varint!(u16: 1234),
    temperature: varint!(i8: -10),
    data_points: vec![varint!(i32: -100), varint!(i32: 0), varint!(i32: 100)],
};

// Serialize the message
let mut buffer = [0u8; 100];
let mut pos = 0;

// Write fields - types are preserved automatically
pos += msg.id.to_bytes(&mut buffer[pos..]).unwrap();
pos += msg.temperature.to_bytes(&mut buffer[pos..]).unwrap();

// Write number of data points
pos += varint!(u8: msg.data_points.len() as u8).to_bytes(&mut buffer[pos..]).unwrap();

// Write each data point
for point in &msg.data_points {
    pos += point.to_bytes(&mut buffer[pos..]).unwrap();
}

// Total message size
println!("Message serialized to {} bytes", pos);

Complete Examples

Check the examples directory for more examples:

License

Dual-licensed under MIT/Apache-2.0 licenses.

Dependencies