Skip to content

Proposal: debug only code #1394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
PavelVozenilek opened this issue Aug 20, 2018 · 2 comments
Closed

Proposal: debug only code #1394

PavelVozenilek opened this issue Aug 20, 2018 · 2 comments
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.

Comments

@PavelVozenilek
Copy link

TL;DR: I propose dbg annotation to mark debug-only code. In non-debug mode all such code disappears.

dbg var x : bool = true;
...
if (a) { 
  x = false;
}
...
assert(x == true);

willl in non-debug mode turn into:

// dbg var x : bool = true;
...
// if (a) { 
//   x = false;
// }
...
// assert(x == true);

The Problem

Heavily defensive code can get quickly hard to read. In C it looks like:

#ifdef DEBUG
int x = 0;
#endif
...
#ifdef DEBUG
if (a) { 
  ++x;
}
#endif
...
#ifdef DEBUG
  if (b) {
    assert(x < 10);
  }
#endif

In Zig one can probably accomplish similar feat with @import("builtin").Mode == Debug.

In C such verbosity is partially fixable by macro:

#ifdef DEBUG
#  define DBG(...) __VA_ARGS__
#else
#  define DBG(...)
#endif

Zig, AFAIK, doesn't offer such tricks.


The Solution

The compiler could support writing debug-only code with minimal ceremony. One keyword would be needed: dbg (or debug or so).

By saying debug mode I mean "when and only when asserts are enabled", not the misnamed compilation regime. Release with enabled asserts is debug for me.


  1. Local variable could be prefixed with dbg. It then exists only in debug mode.

    dbg var x : bool = false;

    Any assignment to such variable would disappear outside the debug mode,. No annotation would be required.

    x = true; // compiler turns it into empty line in non-debug mode

    Using debug variable in statements would follow the same rule:

     if (a) {
       x  = true; // empty in on-debug, in turn the whole if statement could be ignored safely
     }
    ...
    if (x == true) { // whole if statement dropped in non-debug mode
      ...
    } 
    

    Assigning debug value to non-debug is not clear:

    my_value = x; // error, what does this mean in non-debug mode?

    Statement needs to be explicitly annotated:

    dbg my_value = x;

    Passing debug value to a function would be covered later.


  1. Similarly, dbg could be used to label global variables and struct members. The same rules would apply.

  1. Passing debug parameter to function.

    In C I often do:

    void foo(DBG(const char* file, int line)) { ... }
    ...
    foo(DBG(__FILE__, __LINE__));
    

    In Zig it should be possible to mark function parameter(s) as dbg too.

    fn foo(x : bool, dbg y : int32, dbg z : bool) { ... }

    and use them without any ceremony:

    foo(true, 100, false);

    In non-debug mode compiler turns it into:

    foo(true);

    and the compiler is then free to remove all code that produces these dbg parameters and does nothing else:

    var x : bool = true;
    var y : int32 = 10;
    var z : bool = false;;
    foo(x, y, z);
    

    would turn into:

    var x : bool = true;
    // var y : int32 = 10;
    // var z : bool = false;;
    foo(x /*, y, z */);
    

    Inside the function, dbg parameters would behave like dbg local variables.

    If advanced assert is implemented (Allow assert() with custom error message #1303), then all its parameters would be dbg.


  1. dbg functions.

    Whole function could be annotated as dbg. Then all its parameters are dbg, as well as its return value (no need for explicit dbg). In non-debug such function wouldn't exist and all its invocations will disappear.

    dbg fn log(...) { ... }
    
    log("...");
    ...
    log("...")
    

    To make things simpler, dbg function should not be allowed to return any error. Pointer to such function should go only into a dbg value.


  1. Whole code blocks could be annotated as debug:

    dbg { 
      var x= true;
      ....
      assert(x);
    } 
    

    There could be else block:

    dbg { 
       ... // only in debug
    } else {
     ... // only in non-debug
    } 
    

    These blocks should not create a new scope. Perhaps different syntax could be used:

    dbg [
       ... // only in debug
    ] else [
     ... // only in non-debug
    ] 
    

Where does this help?

  • It would make defensive coding style more readable. (It is a real problem, I estimate that 1/5 of my code is debug related: helper data, checks and #ifdefs).
  • The compiler will have more information for dead code elimination. (Which is not always true in C, hence the #ifdefs everywhere.)
  • Easy feature would encourage people to use it.

The feature should not be generalized (e.g to support different API per customer).

@thejoshwolfe
Copy link
Contributor

all of this is effectively accomplishable in status quo, albeit not as conveniently as you're promising.

const builtin = @import("builtin");
const dbg = builtin.mode == builtin.Mode.Debug;
  1. Local variable could be prefixed with dbg. It then exists only in debug mode.
var x : if (dbg) bool else void = if (dbg) false; // implicit `else {}`

then all usage of the variable would need to be guarded by if (dbg).

  1. Similarly, dbgcould be used to label global variables and struct members. The same rules would apply.

the above trick with void works at top-level scope as well.

  1. Passing debug parameter to function.

declare the parameters to become void in non-debug mode as above, and pass parameters with if (dbg) and no else. void-typed parameters are omitted before llvm ir generation, so they are effectively deleted.

  1. Whole function could be annotated as dbg.

simply assert(dbg); at the start of the function, and then avoid calling it without a dbg-guard.

  1. Whole code blocks could be annotated as debug:

aside from the obvious if (dbg) in a function, there isn't any way to do what you're proposing at top-level scope or without creating a scope. instead you have to do the conditional if (dbg) thing to every item you want to declare.

It would make defensive coding style more readable.

Why not use assert?

Easy feature would encourage people to use it.

The code I've proposed in this comment is as clunky as it is to encourage people not to do it. it's also more confusing for code to behave differently in different build modes, and using an explicit if makes it more clear, albeit verbose.

@andrewrk andrewrk added proposal This issue suggests modifications. If it also has the "accepted" label then it is planned. rejected labels Aug 20, 2018
@PavelVozenilek
Copy link
Author

@thejoshwolfe:

then all usage of the variable would need to be guarded by if (dbg).

This is the problem I wanted to fix. It makes the code harder to read, full of trivial ifs, obfuscating the flow when the debug purpose is already clear. The gist of my proposal was that Zig could make better than the (not very good) status-quo in C.

Why not use assert?

Systematic defensive coding is much more than few asserts. It means a lot of supporting code (outside dedicated tests), code that must not make it into the final executable.

Easy feature would encourage people to use it.

The code I've proposed in this comment is as clunky as it is to encourage people not to do it. it's also more confusing for code to behave differently in different build modes, and using an explicit if makes it more clear, albeit verbose.

The defensive code is unavoidable (well, not really, but I wouldn't go that way). There's opportunity to make the situation better, on a very large scale. This could be one selling point for industrial use of the language, for industries obsessed with reliability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
proposal This issue suggests modifications. If it also has the "accepted" label then it is planned.
Projects
None yet
Development

No branches or pull requests

3 participants