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.
+1. I like this better than both the proposal and the alternatives downthread. It addresses two concerns of mine:
inout as a muddled metaphor when we’re not talking about functions
borrow as an overloaded term when used as the term for both the action on a value (i.e. in an expression) and the behavior of a variable (i.e. in a declaration)
I also think that grid is the best, most concise casual explanation of these concepts I’ve seen. Even sort of mostly understanding these terms, I still find this helpful! It’s something I’d draw on a whiteboard any time I’m teaching these concepts.
I don’t think any terminology schema I’ve heard, including this one, makes the concept completely transparent to someone encountering it for the first time — but this alternative comes closest of the options I’ve seen to Swift’s ideal of progressive disclosure. And for the experts, I like how the terms roll off the tongue in context as I try sample sentences in my head, discussing things to a hypothetical colleague or student:
“A borrowing variable would improve performance here”
“Oops, var x = things[i] just means x is itself mutable; we need a mutating variable to modify the array itself”
“You can’t own a struct value that somebody else owns, except by making a copy. If you want to avoid the copy, you’ll need to borrow the value….” (Aside: Does this line of explanation lead to an incorrect conclusion? I think borrow will still induce copies in some situations…?)
While I'm mostly okay with mutating, this part in particular feels like the weakest link. I can already picture myself explaining that, yes, "mutable" and "mutating" might sound like the same thing but there's actually very important differences.
I don't feel like mutating conveys well enough by itself the ownership significance, which is IMO the more important and less intuitive thing for the keyword to convey. So I continue to lean towards something like borrow(var) or anything else that unites the items in the borrowed column a bit more explicitly.
You are suggesting Rust’s ampersand, which means something different than Swift’s ampersand. Swift’s ampersand means “this value could get mutated”. Rust’s means “this statement forms a reference to this binding”. We are not going to change the meaning of Swift’s ampersand, for source stability among other reasons.
The ampersand is not intrinsically related to pointers, and it is not a marker for unsafety. People associated them because they come up a lot together in C interop, and C’s ampersand is confusingly related as well. However, in Swift the ampersand is intended as a marker for possible mutations.
In Exclusivity and scope, it sounds like we're allowing borrow and inout bindings to the same variable at the same time. Should that be construed as a relaxation of the law of exclusivity? For instance, today, this is illegal:
struct Foo {
var x: Int
mutating func frob(action: () -> Int) -> Int {
return action()
}
}
var foo = Foo(x: 10)
let value = foo.frob { foo.x }
// ^^ Simultaneous accesses to 0x100c38000, but modification requires
// exclusive access.
Will this still be illegal?
In Not implicitly copyable, is copy a new standard library function? If not, how is A being copied: is return a allowed to return a copy?
borrow x = a // Borrow `a`
func copy(_ a: borrow A) -> A { ... }
let z = copy(x) // Legal way to explicitly copy x
In Switch and if-case binding, if you use switch &variable, do you have to use inout bindings for payloads or will it still allow non-inout payloads?
No. There is no intention here of relaxing the law of exclusivity. There is some nuance about when a new binding causes a previous binding to be inaccessible, but there should be no point in the program at which a read/write binding and some other access are both useable.
I'll review that section; it sounds like it may need some additional clarification.
There's a copy function defined on the previous line. I'm assuming that user-defined function knows how to construct a new A instance with the same information as the borrowed original.
A general-purpose copy function could be an interesting Future Direction, but I'm not proposing such a thing.
First, note that the & sigil here means that the variablemight be modified. That means it is required if there are any inout bindings, but does not preclude there being other kinds of bindings. In particular, this leaves open the possibility of mixing different kinds of bindings within a single switch statement.
However, as I explain in "Future Directions", I think mixed bindings could be confusing in practice, so for now I'm proposing that we limit such mixing until we have more experience with this construct.
I saw it, but I didn't feel copy would be trivial enough to be stubbed with no comment! If you get a struct from another module that has no public initializer, today, you can make the assumption that the library author did not want you to make your own values directly but they didn't mind if you copied existing values. As proposed, you need the library author's blessing to copy values that have been passed by borrow. This may well be approximately equivalent to needing the library author's blessing to pass by borrow at all. It feels like requiring explicit copy constructors in C++ to me.
I chatted with a few folks and think I see what got missed...
A copy operator was mentioned in the @noImplicitCopy pitch but not formally proposed there. So I think I'll add it to this proposal.
In essence, the copy operator can be used to explicitly copy a copyable type that is in a non-implicitly-copyable context:
let a = CopyableType()
borrow x = a
let b = x // Illegal. x is not implicitly copyable
let c = copy x // Legal
But this cannot be used to avoid restrictions on non-copyable types:
let u = NoncopyableType()
borrow y = u // Legal to borrow a noncopyable type
let v = y // Illegal; y is not implicitly copyable
let w = copy y // Illegal; y is not copyable at all
The operator form here is consistent with what we're discussing elsewhere for "borrow" and "consume" operators.
It could also be a good idea to clarify the relationship between inout, borrowed and noescape. As I understand it with the copy clarification, an inout/borrowed value can still escape. This is most likely intended.