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 ignored

If 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 warning

This 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.