C2 forum

General Category => Ideas => Topic started by: lerno on October 22, 2018, 10:16:33 AM

Title: Add defer
Post by: lerno 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
Title: Re: Add defer
Post by: bas on October 22, 2018, 12:36:49 PM
How would you define the syntax and semantics of something like that?
Title: Re: Add defer
Post by: lerno 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:


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.
Title: Re: Add defer
Post by: lerno 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
Title: Re: Add defer
Post by: lerno 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.
Title: Re: Add defer
Post by: lerno 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");
}

Title: Re: Add defer
Post by: lerno 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;
}
Title: Re: Add defer
Post by: bas 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..
Title: Re: Add defer
Post by: lerno 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.