Dynamic Types
Runtime type manipulation for DDS without compile-time type knowledge.
Overview
Dynamic Types enable building and manipulating DDS types at runtime, without needing compile-time type definitions. This is useful for:
- Generic tools - DDS monitors, bridges, routers
- Type introspection - Inspecting received data structure
- Protocol adapters - Converting between formats
- Testing - Creating arbitrary test data
Feature Flag
Dynamic Types requires the dynamic-types feature:
[dependencies]
hdds = { path = "../hdds/crates/hdds", features = ["dynamic-types"] }
Quick Start
use hdds::dynamic::{TypeDescriptorBuilder, DynamicData, PrimitiveKind};
// Build a type descriptor at runtime
let descriptor = TypeDescriptorBuilder::new("SensorReading")
.field("sensor_id", PrimitiveKind::U32)
.field("temperature", PrimitiveKind::F64)
.field("timestamp", PrimitiveKind::U64)
.build();
// Create dynamic data
let desc = std::sync::Arc::new(descriptor);
let mut data = DynamicData::new(&desc);
// Set field values
data.set("sensor_id", 42u32).unwrap();
data.set("temperature", 23.5f64).unwrap();
data.set("timestamp", 1702900000u64).unwrap();
// Read field values
let temp: f64 = data.get("temperature").unwrap();
assert_eq!(temp, 23.5);
Type Descriptors
Primitive Types
use hdds::dynamic::PrimitiveKind;
// Integer types
PrimitiveKind::Bool // bool
PrimitiveKind::U8 // uint8
PrimitiveKind::U16 // uint16
PrimitiveKind::U32 // uint32
PrimitiveKind::U64 // uint64
PrimitiveKind::I8 // int8
PrimitiveKind::I16 // int16
PrimitiveKind::I32 // int32
PrimitiveKind::I64 // int64
// Floating point
PrimitiveKind::F32 // float
PrimitiveKind::F64 // double
PrimitiveKind::LongDouble // long double (platform-dependent)
// Character types
PrimitiveKind::Char // char (1 byte)
// String types
PrimitiveKind::String { max_length: None } // unbounded string
PrimitiveKind::String { max_length: Some(100) } // bounded string<100>
PrimitiveKind::WString { max_length: None } // unbounded wstring
PrimitiveKind::WString { max_length: Some(50) } // bounded wstring<50>
Building Struct Types
use hdds::dynamic::{TypeDescriptorBuilder, PrimitiveKind};
use std::sync::Arc;
let descriptor = TypeDescriptorBuilder::new("Point3D")
.field("x", PrimitiveKind::F64)
.field("y", PrimitiveKind::F64)
.field("z", PrimitiveKind::F64)
.build();
// With strings and sequences
let packet = TypeDescriptorBuilder::new("DataPacket")
.field("id", PrimitiveKind::U32)
.string_field("label")
.bounded_string_field("code", 8) // string<8>
.sequence_field("data", PrimitiveKind::U8)
.bounded_sequence_field("samples", PrimitiveKind::F32, 100)
.build();
Building Arrays
let matrix = TypeDescriptorBuilder::new("Matrix3x3")
.array_field("values", PrimitiveKind::F64, 9)
.build();
Building Nested Structs
let point = Arc::new(
TypeDescriptorBuilder::new("Point")
.field("x", PrimitiveKind::F64)
.field("y", PrimitiveKind::F64)
.build()
);
let rectangle = TypeDescriptorBuilder::new("Rectangle")
.nested_field("top_left", point.clone())
.nested_field("bottom_right", point)
.field("area", PrimitiveKind::F64)
.build();
Building Enums
use hdds::dynamic::EnumBuilder;
// Auto-incrementing values (0, 1, 2)
let color = EnumBuilder::new("Color")
.variant("RED")
.variant("GREEN")
.variant("BLUE")
.build();
// Explicit values
let http_status = EnumBuilder::new("HttpStatus")
.variant_value("OK", 200)
.variant_value("NOT_FOUND", 404)
.variant_value("SERVER_ERROR", 500)
.build();
Building Unions
use hdds::dynamic::UnionBuilder;
let value = UnionBuilder::with_u32_discriminator("Value")
.primitive_case("int_val", 0, PrimitiveKind::I32)
.primitive_case("float_val", 1, PrimitiveKind::F64)
.primitive_case("str_val", 2, PrimitiveKind::String { max_length: None })
.build();
DynamicData API
Creating and Setting Values
use hdds::dynamic::{DynamicData, TypeDescriptorBuilder, PrimitiveKind};
use std::sync::Arc;
let desc = Arc::new(
TypeDescriptorBuilder::new("Message")
.field("id", PrimitiveKind::U32)
.field("payload", PrimitiveKind::String { max_length: None })
.build()
);
let mut data = DynamicData::new(&desc);
// Set primitive fields
data.set("id", 123u32)?;
data.set("payload", "Hello, DDS!")?;
Getting Values
// Typed get
let id: u32 = data.get("id")?;
let payload: String = data.get("payload")?;
// Raw field access
let field_value = data.get_field("id")?;
let field_mut = data.get_field_mut("payload")?;
Working with Sequences
use hdds::dynamic::DynamicValue;
let desc = Arc::new(
TypeDescriptorBuilder::new("DataArray")
.sequence_field("values", PrimitiveKind::I32)
.build()
);
let mut data = DynamicData::new(&desc);
// Push elements
data.push_element(DynamicValue::I32(10))?;
data.push_element(DynamicValue::I32(20))?;
data.push_element(DynamicValue::I32(30))?;
// Get element by index
let elem = data.get_element(1)?; // 20
// Set element by index
data.set_element(0, DynamicValue::I32(100))?;
// Get length
let len = data.len()?; // 3
// Iterate
for elem in data.elements() {
println!("{:?}", elem);
}
Working with Arrays
let desc = Arc::new(
TypeDescriptorBuilder::new("Vector3")
.array_field("coords", PrimitiveKind::F32, 3)
.build()
);
let mut data = DynamicData::new(&desc);
// Arrays are pre-initialized with default values
data.set_element(0, DynamicValue::F32(1.0))?;
data.set_element(1, DynamicValue::F32(2.0))?;
data.set_element(2, DynamicValue::F32(3.0))?;
Iterating Over Fields
for (name, value) in data.fields() {
println!("{}: {:?}", name, value);
}
DynamicValue Enum
The DynamicValue enum represents any DDS value:
pub enum DynamicValue {
// Primitives
Bool(bool),
U8(u8), U16(u16), U32(u32), U64(u64),
I8(i8), I16(i16), I32(i32), I64(i64),
F32(f32), F64(f64),
LongDouble([u8; LONG_DOUBLE_SIZE]),
Char(char),
String(String),
WString(String),
// Complex types
Struct(HashMap<String, DynamicValue>),
Sequence(Vec<DynamicValue>),
Array(Vec<DynamicValue>),
Enum(i64, String), // (value, variant_name)
Union(i64, String, Box<DynamicValue>), // (discriminator, case_name, value)
// Special
Null,
}
CDR Encoding/Decoding
Dynamic data can be encoded to/decoded from CDR wire format:
use hdds::dynamic::{encode_dynamic, decode_dynamic, DynamicData, TypeDescriptorBuilder, PrimitiveKind};
use std::sync::Arc;
let desc = Arc::new(
TypeDescriptorBuilder::new("SensorData")
.field("sensor_id", PrimitiveKind::U32)
.field("value", PrimitiveKind::F64)
.string_field("unit")
.build()
);
// Create and populate data
let mut data = DynamicData::new(&desc);
data.set("sensor_id", 42u32)?;
data.set("value", 23.5f64)?;
data.set("unit", "celsius")?;
// Encode to CDR bytes
let bytes: Vec<u8> = encode_dynamic(&data)?;
// Decode from CDR bytes
let decoded = decode_dynamic(&bytes, &desc)?;
assert_eq!(decoded.get::<u32>("sensor_id")?, 42);
CDR Encoding Rules
| Type | Alignment | Size |
|---|---|---|
bool, u8, i8, char | 1 | 1 byte |
u16, i16 | 2 | 2 bytes |
u32, i32, f32 | 4 | 4 bytes |
u64, i64, f64 | 8 | 8 bytes |
string | 4 | 4 (length) + N + 1 (null) |
sequence<T> | 4 | 4 (length) + N * sizeof(T) |
T[N] | align(T) | N * sizeof(T) |
Error Handling
use hdds::dynamic::{DynamicDataError, DynamicCdrError};
// DynamicData errors
match data.get::<u32>("nonexistent") {
Err(DynamicDataError::FieldNotFound(name)) => {
println!("Field not found: {}", name);
}
Err(DynamicDataError::TypeMismatch { expected, got }) => {
println!("Type mismatch: expected {}, got {}", expected, got);
}
Err(DynamicDataError::IndexOutOfBounds { index, length }) => {
println!("Index {} out of bounds (length {})", index, length);
}
Err(DynamicDataError::SequenceTooLong { length, max }) => {
println!("Sequence too long: {} > {}", length, max);
}
_ => {}
}
// CDR errors
match decode_dynamic(&bytes, &desc) {
Err(DynamicCdrError::BufferTooSmall { need, have }) => {
println!("Buffer too small: need {}, have {}", need, have);
}
Err(DynamicCdrError::TypeMismatch { expected, found }) => {
println!("Type mismatch: expected {}, found {}", expected, found);
}
_ => {}
}
Type Descriptor Methods
// Check type kind
descriptor.is_primitive();
descriptor.is_struct();
// Get struct fields
if let Some(fields) = descriptor.fields() {
for field in fields {
println!("{}: {:?}", field.name, field.type_desc.kind);
}
}
// Get field by name
let field = descriptor.field("sensor_id");
let index = descriptor.field_index("sensor_id");
// Get size and alignment info
let min_size = descriptor.min_size();
let alignment = descriptor.alignment();
Example: Generic DDS Bridge
use hdds::dynamic::{TypeDescriptorBuilder, DynamicData, encode_dynamic, decode_dynamic, PrimitiveKind};
use std::sync::Arc;
/// Bridge that transforms sensor data between formats
fn transform_sensor_data(
input: &[u8],
input_desc: &Arc<TypeDescriptor>,
output_desc: &Arc<TypeDescriptor>,
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
// Decode input
let input_data = decode_dynamic(input, input_desc)?;
// Create output
let mut output_data = DynamicData::new(output_desc);
// Transform fields (example: copy common fields)
if let Ok(id) = input_data.get::<u32>("id") {
output_data.set("sensor_id", id)?;
}
if let Ok(value) = input_data.get::<f64>("reading") {
output_data.set("value", value)?;
}
// Encode output
Ok(encode_dynamic(&output_data)?)
}
Related
- XTypes - XTypes type system
- CDR2 Overview - CDR encoding
- Interoperability - Cross-vendor compatibility