47 Reserved Fields and Reserved Numbers
In the world of software engineering—especially when developing communication protocols, data serialization, and API design—the terms reserved fields and reserved numbers come up quite often. Although at first glance they may sound like “leaving slots empty” with no real purpose, both play a strategic role in maintaining compatibility and enabling the evolution of a system.
This article breaks down the concepts of reserved fields and reserved numbers in depth—complete with code examples, a simple simulation, and a discussion of their strategy in professional software engineering.
1. What Are Reserved Fields and Reserved Numbers?
Reserved Fields
Reserved fields are parts of a data structure (packet, message, struct, and so on) that are skipped over—in other words, set aside for future use. These fields have no meaning right now, must be ignored by the receiver, yet are still preserved within the structure.
Example situations:
- A new protocol is about to be born, but its design is expected to evolve.
- You want to extend something without breaking compatibility (backward/forward compatible).
Reserved Numbers
Reserved numbers are specific values/numbers within a field (typically an enumeration or message type) that are reserved—not used now, but designated for a future revision.
They can also mean that certain numbers must not be used by custom implementations (reserved for internal use).
2. Why Are Reserved Fields and Numbers Important?
Here is a concise table summarizing the reasons for using both:
| Goal | Reserved Fields | Reserved Numbers |
|---|---|---|
| Compatibility | ✔︎ | ✔︎ |
| Future-proofing | ✔︎ | ✔︎ |
| Avoiding conflicts | ❍ | ✔︎ |
| Speeding up revisions | ✔︎ | ✔︎ |
- Compatibility: So that new and old systems can keep “talking” to each other.
- Future-proofing: Ensures the structure/protocol stays relevant years down the line.
- Avoiding conflicts: For certain assigned enums/IDs, reserved numbers prevent duplicated meanings across implementations.
- Speeding up revisions: Allows new features to be added without overhauling the entire system.
3. A Concrete Example: Reserved Fields in a Protocol
Let’s illustrate reserved fields in the design of a simple message format. Say you are designing a protocol for exchanging messages between an IoT Device and a Server.
Defining the Data Structure
1// DevicePacket mirrors the C struct: device_packet_t
2type DevicePacket struct {
3 MessageType uint16 // 2 bytes
4 DeviceID uint32 // 4 bytes
5 Status uint8 // 1 byte
6 Reserved [5]byte // 5 bytes
7 Timestamp uint32 // 4 bytes
8}
9
10// Encode converts a DevicePacket to []byte (big-endian)
11func (p *DevicePacket) Encode() ([]byte, error) {
12 buf := new(bytes.Buffer)
13 err := binary.Write(buf, binary.BigEndian, p)
14 return buf.Bytes(), err
15}
16
17// Decode converts []byte back into a DevicePacket
18func Decode(data []byte) (*DevicePacket, error) {
19 var p DevicePacket
20 buf := bytes.NewReader(data)
21 err := binary.Read(buf, binary.BigEndian, &p)
22 return &p, err
23}Note: reserved[5] will likely be filled with 0x00 by the sender, but the receiver must still read and ignore this field.
The Send and Receive Flow
flowchart LR
Start -->|Isi struct| Sender[Device]
Sender -->|Isi reserved = 0| Packet
Packet -->|Serialisasi| Network
Network -->|Deserialisasi| Receiver[Server]
Receiver -->|abaikan reserved| ProsesData
Simulation code (Python mock):
1package main
2
3import (
4 "bytes"
5 "encoding/binary"
6 "fmt"
7 "log"
8)
9
10func encodePacket(messageType uint16, deviceID uint32, status uint8, timestamp uint32) []byte {
11 buf := new(bytes.Buffer)
12
13 // Write according to order and endianness
14 binary.Write(buf, binary.BigEndian, messageType)
15 binary.Write(buf, binary.BigEndian, deviceID)
16 binary.Write(buf, binary.BigEndian, status)
17 buf.Write(make([]byte, 5)) // reserved 5 zero bytes
18 binary.Write(buf, binary.BigEndian, timestamp)
19
20 return buf.Bytes()
21}
22
23func decodePacket(data []byte) (messageType uint16, deviceID uint32, status uint8, timestamp uint32, err error) {
24 buf := bytes.NewReader(data)
25
26 if err = binary.Read(buf, binary.BigEndian, &messageType); err != nil {
27 return
28 }
29 if err = binary.Read(buf, binary.BigEndian, &deviceID); err != nil {
30 return
31 }
32 if err = binary.Read(buf, binary.BigEndian, &status); err != nil {
33 return
34 }
35 reserved := make([]byte, 5)
36 if _, err = buf.Read(reserved); err != nil {
37 return
38 }
39 if err = binary.Read(buf, binary.BigEndian, ×tamp); err != nil {
40 return
41 }
42 return
43}
44
45func main() {
46 // Simulation
47 packet := encodePacket(1, 12345, 2, 1721548800)
48
49 mt, did, st, ts, err := decodePacket(packet)
50 if err != nil {
51 log.Fatal(err)
52 }
53 fmt.Printf("type=%d, device=%d, status=%d, ts=%d\n", mt, did, st, ts)
54}4. Reserved Numbers: An Enum & Protobuf Case Study
In protobuf, you’ll often come across the reserved keyword, used either for field numbers or names. This is crucial so that future revisions don’t cause unwanted overwrites (a breaking change).
Enum Example with Reserved Numbers
1enum UpdateStatus {
2 UNKNOWN = 0;
3 PENDING = 1;
4 COMPLETED = 2;
5
6 // Reserved for future use
7 reserved 100 to 199;
8}If a new status is added to the protobuf definition later, you simply assign it a number from the reserved block. On top of that, reserved numbers prevent external developers from populating that enum with values in the 100–199 range (in plugins or custom distributions), keeping the protocol’s integrity intact.
Simulating an Error When Violating a Reservation
1// This will error out!
2enum UpdateStatus {
3 UNKNOWN = 0;
4 RESERVED2 = 120; // <--- Error, falls within the reserved block
5 reserved 100 to 199;
6}The protobuf compiler will throw an error, keeping the implementation disciplined.
5. Reserved Fields in APIs & JSON
Reserved fields aren’t limited to protocols or binary formats—they are frequently adopted in JSON-based REST APIs as well.
Response format:
1{
2 "result": "ok",
3 "device_id": 12345,
4 "reserved": null
5}The documentation notes: “The ‘reserved’ field may be ignored, but it must be included for forward compatibility.”
The benefit? When v2 is released, that field can start carrying new meaning without major changes on the part of older clients or servers.
6. Reserved Fields/Numbers in the Real World
TCP/IP Options:
The TCP packet header contains a 6-bit ‘Reserved’ field. The RFC instructs that its value be 0 and that it be ignored by the receiver—ready to be used for future options (and to this day, some of it has in fact never been used).
HTTP Headers:
Many custom headers used to be prefixed with ‘X-’, which then became a convention marking a reserved area for vendor headers, before they were standardized.
7. Best Practices for Reserved Fields & Numbers
Always fill reserved fields with their default value (zero/NULL).
Whoever fills the field must remain disciplined even when the field is unused.Don’t assign any meaning to reserved entries in the current version.
The explanation in the documentation and comments must be explicit:// reserved, do not use - for future compatibility
Use reserved numbers to “block” external/plugin abuse.
For example: enum 0–100 for the core protocol, 200–250 reserved for internal experimental/extension use.Documentation is mandatory.
Without documentation, a reserved field/number only ends up confusing the next developer.
Conclusion
Reserved fields and reserved numbers are a mature strategy for keeping protocols, APIs, and data systems scalable and compatible into the future. Like setting aside an empty plot of land next to your house, reserved fields make expansion possible without disturbing the existing structure.
As engineers, discipline and transparency in maintaining reserved spots are part of a healthy engineering culture—good for the next version, and good for the generation of developers yet to come.
References
✨ I hope this article helps you design protocols or APIs that are built to last.