I am proposing a change for the GA release of UAVCAN v1 where we remove the default extent specified in section 184.108.40.206 and change this section to read:
By default, a definition is
notsealed unless explicitly indicated otherwise using the extent directive described in section 220.127.116.11.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.
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:
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.
This scenario haunts me:
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 18.104.22.168, 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 22.214.171.124.
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:
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 126.96.36.199 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
I am utterly unsurprised by this outcome and go to bed at 10 pm after a nice cup of hot chocolate.
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.