2.2 Verifying Type Consistency: Type Assignability

Connext DDS determines if a DataWriter and a DataReader can communicate by comparing the structure of their topic types.

In Connext DDS releases before 5.0.0, the topic types were represented and propagated on the wire using TypeCodes. The Extensible Types specification introduces TypeObjects as the wire representation for a type.

To maintain backward compatibility, Connext DDS can be configured to propagate both TypeCodes and TypeObjects. However, type comparison is only supported with TypeObjects.

Depending on the value for extensibility annotation used when the type is defined, Connext DDS will use a different set of rules to determine if matching shall occur.

If the type extensibility is final, the types will be assignable if they don't add or remove any elements. If they are declared as extensible, one type can have more fields at the end as long as they are not keys.

If the type extensibility is mutable, a type can add, remove or shuffle members in at any position, as long as:

For example, in Table 2.2 Mutable Types Example 1 the middleware can assign MyMutableType1 to or from MyMutableType2, but not to or from MyMutableType3.

Table 2.2 Mutable Types Example 1

@mutable struct MyMutableType1 {
    int32 x;
    int32 y;
}
@mutable struct MyMutableType2 {
    @id(1) int32 y; 
    @id(2) int32 z; 
    @id(0) int32 x; 
}
@mutable struct MyMutableType3 {
    int32 y;
    @key int32 z;
    int32 x;
}

Note: If you do not explicitly declare member IDs, they are assigned automatically starting with 0.

MyMutableType1 and MyMutableType2 can be assigned to each other.

MyMutableType3 has two issues:

The member IDs x and y do not match those of MyMutableType1. For example, the member ID of x is 0 in MyMutableType1 but 2 in MyMutableType3.

MyMutableType3 has an extra key member (z).

The type of a member in a mutable type can also change if the new type is assignable. For example, in Table 2.3 Mutable Types Example 2, MyMutableType4 is assignable to or from MyMutableType5 but not to or from MyMutableType6.

Table 2.3 Mutable Types Example 2

@mutable 
struct NestedMutableType1 {
@id(10) int32 a;
}

struct NestedExtensibleType1 {
string text;
};
@mutable struct MyMutableType4 { NestedMutableType1 m1; NestedExtensibleType1 m2;
}
@mutable 
struct 
NestedMutableType2 {
    @id(20) int16 b;
    @id(10) int32 a;
};

struct 
NestedExtensibleType2 {
    string text;
    string title;
};

@mutable
struct MyMutableType5 {
    NestedMutableType2 m1;
    NestedExtensibleType2 
m2;
}
@mutable
struct NestedMutableType3 {
    @id(20) int16 b;
    @id(10) int16 a;
};

struct NestedExtensibleType3 {
    string title;
    string text;
};
 
@mutable
struct MyMutableType6 {
    NestedMutableType3 m1;
    NestedExtensibleType3 m2;
}

MyMutableType6 and MyMutableType4 are not assignable because the types of m1 and m2 are not assignable. NestedExtensibleType3 is just extensible but adds a new member at the beginning. NestedMutableType3 changes the type of ‘a’ but the new type (int16) is not assignable.

The member types in an Extensible or Final type can also change as long as the member types are both mutable and assignable. If the new member types are extensible or final, they need to be structurally identical.

If you use CDR encoding version 2 (XCDR2) (see 4.3 Extended CDR (encoding version 2)), appendable types that are nested into another type can add members at the end of their definition. In the following example, ObservedPosition1 and ObservedPosition2 will not be assignable when using XCDR, but they will be assignable if the encoded version is XCDR2.

Table 2.4 Type Assignability Example

@appendable
struct Coordinates1 { 
    float x; 
    float y; 
}; 

@appendable
struct ObservedPosition1 {
    Coordinates1 position; 
    int64 timestamp; 
}; 
@appendable
struct Coordinates2  { 
    float x;
    float y;
    float z; // Extra field 
};
 
@appendable 
struct ObservedPosition2 { 
    Coordinates2 position;
    int64 timestamp; 
}; 

In the case of union types, it has to be possible, given any possible discriminator value in the DataWriter's type (T2), to identify the appropriate member in the DataReader's type (T1) and to transform the T2 member into the T1 member.

A mutable type that declares a member as optional (see 3.2 Optional Members) is compatible with a different mutable type that declares the same member as non-optional (the default). This rule does not apply to optional members in final and extensible types.

The following rules apply to other types:

For more information on the rules that determine the assignability of two types, refer to the DDS-XTypes specification1http://www.omg.org/spec/DDS-XTypes/ .

By default, the TypeObjects are compared to determine if they are assignable in order to match a DataReader and a DataWriter of the same topic. You can control this behavior in the DataReader’s TypeConsistencyEnforcementQosPolicy (see 2.3 Type-Consistency Enforcement).

The DataReader's and DataWriter's TypeObjects need to be available in order to be compared; otherwise their assignability will not be enforced. Depending on the complexity of your types (how many fields, how many different nested types, etc.), you may need to change the default resource limits that control the internal storage and propagation of the TypeObject (see 5.1.1 TypeObject Resource Limits).

If the logging verbosity is set to NDDS_CONFIG_LOG_VERBOSITY_WARNING or higher, Connext DDS will print a message when a type is discovered that is not assignable, along with the reason why the type is not assignable.

© 2020 RTI