Compiler will out-of-memory with noreturn error union
ikskuh opened this issue · comments
When compiling the following program, the zig compiler will allocate a lot (> 24 GB) memory and will be killed by the OS. This is probably to some infinite loop.
extern fn someData() bool;
fn loop() !noreturn {
while(true) {
if(someData())
return error.GenericFailure;
}
}
export fn square() bool {
return loop() catch false;
}
Why?
The !
implies you may return an error, noreturn
implies the function doesn't ever return.
If the noreturn
is passed down to LLVM shit may hit the fan:
This function attribute indicates that the function never returns normally, hence through a return instruction. This produces undefined behavior at runtime if the function ever does dynamically return. Annotated functions may still raise an exception, i.a., nounwind is not implied.
I don't think we should try to fit the LLVM detail here, because the message conveyed by !noreturn
is exactly what you said:
The ! implies you may return an error, noreturn implies the function doesn't ever return.
The function will either never return or throw an error, if so. So the whole non-catching part could be marked as unreachable by the compiler and emit the "code unreachable" error message.
Use case is pretty much any program that is not designed to ever quit working (like firmware or such), but may throw an error that is not a panic and need a soft-recover the system (as opposed to panics which are by-definition not recoverable)
#3263 is also a duplicate of this issue.
The f() catch val
generates a pointer to noreturn
that sends the analysis phase in a endless loop. Fixing this is quite tricky as we go directly from AST to a set of IR instructions that "explode" the error union when we don't know yet the type of f()
nor of val
.
This is another example where a multi-pass analysis design would have helped: the catch
is desugared into a IrCatchInst
and once its operands are analyzed we can selectively unwrap the pieces we need.
I tried using !noreturn
today as I had a use case for it and it completely locked up my PC forcing me to restart it :(
I looked up to see if this was an existing issue and found this thread.
The usecase I had for it is I have an event loop function with sockets that's supposed to run forever unless it fails. If it returns an error, the calling function will re-initialize and then call the function again, i.e.
pub fn main() void {
while (true) {
const sock = connect(host);
defer os.close(sock);
eventLoop() catch |e| switch (e) {
error.Disconnected => continue, // ignore error and reconnect
};
}
}
fn eventLoop(fd: fd_t) !noreturn {
// ...
// if we detect disconntion
return error.Disconnected; // caller will connect again and restart the event loop
}
Would using error{Something}
as a return type work for this use case? (a function that can only return an error.) You won't be able to use try
or catch
when calling it though, since the function isn't technically returning an error union.
@dbandstra That seems to work to a point. The only issue I'm seeing is that it forces me to maintain the set of errors for each function that does this rather than being able to use inferred error sets. I've been finding that inferred error are a very nice feature.
Hi, just a thought: Maybe it isn't so much work to detect return type !noreturn and then stop the compiler with a message?
The final fix can be done later, but with the current compiler behaviour I had to turn the PC off, lost a lot of data and so on. Even worse the HDD could be messed up. Very dangerous.
using the signature !noreturn
should be an immediate compile error
@nektro: What main signature would you suggest for a program that never should end (e.g. a a small program on a mcu for a room temperature sensor or a web server)? MasterQ32 explained also why !noreturn makes sense: (#3461 (comment))
either !void
or bare noreturn
. !noreturn
is a contradiction
I use the former in my web server here https://github.com/nektro/aquila/blob/r21/src/main.zig#L27
!noreturn
has been accepted in #3257.
Another (longer) way I ran into with specific error types: https://gist.github.com/Techcable/88808f0ff219e22f6bfd02ce1dbae0d9