Hacker News new | past | comments | ask | show | jobs | submit login

I just wish C had something like defer in go, That would cover most cases.



If you're willing to use macros, this can do the trick. Return and break will preempt the expression, but continue should work fine.

  #define DEFER(EXPR) for(int _tmp=1; _tmp; _tmp=0,(EXPR))
Example:

  char * data = malloc(32);
  DEFER(free(data))
  {
          // do stuff
  }


Perhaps I'm misunderstanding this, but this doesn't solve the problem at all -- returning within the block statement wouldn't call the deferred expression, which is entirely the point of a defer statement, no? If control is guaranteed to reach the end of the block statement, of course; but requiring such a constraint would make this defer very handicapped. You have tons of function exit points and you most likely want to free memory at every single one.


Yes, the only way I could imagine implementing a "proper" defer in C would be to use (like for most insane C hacks) setjmp/longjmp. I'm fairly sure it's explicitly forbidden by the Geneva Conventions though.

Alternatively you might be able to use nested functions to guard against a stray return but that's not standard.


I guess you could do something like (couldn’t get Xcode’s clang to compile this, so it probably is buggy)

  static unsigned int __deferDepth = 0;

  #define RETURN return
  #define return ((__deferDepth == 0) ? RETURN : break do_return)

  #define DEFER(EXPR) \
    __label__ do_return; \
    for(int _tmp = 1; _tmp; _tmp = (do { \
      __deferDepth += 1; \
      EXPR; \
      __deferDepth -= 1; \
      break; \
      do_return: \
        RETURN; \
    }while(0);))
Nested DEFERs could get hairy, though (do you need a stack of __returnNotBreak values?), and there probably are issues if EXPR contains some nested loops with break or return.

Regardless, this isn’t C. Statement expressions (and __label_) are a gcc* extension, and if you’re willing to go there, __attribute__((cleanup)) seems the wiser route.


How would you implement defer with setjmp/longjmp? I can’t think of anything off the top of my head.


You'd probably have to #define "return" to call longjmp instead. Although I guess at this point you might be better off just have return call the cleanup code. You'd have to be careful to handle the nesting correctly though, which might be slightly easier with setjmp contexts.


Regarding having tons of exit points, I find it actually adds no more cognitive load to use only a single return per function, compared to having multiple returns and keeping resource-freeing up-to-date for each.

Most of the challenge in writing code with a single return seems to come from the fact that I'm not used to doing it. Of course, sometimes it leads to heavily nested control flow, although I'm not sure yet whether or not I mind that. It does certainly simplify reasoning about control flow though, especially in large functions.

That all being said, I'd still rather just have defer.


What you say makes sense but I'd advise beginners reading this comment not to overdo it. I've seen new coders "cargo culting" this one-return-per-function rule and leading to code that was, in my opinion, unnecessarily complicated.

For instance parameter validation is one instance where I think an early return is very much warranted. There you typically have no cleanup to do since it's effectively the prelude of the function.

Regarding nested error handling, I prefer the "goto fail" pattern. Some people are opposed to the use of goto as a matter of principle but I think in this case it's fine. It's effectively the poor man's RAII when you don't have destructors.


Not unreasonable for functions to have guard returns, a 'happy' return. And a goto fail return. That's decent pattern when you have a function that handles modifying state.


> For instance parameter validation is one instance where I think an early return is very much warranted. There you typically have no cleanup to do since it's effectively the prelude of the function.

That's what the assert statement is for; checking pre- and postconditions.


Oh, that's pure evil/genius.


That is really clever, I don't know why I never thought about the for loop being a way to run an expression after a scope ends.


The primary use-case of defer (cleaning up resources) can be done with __attribute__((__cleanup__(<destructor>))). This effectively allows you to do RAII in C, though it does require some careful thought when copying variables (you need to "move" them a-la Rust).

But the really classic way of doing "defer" in C is to do what Linux does:

    int foo(void)
    {
        char *ptr = malloc(13);
        int error = 0;

        error = bar();
        if (error < 0)
           goto out_free;

        // do something else

    out_free:
        free(ptr);
        return error;
    }
