I am proposing a change for the GA release of UAVCAN v1 where we remove the default extent specified in section 3.4.5.5 and change this section to read:
By default, a definition is
notsealed unless explicitly indicated otherwise using the extent directive described in section3.6.33.6.2.
obviously there are other changes to the specification needed but I’m proving the minimum, important change here so we can debate the issue and not the text.
Reason the first: Intuitive Behaviour
I think the best designs behave as expected and reveal additional functionality when inspected. If I’m getting started with UAVCAN and I create my first type as:
uint6 hello_world
I expect the message to need only a 1-byte buffer to deserialize and would be quite confused to learn that Nunavut (or some other compliant DSDL compiler) allocated a 2-byte buffer.
If we change to types being sealed by default I get the expected 1-byte buffer for my hello world example. If I then dive into the specification or a well-written tutorial I might discover @extent
as I read about extensible type design and UAVCAN best practices. In this context I’m able to understand how and when to use @extent
and can be convinced by arguments against making everything sealed. This creates a natural learning curve where the minimally functional use is intuitive and Uncomplicated but greater depth is revealed as one’s experience increases.
Reason the second: Nobody Reads the Manual
This scenario haunts me:
drony/BoringType.uavcan
uint10 generic_field_name
drony/298.MundaneTopLevelType.uavcan
drony.BoringType one
uint8 two
Given the current default extent:
… MundaneTopLevelType
will be three bytes both because of the default extent but also because of section 3.4.5.4, byte-alignment of composite types. But wait, that’s not right! MundaneTopLevelType
is actually seven bytes because of the delimited type header as specified in section 3.7.5.3.
For such a simple example there’s a lot of magic and rules to remember but let’s say I don’t know all the rules except for the alignment of composite types (let’s face it, this one is much less magical given the ubiquity of alignment and padding in C data structures). I change my BoringType
thinking I can be clever and maintain backwards compatibility by doing this:
drony/BoringType.uavcan
uint10 generic_field_name
uint6 tedious_type_extension
I’m expecting this to work but no! MundaneTopLevelType
is suddenly eight bytes long?!? (╯°□°)╯︵ ┻━┻
Maybe I read the spec now. Maybe I find section 3.4.5.5 and realize why this happened. Maybe I read section 3.6.2 and understand that I need to use an explicit extent marker to maintain backwards compatibility. Or maybe I write angry forum posts about how obtuse UAVCAN is while swigging my fourth can of Mountain Dew at 3am.
The inverse offers no surprises. If we change to make types sealed by default then MundaneTopLevelType
is initially:
…and then, with the addition of the tedious_type_extension
becomes:
I am utterly unsurprised by this outcome and go to bed at 10 pm after a nice cup of hot chocolate.
Reason another: You Can’t Force People to do the Right Thing
While we are trying to make UAVCAN extensible by default we are doing so at the cost of simplicity and intuitive behaviour and I don’t agree this is the right trade off. Furthermore, I don’t think we’ve actually succeeded at making UAVCAN extensible by default for the reason I demonstrated above, that you still need to know how to use @extent
properly to actually make use of the extra padding the default extent quietly inserted into your initial data type. Finally, and per my first argument, if you allow people to discover and learn about the type extensibility features of UAVCAN when they have the desire to utilize it and the context to understand it you will be more effective in promoting good type design then if we continue to sneak it in while they aren’t paying attention and forcing them to figure it out when nothing works the way they expected it to.