Pitch: `borrow` and `inout` declaration keywords

I think it’s pretty important that the pieces feel like they mesh and layer well from the outset. Swift’s adoption level is too high nowadays for big spelling changes to be tolerated, even in major language revisions. Trying to retroactively change anything about sendability produces serious frustration.

By the way, the chart you drew up reminds me very much of my own chart from back in December.

Hah, fair. :smiley:

Probably! Rust is only a very casual hobbyist interest for me at this point. :sweat_smile:

1 Like

Oh I'm definitely not proposing retroactive changes, at least not to anything that's been released in i.e. Swift 5.7, only that perhaps we can make revisions to proposals that are accepted but have not yet made it into a major Swift release.

Ahh, that is quite similar! Definitely thinking along the same lines, it seems.

1 Like

We're definitely open to changing accepted proposals that aren't yet part of an official Swift release if there's additional information that we should consider. However, we did have an extensive review of those proposals, and we considered a lot of alternatives, and we do need to make progress eventually. So there is a certain burden that has to be met before we're going to revisit these things.

I am well aware of how the term "borrow" is used in Rust, but I don't think we have to consider Swift completely beholden to that precedent, especially because borrow is not even a keyword in Rust. And I think having to use compound keywords everywhere we want to express references to storage just to maintain that precedent is a pretty high price. Assigning borrow to mean what Rust calls an immutable/shared borrow, especially when we have an existing term in the language for what Rust calls a mutable/exclusive borrow, seems like an acceptable decision that still pays respect to what Rust has done.

9 Likes

I can certainly respect that; mostly I just wanted to clarify what the policy was.

To be clear, I brought up Rust only because it was the closest existing precedent I could think of that generally fits my mental model of what's being proposed, not because I think Swift should model its syntax entirely off of it.

I personally find the compound keyword version preferable not because of prior precedent, but because I personally think it conveys what's happening more simply & clearly, and I find the addition of both inout and borrow to be just slightly more mentally taxing.

This is all just my opinion of course, and honestly it's a pretty minor gripe. Overall I'm excited about the direction, which is why I've participated in this probably more than any other proposal so far. :sweat_smile:

2 Likes

Right, I feel like there’s a couple ways to look at this. I like the re-use of borrow for the mutable/immutable cases because it conceptually links the ownership relation as the ‘thing’ that the two different versions share. This parallel didn’t exist for the borrow/consume parameter modifiers because the ownership relation for those two keywords were different.

I take the point that we already have a keyword for mutable borrow, but I don’t consider myself overly pleased with the inout terminology. It makes some amount of sense as a parameter modifier where there’s already a strong metaphor of passing values in and getting values out of a function, but I think it strains the metaphor somewhat when extended to local ‘inout’ variables. I’m not sure this outweighs the added complexity of an entirely new specifier when inout is ‘right there,’ but I think it’s worth considering.

One way to avoid the compound keywords issue everywhere while also maintaining some of the ‘rule’ about borrows being immutable would be to invert the parameterization so that you would say borrow(var) x: Int = &…, and the rule would then be borrow is mutable immutable unless explicitly specified otherwise. This would maybe also suggest allowing borrow(var) as a synonym for inout in parameter position. But this is likely also coming from a Rust-inclined thought process. :sweat_smile:

6 Likes

How about:

borrow x => let & x
inout x  => var & x
consume x => var && x
2 Likes

i like the idea of borrow(var), but i think immutability should be the default, because i think borrow(let) is going to be a lot more common than borrow(var).

Sorry, yes, that’s a typo. The rule would be immutable unless explicitly specified otherwise.

I'm curious what folks think about these as a possibility:

owned borrowed
immutable let borrowing
mutable var mutating

I like how these naturally fit into a discussion of "borrowing references" or "mutating references".

4 Likes

