In Python there is a thing called a context manager. It looks like this:
with open('file.txt') as f:
# setup code runs here
print f.read()
# teardown code runs here
do_other_thing(f) # error: f is not bound here
Inside the indented block the object f
is alive. Outside it's not. In addition, the context manager open
has the option to run some code just both before entering the indented block, and just before leaving it. In this case it has to do with opening and closing the file behind the scenes.
A different example: say you wanted to time the execution of a few lines of code. You could implement a context manager that does this:
timings = {}
with time_this(timings, "post_and_get"):
api.post("new thing")
api.get("new thing")
# timings is now: {"post_and_get", 1.819}
Behind the scenes time_this
would start a timer, then run the indented block, stop the timer, and finally insert an entry into the timings
dictionary.
Coming back to Rust I'm looking at some server code that I need to instrument with timers to measure the performance of different parts. I'm wondering what the Rust way would be....
Starting from the simple but clunky way:
let mut timings = Timings::new();
timings.start_timer("post_and_get");
api.post("new thing");
let rv = api.get("new thing");
timings.stop_timer("post_and_get");
This is verbose and easy to use incorrectly by failing to match start_timer with stop_timer and not using the same name in both calls.
A C++ inspired variant:
let mut timings = Timings::new();
let rv = {
// A timer is started here
let t = Timer::new(&mut timings, "post_and_get"); // like this
// Timer::new(&mut timings, "post_and_get"); // not like this!!!
api.post("new thing");
api.get("new thing")
// Timer implements Drop, so teardown code runs here
};
Here Timer
is a throw-away value that only serves to mutate timings
such that on creation it starts the timer, then once it goes out of scope it stops the timer and inserts the result into timings
.
This is not bad, but it's easy to use incorrectly, because without the let
the Timer
value goes out of scope immediately and doesn't time the rest of the block. It also means we have to use a name like t
to create the binding, even though we'd rather not have it potentially collide with other variable names we happen to be using in this code already.
Finally, I can think of the macro way:
let mut timings = Timings::new();
let rv = time_this!(&mut timings, "post_and_get", {
api.post("new thing");
api.get("new thing")
});
Here the macro would call timings.start_timer
and timings.stop_timer
behind the scenes for us.
This seems the least error prone, but it feels kind of heavy handed to me syntactically speaking.
Any ideas?