In this lesson, you will learn how to design a rigid return code architecture that forces calling functions to acknowledge and handle every possible failure state.
The Problem with "Silent" Failures
In high-reliability embedded systems, the most dangerous bugs are often those that fail silently. If a function returns an int or a bool where success is ambiguous, the caller might inadvertently proceed with corrupted data. To build a robust system, we must move away from ad-hoc error reporting toward a mandatory validation pattern.
Enforcing Acknowledgement
To ensure every error is handled, we define a standardized error_t enumeration. Crucially, we use compiler attributes—such as [[nodiscard]] in C++ or __attribute__((warn_unused_result)) in C—to force the caller to process the return value. If the compiler detects an ignored return code, it generates an error, effectively turning a potential runtime failure into a build-time requirement.
Consider this robust pattern:
typedef enum {
SUCCESS = 0,
ERR_TIMEOUT,
ERR_INVALID_PARAM,
ERR_HARDWARE_FAULT
} error_t;
[[nodiscard]] error_t read_sensor(uint16_t *data) {
if (data == NULL) return ERR_INVALID_PARAM;
// ... perform hardware operation
return SUCCESS;
}
// Caller usage:
void process(void) {
uint16_t val;
// The compiler will issue a warning/error if the result is ignored.
if (read_sensor(&val) != SUCCESS) {
// Explicitly handle failure (e.g., retry or enter safe state)
handle_sensor_fault();
}
}
Structuring the Interface
When designing your return codes, categorize them into specific domains (e.g., logical errors, hardware faults, or safety timeouts). By forcing the developer to explicitly switch over the error_t result, you eliminate the possibility of "happy path" bias. This ensures that when a failure occurs—whether it is a transient hardware fault or an invalid parameter—the system has a deterministic path to maintain safety. By coupling this with your existing defensive input validation and runtime assertions, you create a "fail-fast" environment where logic errors are caught and mitigated immediately.
Key Takeaway: By combining mandatory return types with compiler attributes like [[nodiscard]], you transform error handling from an optional suggestion into a hard architectural requirement for the caller.