1. Use Auto-Detection for Subscribers
Unless you have a specific reason, use auto-detection:
use cerulion_core::prelude::*;
// ✅ Good: Auto-detects best transport
let subscriber = Subscriber::create("topic", None)?;
// ⚠️ Only if you need specific behavior
let subscriber = Subscriber::create("topic", Some(true))?; // Force network
let subscriber = Subscriber::create("topic", Some(false))?; // Force local
Auto-detection (None) allows the subscriber to choose the optimal transport:
- If a local publisher exists, it uses high-performance local IPC
- If no local publisher exists, it falls back to network transport
- If a local publisher appears later, you can enable it with
enable_local_subscriber()
2. Start Publishers with Network Disabled
Start with network disabled and enable it only when needed:
use cerulion_core::prelude::*;
// ✅ Good: Start local-only, enable network on demand
let publisher = Publisher::create("topic", false)?;
// Later, enable network when remote subscriber discovered
publisher.enable_network();
// ⚠️ Only if you know remote subscribers exist
let publisher = Publisher::create("topic", true)?;
This saves resources when only local communication is needed. Network publishing consumes CPU and network bandwidth even when no remote subscribers exist.
3. Handle None Gracefully
Handle cases when no message is available:
use cerulion_core::prelude::*;
// ✅ Good: Handle None case and deserialization
if let Ok(Some(bytes)) = subscriber.receive() {
if let Ok(data) = SensorData::from_bytes(&bytes) {
process(data);
}
}
// ❌ Bad: Assumes message always available
let bytes = subscriber.receive()?.unwrap();
let data = SensorData::from_bytes(&bytes).unwrap(); // May panic
The receive() method returns Ok(None) when no message is available. Always handle this case gracefully.
4. Keep Publishers and Subscribers Alive
Publishers and subscribers must remain in scope for the lifetime you need them:
use cerulion_core::prelude::*;
// ✅ Good: Publisher stays alive
let publisher = Publisher::create("topic", true)?;
// ... use publisher ...
// ❌ Bad: Publisher dropped too early
{
let publisher = Publisher::create("topic", true)?;
} // Publisher dropped here
let subscriber = Subscriber::create("topic", None)?;
// Subscriber won't receive messages from dropped publisher
If a publisher is dropped, local subscribers will stop receiving messages. If a subscriber handle is dropped, the subscription is closed.
5. Use Shared Sessions for Multiple Pub/Sub Pairs
If you have multiple publishers/subscribers, share sessions and nodes to reduce resource overhead:
use cerulion_core::prelude::*;
use std::sync::Arc;
// ✅ Good: Share session and node across multiple pub/sub
let config = zenoh::Config::default();
let session = Arc::new(zenoh::open(config).wait()?);
let node = Arc::new(NodeBuilder::new().create::<ipc::Service>()?);
let pub1 = Publisher::create_with_session("topic1", true, Some(session.clone()), false, Some(node.clone()))?;
let pub2 = Publisher::create_with_session("topic2", true, Some(session.clone()), false, Some(node.clone()))?;
let sub1 = Subscriber::create_with_session("topic1", None, Some(session.clone()), Some(node.clone()))?;
let sub2 = Subscriber::create_with_session("topic2", None, Some(session.clone()), Some(node.clone()))?;
For multiple topics, consider using TopicManager instead, which automatically manages shared sessions and provides centralized control.
6. Use Typed Messages When Possible
Use send_message() for type safety:
use cerulion_core::prelude::*;
// ✅ Good: Use send_message for type safety
struct SensorData {
temperature: f32,
timestamp: u64,
}
impl SerializableMessage for SensorData {
fn to_bytes(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
// Serialization logic
Ok(vec![])
}
}
let data = SensorData { temperature: 23.5, timestamp: 1234567890 };
publisher.send_message(&data)?;
// ⚠️ Use send only when necessary (raw bytes, pre-serialized data)
let raw_bytes = vec![0x01, 0x02, 0x03];
publisher.send(&raw_bytes)?;
send_message() provides compile-time type checking and makes your code more maintainable. Use send() only when you have raw bytes or need to send data that doesn’t implement SerializableMessage.
7. Handle Deserialization Errors
Always handle deserialization errors gracefully:
use cerulion_core::prelude::*;
match subscriber.receive() {
Ok(Some(bytes)) => {
match SensorData::from_bytes(&bytes) {
Ok(data) => {
// Process message
process(data);
}
Err(e) => {
// Log error but don't crash
eprintln!("Deserialization error: {}", e);
// Optionally: log the raw bytes for debugging
}
}
}
Ok(None) => {
// No message available
}
Err(e) => {
eprintln!("Receive error: {}", e);
}
}
Deserialization can fail if:
- The message format doesn’t match your struct
- The message is corrupted
- The message is from a different version of your struct
8. Consider TopicManager for Multiple Topics
If you have multiple publishers/subscribers, consider using TopicManager:
use cerulion_core::prelude::*;
// ✅ Good: Centralized management with TopicManager
let manager = TopicManager::create()?;
let pub1 = manager.register_publisher("topic1", false, false)?;
let pub2 = manager.register_publisher("topic2", false, false)?;
let sub1 = manager.register_subscriber("topic1", None)?;
let sub2 = manager.register_subscriber("topic2", None)?;
// vs. Direct APIs (more verbose, manual session management)
let session = Arc::new(zenoh::open(config).wait()?);
let node = Arc::new(NodeBuilder::new().create::<ipc::Service>()?);
let pub1 = Publisher::create_with_session("topic1", false, Some(session.clone()), false, Some(node.clone()))?;
let pub2 = Publisher::create_with_session("topic2", false, Some(session.clone()), false, Some(node.clone()))?;
// ... more boilerplate
TopicManager provides:
- Automatic discovery
- Shared session management
- Centralized control
- Less boilerplate code
Use direct Publisher/Subscriber APIs only when you need fine-grained control that TopicManager doesn’t provide.
9. Use Appropriate Polling Strategy
Choose the right polling strategy for your use case:
use cerulion_core::prelude::*;
// For low-latency applications (tight loop)
loop {
if let Ok(Some(bytes)) = subscriber.receive() {
// Process message immediately
process_message(&bytes);
}
// No sleep - fastest response time
std::hint::spin_loop();
}
// For CPU-friendly applications (with sleep)
loop {
if let Ok(Some(bytes)) = subscriber.receive() {
process_message(&bytes);
}
// Small sleep to avoid busy-waiting
std::thread::sleep(std::time::Duration::from_millis(1));
}
For high-frequency data, consider removing the sleep. The receive() call is very fast (< 1 μs for local transport), so busy-waiting may be acceptable for real-time applications.
10. Check Network Status When Debugging
If network communication isn’t working, check the network status:
use cerulion_core::prelude::*;
let publisher = Publisher::create("topic", true)?;
// Check if network is actually enabled
if publisher.is_network_enabled() {
println!("Network publishing is active");
} else {
println!("Network publishing is disabled");
}
// Check logs for network errors
// Network failures are logged but don't propagate to the caller
Network failures are logged but don’t propagate to the caller. This ensures local communication continues even if the network is down. Check logs if network communication isn’t working.
Next Steps