I recently edited the mentioned issue to make some things clearer and leave this reference as an answer as it is somehow a separate topic and only insofar of value here as it is could avoid source breaks without warnings in seldom cases that this pitch would bring.
I understand how syntax becomes simpler by removing keywords/symbols. I don't see how it becomes simpler by optionally keeping or omitting them.
Just to see the forest as you said, Swift has simpler syntax than Objective-C (at least for many), because it removed ceremony, not because it made ceremony optional.
This optionality is already a given throughout the Swift language and of course a question of style, and many times a good one. E.g. I often write the type of a literal array despite type inference can infer the right type, just to avoid that I or someone else would add something of a wrong type later without getting an error exactly at this place. This is a question of style and I do not see why a linter or some other obligatory check should make a decision here. The creators of a programming language might not foresee how idioms might be used in a good way, many times it might be good to have options and then it might not be good to draw a too narrow line where the optionality of constructs should stop. Of course some might not want this line where this pitch is pushing it, and this a completely reasonable opinion.
There should be two polls - one for whether or not you personally like the feature, but also to ask what linting rules you would apply to your repos, if you (perhaps hypothetically) used a linter.
The reason I ask is that, if this change made it through, I can quite easily imagine myself using implicit returns from functions, despite being -1 on them, either out of laziness or my strange desire for terseness that doing embedded C programming gave me ~20 years ago that I've not been able to shake off. But when it comes to the standard for a code base, particularly one in a commercial or professional environment, the rules would 100% be 'no implicit returns from functions'.
Careful with polls.
If it was for “traditional” commercial environments, Swift would not have type inference. This was the reason why you always had to write all those types also in later versions of Java, because corporate environments wanted them (and distrusted their own developers).
I'm not talking about traditional environments, at least not in the old-school Enterprise Java sense (I also don't really advocate for a poll!) I just wanted to illustrate how 'raising the stakes' for me increased my dislike of the proposal, and wondered if it was a productive thought experiment for other people who are on the fence.
A feature that I'd fall into using in personal projects, but disallow in professional work, feels like a feature that belongs more in loosey-goosey languages like Typescript, not a language like Swift.
OK sorry I was a bit lazy to cite you instead of the source of the idea.
The problem is it is opt out. Not opt in. The return is implicit by default and therefore your code means something different than it meant before.
If this proposal goes through you WILL be using implicit returns whether you like it or not. Your code base will be filled with implicit returns. In most cases it won’t matter much at all because you’ll just be returning Void like you already were.
But in other cases, your code will change meaning. Your if and switch statements will suddenly become expressions. Many of them will be invalid because not all branches are the same type. So you will have manually fix that. Some rare cases will be valid but produce a different type.
You can opt IN to explicitly returning but that’s not helpful because that’s not what your code was doing before the proposal.
So now you just need to refactor.
This problem is pretty small. But multiplied across every single function and closure it is definitely not small.
I don't have this feeling. The previous Pitch was the exact opposite, it had no implicit return and it had the keyword then
in multi-line expressions which was not optional.
Remember that if
and switch
are only allowed to be expressions when assigned to a value or used as the value of a return statement. Any if
or switch
that is not an expression, could only become an expression under this proposal if the if
or switch
is both the last statement in a function and contains no return statements. This is very unlikely to be true in a function that doesn't return void
.
The only reasonable expectation of breakage lies in the use of @discardableResult
functions, which already cause problems in single expression closures. Those cases often already result in an unused variable warning, as demonstrated on godbolt.
As a suggestion, I don't think it's unreasonable that the compiler should infer that a final if
or switch
is the return expression instead of a statement only if the inferred type of said expression is compatible with the function result.
im about as polar an opposite as could exist to a “corporate” creature and i still use type annotations everywhere. they speed up builds and make it a lot easier to read code i (or someone else) wrote a long time ago.
There's an interesting thought: What if parentheses are mandated for the "last expression" rule? Only for multi-line of course.
// 🚫 Invalid - no return
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
}
}
// ✅ Valid - explicit return on the if expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
return if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
}
}
// ✅ Valid (but you obviously wouldn't do this) - parentheses around entire if expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
(if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
})
}
// ✅ Valid - parentheses around last expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
let v = if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
false
}
(v)
}
// ✅ Valid - parentheses required around last line in multiline if expression
func hasSupplementaryViewAfter() -> Bool {
let cellIndex = elements.firstIndex { $0.elementCategory == .cell }
return if let cellIndex {
elements[cellIndex...].contains { $0.elementCategory == .supplementaryView }
} else {
print("cell not found")
(false)
}
}
I'm not advocating for this necessarily, just throwing it out there.
I haven't been able to think of a legitimate use case for any keyword-fronted expression returning Void
. Do they exist? (It's supported for if
/else
/switch
but I bet nobody would notice if it wasn't.)
If they don't exist, there's no problem reusing an old keyword. I don't like continue
but it's available.
let x = do { // x is `Int` if `Void` is disallowed.
let xs = [1, 2, 3, 4]
for x in xs {
doSomething(with: x)
guard x > 2 else { continue }
print(x)
doSomethingElse(with: x)
if x == 5 {
continue x // This is confusing if the for loop or do statement is labeled as `x` but nobody cares because nobody would do that.
}
}
continue 55
}
To clarify, are you saying that if the "inferred type of said expression" is incompatible with the function result then it should be interpreted as a statement instead of an expression? Does that mean that the function would now return Void
? What if the function signature doesn't return Void
either?
Please don't second-guess people's motivations this way. Ben is pitching this and strongly arguing for it because he thinks it's a good idea, and you are arguing against it because you don't think it's a good idea. We do not need to imagine additional hidden interests.
You are right that this is not a democratic process. There is no way to make it a democratic process, even if we wanted to. The membership of these forums is far from representative, and that only gets worse as you drill down to the ever-narrower sets of people who post at all, participate in evolution, and actively engage in arguments in pitch threads. Even if we could magically solve all that, it would still be an internet poll, prone to brigading and all sorts of other manipulation. That is why we always talk about the evolution process as an opportunity to evaluate arguments and gather feedback rather than as anything like a vote.
goto fail says hello.
No, it means that the function returns whatever you declare it to return, and untyped closures will default to assuming that you are returning the last expression just like they currently do with single expression closures. The assumed to be a statement idea was only meant for functions and closures that have a declared return type to check against.
Although I just realized that if the inferred type of the last expression expression doesn't match the return type, then it wouldn't have compiles in the current version of Swift anyway, because it runs off the end without a return.
Things brings up a diagnostics quality-of-implementation question—
Currently, if you leave off an appropriate return statement in an early-exit branch, the diagnostic will highlight your error at the correct spot, while the rest of the code on the happy path will return as intended.
But recall as discussed above that, to avoid backtracking, if
expressions use only the first branch for type inference (unlike ternary a ? b : c
).
With this proposal, will there be scenarios (maybe only in closures?) where the first early-exit branch that "runs off the end" will be taken as correctly written, and the rest of the code will have unexpected errors or (worse) compile with unintended return types?
I'm thinking in particular of what would happen if later branches end returning the result of calling a function f<T>(..., _: T.Type = T.self) -> T
, and T
ends up inferred to be whatever arbitrary thing was left as the unintended last-statement-as-return-expression in the first branch.
That’s assuming the reader is correctly counting nested braces, and has already determined the scope of the block. I don’t think this is how everyone scans code.
Another criteria is the likelihood of introducing errors that are syntactically correct, which I believe is higher and significant since it’s only a one-character shift across braces. By hypothesis, these errors require testing to be discovered, so they are also expensive. That risk is not presented in one-liners.
I say this reluctantly because the proposal would be my personal preference, but I find it concerning for disparate teams and application developers.