Skip to Content
Course content

244: Attributes in C23: [[nodiscard]] and [[deprecated]]

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're working in a busy professional kitchen. The head chef hands you a tray of incredibly expensive saffron and says, "Take this to the pastry station." If you just set the tray down on a random counter and walk away without making sure the pastry chef actually received it, you've just wasted a huge amount of money. In that scenario, the head chef's instruction wasn't just "deliver this," it was "ensure this delivery is acknowledged."

Now, imagine there's an old, temperamental meat slicer in the corner. There's a sticky note on it that says, "This slicer is being replaced on Friday; please start using the new industrial one in the cold room." The old slicer still cuts meat, but using it is a sign that you're out of the loop and ignoring the new workflow.

In C23, attributes like [[nodiscard]] and [[deprecated]] are exactly like those kitchen instructions. They aren't laws that stop the code from compiling—the code will still "run"—but they are loud, clear signals to the developer (and the compiler) that something is being handled poorly.

Stop Ignoring Your Return Values

I can't tell you how many bugs I've chased down that were caused by someone ignoring a return value. Usually, it's a function that returns an error code or a pointer to newly allocated memory. If you call a function that returns a NULL on failure and you don't check it, you're just praying that the program doesn't segfault five minutes later.

[[nodiscard]] tells the compiler: "If the caller ignores the return value of this function, throw a warning." It turns a silent failure into a loud build-time alert.

#include <stdio.h>
#include <stdlib.h>

[[nodiscard]] void* secure_malloc(size_t size) {
    printf("Allocating %zu bytes securely...\n", size);
    return malloc(size);
}

int main() {
    // This will trigger a compiler warning in C23
    // because we are ignoring the returned pointer!
    secure_malloc(1024); 
    
    return 0;
}

In the example above, ignoring secure_malloc is a disaster waiting to happen. By marking it [[nodiscard]], I'm forcing you to at least acknowledge the pointer, which usually leads you to actually check if it's NULL.

Handling the Old Guard

Software evolves. You'll eventually realize that a function you wrote six months ago is inefficient, buggy, or just poorly named. But you can't just delete it—you might have a hundred other files relying on it. This is where [[deprecated]] comes in.

Instead of deleting the function and breaking the whole build, you mark it as deprecated. This tells anyone using the function, "This still works for now, but you really should move to the new version before we eventually delete this." You can even provide a helpful message to guide them.

#include <stdio.h>

// The old way of calculating a checksum
[[deprecated("Use calculate_checksum_v2() for better collision resistance")]]
unsigned int old_checksum(const char* data) {
    return 0; // Simplified for the example
}

// The new, improved way
unsigned int calculate_checksum_v2(const char* data) {
    return 12345; 
}

int main() {
    // This will compile, but the compiler will nag you with a warning
    unsigned int sum = old_checksum("Hello C23");
    
    return 0;
}

I love this feature because it turns the compiler into a documentation tool. Instead of hunting through a PDF or a Wiki to find out that a function is obsolete, the warning pops up right in your IDE the moment you try to use it.

When to Actually Use These

Don't go overboard. If you put [[nodiscard]] on every single function, you'll create "warning fatigue," and people will start ignoring the compiler entirely. Use it for things that must be checked: memory allocations, status codes, or functions where the return value is the entire point of the function (like sqrt()—why call square root if you don't use the result?).

Use [[deprecated]] whenever you're transitioning an API. It's the polite way to tell your teammates (or your future self) that there's a better way to do things without causing a massive breakage in the codebase.




📋 Practical Task

Refactoring the Legacy System Telemetry

You have inherited a piece of telemetry code for a satellite system. It's old, and some of the functions are dangerous or obsolete. Your task is to apply C23 attributes to make the code safer and guide other developers toward the new API.

Requirements:

  • Find the get_satellite_status function. It returns a critical status code that must be checked to prevent system crashes. Mark it so the compiler warns anyone who ignores the return value.
  • Find the send_data_legacy function. It is being replaced by send_data_secure. Mark the legacy function as deprecated and include a message telling the user to switch to send_data_secure.
#include <stdio.h>

// This returns 0 for OK, -1 for ERROR. 
// It is dangerous to ignore this!
int get_satellite_status() {
    return 0; 
}

// This is the old, insecure way of sending data.
void send_data_legacy(const char* msg) {
    printf("Sending insecurely: %s\n", msg);
}

// This is the new, encrypted way of sending data.
void send_data_secure(const char* msg) {
    printf("Sending securely: %s\n", msg);
}

int main() {
    // TASK: These two lines should trigger compiler warnings after your changes
    get_satellite_status(); 
    send_data_legacy("Ping");

    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.