Why isn't "defer" sentence executed?

const std = @import(“std”);
const print = std.debug.print;

pub fn main() void {
    defer {
        print(“Final output.”, .{}); // “defer” doesn’t run when panic occurs. Why?
    }
    @panic(“panic occurred!”);
}

defer executes an expression unconditionally at scope exit.

Since you never exit scope, if you panic, the defer is not executed.

5 Likes

I don’t know how to explain well, but here you need a errdefer.

type ``` to make a code section in your posts, it will highlight correctly, and has convenient copy and expand buttons.

@panics dont exit the scope, it prints a stack trace and terminate the program immediately.

The only ways to exist a scope are, code execution reaches the end of the scope, return, try, break (loops, switches, labelled blocks) and continue.

1 Like

defer runs when the scope exits.
panic has a return type of noreturn, which means that as soon as it starts executing, control will never return to the calling function. So it never returns to main’s scope, main’s scope never exits, and the defer never runs.

If by chance you’re coming from Go, neither defer nor panic work like they do in Go.

Go: defer runs when function exits, panic unwinds the stack and potentially recovers in a defer.

Zig: defer runs when the scope exits, panic calls the panic implementation, which by default prints the error and stack trace, then exits.

7 Likes

When panic occurs, will some resources in “defer” block be freed?

Panics won’t trigger defers because they don’t exit the scope, resources from the OS like files, memory, will be cleaned up (at least in all mainstream OS’). Other resources won’t be.

That is one of the use cases for a custom panic handler, to clean up things before terminating.

1 Like

But maybe panic is inevitable, I worry that resources cannot be freed in time…

You can make a custom panic handler, to run your own code when panics occur, so you can clean up your stuff when panicing.

that would look something like this:

//root of your exe

// needs a namespace of panic functions, for different inbuilt reasons, suche as overflows, out of bounds, etc.
pub const panic = std.debug.FullPanic(panicFn); // generates the required functions from a base function

fn panicFn(msg: []const u8, return_address: ?usize) noreturn {
    global_state_that_needs_to_be_cleaned_up.deinit(); // cleanup
    std.debug.defaultPanic(msg, return_address); // default panic function
}
var global_state_that_needs_to_be_cleaned_up = Foo.init();
3 Likes

I’ll study your case code later, thanks for your help!

Thanks for everyone’s replies!

Unless you are on an Amiga, the OS will clean up behind your process anyway (close open file handles and sockets, return allocated memory back to the OS etc…).

If your app requires some ‘graceful shutdown procedure’ before the process is killed, a panic handler might make sense, but if that has the same restrictions as a Linux SIGTERM handler then it’s better to not do any complex things in the panic handler but instead design your code so that a graceful shutdown procedure isn’t required.

3 Likes

errdefer wouldn’t work either.

4 Likes

Thanks for your reply! Maybe the powerful designing is so difficult or complex that I still worry about some inevitable panic which causes memory leakage…

Memory leakage isn’t an issue, the OS will clean it up for you. In fact, deallocating all your memory before you exit your program is a common cause for your program being slow to close.

Same story with files, network connections, anything you use through a handle from the OS. (there might be some exceptions, but if you are using such things, you probably know already)

If you have non OS resources that you need to clean up, that’s a use case for a panic handler. @floooh is correct in encouraging to design that problem away if possible

3 Likes

Got it with thanks!

Excuse me, I still want to confirm whether the OS will clean up the Database Connections when panic occurs…

I still worry that the Database Connections will occupy the resources on the database server…

The OS will clean up the connection to the DB, it’s the DB’s responsibility to clean up its end, best to close it properly if you don’t know its behaviour if you don’t.

The code action to close the Database Connection is usually in the defer block, but once panic occurs, the defer block doesn’t work any longer… So, I worry that the Database Connection cannot be closed properly!

The kernel will practically call close for you (or whatever it’s called on your OS) when exiting the process. So letting the kernel call close for you or doing it yourself right before terminating the process doesn’t really matter besides the fact that you have control of the order (which may matter) and the ability to add stuff.

In case of sockets the exact behaviour of calling close depends on the protocol.
In case of connection-oriented ones like TCP, this includes the Connection Termination packets (which is also a 3-way handshake). In this case the database should get an error on reading from its socket if it expected data. If the database can handle that or not depends on its implementation, but the same problem exists as if you would call it in an for the server unexpected place.
In case of connection-less ones like UDP, the kernel will likely not do anything in regards to the network. So the only chance for the server to notice is via a timeout (which the server hopefully implemented).

From this follows: If you must run the cleanup code or not depends on the protocol and software built on top of the sockets you get from the kernel (besides the kernel itself as some pointed out with Amiga).

2 Likes