Author Topic: Add defer  (Read 5528 times)

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Add defer
« on: October 22, 2018, 10:16:33 AM »
I suggest adding defer statements to C2. In the vein of the same feature in Zig, Jai, Go etc

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: Add defer
« Reply #1 on: October 22, 2018, 12:36:49 PM »
How would you define the syntax and semantics of something like that?

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Add defer
« Reply #2 on: October 22, 2018, 05:42:38 PM »
I'm thinking quite straightforward:

Code: [Select]
File *file = open("foo.txt");
if (!file) return false;
defer {
  file.close();
}
...
if (something) return false;
...
return true;

This is transformed to the following code (can be done when parsing):

Code: [Select]
File *file = open("foo.txt");
if (!file) return false;
...
if (something)
{
  file.close();
  return false;
}
...
file.close();
return true;

(It can also be handled with a jump obviously)

I would suggest the following:

  • It's possible to use a plain return within a defer, it only exits the defer early (the defer acts as an anonymous function / closure).
  • It's possible to stack defers

One thing worth discussing is what happens when you actually put a defer in a block.

Consider this:

Code: [Select]
...
defer { ... } // A
....
{
    defer { ... } // B
} // C
....
return; // D

In this case it's unclear whether the best thing would be defer B to act on exit of the block (at point C) or (point D, before defer at A is executed.

But if we pick point C (which would be nice), we have some choices to make. For example, when is the defer called here:

Code: [Select]
if (some_condition) defer { .... }; // A
if (some_condition) {
   defer { .... }; // B
}
if (some_condition) {
   defer { .... }; // C
   x = y;
} // D

...
return; // E

One probably wants the defer of both A and B to occur at E, but the defer at C is probably intended for D.

Go makes the decision to use E for all defers, while Swift would allow execute A and B directly, just like defer at C.

Zig follows Swift and adds an "errdefer" as a special case defer on error exit.

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Add defer
« Reply #3 on: November 03, 2018, 03:51:29 AM »
A limited form of defer exists as a GCC extension __cleanup__:

http://echorand.me/site/notes/articles/c_cleanup/cleanup_attribute_c.html

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Add defer
« Reply #4 on: November 04, 2018, 02:14:41 PM »
I also propose that we add an attribute called @autofree that does an implicit defer:

This is ok:
Code: [Select]
func void something() {
  Foo *f = malloc(f);
  defer { free(f); }
  ... code ...
}

But this might be better:
Code: [Select]
func void something() {
  Foo *f @autofree = malloc(sizeof(Foo)) ;
  // Foo *f = nil; defer { if (f) free(f); } f = malloc(sizeof(Foo));
}

func void something() {
  Foo *f @autofree = malloc(sizeof(Foo)) @autofree;
  // Temp *t = malloc(sizeof(Foo)); if (t) defer free(t); f = t; 
}

Note that we achieve some subtle differences depending on where we put the attribute.

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Add defer
« Reply #5 on: November 04, 2018, 07:06:19 PM »
A further refinement / extension of the idea above, suggested is to have a defer attribute which refers to a function:

Code: [Select]
void foo1() {
  Foo *f @defer(free) = malloc(sizeof(Foo));
  FILE *file @defer(fclose) = fopen("file.txt","r");
}


lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Add defer
« Reply #6 on: November 05, 2018, 06:40:08 PM »
Here is an extended example of defer and @autofree:

Code: [Select]
{
   int x = z();
   defer{ A(); }
   Foo *foo = malloc(sizeof(Foo *)) @autofree;
   if (foo == NULL) return -1;
   int x += w();
   if (x > 10) return 10;
   if (x == y()) defer { B(); }
   if (x < 0) {
     defer { C(); }
     if (D() > 10) return -2;
     x = 0;
   }
   return d() + x;
}
// Compiles to:
{
   int __res;
   int __defer_cond_1;
   void *__autofree_1;

   x = z();
   __autofree_1 = malloc(sizeof(Foo *));
   foo = __autofree_1;
   if (foo == NULL) {
     __res = -1;
     goto DEFER_2;
   }
   x += w();
   if (x > 10) {
     __res = 10;
     goto DEFER_2;
   }
   __defer_cond_1 = x == y();
   if (x < 0) {
     int __early_exit = 0;     
     if (D() > 10) {
        __early_exit = 1;
        __res = -2;
        goto DEFER_4;
     }
     x = 0;
     DEFER_4:
     C();
     if (__early_exit) goto DEFER_3;
   }
   __res = d() + x;
   DEFER_3:
   if (defer_cond_1) B();   
   DEFER_2:
   free(__autofree_1); // Assume free(NULL) is no-op
   DEFER_1:
   A();
   return temp;
}

bas

  • Full Member
  • ***
  • Posts: 220
    • View Profile
Re: Add defer
« Reply #7 on: November 09, 2018, 08:48:34 PM »
Hmm seems like an interesting idea.
Since C2 is a language where nothing happens behind the screens, I do prefer a situation where defer must be called manually.

What I don't get from the Go examples is that the defer expr seems to be evaluated at the moment the defer is set, not when
the scope ends and it's actually run..

I'll put defer on the Roadmap..

lerno

  • Full Member
  • ***
  • Posts: 247
    • View Profile
Re: Add defer
« Reply #8 on: November 09, 2018, 09:46:27 PM »
Go essentially creates a "defer list" with closures it will invoke when the function ends. It's pretty weird.

Golang's defer { close(foo) } could in C++ be implemented like this:

Code: [Select]
int do_something()
{
   defer_list __defer_list {}; // Always at top
   ... code ...
   __defer_list.push_back([foo] { close(foo); }); // defer { close(foo) } in Go
   ... code ...
   return 0;
}

defer_list::push_back(std::function<void()> &closure) { entries.push_back(closure); }

~defer_list() {
  for (auto &defer_entry : entries) {
    defer_entry();
  }
}

So if you do something like this:
Code: [Select]
func main() {
  for i:= 0; i < 3; i++ {
    defer fmt.Println(i)
  }
  fmt.Println("foo")
}

You'd get:

Code: [Select]
foo
2
1
0

So there's an actual defer stack in Go! So weird.

Rust, Zig, Jai all use the more reasonable RAII-like defer.