The explanatory text in the gist talks about “value bindings” and “reference bindings” so perhaps a riff on that would add clarity:
value
reference
immutable
let
rlet
mutable
var
rvar
Because they’re odd keywords they would be eminently searchable on Stack Overflow and the forums, and the r reinforces that this is a reference binding.
That's certainly an interesting possibility. What would this look like for a switch statement?
I notice that in the for..in loop, you annotate the reference itself for &item in collection, which is different than in the other uses. It seems the consistent thing would then be to annotate the reference name elsewhere: var &x = y, if let &foo = optional_value, etc. That's certainly a big change from the existing use of & in the language, though.
@Andrew_Trick has been working on some ideas for returning borrowed values. I believe the current thinking is that no special syntax would be required on the caller side at all -- it's transparent to the caller apart from some compiler-enforced constraints that prevent you from escaping the value.
My hope is that new Swift users will mostly be able to ignore these concepts entirely. For most routine purposes, copyable values are appropriate and the existing let/var tools work very well. The ownership features come into play with certain performance-constrained application areas where memory management and copy behavior need to be more carefully controlled.
I had a possibly somewhat radical idea that I suspect won't be liked, but I want to propose it anyways.
Add var as a keyword for function parameters (meaning copy in the value and make it immediately mutable)
Make inout in function parameters an alias for borrow var
Use borrow let and borrow var as I proposed earlier for declarations of references
This would unify the keywords a bit and also allow for some additional functionality.
func exclaim(_ string: borrow var String) { // equivalent to `inout String`
string.append("!")
}
func exclaimed(_ string: var String) -> String { // passes in a copy of `string` that can be immediately mutated; no effect on caller's value
//`var string = string` // no longer necessary
exclaim(&string)
return string
}
let hello = "Hello"
let exclaimedHello = exclaimed(hello) // "Hello!"
func doSomething(with string: borrow String) {
//...
}
let a = 40
var b = 42
borrow let x = &a
borrow var y = &b
My concern has always been that “performance sensitive areas” include entire classes of applications, like game engines. And these kinds of programs will wind up having to spell these keywords everywhere. I do concede that future is not certain! But I haven’t had much time to play with prerelease toolchains to see whether my fears are confirmed.
I think alias could make sense for an immutable borrow. But mutating alias for a mutable borrow makes little sense to me because in that case the language is giving exclusive access, and thus prevents any aliasing from occurring.
The problem with using the term alias for a borrow is that we will still take immutable exclusivity. So for instance, one would not be able to take mutable exclusivity until the borrowed binding ends. Also, if we were to try to take an "alias" to a computed getter property, we would perform a get into memory and then use the address to that memory (this follows the manner today in which one passes a computed var to an inout parameter, except that the computed var is set at the end).
Another problem with using "alias" is that it's potentially interesting to define aliases in the abstract, i.e. without immediately initiating an access. This would be most useful in something like property context, e.g.:
struct Employee {
var underlying: Person
varalias name = underlying.name
}
This has a nice analogy to typealias, and it's generally both more compact and more optimizable than the usual alternative of writing out a getter and setter. But the act of defining this alias is just an abstract statement and doesn't require us to resolve underlying.name immediately, and presumably we would want that to also apply in local contexts.
(There are other ways you could choose to define this feature, e.g. with an aliasOf accessor that could be added to any arbitrary var or subscript. I do think "alias" is a nice term to reserve for it, though, even if it's not part of the primary declaration keyword.)
It was in func foo(inout x: T) form in Swift 1 (or 2?), then changed to the way it is now (any language historians to know why? Something about simplifying compiler IIRC).
That's "easy". Create a new (ideally renamed, but could be the same name) modifier in the other position, disallow having both at once (per declaration), then soft deprecate (with a fix-it suggestion) → then deprecate the elder inout over a couple of releases, potentially changing from a "per declaration" ruling to a "per file" or module – eventually most code will follow the new way (having Apple established swift linting tool built into Xcode out of the box with canonical presets would speed such things up). Make sure to keep calm and ignore the angry noise here or on twitter about the change during the period of transition. In a few years time the usage of the older way becomes insignificantly small to be important. Done. .
I'm just popping in to state my enthusiastic support for this idea! I'd start heavily using both variants as soon as they become available.
The inability to mutate enum payloads in place without absurd and/or unreliable contortions has been one reason I've come to avoid using enum types whenever possible. inout bindings would eliminate this pain, opening up refreshingly simple design choices.
Borrowing variables would make a similarly large quality-of-life benefit. For example, another source of painful contortions for me is the need to avoid copying generic Element types into temporary local variables while I'm working on data structure implementations. It seems borrow variables would be a great way to fix that.
I have no strong opinions about the proposed syntax. (Please don't make it too weird.)
Why are immutable borrows exclusive? We want to be able to borrow a member of a borrowed value, don’t we? What’s the difference between borrowing myAlias.foo and myAlias.self? Or myAlias.resilientPropertysThatHappensToReturnSelf?
“Exclusivity” in Swift refers to the general rule; I think Michael is using “immutable exclusivity” to mean the rule for non-mutating accesses under that, which does allow multiple simultaneous accesses.
To me it looks like Michael's comment about exclusivity assumed the "substitute name" meaning of alias as self evident and jumped right ahead into explaining how a borrow is different from a substitute name. I'd take this as a hint that alias could be a confusing name for the feature.
I'm going to mostly stay clear of the bikeshedding this time, but I'll note that both mutating and inout exist today and refer to copyable move-in/move-out values. Having them diverge would be a bit confusing for existing mutating methods.
Re: placement of inout today: as fclout neatly summarized, it's part of function types, but not arbitrary other types, and doesn't stand on its own as a type modifier. We have at least one other modifier that works like this: @escaping. Unfortunately, this doesn't suggest any perfect syntax to me that both maintains the analogy and also visually binds appropriately.
I am glad to see that these bindings formally extend to end-of-scope, and wish to explicitly support that decision.
I don't understand why global variables are excluded. Formally, stored global variables are no different from any other computed property at the use site, except for the under-the-table guarantee that if they are in the current module and do not have observing accessors, they will have a consistent address in inout-to-pointer conversions.
I agree that there doesn’t seem to be a good reason to exclude global variables from being accessed this way. There aren’t any conceptual or implementation problems this protects against which aren’t already present because of, say, instance properties of classes.
Right, and this question was settled in SE-0031 back in 2016, which the calendar tells me was somehow seven years ago.
var parameters were removed from the language in SE-0003; explicit let parameters, which probably no one remembers, were also removed from the language in SE-0053.
Tying back to the start of the thread where we discussed having mutable bindings inferred from &value, I’d like to see something like borrow used on the side of the value:
let x = 5
let y /*: borrow Int*/ = borrow x
var m = 5
var n /*: inout Int*/ = &m
I think inferring ownership from either the type or the value of the binding fits Swift’s design better. Statements like for and switch don’t have a keyword like let because it’s verbose, and ownership can be clear from context. Additionally, function types only expose their types (without labels unfortunately), so it would make sense to naturally express a (borrow Int) -> Int. I’m not necessarily advocating for the above syntax, but I do think we should end up with something that’s part of the type or the declaration’s right hand side.
(Off topic)
Alas!... I wish this to be reverted. This shouldn't have been accepted in the first place (my personal opinion); as it eliminates implicit returns and also complex for beginners to understand in terms of variable redeclaration.
If we look at SE-31, we have the following arguments:
inout on the name side is less desirable because it creates inconsistency with function types that have inout parameters
inout on the name side is notationally similar to labeled arguments
inout on the name side makes it impossible to use inout as a variable name
inout on the type side matches similar patterns in other languages, like Rust borrows, that we could want to add to Swift some day
I (possibly incorrectly) view the bar to revisit a "settled" proposal as simply that we learned new things that are relevant to it, and in this case we learned (7 years later) that once the rubber hits the road, one of the motivations for it is actually backwards!
IMO, there's one new good reason to move inout back to the variable side (it results in a more consistent syntax for mutable borrows, the opposite of what we thought before) and the main reasons to keep it where it is are that the function type syntax would be less consistent and it would be a source-breaking change. It's reasonable to end up deciding that inout as a variable introducer is not a good idea. If that's the case, naming the reasons that we're not going to do it will help move on.
This is also possibly a cautionary tale about using far-out features as the justification for anything, although I believe we got a lot better with this over the years.