DSDL v1.1: Introduce the concept of default field initialization and allow customizing this behavior per field

Currently, the DSDL specification does not dedicate much attention to the default value initialization of DSDL fields. I suggest amending it by Cyphal v1.1 as follows:

  • Define and require zero-initialization of primitive-typed DSDL fields.

  • Extend the DSDL syntax to allow overriding the default initializer for primitive-typed fields as follows:

float32 PI = 3.14               # A regular constant.
float32 default_zero            # A regular field, the default initializer is zero.
float32 default_custom <- 42.0  # A regular field with a custom non-zero default initializer.
float32 default_2pi <- PI * 2   # Ditto, with an expression.
float32[3] array  # Default initializers for non-primitive types may be introduced later.

How do default values relate to the message version? Do we consider a change in the default value to be a change in the semantics?

It would appear to be in line with the existing DSDL versioning policy to require at least a minor version bump upon a default value change. As for the greater question of wire compatibility, the current edition already delegates this matter to the system integrator, and the introduction of custom default initialization, as I understand it, is not going to affect it.

What’s the precedent for using <- as an assignment operator? Is this borrowed from another language?

Yes, from R.

Usage of = is impossible because it is already used for constant definition, and in the strict sense it would not be correct because this symbol is meant to represent equality, not assignment. The well-known walrus form := is risky due to its visual similarity to =.

I do not have strong feelings about <- though and if there are other ideas, please share.

I am concerned with the syntax here. Currently, constants are easy to find because they are the only statements with values. Adding defaults muddles this clarity. The C way to do this is to use a keyword “static” or “constexpr” for c++ but we don’t want to break all DSDL by requiring a constant keyword. I’m wondering if using the more draconian C++ initializer syntax would help make this feature visually distinct?

float32 PI = 3.14
float32 default_2pi { PI * 2 }

The C++ syntax is workable but I find braces a little off-putting. Should we not explore the possibility of adding a const qualifier in a backward-compatible way? One idea (not very thought out) is to pick some assignment operator (<- or :=) and define an alternative syntax for constant definition with a const prefix. The assignment operator would only be legal to use in definitions where constants are annotated with a const keyword.

Legal:

const float32 PI = 3.14  # New keyword
float32 angle <- PI * 0.5

Also legal (existing definitions):

float32 PI = 3.14  # Deprecated but legal
float32 angle  # Can't use assignment here

Not legal:

float32 PI = 3.14
float32 angle <- PI * 0.5  # No way! Either remove the assignment or add const above.

Before I continue to debate the colour of the syntax shed, what about correctness and consistency of the default values. Do the current rules for rational numbers, as they relate to constants, ensure consistent representations of default values across all platforms? I think they do but I’m wondering if you’ve thought about this at all?

There is nothing special to default values in this regard, they are to behave exactly like constant initialization expressions: within the DSDL context all computations are exact (no information loss); what happens afterward is implementation-defined. A DSDL front-end can produce a pre-computed rational number for each such initialization expression but whether it incurs information loss during compilation for the target platform is entirely out of the scope of DSDL.

Example:

float32 PI = 3.141592653589793
# PI is represented internally as 884279719003555/281474976710656.

One possible way to generate code:

inline constexpr float PI = 884279719003555.0F / 281474976710656.0F;

The resulting value obtained in an IEEE754-compliant platform where float is binary32 is exactly 3.1415927, which is off by 4.641020678874952e-08 from the original constant.

The above reasoning applies entirely to default initializers as well; there are no new behaviors to define, as far as I see.

Then I think the new keyword strategy you propose is acceptable but const is a terrible keyword given its ambiguity in the C++ programming language. What about local to stress that the value is never transmitted on the wire?

This is not C++ we’re talking about, so why does it concern us? The value is a constant that is known to all users of the data type, so it makes sense to call it exactly that.

because const is guilty by association. How about constant?

Not sure I understand why the association matters but I am okay with constant as well.

Not opposed to <- syntax but would just using the word default be clearer?

uint32 foo default 500

What about a more generalized “attribute” syntax that could expand to specify additional things in the future?

uint32 bar { default:7, min:2, max:100 }

Probably not, this is not Ada.

Future-proofness is nice to have, but this specific approach might be in conflict with the set notation adopted in DSDL.

Is it the attribute idea or just the syntax that you object to?
There could be other ways to do the syntax:

uint32 bar (default=7, min=2, max=100)

Brainstorming…

uint32 bar
bar.default=7
bar.min=2
bar.max=100

(That kind of makes the case where you just want to specify the default kind of klunky though)

huh. min, max, and default as a single attribute of a field is compelling…