Syntactic Addition for Setting and Returning Values
In Swift, there are often times where one would like to return and set the same value. This is not good for a few significant reasons.
1. Firstly and most importantly, this situation can require additives that negatively impact performance:
-
The value to be read (an extra time unnecessarily).
extension Int {
mutating func square() -> Int {
self = self * self
return self
}
}
var num = 5
print(num.square()) //Prints 25
print(num) //Prints 25
-
The recalculation of a value (i.e. if the value it is being set to has a didSet block that changes the value). This can also require the creation of a separate variable or constant to store the value in the interim.
var n : Int = 5 {
didSet {
n = n + 1
}
}
//I want to return the actual square, not the value of x after it has been squared
func square(_ x: inout Int) -> Int {
let y = x * x
x = y
return y
}
print(square(&n)) //Prints 25
print(n) //Prints 26
2. This can also make for more redundant and less efficient code.
Proposed Solution
Returning and setting a value in the same line, while enhancing readability, can speed up performance and reduce the amount of redundant code.
Solution: return variable = n
This would return n and set variables value to n at the same time. This decreases redundancy while simultaneously improving efficiency as there is no need to create separate variables/constants to hold values or read additional values.
Also, this would not affect code that has already been written without this addition as it does not change any of Swift's syntax, it adds a faster alternative for these situations.
Improvements for Aforementioned Situations Utilizing the Proposed return variable = n
-
The value to be read (an extra time unnecessarily).
extension Int {
mutating func square() -> Int {
self = self * self
return self
}
}
Improvements with return variable = n:
extension Int {
mutating func square() -> Int {
return self = self * self //Proposed syntactical addition
}
}
var num = 5
print(num.square()) //Prints 25
print(num) //Prints 25
-
The recalculation of a value (i.e. if the value it is being set to has a didSet block that changes the value). This can also require the creation of a separate variable or constant to store the value in the interim.
var n : Int = 5 {
didSet {
n = n + 1
}
}
//I want to return the actual square, not the value of x after it has been squared
func square(_ x: inout Int) -> Int {
let y = x * x
x = y
return y
}
print(square(&n)) //Prints 25
print(n) //Prints 26
Improvements with return variable = n:
var n : Int = 5 {
didSet {
n = n + 1
}
}
//I want to return the actual square, not the value of x after it has been squared
func square(_ x: inout Int) -> Int {
return x = x * x //Proposed syntactical addition
}
print(square(&n)) //Prints 25
print(n) //Prints 26
Naming¹
Possible names for this syntactical addition:
- Mutating return statements
- Return setters
¹. If you have a good name, please write it in the comments and – assuming the name appropriately fits this syntactical proposal – I will include it in this list.
Alternative Syntax for return variable = n
variable = return n
- This can provide more clarity regarding what value is actually being returned.
Final Words
All comments, additions, suggestions, criticism, and improvement are welcome. Thank you for taking the time to read my proposal for the evolution of Swift.
allevato
(Tony Allevato)
2
The capability you're interested in could also be achieved by simply making assignment an expression, rather than a statement, such that the value of the assignment expression is the value that was assigned. This is how C and some other C-derived languages treat it, which is why you can say things in those languages like
int x, y, z;
x = y = z = 0;
Given that Swift is syntactically like C in many ways, sometimes I've wondered why the core team opted to leave this out—maybe they feel that it's along the same lines as ++, --, and C-style for loops which were later removed, as features that aren't truly necessary and don't strictly improve the legibility of code.
It doesn't look like there's been much discussion about this on the forums that I can find, but @jrose has some interesting insight in this post about how it's not immediately obvious what the correct thing is to do when properties with possible side effects to assignment are involved.
1 Like
This is a source breaking change since the magical assignment operator = returns Void already, in which case something like optionalValue.map { nonOptionalVariable = $0 } will start returning optional type of $0 while it should stay Void?.
In other cases you could already have written something like this:
func doImportantWork() -> Void { // I just want to highlight that the functions returns `Void`
[...]
guard hasNoError else {
[...]
return errorCode = errorXYZ
}
[...]
}
This will also break. Even though I do not recommend to write it like this if you're working with non-experts, it's still totally possible and if you know that = operator returns Void, then that code can make total sense in some situations.
You can imagine the assignment operator to be defined something like that (but not entirely):
func = <T>(lhs: inout T, rhs: T) -> Void
If we would add your rule it will return T instead and act like a weird overload.
1 Like
Check out the alternative syntax section I just added.
AlexanderM
(Alexander Momchilov)
5
For one, it prevents this common error: if this = that { ...
6 Likes
I read in TSPL that the reason they didn't make assignment an expression was to avoid inadvertent = vs == flaws (that have had historically bad consequences):
if userIsRoot = true {
...
}
simply won't work if assignment isn't an expression.
(edited per observation by @Avi.)
2 Likes
jrose
(Jordan Rose)
7
Yeah, I don't think we want to make assignment a non-Void-returning expression. It's rare that you actually need this anyway. Assign-and-return could be separated out from that, so I won't comment on the main proposal…except to say "what about x += y; return x or x += y; return y?"
(Okay, I lied: I'll also mention that there's no efficiency gain here. The compiler is smart enough even at -Onone to not do the extra copy if you use let, and probably even if you use var and never modify it. It's definitely smart enough to do all that with -O.)
8 Likes
I'm a bit new to proposals; could the Core Team make it specific to only mutable values associated with the keyword return?
Avi
9
That wouldn't work anyway, as if requires an expression of type Bool. But of course we also want to prevent if x = true { } 
I thought one of the main design decisions of Swift was that expressions are limited. If we wanted to start relaxing this, then I would probably go even farther than this, and say that almost everything is an expression.
func doesThing() -> Int {
if thing == otherThing { 2 } else { 5 }
}
But of course, this is totally impossible at this point. So I don't think this proposal is really solving anything actively harmful.
Avi
11
Or what about
let block = {
x = 5
}
What is the inferred return type? Should the compiler warn if you don't assign its result? Not a path we should head down.
2 Likes
If the assignment operator were effectively defined as @discardableResult func = <T> (lhs: inout T, rhs: T) -> T then we'd have to write a lot more semicolons, otherwise the resulting T would start changing inferred types everywhere.
It doesn't work even if assignment is an expression, because there's no implicit conversion to Bool anywhere. Having said that, if we ever get generic protocols, it would be nice in the future to have an ImplicitlyConvertibleTo<T> protocol. Because sometimes you actually do want conversion, you just don't want it everywhere.
Would variable = return n be a better alternative syntax for this proposal?
jeremyp
(Jeremy Pereira)
15
I don't understand this. Is it your contention that
self = foo
return self
requires an extra variable access? Because I think that the Swift compiler should be more than capable of optimising it away.
More importantly, I think the Swift compiler does optimize that away.
The lack of the functionality you’re proposing has been considered a feature and not a bug since Swift’s inception.
As others have said, there’s no efficiency to be gained here and the ambiguities that would arise make this a non-starter for me.
felix91gr
(Félix Fischer)
18
Related to that: the modulo operation with Mersenne Primes can be written in a very efficient way. Instead of writting %, you use a shift operation and something else. Anyway.
I was implementing this in C, and when compiling with Clang, using % in my code was faster than using the shift trick. Turns out, the compiler knew about this property and could optimize my code using it. I could still use % instead of numeric trickery and it would be very fast (actually, faster) anyway bc I was using a Mersenne Prime.
LLVM backs swiftc and clang. The smarts of both are in many places the same. Thank god for LLVM which lets us avoid making optimizations by hand.
4 Likes
Fun fact, Swift (semantically) uses call by value-result for inout parameters, meaning that your second example can actually be implemented like this:
var n : Int = 5 {
didSet {
n = n + 1
}
}
//I want to return the actual square, not the value of x after it has been squared
func square(_ x: inout Int) -> Int {
x = x * x
return x
}
print(square(&n)) //Prints 25
print(n) //Prints 26
Also, when talking about performance, I highly recommend testing performance using a -O build before saying anything, what the optimizer does and doesn't do is pretty random and you can easily be surprised both ways. It's generally incredibly good at optimizations which could also apply to C (like this one), Swift-specific ones can be hit or miss though that's improving.
6 Likes