C like macros in golang

11,435 views
Skip to first unread message

Tong Sun

unread,
Feb 23, 2013, 9:20:34 AM2/23/13
to golan...@googlegroups.com
Hi, 

Expressed in C macros, I'd like to define something like this:

 #define Debugln(X) if debugging { println(X) } 

What's the closest way to do it in go? 

Thanks

Jan Mercl

unread,
Feb 23, 2013, 9:27:16 AM2/23/13
to Tong Sun, golang-nuts
const debug = true

...
if debug { // if body is not linked in for debug == false
log.Printf("%v", foo)
}
...

-j

Tong Sun

unread,
Feb 23, 2013, 9:32:34 AM2/23/13
to golan...@googlegroups.com, Tong Sun
Thanks Jan, but the spirit of C macros is that all your above 3 line would condensed to "Debugln(foo)", so that I don't need to do duplicate such 3-line code everywhere. 



Jan Mercl

unread,
Feb 23, 2013, 9:45:16 AM2/23/13
to Tong Sun, golang-nuts
On Sat, Feb 23, 2013 at 3:32 PM, Tong Sun <sunto...@gmail.com> wrote:
> the spirit of C macros is that all your above 3 line would
> condensed to "Debugln(foo)", so that I don't need to do duplicate such
> 3-line code everywhere.

I see, but that's what I suggest to do in Go. Of course one can have:

func DebugLn(s string, args ...interface{}) {
if debug {
log.Printf(s, args...)
}
}

But that:

- Gets included in the production binary
- Arguments are evaluated even for debug == false.

So I cannot recommend it.

-j

PS: Ad "spirit". Well, Go intentionally doesn't has a preprocessor, so
recreating C preprocessor macros in Go code is not going to work in
the same way is it works in C. Or perhaps use cpp (really), it would
probably work with most of Go source code, I guess.

Gerard

unread,
Feb 23, 2013, 9:53:11 AM2/23/13
to golan...@googlegroups.com

Daniel Morsing

unread,
Feb 23, 2013, 9:54:52 AM2/23/13
to Jan Mercl, Tong Sun, golang-nuts
On Sat, Feb 23, 2013 at 3:45 PM, Jan Mercl <0xj...@gmail.com> wrote:
> - Arguments are evaluated even for debug == false.
>

Having had the displeasure of debugging a program where this mattered,
I am strongly in favor of this property.

However, not in favor leaving scattershot prints that you have to
recompile to get. Either it's useful shipped, or it's trivial enough
to be recreated in a debugger.

Tong Sun

unread,
Feb 23, 2013, 9:57:36 AM2/23/13
to golan...@googlegroups.com


On Saturday, February 23, 2013 9:53:11 AM UTC-5, Gerard wrote:
Another approch


BinGO!  really neat! 

Rui Maciel

unread,
Feb 23, 2013, 10:03:13 AM2/23/13
to golan...@googlegroups.com
I don't know if it's the closest way, but what about this:

http://play.golang.org/p/bnLdpNWsxE


Rui Maciel

Gerard

unread,
Feb 23, 2013, 10:05:27 AM2/23/13
to golan...@googlegroups.com
Thanks. And of course it is very easy to create a "debug" package with fmt Print like functions. 

Btw Rob Pike had this idea first. I just couldn't find the thread.

Gerard


Op zaterdag 23 februari 2013 15:57:36 UTC+1 schreef Tong Sun het volgende:

Gerard

unread,
Feb 23, 2013, 10:24:00 AM2/23/13
to golan...@googlegroups.com
Ok, my final approach. 

If you have a multiple package piece application the previous example may not work as expected. 

With this http://play.golang.org/p/gjszhNzoOk package you have one global variable for your app: dbg.Debug. 

And maybe it's better to use the log package instead of fmt (or print to os.Stderr).

Gerard


Op zaterdag 23 februari 2013 16:05:27 UTC+1 schreef Gerard het volgende:

Tong Sun

unread,
Feb 23, 2013, 10:41:02 AM2/23/13
to Gerard, golang-nuts, Rui Maciel

On Sat, Feb 23, 2013 at 10:24 AM, Gerard <gvds...@gmail.com> wrote:
Ok, my final approach. 

Oh thank you Gerard for going through the whole nine yards in helping. 

@Rui, thanks for your simple solution too. 


