[swift-evolution] Compiler Optimization of Optional-returning Functions followed by '!'

Xiaodi Wu xiaodi.wu at gmail.com
Thu Jan 19 18:17:59 CST 2017

On Thu, Jan 19, 2017 at 4:35 PM, Joe Groff via swift-evolution <
swift-evolution at swift.org> wrote:

> > On Jan 19, 2017, at 2:17 PM, Jonathan Hull via swift-evolution <
> swift-evolution at swift.org> wrote:
> >
> > Hi all,
> >
> > I would like to propose an optimization to the compiler where, when a
> call to an optional-returning function is immediately followed by a ‘!’, we
> allow the compiler to generate an alternate version of the function where a
> trap replaces 'return nil’ and the function returns a non-optional result.
> This would save the build-up and tear-down cost of the optional.
> >
> > Is this possible?
> >
> >
> > The main reason I would want this is to be able to later propose moving
> our default array index handling to a safer optional-returning version.  I
> know that a safe variant is on the horizon, but defaults really matter…
> especially with newcomers to the language.  Array indexing should return an
> optional, forcing the programmer to deal with an out-of-bounds case
> appropriately. Where the current behavior is desired, the access is
> immediately followed by a ‘!’ indicating the potential crash-point
> explicitly in code.  Migration would simply add a ‘!’ after every array
> subscript access. The above proposal is meant to mitigate any performance
> issues from the change, because the generated function for the cases
> followed by ‘!’ would be identical to the current version.
> >
> > It should also have a good effect on runtime performance for any place
> in code where a function result is immediately force unwrapped...
> There are other reasons collection indexing doesn't return Optional
> besides performance; there have been a number of threads on this topic in
> the past already. I'd be surprised if building an optional were really all
> that expensive to begin with—the binary representation of '.some(x)' inside
> an Optional is always the same as 'x', with at most a zero byte tacked on
> the end. Checking an optional in turn only needs to check for an invalid
> representation of x (like a null pointer) or check that that extra bit is
> unset.

All other things being equal, it's hard to come out _against_ making code
run faster.

But as Joe already touched on already, it's certainly not the case that
indexing traps _only_ because of performance. This was touched on in the
extensive discussions on "lenient" indexing. As here, some were calling
that proposed feature "safe" indexing, and it was pointed out to them (by
Dave A, I think?) that this use of the term "safe" misunderstands the kind
of safety that Swift has prioritized. When you write `array[10]`, it's not
saying "I don't know how many elements there are, try and find the
eleventh"; it's saying "I _know_ there are at least eleven elements, so get
me that one."

When you _know_ that there are 11 elements, but in fact there are only 10,
then something has gone badly wrong (because `array.count` doesn't
lie). Trapping on a logic error is safe: an undefined state cannot do any
more harm. Continuing on a logic error is unsafe. As Swift chooses safety
by default, the default must be the current behavior.

For those times when you _don't_ know how many elements there are, don't
care, and for some reason can't be bothered to get `array.count`, but you
need to explicitly access an element by its index *and* have a useful
fallback value, IMO it's reasonable to have an alternative subscript like
the proposed `array[lenient: 10]`. But with facilities like `for...in`,
`map`, etc., and others like `count` and `enumerated`, it's hard to argue
that it's nearly as common a scenario as those where you are given a
known-good index.

Alternatively, consider the effect of your proposed use case on a practical
level. You have two arrays of known equal length and wish to swap every
other element (this itself may be uncommon, but having two arrays and a
task that involves both is, I'm sure you'll agree, very common):

for i in stride(from: 0, to: a.count, by: 2) {
  swap(&a[i], &b[i])

Even with no performance penalty, it would look much uglier with:

for i in stride(from: 0, to: a.count, by: 2) {
  swap(&a[i]!, &b[i]!)

// Actually, would this even be possible? At first blush,
// it seems like it might not, since &a[i] once unwrapped
// would have to be a _copy_ of the original, and a
// compiler optimization must not change the _semantics_
// of `!`, only its performance.

Littering `!` everywhere is not conducive to readable code.

> -Joe
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170119/160f1ead/attachment.html>

More information about the swift-evolution mailing list