I’m very new to zig and found a hello world example that writes to stdout using a buffered writer ie: var bw = std.io.bufferedWriter(stdout_file);. At the end of the main function it has try bw.flush();.
I thought I’d try putting this inside a defer expression right underneath where bw is defined, but haven’t been able to make it work. If I write: defer try bw.flush(); I get error: 'try' not allowed inside defer expression
If I write: defer bw.flush(); I get error: error union ignored
Reading up on defer I can’t find an explanation for what is and isn’t allowed in a defer expression.
You can’t have control flow inside a deferred statement. In this case try bw.flush() is short for bw.flush() catch |err| return err; and return is control flow. One way to think of this is that by the time a defer statement is running the scope has ended and the return value will have already been decided.
I thought that because this main function has return type !void that defer bw.flush() failing would fit with the type of the function. However if as you say the return value has already been decided, that’s more understandable.
You can’t have control flow inside a deferred statement.
That’s clumsy wording; you can absolutely do control flow, including calling functions, having conditionals, etc. What’s not allowed is for a deferred statement to contain control flow which exits the defer/errdefer; for instance, breaking from a block outside of the defer, or, as in this case, return/try.
The accepted solution is technically accurate, and the other comment answers the question posed in the thread title (but note the above clarification). However, I think these answers are, if not misleading, a little incomplete, because they omit a crucial correction: flushshould not be used in a defer. The use case for defer is for something you want to happen on all exits from a scope; this is almost always related to releasing resources (freeing memory, closing a file handle, etc). (errdefer is “the same thing, except only when i exit the scope by returning an error”.) flush is not doing that. flush is a failable operation which only makes sense to attempt on the success path of your operation. Don’t defer bw.flush() catch ... or anything of the sort; it is correct to write this as a normal statement in your success path. It’s like how you wouldn’t try to write defer try w.writeByte('\n'); in a “write line” function, you’d just do it at the end!
There’s a related thread which goes into more detail about why you probably don’t want to flush from a defer, as well as why you can’t try from a defer.
It can be worthwhile to flush from a defer in cases where the output gives a nicer experience when an error is returned. But I agree, in general, flushing on successful completion is better.