Difference between calling exit(0) and not?

I'm using Swift to build an executable (command-line tool).

What's the difference between appending the line exit(EXIT_SUCCESS) to my executable's main.swift file and omitting it? Either way the program seems to function the same (including exiting with an exit status of 0).

My question is similar to the question below but for Swift (instead of C).

I mean, if I leave out exit(EXIT_SUCCESS), does Swift call it for me? If so, where does that happen in Swift's source code?

For example, let's say I have the following code in my executable's main.swift file:

import Darwin.C.stdlib

private func main() -> Int32 {
  let arguments = CommandLine.arguments

  if arguments.count == 1 {
    print("No action specified.")
    return EXIT_FAILURE
  }
  if arguments.count > 2 {
    print("Can only perform one action at a time.")
    return EXIT_FAILURE
  }

  let action = arguments[1]
  switch action {
  case "formatTransactions":
    formatTransactions()
  case "calculateCapitalGains":
    calculateCapitalGains()
  default:
    print("Action not found.")
    return EXIT_FAILURE
  }

  return EXIT_SUCCESS
}

exit(main())

What's the difference if I replace the last line of my executable's main.swift file with:

private let exitStatus = main()
if exitStatus != EXIT_SUCCESS { exit(exitStatus) }

Bonus question: Why doesn't Swift let you return (like you can from the main function in C) or set (like you can with process.exitCode in Node.js) the exit status?

1 Like

Nothing.

The exit call is setting the status. If you don't call exit, the runtime performs the equivalent of exit(0) when tearing down the application. Can't say anything about Node.js, I try and stay away from Javascript. However, the C runtime takes the return value and performs the equivalent of exit(return_value). You can also call exit(value) anywhere in a C program, including the main function

The same subtle difference in your StackOverflow link applies to Swift as well. exit(_:) is the C function and only reachable by importing it from C. It directly terminates the application immediately, bypassing any deferred statements you might have queued up, as well as ARC destruction and thereby deinit calls. Mostly this won’t matter, but if you were relying on one of those to close a file or disconnect from a server, the other side of the transaction would be left hanging.

However, arranged the way you have it, there is nothing in the local scope except the exit code. So all the relevant hooks would already have fired when your custom main() returned. It is only if you had been doing actual work in the outermost scope of main.swift that any of this would apply.

1 Like

Thank you, @jonprescott.

Do you mean the Swift runtime? Could you point me to where in the source code that happens?

Thank you, @SDGGiesbrecht.

Defer Statements

I didn't realize calling exit(_:) prevents defer statements from being executed. How did you know that?

Anyway, I confirmed it by running this main.swift file:

import Darwin.C.stdlib

defer { print("deferred") }

exit(EXIT_SUCCESS)

Automatic Reference Counting (ARC)

Well, even when I don't append the line exit(EXIT_SUCCESS) to my main.swift file, ARC still doesn't seem to deallocate class instances referred to by global constants or global variables.

For example, "deallocated" doesn't get printed when I run this main.swift file:

class Foo {
  deinit {
    print("deallocated")
  }
}

let foo = Foo()

Yes, sorry. Global variables have a permanent lifetime. I was referring to local variables:

func run() {
  let x = SomeClass()
  exit(0) // which is “-> Never”
  // x’s lifetime ends here, but this line is never reached.
}
run()

It was just a logical deduction for the same reason as the deinit example above. (Although I went to the extra work of trying it before posting, because it also seemed conceivable that the compiler might have somehow special‐cased it.)

1 Like

I see. Nice. So I guess the "subtle difference" is a little different in Swift, since C++ calls destructors for global objects.

I found this related question:

Cool, thanks, @SDGGiesbrecht.

Not the Swift runtime. The lower-level C runtime and operating system calls to recover resources from the executable as it gets torn down.

The C++ runtime has startup and shutdown routines to handle the standard's requirements for initialization(constructors)/de-initialzation(destructors) of static and global objects so that they are ready to use when the program's execution begins with the call to main() made by the startup code, and resource recovery during program teardown. Not sure what the differences are between clang++/gcc++/VS++ to generate/handle the necessary calls to constructors and destructors before and after program execution begins/ends, but, the standard states that some mechanism is invoked. Also, the C++ standard requires that returning from main() and calling exit() produces the same results for C++11 and beyond means that calling exit() from anywhere in the program results in static object destructors being invoked.

OK, thank you, @jonprescott.