How nodiscard Prevents Ignored Return Codes in C
The [[nodiscard]] attribute in the C programming
language is a compile-time feature that prevents developers from
accidentally ignoring critical function return values, such as error
codes or allocated resources. By annotating functions or types with this
attribute, the compiler is instructed to issue a warning whenever a
caller discards the returned value. This article explains how the
attribute functions, why traditional C patterns left programs vulnerable
to silent failures, and how incorporating [[nodiscard]]
leads to safer, more robust C code.
The Problem with Discarded Return Codes in Traditional C
Historically, C allows function return values to be discarded silently. If a function returns an integer indicating success or failure, a caller can invoke the function as a standalone statement:
allocate_resource(); // Return code silently ignoredIf allocate_resource() returns an error code or a memory
pointer that requires cleanup, failing to check or store that result can
lead to silent data corruption, resource leaks, or undefined behavior.
Before standard attribute support, developers relied on
compiler-specific extensions like GCC's
__attribute__((warn_unused_result)) or external static
analysis tools to catch these oversights.
How the
[[nodiscard]] Attribute Works
Standardized in C23, [[nodiscard]] provides a standard,
vendor-neutral syntax to declare that a return value must not be
ignored. When a function marked with [[nodiscard]] is
invoked without capturing, evaluating, or explicitly casting its return
value, the compiler emits a diagnostic warning during compilation.
[[nodiscard]] int open_connection(const char *address);
void run(void) {
open_connection("127.0.0.1"); // Compiler warning: return value discarded
}To resolve the warning, the developer must explicitly handle the value:
void run(void) {
int status = open_connection("127.0.0.1");
if (status != 0) {
// Handle error
}
}Providing Context with Custom Messages
C23 also supports attaching an explanatory string literal to the
attribute using the syntax [[nodiscard("reason")]]. This
allows API authors to inform developers why the return value must be
checked:
[[nodiscard("Ignoring this error code can cause resource leaks")]]
int release_handle(int handle);When a developer ignores the result of release_handle(),
the compiler includes the custom message in the warning output, offering
immediate context and instructions on how to handle the result
properly.
Applying [[nodiscard]]
to Types
In addition to individual functions, [[nodiscard]] can
be applied directly to type definitions, such as structures or
enumerations:
typedef struct [[nodiscard]] {
int code;
const char *message;
} Status;
Status perform_action(void);Any function returning a type annotated with
[[nodiscard]] automatically inherits the requirement that
its return value must not be discarded. This ensures system-wide
error-handling types cannot be bypassed anywhere in the codebase.
Overriding the Warning Intentionally
There are rare cases where a developer deliberately chooses to ignore
a return code. The compiler warning can be cleanly suppressed by
explicitly casting the function call to (void):
(void)open_connection("127.0.0.1"); // Suppresses the warningThis cast acts as an explicit signal to future maintainers and code reviewers that the return code was omitted intentionally, rather than forgotten through negligence.
Benefits to Code Safety
Using [[nodiscard]] shifts runtime error discovery to
compile-time verification. It directly prevents:
- Resource Leaks: Forcing verification of functions that return handles, pointers, or file descriptors.
- Unchecked Preconditions: Ensuring validation functions are evaluated before critical operations proceed.
- Silent Logic Errors: Flagging pure functions whose results are computed but never used.
By turning overlooked return values into visible compilation
diagnostics, [[nodiscard]] eliminates an entire class of
subtle bugs at the earliest stage of development.