Module comparison

Source
Expand description

Detailed comparison with rumqttc

This module provides comprehensive side-by-side comparison between mqtt-typed-client and the underlying rumqttc library.

§MQTT Typed Client vs rumqttc: Detailed Comparison

This document provides a comprehensive comparison between mqtt-typed-client and the underlying rumqttc library, highlighting the advantages of the type-safe, high-level approach.

§TL;DR

  • rumqttc: Low-level MQTT protocol implementation with manual topic handling
  • mqtt-typed-client: High-level, type-safe wrapper with automatic topic parsing and routing

§Table of Contents

§Basic Connection

§rumqttc

use std::time::Duration;
use rumqttc::{MqttOptions, AsyncClient, EventLoop};

let mut mqttoptions = MqttOptions::new("client_id", "localhost", 1883);
mqttoptions.set_keep_alive(Duration::from_secs(60));

let (client, mut eventloop) = AsyncClient::new(mqttoptions, 10);

// Manual event loop handling
tokio::spawn(async move {
    loop {
        match eventloop.poll().await {
            Ok(event) => {
                // Handle events manually
            }
            Err(e) => {
                eprintln!("Connection error: {}", e);
                break;
            }
        }
    }
});

§mqtt-typed-client

use mqtt_typed_client::prelude::*;

// Single line connection with automatic event loop handling
let (client, connection) = MqttClient::<BincodeSerializer>::connect(
    "mqtt://localhost:1883?client_id=my_client"
).await?;

// Automatic cleanup on drop
// connection.shutdown().await?; // Optional explicit shutdown

Advantages:

  • ✅ URL-based configuration
  • ✅ Automatic event loop management
  • ✅ Built-in error handling and reconnection
  • ✅ Graceful resource cleanup

§Publishing Messages

§rumqttc

use rumqttc::QoS;
use serde_json;

// Manual topic construction
let sensor_id = "sensor_001";
let location = "kitchen";
let topic = format!("sensors/{}/{}/temperature", location, sensor_id);

// Manual serialization
let data = SensorData { temperature: 23.5, humidity: 45.0 };
let payload = serde_json::to_vec(&data)?;

// Publish with manual parameters
client.publish(topic, QoS::AtLeastOnce, false, payload).await?;

§mqtt-typed-client

use mqtt_typed_client_macros::mqtt_topic;

#[mqtt_topic("sensors/{location}/{sensor_id}/temperature")]
struct TemperatureTopic {
    location: String,
    sensor_id: String,
    payload: SensorData,
}

// Type-safe publishing
let topic_client = client.temperature_topic();
let data = SensorData { temperature: 23.5, humidity: 45.0 };

// Automatic topic construction and serialization
topic_client.publish("kitchen", "sensor_001", &data).await?;

Advantages:

  • ✅ Compile-time topic validation
  • ✅ Automatic serialization
  • ✅ No string formatting errors
  • ✅ Type-safe parameter passing

§Subscribing to Topics

§rumqttc

use rumqttc::{Event, Packet};

// Manual subscription
client.subscribe("sensors/+/+/temperature", QoS::AtMostOnce).await?;

// Manual message handling
while let Ok(notification) = eventloop.poll().await {
    match notification {
        Event::Incoming(Packet::Publish(publish)) => {
            // Manual topic parsing
            let topic_parts: Vec<&str> = publish.topic.split('/').collect();
            if topic_parts.len() == 4 && topic_parts[0] == "sensors" && topic_parts[3] == "temperature" {
                let location = topic_parts[1];
                let sensor_id = topic_parts[2];
                
                // Manual deserialization
                match serde_json::from_slice::<SensorData>(&publish.payload) {
                    Ok(data) => {
                        println!("Sensor {} in {} reported: {}°C", 
                            sensor_id, location, data.temperature);
                    }
                    Err(e) => eprintln!("Deserialization error: {}", e),
                }
            }
        }
        _ => {}
    }
}

§mqtt-typed-client

// Automatic subscription and typed message handling
let mut subscriber = topic_client.subscribe().await?;

while let Some(result) = subscriber.receive().await {
    match result {
        Ok(message) => {
            // Automatic topic parameter extraction and deserialization
            println!("Sensor {} in {} reported: {}°C", 
                message.sensor_id, message.location, message.payload.temperature);
        }
        Err(e) => eprintln!("Deserialization error: {}", e),
    }
}

Advantages:

  • ✅ Automatic parameter extraction from topic
  • ✅ Type-safe message handling
  • ✅ No manual topic parsing
  • ✅ Built-in error handling for deserialization

§When to Use Which

§Use rumqttc when:

  • You need complete control over MQTT protocol details
  • Working with non-standard MQTT implementations
  • Building your own high-level abstraction
  • Memory constraints are extreme (embedded systems)
  • You need features not yet supported by mqtt-typed-client

§Use mqtt-typed-client when:

  • You want type-safe MQTT communication (recommended for most use cases)
  • You have complex topic patterns with parameters
  • You need automatic message routing to multiple handlers
  • You want to reduce boilerplate and prevent runtime errors
  • You’re building applications (vs libraries) with MQTT
  • You want built-in serialization support

§Migration Guide

§Step 1: Replace Connection

// Before (rumqttc)
let mut mqttoptions = MqttOptions::new("client", "localhost", 1883);
let (client, eventloop) = AsyncClient::new(mqttoptions, 10);

// After (mqtt-typed-client)
let (client, connection) = MqttClient::<BincodeSerializer>::connect(
    "mqtt://localhost:1883?client_id=client"
).await?;

§Step 2: Define Typed Topics

// Before: String-based topics
// "sensors/{location}/{id}/data"

// After: Typed topics
#[mqtt_topic("sensors/{location}/{id}/data")]
struct SensorTopic {
    location: String,
    id: String,  
    payload: SensorData,
}

§Step 3: Replace Publishing

// Before
let topic = format!("sensors/{}/{}/data", location, id);
let payload = serde_json::to_vec(&data)?;
client.publish(topic, QoS::AtLeastOnce, false, payload).await?;

// After  
client.sensor_topic().publish(&location, &id, &data).await?;

§Step 4: Replace Subscribing

// Before
client.subscribe("sensors/+/+/data", QoS::AtLeastOnce).await?;
// ... manual event loop and topic parsing

// After
let mut subscriber = client.sensor_topic().subscribe().await?;
while let Some(Ok(message)) = subscriber.receive().await {
    // message.location, message.id, message.payload are ready to use
}

§Conclusion

mqtt-typed-client provides a high-level, type-safe abstraction over rumqttc that eliminates most boilerplate code while adding compile-time guarantees and automatic optimizations. For most MQTT applications, the type safety, reduced error potential, and improved developer experience make it the preferred choice over direct rumqttc usage.

The underlying rumqttc library remains the solid foundation that powers mqtt-typed-client, and direct rumqttc usage is still appropriate for specialized use cases requiring full protocol control.