Hacker News new | ask | show | jobs
by taeber 1619 days ago
I like the idea of automatic resource cleanup in C and was excited when I read the headline, but I agree with others that the proposed syntax doesn't seem very C-like.

I previously shared a "with" statement idea on HN that I thought fit with the rest of the language.

  with (FILE *fp; fp = fopen(path, "wb"); fclose(fp)) {
  ...
  } else {
    perror("Failed to open file");
  }
https://github.com/taeber/cwith

https://news.ycombinator.com/item?id=23370693

edit: formatting

1 comments

I wonder if this can be emulated with for loop:

  for(FILE *fp = fopen(path, "wb");
      fp != NULL;
      fclose(fp), fp = NULL) {
    ...
  }
I'm glad you suggested that, because one of the first comparisons I drew in my article[1] about the "with"-syntax was how it looks like a for-loop.

If you are interested, I did provide a few macros[2], but as another commenter pointed out, they won't handle an early return. You can use "break" to exit early though:

  #define with(declare, startup, cleanup, block) \
      {                      \
          declare;           \
          if (( startup )) { \
              do {           \
                  block;     \
              } while(0);    \
              cleanup;       \
          }                  \
      }

  with (FILE *fp, fp = fopen(path, "rb"), fclose(fp), {
    fread(buf, 1, sizeof(buf)-1, fp);
    if (ferror(fp)) {
      perror(path);
      break;
    }
    printf("%s\n", buf);
    success = 1;
  })
There's also "withif"

  #define withif(declare, startup, cleanup, block, otherwise) \
      {                      \
          declare;           \
          if (( startup )) { \
              do {           \
                  block;     \
              } while(0);    \
              cleanup;       \
          } otherwise;       \
      }
[1]https://github.com/taeber/cwith

[2]https://github.com/taeber/cwith/blob/master/with.h

No, because exiting the block early (e.g., with return) won't run the cleanup code.
That's a little bit like saying, it's impossible to write functioning code because you could make a mistake. But an abstraction that can be misused might still be an abstraction worth using.

Here is a macro that can be used to emulate a defer

    #define CONCAT_(a, b) a ## b
    #define CONCAT(a, b) CONCAT_(a, b)
    #define UNIQUENAME() CONCAT(i_, __LINE__)

    #define SCOPE_(counter, init_stmt, exit_stmt)  for (int counter = ((init_stmt), 1); counter--; (exit_stmt))
    #define SCOPE(init_stmt, exit_stmt) SCOPE_(UNIQUENAME(), (init_stmt), (exit_stmt))
Granted it's a hack, but it can be useful at times. I've used something like it to define a large data hierarchy in code for example, as having to close all the nodes manually is tedious.

You could wrap a second for-loop around the definition of the macro to at least be able to catch misplaced "break" statements.

Looks very interesting. Can you please provide usage example?
Sure, you'd code like

    SCOPE(Resource *ptr = acquire_resource(),
          release_resource(ptr))
    {
        // do stuff with resource ptr.
    }
Actually, to allow variable declarations in the init_stmt like above, you'll need to use two nested for-loops:

    #define SCOPE_(name, begin_stmt, end_stmt) for (int name = 0; !name; assert(name && "should never break from a SCOPE")) for (begin_stmt; !name; name++, (end_stmt))
It is natural to add another layer of specific usage macro like this:

    #define UI_NODE(ctx) SCOPE(push_ui_node(ctx), pop_ui_node(ctx))

    UI_NODE(ctx)
    {
        ui_color(ctx, UI_COLOR_RED);

        const char *items[3] = { "a", "b", "c" };
        for (int i = 0; i < 3; i++)
        {
            UI_NODE(ctx)
                ui_text(ctx, items[i]);
        }
    }
Naturally, if you're already on C++, better code these macros in terms of RAII instead of abusing for-loops. That will add a little robustness.
I'm on C89, but this is a very useful trick. Thanks!