I don't care too much about the specific names we end up with, but I've been thinking about possible future language integrations, where there might be some coercion or conversion available between types involving "primitive" references and explicit reference types. I think it would be easier to explain that there is interoperability between, for example, function types (inout A, borrowing B) -> inout C and (Inout<A>, Borrow<B>) -> Inout<C>, or between a reference binding declaration inout x = &y and a variable declaration of reference type let x = Inout(&y), if we maintain some connection between the reference forms and the reference type names.
Maybe, but any future language magic would need explicit explanation anyway.
And since an argument for having these types now is that they will always serve some standalone purpose regardless of how they might be sugared for bindings, I'm inclined to agree with @fclout that the point is better made when their names are in line with (Mutable)Span and Unsafe(Mutable)(Buffer)Pointer.
In that vein, I do think (Mutable)Ref would be just fine.
(And along those lines, if value is unloved and merely a placeholder for some later spelling of the dereferencing operation, between having magical conversions, introducing prefix *, or restoring the pitched [] syntax, I'd choose the last optionâagain by considering its relationship to Span.)
Overall, I think first-class references like this are necessary in the long run.
Personally, I think that the Borrow and Ref names are good options (short and to the point), but Inout is a strange case. Recently, it seems like new reference-related proposals are using Mutable instead (like MutableSpan and mutate accessors). IMO, it makes more sense to use Mutable⌠or Mutating... for this type. It might even make sense to allow mutating to be used for function args as well and fully deprecate inout to unify the naming.
As for this proposal as a whole, I do agree that this feels slightly rushed and half-baked (even if it is not, it looks that way to the community). I think that it would be better to pause these reference-related pitches like this and borrowing accessors until the authors can write up a vision document. I think the problem right now is that we arenât on the same page because these related proposals are being reviewed separately without reviewing how they should work together (or whether they are even both needed). Even if the end result is the same, I think it would be worthwhile to at least have a discussion about the bigger picture.
With parameter modifiers, inout arguments can be passed to borrowing parameters because exclusive access can be reborrowed as shared access.
With the new reference types, is there an equivalent relationship between Inout<T> and Borrow<T>?
In other words, can Inout<T> be implicitly converted to Borrow<T> so that APIs taking Borrow<T> can accept an Inout<T>?
Not as currently implemented or proposed. We could consider it. My hope, given the history of implicit conversions in the language, would be that we don't really need it, since the primary thing you do with either type is access the target value through it, and that target value reference can still be used in any manner aligned with its ownership; you can pass Inout.value as a borrowing parameter to another function.
Implicit conversions of function types between using reference bindings and these types, is from what I can recall, a newly suggested future direction for a future direction. I dislike the thought of giving the argument much weight when considering the current proposal, without it being mentioned in the proposal or another formal document.
I, of course, could have missed it if implicit conversions between modifiers and these types was in fact mentioned more than just in the forums.
And since an argument for having these types now is that they will always serve some standalone purpose regardless of how they might be sugared for bindings
I agree with Xiaodiâs point here, regarding these being more stand-alone and thus having less justification to tie them to the naming of accessors or bindingsâ keywords.
As for âdereferencingâ these types, I find both the empty subscript banana[].peel() and a member accessor banana.value.peel() to be clear. I have an evidence-free feeling that a subscript would be inconvenient in some ways, making it more appealing to support both.
Hi all,
The LSG has discussed this proposal and the review feedback thus far. Weâre going to extend the review another week to encourage more discussion, although with a somewhat narrower focus. There were several major points of discussion during the review thread: the relationship of these types to reference bindings (e.g., inout x = &y), implicit dereferencing as a future direction, and the naming of these types especially as it relates these other concepts (reference bindings, accessors).
The proposal argues that the Borrow and Inout types are necessary even if reference bindings (e.g., borrow x and inout x) also become part of the language, because the Borrow and Inout types can be used anywhere (such as in a generic argument) whereas reference bindings are fundamentally limited to places where a value is introduced. The LSG agrees with this conclusion: the ability to use the Borrow and Inout types as generic arguments (for example, to create values of type Optional<Borrow<T>>) is important for the language, and the presence of reference bindings in the language wonât eliminate the need for these types.
During the review thread, there has been a bit of discussion about the ordering of this proposal relative to the introduction of âborrowâ and âinoutâ reference bindings. Concerns included the idea that reference bindings are more fundamental to the language and are therefore the building blocks on which Borrow and Inout should be built, and also that the evolution should more closely follow the progressive disclosure curve itself. The LSG agrees that local borrow and inout bindings follow naturally from borrowing and inout parameters. However, inout and borrowing properties do not follow directly from there, because their lifetimes are tied in with the instance of the type, so this approach does not lead directly to the point where the Borrow and Inout types can be expressed without special treatment in the language. These reference bindings may end up being valuable to help smooth the progressive disclosure curve, but they arenât more fundamental. Introducing the Borrow and Inout types before (or alongside) the reference bindings can be an advantage for progressive disclosure: the use of Borrow and Inout follow the rules of non-escapable types, and the reference bindings can be specified to âdesugarâ to them, much like Optional syntactic sugar (e.g., T? being sugar for the Optional<T> generic type in the library, ?. desugaring to a form of map, and if let desugaring to a pattern match) desugars to more primitive ideas.
Also discussed was whether the referencing bindings need to be introduced alongside the Borrow and Inout types, building on the precedent of earlier type sugar (optionals, arrays, dictionaries) and more recently reinforced with the introduction of InlineArray and its type sugar. The LSG notes that the concepts of borrowing and inout are already deeply ingrained in the Swift language, with the pre-existing limitation that they only apply to parameters. Delaying their generalization to local reference bindings (or beyond) continues this limitation for longer, but doesnât undermine the introduction of Borrow or Inout.
The naming of the Borrow and Inout types has been particularly difficult because they are related to several other constructs from which the names could draw inspiration. As noted above, there are borrowing and inout parameters, and may in the future be similar local reference bindings. Participants in the discussion also noted the relationship to the borrow and mutate accessors from SE-0507, the unsafe pointer types, and the Span family of types. The LSG does not currently have a clear preference for the names of Borrow and Inout and would like further discussion there. However, the LSG explicitly does not want to consider names with either âaliasâ or âpointerâ in them: both names bring a lot of semantic baggage from the C world that the LSG would rather avoid.
Thank you,
Doug
There are a few existing constructs that refer to ownership: borrowing/inout/consuming parameters, borrowing/nonmutating/mutating/consuming methods, and borrow/mutate/get accessors.
Each of these, mostly, follows a broader convention regarding the part-of-speech. Parameter modifiers can be seen as adverbial, specifically adverbial present participles such as sending and borrowing. Method modifiers can be seen as adjectival, specifically adjective words such as final and adjectival present participles such as borrowing. Accessors are verbs, such as get, set, and borrow. Type names are nouns, and occasionally, generic types are adjectives such as Optional, so I think it makes sense for the names of the proposed types to also be nouns or adjectives.
In addition, each of these, mostly, is based on the consistent terminology of "borrow", "mutate", or "consume", with part-of-speech variation. The only exceptions are those that predate ownership: inout, get, and nonmutating. Ideally, these would be mutating, consume, and borrowing respectively. I think the "inout" terminology (and also "get" and "nonmutating") is inconsistent and should be considered legacy baggage, not something to be expanded into new features. I even think it'd be a good idea to allow mutating to be used in place of inout (like how borrowing can be used in place of nonmutating), even if removing inout entirely isn't feasible for compatibility reasons.
In the future, it could be useful to have "consuming references": references that have ownership of an existing value without moving it, like rvalue references in C++. Consuming references could be useful for handling values that are expensive to move, like large InlineArray values, or non-movable types if those are ever added to Swift. Perhaps it would be worthwhile to consider how consuming references would fit into the naming scheme.
That sounds reasonable, given the accepted direction in SE-0507 to use borrow and mutate as the keywords for accessors.
As others have expressed in this thread, the inout keyword on parameters is problematic in the language today. It can mean âcopy-in copy-outâ this value (for Copyable parameters) or âmutably borrowâ this value (for ~Copyable parameters). When reading a function signature, the copyability of a type is not readily apparent, so inout is a bit ambiguous. For SE-0519, tying the âmutable referenceâ meaning to Inout feels like building on muddy ground.
I actually discuss this further in my pitch, and would advocate for pushing inout to its original âcopy in copy outâ semantics over time, and favor a new keyword like mutating for parameters to mean âmutably borrowâ, regardless of the copyability of the argument.
More 2 cents for the naming focus:
To give a perspective in-favor of existing terminology: I think inout is actually a fine term. Sure I understand originally it was used for âcopy in copy outâ, but definitions evolve and now I think of it as a term of art in the Swift language for a mutable reference (even before this proposal).
From that, I think Borrow and Inout are good names.
Also perhaps in this direction the mutate accessor should be called inout, though if you think of Inout as a noun in this context, then it could be reasonable to say the action of a `mutate accessor` produces an Inout (noun for a mutable reference)?
inout is a long-standing term in the language and I donât see the âmutating/mutableâ terminology being enough of an improvement to drop this super prevalent term and go in a new direction. I think itâs completely reasonable to just explain âInout is Swiftâs term for a mutable referenceâ⌠and move on.
mutating and Mutable have their own concerning imprecisions IMO. They are both too âon the noseâ about the mutability part, and missing the detail about being a reference, which is just as important if not more important. You are not obliged to actually mutate a mutating parameter, and for Mutable<T>, it is confusing to thing about a var x: Mutable<T>⌠var already means mutable. From very literally looking at Mutable vs Inout in that example you might say âwell isnât Inout even less descriptive? What does it mean there?â. But I think thatâs actually a pro, not a con. Inout is a more abstract term that will make you take a moment to look into what it actually means, where youâd then quickly find out âok that means mutable referenceâ. Whereas `Mutable` could make it sound simpler than it actually is.
If length of the term were not a concern, I think MutableBorrow is probably the most âcompleteâ spelling, but for a type that is so core, it might be too long. So short of that I prefer the more âterm of artâ style that Inout has
inout can also mean âmove-in move-outâ, which makes it suited for ~Copyable.
inout is suited for both Copyable and ~Copyable, but it doesn't communicate the ownership where the keyword is used. That's the issue I'm highlighting.
Today there isn't a way to express mutable borrow / move-in move-out semantics for Copyable types. That matters for performance-oriented code.
The inout keyword has copy semantics baggage with Copyable that is in tension with the Inout<T> naming in SE-0519, so I think that is at least a reason to revisit the name.
At the end of the day, we care about the capability. The goal is to be able to express the semantics we seem to agree on: mutable borrow of the initialized storage location of a value, so we can read/write it, call mutating methods, and replace the value in place.
The proposal (and our discussion) is taking its naming inspiration from language features. These are library types, so we should be following the precedent from the library itself.
The proposed Borrow and Inout are a borrow and a mutable borrow, respectively. There is well-established precedent set with the Mutable prefix for such cases: UnsafePointer/UnsafeMutablePointer, Span/MutableSpan, and so on. An easy answer here is Borrow and MutableBorrow, which avoids the inout oddities discussed here and follows the library pattern.
I'm also not really a fan of Borrow as the fundamental name. Borrow is focusing on how this type operates, not on what it is or how it's used. It's very much emphasizing the lifetime aspect of the type, but lifetimes are a safety feature that (we hope) are there to eliminate errors but that you don't need to focus on all the time. For example, when working with Span, most of the time you don't think about lifetimes---you pass them down as arguments and operate on them. The borrowing aspect of Span only comes up when you make a mistake, and then you think about it. I expect these Borrow/MutableBorrow types to be similar.
I think what I want is for these to be Reference and MutableReference. Conceptually, they're a reference to a single value, like Span is a reference to a contiguous block of memory. The major wrinkle here is that Reference isn't backed by an actual pointer due to the optimization for small bitwise-borrowable values. That doesn't express itself in the programming model, and Swift quite deliberately does not expose the addresses of most properties. However, it could be a point of confusion.
Either way, I also think these types should tie into Span types and each other better. Just like you can get a Span from a MutableSpan (via the .span property), we should have a property that gets a Reference from a MutableReference. We should potentially also have a way to get a single-element Span and MutableSpan from Reference and MutableReference, because there will be lots of span-based APIs and its the straightforward way to create a span-of-one from a reference.
Doug
While I like the name Reference, it's worth pointing out that another Swift core library, RegexBuilder, already defines a type named Reference to represent references to capture groups.
I don't think this alone is a reason to exclude it from consideration, and I don't know if there would be much overlap between files that use RegexBuilder and files that are using the proposed type here, but it does raise the possibility that using either of those types could be more ergonomically difficult if they shared the same basename.
(IMO, the RegexBuilder type is the one squatting on too broad a name, but that ship has already sailed.)
I think that's a very good point, with (I think) a good solution to answer for it:
Swift rarely shortens words, but it does so for var, enum, struct, etc. where the meaning doesn't suffer and there's ample precedent. I think the point about other types being "references" in a generic sense bolsters the case that we should look to those examples and consider Ref and MutableRef.
The readability doesn't suffer for it, and it allows us not to step on the generic meaning of the word "reference" where it's already used.
I was going to propose Ref/MutableRef if no one else did.
I agree it would be nice for first-class references to follow a similar naming convention as Span/MutableSpan, especially because such a naming convention could be even more broadly applied to other "handle"-like types, such as FileDescriptor and MutableFileDescriptor. And perhaps "consuming references" could be prefixed with "Owned", analogous to a hypothetical OwnedFileDescriptor type.
Another way the term "reference" is already used is with the concept of "reference semantics" as a rough synonym for "shared mutable state", such as in ReferenceWritableKeyPath; which neither Borrow nor Inout would have.
Personally, I think "reference" potentially has similar baggage as "pointer", not only because of the connotation of shared mutable state, but also because it's the name of a memory-unsafe feature in C++. To me, "reference" isn't specific enough to refer specifically to the "shared xor mutable" semantics of Swift's ownership system.
I think the term "borrow" is growing on me. I don't think it inherently evokes the complication of lifetimes, because it could be as simple as "this function borrows its argument when it's running". I also think it's a pretty fundamental concept of the ownership model, and is inherent to the semantics of first-class references. A wrinkle, though, is that the borrow and borrowing keywords currently denote specifically shared/immutable borrowing. Maybe a way to rationalize this is that shared/immutable borrowing is simply the presumed default if no other ownership is specified, so the "mutating" keyword can be thought of as shorthand for "mutablyBorrowing"; hence why the type would be called Borrow instead of ImmutableBorrow.
Another wrinkle is that the term "borrow", and other terms such as "reference", are not used anywhere else in the syntax, even where it would arguably be appropriate, such as for parameters and property/subscript accessors. But if we were to try to take inspiration from those existing features, the only other term I can think of is "storage".
I share the preference for Ref. In particular, I like the direction of let, var. and ref as the binding equivalent. I think that while it may seem frivolous, I think it would be really valuable to have ref be 3 character long just like let and var.
It's reasonable to say "we have Optional not Opt" and that we might have Reference as the type, and ref as the binding. So Reference would be my next preference.
Borrow doesn't abbreviate (bor is clearly an abomination). And while we have .none vs nil (rather than .nil or none), I think calling the type Borrow but the binding ref would be odd.
An argument in favor of ref and Reference is that MutableReference could then be abbreviated to mut.
Well, borrow is used in accessors, both for yielding borrow (SE-0474) and just borrow (SE-0507), so Iâm not quite sure what you mean here.
Oops, that was a rather silly mistake. I think what I meant to say, clumsily, is that in the language syntax, these existing features don't seem to converge on a single term that can straightforwardly be used as the basis for consistently naming the first-class reference types. I guess for "borrow" specifically, that concern is at least in part addressed by the idea that "mutating" is short for "mutably borrowing". And outside the language syntax, the best term I can think of that's used to describe these different existing features seems to be "storage"; I guess some other possible terms are "place", "location", and "lvalue".