125

What's the difference between precondition(condition: Bool, message: String) and assert(condition: Bool, message: String) in Swift?

Both of them look same to me. In which context should we use one over the other?

overactor
  • 1,759
  • 2
  • 15
  • 23
Chao Ruan
  • 1,409
  • 2
  • 14
  • 13

6 Answers6

134

assert is for sanity checks during testing, whereas precondition is for guarding against things that, if they happen, would mean your program just could not reasonably proceed.

So for example, you might put an assert on some calculation having sensible results (within some bounds, say), to quickly find if you have a bug. But you wouldn’t want to ship with that, since the out-of-bound result might be valid, and not critical so shouldn’t crash your app (suppose you were just using it to display progress in a progress bar).

On the other hand, checking that a subscript on an array is valid when fetching an element is a precondition. There is no reasonable next action for the array object to take when asked for an invalid subscript, since it must return a non-optional value.

Full text from the docs (try option-clicking assert and precondition in Xcode):

Precondition

Check a necessary condition for making forward progress.

Use this function to detect conditions that must prevent the program from proceeding even in shipping code.

  • In playgrounds and -Onone builds (the default for Xcode's Debug configuration): if condition evaluates to false, stop program execution in a debuggable state after printing message.

  • In -O builds (the default for Xcode's Release configuration): if condition evaluates to false, stop program execution.

  • In -Ounchecked builds, condition is not evaluated, but the optimizer may assume that it would evaluate to true. Failure to satisfy that assumption in -Ounchecked builds is a serious programming error.

Assert

Traditional C-style assert with an optional message.

Use this function for internal sanity checks that are active during testing but do not impact performance of shipping code. To check for invalid usage in Release builds; see precondition.

  • In playgrounds and -Onone builds (the default for Xcode's Debug configuration): if condition evaluates to false, stop program execution in a debuggable state after printing message.

  • In -O builds (the default for Xcode's Release configuration), condition is not evaluated, and there are no effects.

  • In -Ounchecked builds, condition is not evaluated, but the optimizer may assume that it would evaluate to true. Failure to satisfy that assumption in -Ounchecked builds is a serious programming error.

Airspeed Velocity
  • 40,491
  • 8
  • 113
  • 118
  • 3
    "But you wouldn’t want to ship with that, since the out-of-bound result *might* be valid, and not critical so shouldn’t crash your app" that's very vague to me. Kindly can you include an exact example? Perhaps some code. – mfaani Feb 24 '17 at 11:53
  • 2
    Responding to your question, I personally use asserts to catch things that just shouldn't happen in my build while I am writing and testing it. Imagine a guard statement reading JSON where `data["name"]` does not exist, but it should. Having an assert inside of the guard..else{} would help me catch my error by crashing and bringing me to the problem. Similarily, if this code was in production, the assert wouldn't crash the program, and whatever backup code that I used (`return nil`) would take over. – Alec O Sep 11 '17 at 16:19
  • 2
    Shouldn't you check the index and not do anything instead of crashing the whole app? – Iulian Onofrei Nov 21 '18 at 13:00
  • Yes, you should check the index, but everyone slips up sometimes, and using asserts helps you to realize you should have checked the index when you forgot. – Victor Engel Jul 25 '19 at 15:03
  • "But you wouldn’t want to ship with that, since the out-of-bound result might be valid, and not critical so shouldn’t crash your app". You can ship your app with as many assertions as you want. Swift just won't evaluate your conditions inside assertion block in release app – Akshansh Thakur Aug 19 '20 at 20:00
  • I think it would be better to use unit tests to validate the result of the computation instead of asserting at runtime. If the invalid result handled gracefully then you should not use assert. Both assert and precondition are for for terminating execution early when you can not *meaningfully* recover from an invalid state. The main difference is if the code your write is going to be used internally or by others. If you ship the a release of library to be used by an other team then you have to use preconditions. – Christos Koninis Apr 22 '22 at 09:17
108

I found Swift asserts - the missing manual to be helpful

                        debug   release   release
function                -Onone  -O       -Ounchecked
assert()                YES     NO        NO
assertionFailure()      YES     NO        NO**
precondition()          YES     YES       NO
preconditionFailure()   YES     YES       YES**
fatalError()*           YES     YES       YES

And from Interesting discussions on Swift Evolution

– assert: checking your own code for internal errors

– precondition: for checking that your clients have given you valid arguments.

Also, you need to be careful on what to use, see assertionFailure and Optimization Level

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
onmyway133
  • 45,645
  • 31
  • 257
  • 263
  • 1
    Can you elaborate on the difference of own code and client? As for client do you mean like inserting numbers where a String is expected? Shouldn't that be treated with some simple error handling? – mfaani Feb 24 '17 at 11:56
  • @Honey I think he means about the arguments / results from network API calling, or client's own plugins. – Chen Li Yong Jun 16 '17 at 11:03
  • 1
    Client would be someone using your code, say you're writing a library and a programmer passes invalid data. You wouldn't want to gracefully continue since that could be considered a serious programming error. You should probably never crash on invalid networking API data since that is very unhelpful for the user. – bompf Jul 16 '18 at 18:00
  • @onmyway133: From the Xcode QuickHelp, I think `precondition()` and `preconditionFailure()` are *having the same behaviors*. The different between these functions is: `precondition` need a condition inside, while `preconditionFailure` just throw out. – nahung89 Jan 18 '19 at 05:07
20

The precondition is active in release mode so you when you ship your app and the precondition failed the app will terminate. Assert works just in debug mode as default.

I found this great explanation when to use it on NSHipster:

Assertions are a concept borrowed from classical logic. In logic, assertions are statements about propositions within a proof. In programming, assertions denote assumptions the programmer has made about the application at the place where they are declared.

When used in the capacity of preconditions and postconditions, which describe expectations about the state of the code at the beginning and end of execution of a method or function, assertions form a contract. Assertions can also be used to enforce conditions at run-time, in order to prevent execution when certain preconditions fail.

Greg
  • 25,317
  • 6
  • 53
  • 62
6

precondition

func precondition(condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = default, file: StaticString = default, line: UWord = default)

Check a necessary condition for making forward progress.

  1. Use this function to detect conditions that must prevent the program from proceeding even in shipping code.
  2. In playgrounds and -Onone builds (the default for Xcode's Debug configuration): if condition evaluates to false, stop program execution in a debuggable state after printing message.
  3. In -O builds (the default for Xcode's Release configuration): if condition evaluates to false, stop program execution.
  4. In -Ounchecked builds, condition is not evaluated, but the optimizer may assume that it would evaluate to true. Failure to satisfy that assumption in -Ounchecked builds is a serious programming error.

assert

func assert(condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String = default, file: StaticString = default, line: UWord = default)

Traditional C-style assert with an optional message.

  1. Use this function for internal sanity checks that are active during testing but do not impact performance of shipping code. To check for invalid usage in Release builds; see precondition.

  2. In playgrounds and -Onone builds (the default for Xcode's Debug configuration): if condition evaluates to false, stop program execution in a debuggable state after printing message.

  3. In -O builds (the default for Xcode's Release configuration), condition is not evaluated, and there are no effects
  4. In -Ounchecked builds, condition is not evaluated, but the optimizer may assume that it would evaluate to true. Failure to satisfy that assumption in -Ounchecked builds is a serious programming erro
Keshav
  • 1,123
  • 1
  • 18
  • 36
1

Just wanted to add my 2 cents. You can add as many assertions in your code as you want. You can Ship your code with these assertions. Swift does NOT evaluate these code blocks for production apps. These are only evaluated in case of debug mode.

Adding documentation link

Also attaching image from swift.org

enter image description here

Also note, this is not the case with preconditions. Code shipped with preconditions will crash and app will terminate if preconditions are not evaluated to be true.

So in short, assertions are for debugging, but can be shipped without impacting production. Assertions will be evaluated in debug mode but not in production.

And

PreConditions are for making sure unexpected things do not happen on production environment. Those conditions are evaluated and will terminate your app in case they're evaluated to be false

Akshansh Thakur
  • 5,163
  • 2
  • 21
  • 38
0

iOS assert vs precondition vs fatal

General rule:

1. -O(-O, -Osize) - compile out(do not execute) `assert` and `assertionFailure`
2. -Ounchecked(as `SWIFT_OPTIMIZATION_LEVEL = -Ounchecked` or `SWIFT_DISABLE_SAFETY_CHECKS` = true) 
 2.1 set `condition: true` into `assert(condition: true)` and `precondition(condition: true)` that is why app is not terminated
 2.2 optimizer is free to assume that `assertionFailure` and `preconditionFailure` code are never called

Error looks like

Experiments2/ViewController.swift:20: Assertion failed: Some error
2022-12-11 15:17:28.772447+0200 Experiments2[95061:10414104] Experiments2/ViewController.swift:20: Assertion failed: Some error
  1. assert(condition: Bool, message: String)
  2. assertionFailure(message: String)
  3. precondition(condition: Bool, message: String)
  4. preconditionFailure(message: String) -> Never
  5. fatalError(message: String) -> Never

Never create build time warning

Will never be executed

[Optimization Level(SWIFT_OPTIMIZATION_LEVEL)]

|                                   | -Onone    |                    -O                     |                 -O -Ounchecked                    |                   -Ounchecked                     |
|--------------------------------   |:------:   |:----------------------------------------: |:----------------------------------------------:   |:----------------------------------------------:   |
| assert(condition: false)          |  TRUE     |       FALSE -O (compile out assert)       |          FALSE -O (compile out assert)            |      FALSE -Ounchecked (condition == true)        |
| assertionFailure                  |  TRUE     | FALSE -O (compiled out assertionFailure)  |    FALSE  -O (compile out assertionFailure)       | TRUE or FALSE -Ounchecked(may never be called)    |
| precondition(condition: false)    |  TRUE     |                   TRUE                    |      FALSE -Ounchecked (condition == true)        |      FALSE -Ounchecked (condition == true)        |
| preconditionFailure               |  TRUE     |                   TRUE                    | TRUE or FALSE -Ounchecked(may never be called)    | TRUE or FALSE -Ounchecked(may never be called)    |
| fatalError                        |  TRUE     |                   TRUE                    |                      TRUE                         |                      TRUE                         |
TRUE - app is terminated
FALSE - app is not terminated
yoAlex5
  • 29,217
  • 8
  • 193
  • 205