11

I wonder if there is any idiomatic way to represent scoped semantics. By scoped I mean things like:

  • scoped mutex (oneliner instead of explicit Lock + deffered Unlock),
  • logging function (or any code block) entrance and exit,
  • measuring execution time.

Example code for first two bullets:

package main

import "log"
import "sync"

func Scoped(m *sync.Mutex) func() {
    m.Lock()
    return func() {
        m.Unlock()
    }
}

func Log(what string) func() {
    log.Println(what, "started")
    return func() {
        log.Println(what, "done")
    }
}

func main() {
    defer Log("testing")()

    m := &sync.Mutex{} // obviously mutex should be from other source in real life
    defer Scoped(m)()
    // use m
}

https://play.golang.org/p/33j-GrBWSq

Basically we need to make one function call just now (eg mutex lock), and one call should be postponed to defer (eg mutex unlock). I propose just returning unnamed function here, but it can be easily named (return struct with function field).

There is only one problem: user can forget to 'call' result of first call.

This code is (can be) idiomatic?

Cœur
  • 37,241
  • 25
  • 195
  • 267
Kokos
  • 442
  • 4
  • 12
  • I can't think of a shorter way to do it. The "idiomatic" way is to explicitly call `lock()` and then `defer unlock()` – Not_a_Golfer Jan 27 '15 at 12:52

4 Answers4

7

Take anonymous function as a scope:

func() {
    Entrance()
    defer Exit()
    // anything you want to do in this scope
}()
rtxu
  • 79
  • 1
  • 5
5

Your proposed solution is already nice. You return a value of func type which you also have to call at the end of the defer.

You can avoid that (returning a func value), but there have to be 2 function calls, one that logs the start event and another one that logs the end event.

The alternative is to make a function call which produces the parameter value of the function that is deferred (rather than returning a function) which is evaluated with the defer statement, and this way it still can remain one line.

You can also try it on the Go Playground:

func start(s string) string {
    fmt.Println("Started", s)
    return s
}

func end(name string) {
    fmt.Println("Ended", name)
}

func main() {
    defer end(start("main"))

    fmt.Println("Doing main's work...")
}

Output:

Started main
Doing main's work...
Ended main
icza
  • 389,944
  • 63
  • 907
  • 827
  • Looking on it after half a decade, it looks slightly better than accepted answer, mostly because it uses simpler concepts. I would only change return type of `start()`/argument of `end()` to some custom type, to enforce that user will not forget to call `start()` (it still could omit `end()` but it is perhaps less likely. – Kokos Apr 07 '21 at 22:50
4

I do not believe there is an idiomatic way to do this. I'm not sure why you'd want to either, is it really so bad to write

m.Lock()
defer m.Unlock()

?

Evan
  • 6,369
  • 1
  • 29
  • 30
  • 3
    This is indeed the idiomatic way (god I'm beginning to hate all this idiomatic nonsense), but the pattern proposed by OP is rather nice IMO. – Not_a_Golfer Jan 27 '15 at 12:53
  • 'Logging engine' is best when you must type just one line - with more there is increasing chance that users abaddon logging in some small functions. – Kokos Jan 27 '15 at 13:06
  • There is also possibility to use bad variable (_i_ instead of _j_) or type different strings in our example logger (which may cause confusion in log analysis tools). – Kokos Jan 27 '15 at 13:10
0

I think question isn't relevant to Go idiomaticity, Seems it's generally better to reason about code when function behave identically either call. To keep state I'd better make an object and define function as method on that object. Means something like

type message string
func (foo message) Log(bar string){
    if bar==nil{doSomethingSpecial()}
    switch foo{
        case something: doSomething()
        ...
        case nil: doSomethingInitial()
        default: doDefault()
    }   
    log.Println(bar, "started")
    foo=bar
} 
Uvelichitel
  • 8,220
  • 1
  • 19
  • 36