Because decades of written material about Unix says fork() is really cool (even though it isn't)?
for example, here's the caveats section from the macOS fork man page:
There are limits to what you can do in the child process. To be totally safe you should restrict your-
self to only executing async-signal safe operations until such time as one of the exec functions is
called. All APIs, including global data symbols, in any framework or library should be assumed to be
unsafe after a fork() unless explicitly documented to be safe or async-signal safe. If you need to use
these frameworks in the child process, you must exec. In this situation it is reasonable to exec your-
self.
That spells defeat :)Earlier in the game, copy-on-write had to be created for the same reasons.
To be clear, exec does not necessarily close all but the first three fds -- by default all fds will be inherited. However, you can set the close-on-exec flag on each individual fd (in fact, that's what the Go stdlib does behind the scenes).
Search for FD_CLOEXEC in fcntl(2) and open(2) and you'll see what I'm referring to.