Michael Jones

unread,
Feb 23, 2013, 10:45:52 AM2/23/13
to Gerard, golan...@googlegroups.com
Another approach.


Now, look closely:

marconi-mobile:deb mtj$ go build -gcflags="-S" ex.go
# command-line-arguments

--- prog list "Debug" ---
0000 (/Users/mtj/gocode/src/deb/ex.go:7) TEXT    Debug+0(SB),$64-40
0001 (/Users/mtj/gocode/src/deb/ex.go:7) LOCALS  ,$0
0002 (/Users/mtj/gocode/src/deb/ex.go:11) RET     ,

--- prog list "main" ---
0003 (/Users/mtj/gocode/src/deb/ex.go:13) TEXT    main+0(SB),$40-0
0004 (/Users/mtj/gocode/src/deb/ex.go:13) LOCALS  ,$0
0005 (/Users/mtj/gocode/src/deb/ex.go:14) LEAQ    go.string."Debugging off\n"+0(SB),BX
0006 (/Users/mtj/gocode/src/deb/ex.go:14) LEAQ    (SP),BP
0007 (/Users/mtj/gocode/src/deb/ex.go:14) MOVQ    BP,DI
0008 (/Users/mtj/gocode/src/deb/ex.go:14) MOVQ    BX,SI
0009 (/Users/mtj/gocode/src/deb/ex.go:14) MOVSQ   ,
0010 (/Users/mtj/gocode/src/deb/ex.go:14) MOVSQ   ,
0011 (/Users/mtj/gocode/src/deb/ex.go:14) LEAQ    16(SP),DI
0012 (/Users/mtj/gocode/src/deb/ex.go:14) MOVQ    $0,AX
0013 (/Users/mtj/gocode/src/deb/ex.go:14) STOSQ   ,
0014 (/Users/mtj/gocode/src/deb/ex.go:14) STOSQ   ,
0015 (/Users/mtj/gocode/src/deb/ex.go:14) STOSQ   ,
0016 (/Users/mtj/gocode/src/deb/ex.go:14) CALL    ,Debug+0(SB)
0017 (/Users/mtj/gocode/src/deb/ex.go:15) RET     ,

--- prog list "init" ---
0018 (/Users/mtj/gocode/src/deb/ex.go:15) TEXT    init+0(SB),$0-0
0019 (/Users/mtj/gocode/src/deb/ex.go:15) MOVBQZX initdone·+0(SB),AX
0020 (/Users/mtj/gocode/src/deb/ex.go:15) LOCALS  ,$0
0021 (/Users/mtj/gocode/src/deb/ex.go:15) CMPB    AX,$0
0022 (/Users/mtj/gocode/src/deb/ex.go:15) JEQ     ,28
0023 (/Users/mtj/gocode/src/deb/ex.go:15) CMPB    AX,$2
0024 (/Users/mtj/gocode/src/deb/ex.go:15) JNE     ,26
0025 (/Users/mtj/gocode/src/deb/ex.go:15) RET     ,
0026 (/Users/mtj/gocode/src/deb/ex.go:15) CALL    ,runtime.throwinit+0(SB)
0027 (/Users/mtj/gocode/src/deb/ex.go:15) UNDEF   ,
0028 (/Users/mtj/gocode/src/deb/ex.go:15) MOVB    $1,initdone·+0(SB)
0029 (/Users/mtj/gocode/src/deb/ex.go:15) CALL    ,fmt.init+0(SB)
0030 (/Users/mtj/gocode/src/deb/ex.go:15) MOVB    $2,initdone·+0(SB)
0031 (/Users/mtj/gocode/src/deb/ex.go:15) RET     ,

Main plays with registers then calls Debug which returns immediately. That's what the OP wants to avoid. Now try this


marconi-mobile:deb mtj$ go build -gcflags="-l -l -l -l -l -l -S" ex.go
# command-line-arguments

--- prog list "Debug" ---
0000 (/Users/mtj/gocode/src/deb/ex.go:7) TEXT    Debug+0(SB),$64-40
0001 (/Users/mtj/gocode/src/deb/ex.go:7) LOCALS  ,$0
0002 (/Users/mtj/gocode/src/deb/ex.go:11) RET     ,

--- prog list "main" ---
0003 (/Users/mtj/gocode/src/deb/ex.go:13) TEXT    main+0(SB),$64-0
0004 (/Users/mtj/gocode/src/deb/ex.go:13) LOCALS  ,$0
0005 (/Users/mtj/gocode/src/deb/ex.go:14) LEAQ    go.string."Debugging off\n"+0(SB),BX
0006 (/Users/mtj/gocode/src/deb/ex.go:14) MOVQ    (BX),BP
0007 (/Users/mtj/gocode/src/deb/ex.go:14) MOVQ    8(BX),BP
0008 (/Users/mtj/gocode/src/deb/ex.go:15) RET     ,

--- prog list "init" ---
0009 (/Users/mtj/gocode/src/deb/ex.go:15) TEXT    init+0(SB),$0-0
0010 (/Users/mtj/gocode/src/deb/ex.go:15) MOVBQZX initdone·+0(SB),AX
0011 (/Users/mtj/gocode/src/deb/ex.go:15) LOCALS  ,$0
0012 (/Users/mtj/gocode/src/deb/ex.go:15) CMPB    AX,$0
0013 (/Users/mtj/gocode/src/deb/ex.go:15) JEQ     ,19
0014 (/Users/mtj/gocode/src/deb/ex.go:15) CMPB    AX,$2
0015 (/Users/mtj/gocode/src/deb/ex.go:15) JNE     ,17
0016 (/Users/mtj/gocode/src/deb/ex.go:15) RET     ,
0017 (/Users/mtj/gocode/src/deb/ex.go:15) CALL    ,runtime.throwinit+0(SB)
0018 (/Users/mtj/gocode/src/deb/ex.go:15) UNDEF   ,
0019 (/Users/mtj/gocode/src/deb/ex.go:15) MOVB    $1,initdone·+0(SB)
0020 (/Users/mtj/gocode/src/deb/ex.go:15) CALL    ,fmt.init+0(SB)
0021 (/Users/mtj/gocode/src/deb/ex.go:15) MOVB    $2,initdone·+0(SB)
0022 (/Users/mtj/gocode/src/deb/ex.go:15) RET     ,

Better -- no call to Debug. We still have the useless string in the text, the useless creation of addressability of it in registers, etc., but no call. Maybe the linker does some magic. I could not find this in the executable using gdb or ndisasm.


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Michael T. Jones | Chief Technology Advocate  | m...@google.com |  +1 650-335-5765

Julien Laffaye

unread,
Feb 23, 2013, 10:59:05 AM2/23/13
to Jan Mercl, Tong Sun, golang-nuts
It would be possible with something like
http://dlang.org/lazy-evaluation.html
Or you could use grep -v to remove the calls from your source code ;p

Michael Jones

unread,
Feb 23, 2013, 11:30:07 AM2/23/13
to Julien Laffaye, Jan Mercl, Tong Sun, golang-nuts
Or you can just use cpp. I use m4 sometimes, and lately, mcpp. I needed the "##" string concatenation operator and grew weary of living without it.

in one place:

const WORDS = 37 // example

elsewhere, where my assembler leaf functions are being called:

FixedAdd37(a, b *Integer)

I tried to live by the Go team's Spartan "no metaprogramming of any kind" rules, but the constant edits were intolerable and error causing. Having it back has been great for me, personally. (Same thing is possible in a full implementation of Knuth's WEB system of literate programming, via the important "define" feature.)

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.


Kevin Malachowski

unread,
Feb 23, 2013, 12:37:12 PM2/23/13
to golan...@googlegroups.com, Jan Mercl, Tong Sun
How about something like this: http://play.golang.org/p/SJ6mLSsA9J . It's only about 20 more characters per debug statement to make them lazily evaluated, and you don't even need to do the lazy one if you don't need to.

I'm not terribly fluent in Go, so there may be an even shorter way to achieve that.

Note that this will save the arguments from being evaluated every time you don't want them to be, but it will only be faster if creating the closure is less expensive than your actual calculation in the first place.

Dan Kortschak

unread,
Feb 23, 2013, 1:35:29 PM2/23/13
to Gerard, golan...@googlegroups.com
It was a combined effort. Rob made it more efficient.

The discussion in that thread was interesting, so here it is.

https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/gU7oQGoCkmg

Kevin Gillette

unread,
Feb 23, 2013, 4:16:03 PM2/23/13
to golan...@googlegroups.com, Tong Sun
On Saturday, February 23, 2013 7:45:16 AM UTC-7, Jan Mercl wrote:
But that:

- Gets included in the production binary
- Arguments are evaluated even for debug == false.

So I cannot recommend it.

First off, I disagree with the recommendation against: if used in an io-bound app, or a cpu-bound app that doesn't have low-latency requirements (such as some batch-processing task), the cost of argument evaluation may be entirely negligible in the first case, and in the second case perhaps result in a processing time of 1h3s instead of 1h. Especially considering that arguments to println and printf style functions are frequently either constant or simple-identifier expressions, there may be minimal, predictable cost to arg evaluation, and in either case, is likely to be far better (in terms of code-maintenance) than performing the manual optimization of sprinkling `if debug { }` statements all over the code.

This is a fundamental tradeoff in practical language design: on one end of the spectrum is the policy of baking manual optimization capabilities into the language any time preeminent compilers can't currently infer that optimization from more general code. C++ is reputed to take this approach. The other extreme is to leave manual optimization out of the language, particularly for anything that a compiler could eventually optimize. Go leans this way.

This particular issue seems like a prime candidate for optimizing:
1) DebugLn is simple enough to inline (as Michael Jones pointed out, gc can already do that), and I expect gccgo can too.

2) Detecting if a func has side effects may, unlike many optimization problems, may be not only tractable for a large number of cases, but also computationally efficient to determine: like a mark-and-sweep collector, scan each function in the call graph finding cases where the original inputs can be and are mutated (pointers and ref types), and find any mutating accesses to ancestor-scope identifiers. If a function makes any func calls, determine if the callees can have side effects, and if so, also mark the caller as having side effects. In unknown or difficult to determine cases (C, reflect, unsafe), take the easy way out and mark the caller as having side effects.

Marking side effects allows the following optimization:

const debug = false
var s = "ghi"
func noSideEffect() string { return "string" }
func sideEffect() string { s = "something else"; return s }

func main() {
   DebugLn("abc", "def" + s, noSideEffect())
   DebugLn("abc", "def" + s, sideEffect())
}

In this case, noSideEffect would have been marked as side-effect free, and the expressions `"abc"` and `"def" + s` are guaranteed to have side-effect-free behavior, since all operator expressions have fixed, idempotent semantics. For the first line in main(), since DebugLn can be inlined into nothingness, and all the arguments are side-effect-free (the side-effect analysis being conservative, having false positives but never false negatives, and so should be semantically "safe"), therefore allowing Go's requirement that arguments be semantically evaluated without actually needing to evaluate them at runtime. The second line in main() would only need to evaluate `sideEffect()` at runtime, but could discard its value.
 
PS: Ad "spirit". Well, Go intentionally doesn't has a preprocessor, so
recreating C preprocessor macros in Go code is not going to work in
the same way is it works in C. Or perhaps use cpp (really), it would
probably work with most of Go source code, I guess.

While Go (the language) doesn't have a preprocessor, concerning build tags and possibly getting cgo started, the popular compiler implementations do have the equivalent of a specialized/restricted preprocessor.

John Nagle

unread,
Feb 24, 2013, 12:46:01 AM2/24/13
to golan...@googlegroups.com
On 2/23/2013 10:35 AM, Dan Kortschak wrote:
> It was a combined effort. Rob made it more efficient.
>
> The discussion in that thread was interesting, so here it is.
>
> https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/gU7oQGoCkmg

With that, each argument still has to be packaged up as an
interface object, then discarded if debug is off. That's at least
unnecessary work, and possibly a heap allocation for each interface
object.

What does it take to get a solution where, when "const debug=false",
the debug code goes away?

John Nagle

Dan Kortschak

unread,
Feb 24, 2013, 1:53:13 AM2/24/13
to John Nagle, golan...@googlegroups.com
Wrap the whole thing in an 'if debug {...}', just like you would in C with a #ifdef.

Daniel Bryan

unread,
Feb 24, 2013, 2:42:41 AM2/24/13
to golan...@googlegroups.com, Tong Sun
> Thanks Jan, but the spirit of C macros is that all your above 3 line would condensed to "Debugln(foo)", so that I don't need to do duplicate such 3-line code everywhere.

func log(msg interface{}) {
  if Debug == true {
    // log it
  }
}

Usage is now a one-liner with none of the issues associated with macros.

Michael Jones

unread,
Feb 24, 2013, 3:15:48 AM2/24/13
to Daniel Bryan, golan...@googlegroups.com, Tong Sun
Seriously, embrace simple preprocessing.

If you have Debug(xxx) in your code, where an example might be (a and c being normal code):

    a
    Debug(if x < 0.0 { fmt.Printf("Woe! x = %f\n", x) })
    c

Then running your code through m4 with  

    m4 -D 'Debug=$@' < example

then you get

    a
    if x < 0.0 { fmt.Printf("Woe! x = %f\n", x) }
    c

but if you preprocess via m4 with 

    m4 -D "Debug=" < example

then you get

    a

    c

The blank line as the replacement (deleting the argument of Debug but leaving the "\n") means that file line numbers are not changed which can be handy with error reports. (So long as your Debug() was on one line)

If you don't want that, and REALLY want Debug stuff to go away, that's easy too...

    m4 -D "Debug=dnl" < example

to get

    a
    c

And the compiler never sees a hint of the Debug code.
     

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.
 
 

Stefan Nilsson

unread,
Feb 24, 2013, 4:32:23 AM2/24/13
to golan...@googlegroups.com
Just for the record, in some instances you might be able to use logging directly and then turn it off when not needed:

    log.SetOutput(ioutil.Discard) // turns off logging

Gerard

unread,
Feb 24, 2013, 5:14:03 AM2/24/13
to golan...@googlegroups.com
+1

Op zondag 24 februari 2013 10:32:23 UTC+1 schreef Stefan Nilsson het volgende:

Rémy Oudompheng

unread,
Feb 24, 2013, 5:25:18 AM2/24/13
to Michael Jones, Daniel Bryan, golan...@googlegroups.com, Tong Sun
On 2013/2/24 Michael Jones <m...@google.com> wrote:
> Seriously, embrace simple preprocessing.
>
> If you have Debug(xxx) in your code, where an example might be (a and c
> being normal code):
>
> a
> Debug(if x < 0.0 { fmt.Printf("Woe! x = %f\n", x) })
> c
>
> Then running your code through m4 with
>
> m4 -D 'Debug=$@' < example
>
> then you get
>
> a
> if x < 0.0 { fmt.Printf("Woe! x = %f\n", x) }
> c
>
> but if you preprocess via m4 with
>
> m4 -D "Debug=" < example
>
> then you get
>
> a
>
> c
>
> The blank line as the replacement (deleting the argument of Debug but
> leaving the "\n") means that file line numbers are not changed which can be
> handy with error reports. (So long as your Debug() was on one line)
>
> If you don't want that, and REALLY want Debug stuff to go away, that's easy
> too...

It gives funny results whenever a struct field is named Debug, or you
write the word "Debug" in your comments, or you wanted to call
logging.Debug(args).

Preprocessing is better done using the go/ast package or a proper Go parser.

Rémy.

Michael Jones

unread,
Feb 24, 2013, 12:07:10 PM2/24/13
to Rémy Oudompheng, Daniel Bryan, golan...@googlegroups.com, Tong Sun
Yes certainly. OP wanted a "zero footprint" solution, and this provides one. In such cases the name either needs to be very different than a typical function name ("DEBUG") or, rather than be weird and non-syntax compliant, the use could look normal like log.Printf() or debug.Printf() except be deleted in it's entirety by the macro system. My post was just an example of the m4 aspects...

Michael

P.S. Awesome progress on the closure optimization!

Nathan Youngman

unread,
Feb 25, 2013, 1:13:42 AM2/25/13
to golan...@googlegroups.com
Tong Sun,

For a "zero footprint" solution you can use build constraints.

I did this when I was fiddling with OpenGL error handling:

Essentially you end up with your release build calling a function that does nothing. I presume the compiler will inline/remove such calls.

The Go blog has a post on build constraints for App Engine, but they are just as applicable to your situation.

Nathan.

recolic...@gmail.com

unread,
Nov 21, 2017, 8:31:21 AM11/21/17
to golang-nuts
I believe it's useful to write a preprocessor for golang. And run it before every go compilation.

So that I'll be happy to feed myself C++-style golang.

在 2013年2月23日星期六 UTC+8下午10:20:34,Tong Sun写道:
Reply all
Reply to author
Forward
0 new messages