Once you get used to it, it feels very natural to write and understand.


Defer can be emulated with the block extensions of Clang and GCC [0], though I'm not sure how much I'd like to see something like that in a codebase.

[0] https://web.archive.org/web/20180426195701/http://fdiv.net/2...


There's also __attribute__ ((__cleanup__(<destructor>))) which can be used to implement RAII in C.


That's exactly what this uses. It just hides it inside a syntax that is more similar to what Go uses.


Ah, apologies, the link wasn't loading at the moment and I assumed it was something else.

Regarding its use in the wild, I've encountered it exactly once, in the lastpass-cli (available on github). I personally wouldn't consider using it for one of my projects because it's too nonstandard, however I suppose that for a security-sensitive application it might reduce the likelihood of messing error handling and leaking things all over the place. That being said using non-standard features might also make it more likely for a contributor to misunderstand what the code does precisely and introduce a problem.


LXC uses it as well, and they have additional macros to "move" pointers and file descriptors to make sure you don't double-free or similar things.


That link seems to be broken? Nothing opens when I click it.


It's a short article, and worth reading if you can work out why the Wayback Machine isn't working for you.

The magic is basically:

    static inline void defer_cleanup(void (^*b)(void)) { (*b)(); }
    #define defer_merge(a,b) a##b
    #define defer_varname(a) defer_merge(defer_scopevar_, a)
    #define defer __attribute__((cleanup(defer_cleanup))) void (^defer_varname(__COUNTER__))(void) =
Which let's you do:

    FILE *a = fopen ("a.txt", "r");
 
      if (!a)
        return EXIT_FAILURE;
      defer
      {
        fclose (a);
      };
It uses cleanup and blocks, both of which are extensions. There may also be some strangeness with the way blocks hold memory. (Block references become const copies).


Works fine for me. It's using __attribute__((cleanup)) to call a function that calls the block you give it.



The original link pops up with a HTTP basic authentication request for me, hence the use of archive.


This seems like a great place to plug C++ - Destructors are what you want. While I'm personally a fan of C++-as-a-whole, there's nothing stopping you using C-with-classes.

You can "emulate" the defer statement with a Scope Guard[0], or if you're willing to use a macro, you can use the __COUNTER__ macro as an "anonymous" variable.

[0] https://github.com/ricab/scope_guard


I know that's not directly what you're asking for, but I found the other day that "C++ Core Guidelines" have a helper library "GSL_util" that provide something similar to Go's defer.

The implementation is very simple, it's just using a class for its destructor:

template <class F>

class final_act

{

public:

    explicit final_act(F f) noexcept : f_(std::move(f)), invoke_(true) {}


    final_act(final_act&& other) noexcept : f_(std::move(other.f_)), invoke_(other.invoke_)
    {
        other.invoke_ = false;
    }


    final_act(const final_act&) = delete;
    final_act& operator=(const final_act&) = delete;


    ~final_act() noexcept
    {
        if (invoke_) f_();
    }

private:

    F f_;
    bool invoke_;
};

Source: https://github.com/microsoft/GSL/blob/ebe7ebfd855a95eb937831...


Of course, in C++ the need for such constructs is reduced due to RAII and scoping running destructors automatically. This is useful when you need to interact with C libraries that you don’t want to wrap in C++ (and when I was doing something like this in the past, I rolled my own as well).


> This is useful when you need to interact with C libraries that you don’t want to wrap in C++

Yep! I really like the defer pattern for those situations :)


If you're willing to use GCC extensions (available also on clang, so should cover most operating systems, even Windows with clang-cl), there's __attribute__((__cleanup__(fn))), as used for instance by systemd: https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Variable...


There’s boost scope exit but you have to use c++


Check out Zig if you’re looking for a C replacement with a go style defer. It also interops about as well with C as C does.


I thought that depends on llvm though? Not to mention it doesn’t allow horizontal tabs which I dislike pretty intensely, different programming styles and environments work with different tab stops and it makes a lot of sense (IMO) to represent indentation with a single character.


Or D, which integrates with native C libs and has the concept of 'exit scope' for deferred code.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: