Field Rules: required, optional, and repeated

In Protocol Buffers (Protobuf), the declaration method for each field when defining message structures is critical. These declarations not only determine the storage format and serialization behavior of data but also directly impact fault tolerance during deserialization and the complexity of code logic. The Proto3 version introduced stricter field rule constraints, while Proto2 offered a more flexible mechanism that required more explicit checks. This article delves into the defining characteristics, usage scenarios, and differences among the three core modifiers: required, optional, and repeated.
required (mandatory) is the strictest field type in Protobuf, applicable only to scalar or enum types with a single value. When a field is marked as required, it means the field's value must exist and be non-empty during deserialization; if the parser encounters a missing or unset state for this value, it will throw an exception and terminate the parsing process.

This mechanism is commonly used to define critical data items in core business logic. For example, in a user information message, "username" is typically a core identifier that cannot be empty:

message User {
  string username = 1 [required]; // Proto3 style or explicit declaration
  int32 age = 2;                  // Implicitly optional
}

In early versions of Protobuf (Proto2), this behavior could be simulated using the optional keyword with single-value types. However, starting from Protobuf 3.9, to simplify syntax and reduce redundant code, the official recommendation is to use an explicit [required] tag or omit the field declaration entirely to indicate necessity (depending on specific configuration); however, in standard Proto2 definitions, the form optional string username = 1; is typically retained as the default behavior. It is worth noting that under the strict mode of Protobuf 3.9+, scalar types are implicitly required if not explicitly marked as optional, but explicit declaration is often clearer for code readability.

optional: Flexible Optional Single-Value Fields

optional (optional) indicates that the value of this field is neither required nor forbidden. During deserialization, the parser does not report an error if this field is missing; instead, it treats it as an unset state. A null check is only performed when actually reading or accessing the value of this field. This feature greatly enhances the flexibility of data structures, allowing clients to send requests without providing certain non-critical information.

In practical development, optional is frequently used in scenarios with high extensibility requirements, such as optional remarks in logging or backup parameters in configuration items:

message Product {
  string name = 1;              // Required product name (Proto3 default)
  optional string description = 2 [default="N/A"]; 
}

In Proto2, all scalar type fields are implicitly optional. Under the strict mode introduced in Protobuf 3.9+, to explicitly express intent and avoid ambiguity, developers should use the optional keyword explicitly to mark single-value fields as optional:

message Order {
  string order_id = 1;          // required
  optional float tax_rate = 2 [default=0.05]; 
}

repeated: Variable-Length Array Type

Unlike single-value constraints, the repeated (repetition) modifier defines a field that can appear multiple times. It essentially extends the storage method of a single field from scalar to list or vector structures. When using repeated, the parser creates a dynamically sized array to accommodate all corresponding elements, supporting operations such as adding, deleting, and iterating over vectors.

This is the standard approach for handling collection data (such as tag lists, permission groups, multiple contacts, etc.):

message Contact {
  string name = 1;              // Single name
  repeated string tags = 2;     // Multiple tags, repeatable
}

At the code generation level, repeated fields typically correspond to list or array types in programming languages. For example, in Java, they map to List<String>, while in Python's Protobuf library (google.protobuf), they manifest directly as native lists:

# Pseudo-code example in Python
contact = Contact()
contact.name = "Alice"
contact.tags.append("developer")
contact.tags.append("designer")

Compatibility Rules for required, optional, and repeated

The key to understanding these three modifiers lies in mastering their mutual exclusivity and combination restrictions. In Protobuf, required and optional cannot be applied to the same field simultaneously; if a field is declared as repeated, it cannot be marked as required or optional (unless under specific loose configurations in Proto2, but this is illegal in strict mode).

Furthermore, for repeated fields containing nested message types, it must be ensured that all fields within the nested message satisfy the corresponding constraint rules. For example:

message Nested {
  required string id = 1;      // Correct: required + single value inside repeated
}

// Incorrect example (in strict mode): message RepeatedNested { optional repeated int32 nums = 1; } 
// In practice, repeated fields themselves cannot be marked with required/optional, but internal elements can be of any type.

In practical applications, reasonably selecting these modifiers not only improves the clarity of protocol definitions but also significantly enhances deserialization efficiency and the accuracy of error handling. Developers should configure field rules precisely based on the actual distribution of business data—i.e., which data is mandatory, which may be missing, and whether there are multi-value collection requirements—to build robust and efficient communication interfaces.