Postcard
シリアライゼーションライブラリ
Postcard
概要
Postcardは、組み込みシステムやメモリ制約のある環境向けに設計されたno-std対応のRustシリアライゼーションライブラリです。Serdeと互換性を持ちながら、極めてコンパクトなバイナリ形式でデータをエンコードします。v1.0.0以降、ワイヤーフォーマットの安定性が保証され、組み込みシステムやIoTデバイスでの通信に最適です。
詳細
Postcardは、主にno-std環境での使用を前提として設計されたメッセージライブラリです。リソース効率(メモリ使用量、コードサイズ、開発時間、CPU時間)を最優先に設計されています。
主な特徴:
- no-std対応: 標準ライブラリなしで動作、組み込みシステムに最適
- ワイヤーフォーマットの安定性: v1.0.0以降、後方互換性を保証
- コンパクトなエンコーディング: Varint(可変長整数)による効率的なサイズ
- Flavorsシステム: シリアライゼーション処理のカスタマイズ機能
- 非自己記述形式: スキーマの事前共有が必要だが、その分コンパクト
技術的詳細:
- リトルエンディアン順序でのエンコード
- 8ビット以上の整数はVarintエンコーディング
- 最大エンコード長の制限による安全性
- 正規化は強制されないが、最大値制限は適用
- Mozillaによるスポンサーシップ
設計優先順位:
- メモリ使用量の最小化
- コードサイズの最小化
- 開発時間の効率化
- CPU時間の最適化
メリット・デメリット
メリット
- 組み込みシステムでの優れたパフォーマンス
- 極めて小さなバイナリサイズ
- no-std環境での完全なサポート
- ワイヤーフォーマットの長期安定性(v1.0.0以降)
- Serdeエコシステムとの統合
- Flavorsによる柔軟なカスタマイズ
デメリット
- 自己記述形式ではない(スキーマの事前共有が必要)
- デバッグが困難(バイナリ形式)
- 他のシリアライゼーション形式との相互運用性なし
- 学習曲線(特にFlavorsシステム)
参考ページ
- GitHubリポジトリ: https://github.com/jamesmunns/postcard
- ドキュメント: https://docs.rs/postcard/
- ワイヤーフォーマット仕様: https://postcard.jamesmunns.com/wire-format
- v1.0リリースブログ: https://jamesmunns.com/blog/postcard-1-0/
書き方の例
基本的な使用方法
use serde::{Serialize, Deserialize};
use postcard::{to_vec, from_bytes};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct SensorData {
temperature: f32,
humidity: u8,
timestamp: u32,
}
fn main() -> Result<(), postcard::Error> {
let data = SensorData {
temperature: 23.5,
humidity: 65,
timestamp: 1640995200,
};
// シリアライズ
let serialized = to_vec(&data)?;
println!("Serialized size: {} bytes", serialized.len());
println!("Data: {:?}", serialized);
// デシリアライズ
let deserialized: SensorData = from_bytes(&serialized)?;
println!("Deserialized: {:?}", deserialized);
assert_eq!(data, deserialized);
Ok(())
}
no-std環境での使用
#![no_std]
use serde::{Serialize, Deserialize};
use postcard::{to_slice, from_bytes};
#[derive(Serialize, Deserialize, Debug)]
struct Command {
id: u16,
action: Action,
payload: [u8; 8],
}
#[derive(Serialize, Deserialize, Debug)]
enum Action {
Read,
Write,
Execute,
}
fn process_command(buffer: &mut [u8]) -> Result<(), postcard::Error> {
let cmd = Command {
id: 0x1234,
action: Action::Write,
payload: [1, 2, 3, 4, 5, 6, 7, 8],
};
// 固定サイズバッファにシリアライズ
let used = to_slice(&cmd, buffer)?;
let serialized = &buffer[..used.len()];
// デシリアライズ
let received: Command = from_bytes(serialized)?;
// コマンド処理
match received.action {
Action::Read => { /* 読み取り処理 */ },
Action::Write => { /* 書き込み処理 */ },
Action::Execute => { /* 実行処理 */ },
}
Ok(())
}
Flavorsシステムの使用
use serde::{Serialize, Deserialize};
use postcard::{
ser_flavors::{Cobs, Slice},
de_flavors::Flavor as DeFlavor,
serialize_with_flavor,
deserialize_with_flavor,
};
#[derive(Serialize, Deserialize, Debug)]
struct Message {
id: u32,
data: Vec<u8>,
}
fn main() -> Result<(), postcard::Error> {
let msg = Message {
id: 42,
data: vec![0x00, 0xFF, 0x00, 0xFF],
};
// COBS(Consistent Overhead Byte Stuffing)エンコーディング
// バイト境界の明確化に有用
let mut buffer = [0u8; 128];
let slice_flavor = Slice::new(&mut buffer);
let cobs_flavor = Cobs::try_new(slice_flavor)?;
let used = serialize_with_flavor(&msg, cobs_flavor)?;
let encoded = used.finalize();
println!("COBS encoded size: {} bytes", encoded.len());
// デコード
let mut dec_buffer = [0u8; 128];
let decoded = postcard::de_flavors::cobs::decode_in_place(&mut dec_buffer, encoded)?;
let message: Message = from_bytes(decoded)?;
println!("Decoded message: {:?}", message);
Ok(())
}
組み込みシステムでの通信
#![no_std]
use serde::{Serialize, Deserialize};
use postcard::{to_slice, from_bytes};
#[derive(Serialize, Deserialize, Debug)]
struct TelemetryPacket {
device_id: u16,
sequence: u32,
voltage: u16, // ミリボルト単位
current: u16, // ミリアンペア単位
temperature: i16, // 0.1度単位
status: StatusFlags,
}
#[derive(Serialize, Deserialize, Debug)]
struct StatusFlags {
power_good: bool,
overtemp: bool,
fault: bool,
}
// UART送信関数(ハードウェア依存)
fn uart_send(data: &[u8]) {
// 実際のUART送信実装
}
// UART受信関数(ハードウェア依存)
fn uart_receive(buffer: &mut [u8]) -> usize {
// 実際のUART受信実装
0 // 受信バイト数を返す
}
fn send_telemetry(packet: &TelemetryPacket) -> Result<(), postcard::Error> {
let mut buffer = [0u8; 64];
let used = to_slice(packet, &mut buffer)?;
uart_send(used);
Ok(())
}
fn receive_telemetry(buffer: &mut [u8]) -> Result<TelemetryPacket, postcard::Error> {
let received_len = uart_receive(buffer);
let packet = from_bytes(&buffer[..received_len])?;
Ok(packet)
}
設定構造の保存
use serde::{Serialize, Deserialize};
use postcard::{to_allocvec, from_bytes};
#[derive(Serialize, Deserialize, Debug)]
struct DeviceConfig {
version: u16,
network: NetworkConfig,
sensors: Vec<SensorConfig>,
}
#[derive(Serialize, Deserialize, Debug)]
struct NetworkConfig {
ssid: heapless::String<32>,
password: heapless::String<64>,
channel: u8,
}
#[derive(Serialize, Deserialize, Debug)]
struct SensorConfig {
id: u8,
enabled: bool,
sample_rate: u16,
threshold: f32,
}
fn save_config_to_flash(config: &DeviceConfig) -> Result<Vec<u8>, postcard::Error> {
// allocを使用できる環境の場合
let serialized = to_allocvec(config)?;
// フラッシュメモリへの書き込み(実装依存)
// flash_write(CONFI)G_ADDRESS, &serialized);
Ok(serialized)
}
fn load_config_from_flash(data: &[u8]) -> Result<DeviceConfig, postcard::Error> {
let config = from_bytes(data)?;
Ok(config)
}
カスタムFlavorの実装
use serde::Serialize;
use postcard::ser_flavors::{Flavor, Slice};
// CRC計算付きFlavor
struct CrcFlavor<'a> {
inner: Slice<'a>,
crc: u16,
}
impl<'a> CrcFlavor<'a> {
fn new(buffer: &'a mut [u8]) -> Self {
Self {
inner: Slice::new(buffer),
crc: 0xFFFF,
}
}
fn update_crc(&mut self, byte: u8) {
// 簡易CRC16計算
self.crc ^= byte as u16;
for _ in 0..8 {
if (self.crc & 0x0001) != 0 {
self.crc = (self.crc >> 1) ^ 0xA001;
} else {
self.crc >>= 1;
}
}
}
}
impl<'a> Flavor for CrcFlavor<'a> {
type Output = (&'a [u8], u16);
fn try_push(&mut self, byte: u8) -> postcard::Result<()> {
self.update_crc(byte);
self.inner.try_push(byte)
}
fn finalize(mut self) -> postcard::Result<Self::Output> {
let data = self.inner.finalize()?;
Ok((data, self.crc))
}
}
#[derive(Serialize)]
struct Data {
value: u32,
}
fn main() -> Result<(), postcard::Error> {
let data = Data { value: 0x12345678 };
let mut buffer = [0u8; 32];
let flavor = CrcFlavor::new(&mut buffer);
let (serialized, crc) = postcard::serialize_with_flavor(&data, flavor)?;
println!("Serialized: {:?}", serialized);
println!("CRC16: 0x{:04X}", crc);
Ok(())
}