Simpler & More Efficient Returning & Setting of Values

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:

  1. 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
    

  2. 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.

  • Less efficient code; poorer performance

    • Extra and unnecessary instances of values.
    • Can require a value to be read an extra time.
  • You have to use multiple lines of code to accomplish what – with this proposal's addition – can be done in only one line of 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

  1. 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
    

  2. 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

  1. 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.


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.

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

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?

That wouldn't work anyway, as if requires an expression of type Bool. But of course we also want to prevent if x = true { } :slight_smile:

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.

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?

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.

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