Now that we’re talking about borrows/aliases independently of implicit copies, I’m not sure whether the lack of brevity will affect me in practice. I do like mutating as a keyword—the existing meaning is naturally retconned as “shorthand for mutating self”.

IMO, borrow var is a little ambiguous. It could be read as implying that you may reassign the reference itself to point somewhere else.

That said, I definitely think we're stretching inout to a place where it no longer makes sense, and I would welcome a new name for mutable borrows. For instance, I'd like to see functions gain the ability to return mutable borrows one day:

var foo: Resource {
  get { ... }
  modify {
    var view = Resource(storage: consume(self).storage)
    defer { self.storage = view.storage }
    yield &view
  }
}

// Should also be possible with methods. Strawman:

mutating func foo(param: SomeParameter) -> inout Resource {
  var view = Resource(storage: consume(self).storage, param: param)
  defer { self.storage = view.storage }
  yield &view
}

Except inout doesn't really make any sense here; we're not passing a value in and getting it back out, but rather getting a variable out when this coroutine yields and passing it back in when it resumes to run its cleanup - more outin than inout, if you will.

Then again, maybe this kind of function (a yield-once coroutine) would just be spelled differently to make this clear in other ways. I could imagine replacing -> with a double-ended arrow, for example. Something like:

mutating func foo(param: SomeParameter) <-> var Resource {
  ...
}
3 Likes

Yes! This is why making 'inout' no-implicity-copy will break source by implicitly borrowing arguments. The violation is likely to show up when passing a value as an argument and passing a closure that captures the same value. Maybe that breakage is worth it under a Swift 6 mode.

do { // Illegal: simultaneous borrow and capture of an inout binding
  inout a = &t.a
  takeBorrowedArgWithClosure([borrow] a) { _ = a } // 🛑 simultaneous access
}

Note that there's no actual modification, but we capture 'inout' variables as mutable values regardless of whether they're mutated in the closure.

Personally, I find the meaning of inout clearer than &var. The value goes in to the local variable, and at the end of scope goes out back to its original position. Same as a function call. It sounds fine to me.


I'm less sure I like borrow in this position. For one thing, the word feels a bit too long to me for a variable declaration. For another, this:

borrow book = swiftAdventuresBook
borrow sameBook = swiftAdventuresBook
// Two people are borrowing the same book simultaneously (what?)

Basically, what I mean is that saying you're borrowing something sounds a bit like exclusive access.


If you want to convey that many people can get their hand on the same book at the same time, wouldn't this work better?

hold book = swiftAdventuresBook
hold sameBook = swiftAdventuresBook
// Two people are holding the same book simultaneously

You can also interpret the value as being "on hold" here, as an explanation of it not being modifiable. Or that you're "holding it in place". Or explain that you can't turn the pages when many people are holding the same book.


I also have to say this: people in this thread talking about inout being a mutable borrow vs. borrow being an immutable borrow is making me dislike the word "borrow" for an immutable borrow. It doesn't feel like borrow should sit side by side with inout. This table sounds better to me:

owned borrowed
immutable let hold
mutable var inout

I get that "borrow" is a term of art, but if the meaning of this term of art means both mutable and immutable, then we're not using it correctly by filling a single cell in that table with it.

5 Likes

borrow makes more sense in the context where it was originally conceived: a function call “borrows” its argument. Still, a caller can provide the same value for two arguments, so that doesn’t alleviate the potential surprise about multiple simultaneous read only borrowings, but that’s part of the model that people will need to learn.

I’ve used the term “alias” a few times in this thread. How about that?

owned borrowed
immutable let alias
mutable var mutating alias

Does this discussion impact the syntax for terminating the lifetime of a borrow/alias? The most recent revision of SE-0366 spells this as _ = consume myAlias. If borrowing no longer implies a jump to move semantics, is consume really the ideal keyword? What about the more concise _ = &myAlias?

4 Likes

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. :thinking:

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

1 Like

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

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.

5 Likes

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.

2 Likes