[swift-evolution] Proposal: remove "assert" and always use "precondition" instead.

Dave Abrahams dabrahams at apple.com
Mon Dec 14 16:39:12 CST 2015


> On Dec 8, 2015, at 3:52 PM, Amir Michail via swift-evolution <swift-evolution at swift.org> wrote:
> 
> Swift is a safe language after all. Moreover, it is all too easy to accidentally type “assert” when you mean “precondition”.

Here’s some rationale that may be missing.  The two functions have distinct roles:

- assert: checking your own code for internal errors.
- precondition: for checking that your clients have given you valid arguments.

It’s actually important to distinguish these two cases because the second one demands public documentation while the first does not.

For example: in Swift’s standard library we promise never to allow memory errors unless you call (Obj)C code or use an explicitly labeled “unsafe” construct.  On the occasions where we need to do a check on the client’s parameters to avoid causing a memory error when we’re given invalid arguments, we document the requirements on the arguments as a precondition, and use (our equivalent of) precondition() to check it.  We also have a bunch of internal sanity checks to make sure our assumptions about our own code, where the type system doesn’t already guarantee them, are correct.  For those, we use (our equivalent of) assert(), because we don’t want to slow down *your* code as an artifact of our development process (the use of sanity checks).

Here are a couple of concrete examples:

/// A collection whose elements are all identical `Element`s.
public struct Repeat<Element> : CollectionType {
  ...
  /// Access the element at `position`.
  ///
  /// - Requires: `position` is a valid position in `self` and
  ///   `position != endIndex`.
  public subscript(position: Int) -> Element {
    _precondition(position >= 0 && position < count, "Index out of range")
    return repeatedValue
  }
}

extension String.UTF8View {
  ...

  private func _encodeSomeContiguousUTF16AsUTF8(i: Int) -> (Int, UTF8Chunk) {
    _sanityCheck(elementWidth == 2)
    _sanityCheck(!_baseAddress._isNull)

    let storage = UnsafeBufferPointer(start: startUTF16, count: self.count)
    return _transcodeSomeUTF16AsUTF8(storage, i)
  }
}

In the first example, we have a precondition that the client doesn’t index past the end of the collection.  In this case we’re not strictly bound to check it because we’re not avoiding a memory error,* but we do check it as a service to our clients; it will help them find their bugs sooner.

In the second example, this is a private function that is meant to be called only on a code path where we’ve ensured elementWidth == 2 and _baseAddress is not null (_sanityCheck is the stdlib’s equivalent of assert).  That is in fact how it is being used, but we want to ensure that ongoingly.  For example, we don’t want someone to add code later that uses it incorrectly. Since we run our tests in both debug and release and we have decent test coverage, the assertions are very likely to fire if that should happen.

You might think from the above that assert() only gets used in private methods and precondition() only gets used in public methods, but that’s not the case; you could inline any private method into the body of the public method it implements, and doing sanity checks there would still make sense.  Precondition checks occasionally appear in private methods, too.  The simplest example is where duplicated precondition checking code is factored out of a bunch of public methods and into a single private helper. 

Hope this helps,
Dave

*  Note that some preconditions actually can’t be checked, so having a rule that all preconditions must be checked isn’t tenable.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151214/a1cc1b3b/attachment-0001.html>


More information about the swift-evolution mailing list