SE-0519: Borrow and Inout types for safe, first-class references

Thanks to the author for proposing this.

I know it is beyond the scope of proposal reviews to argue about back-deployment support, and I haven’t seen anyone else bring this up in the thread, but I did get worried when I saw this:

Generic support for Borrow requires new runtime type layout functionality, which may limit the availability of these types when targeting older Swift runtimes.

I have already found several places in my codebase (targeting iOS 13.0) that can be optimized using Borrow and Inout. It would be really helpful if these types were available as soon as possible, rather than years later.

Furthermore, if the Borrow and Inout types are meant to serve as building blocks for future language features like ref bindings, the adoption of those features would be significantly limited without Borrow and Inout being back-deployed upfront.

It would be great if a solution could be found.

Hi all,

The LSG is accepting this proposal in principle, but is inclined to change the proposed type names based on discussion here. We want to leave a little more time for feedback about the naming before finalizing it.

The LSG intends to change the names of these types to Ref and MutableRef, respectively. There are a number of factors that went into this change:

  • Ref better matches the way we are talking about these as "references", and we found that trying to explain them as "borrows" (which is odd as a known) or "borrow types" (which is odd in our value-centric lifetime system) was awkward.
  • LSG feels that the conceptual overlap with "reference types" (like classes) is generally a helpful association. Reference types are referenced, not copied. You can create a Ref<T> to store a (conceptual) reference to a T. The fact that there's an optimization where we copy small values rather than store them indirectly doesn't invalidate that the type operates like a reference; it's just an implementation detail.
  • These follow the MutableXYZ pattern that is well-established in the standard library. That pattern does not work as well with other alternatives discussed (like Borrow, where "mutable borrow" is a phrase we've been trying to avoid).
  • We could spell out Ref as Reference instead, but this is much longer (especially for MutableReference), and fundamental types in the standard library do often have shorthand names (Int, Bool, etc.).

We'd love to hear your thoughts on this direction.

Doug, Review Manager

27 Likes

(Mutable)Ref is semantically null. It provides no useful information to anyone not already intimately familiar with what it actually means (reference), what reference means in this context (a borrow), and what that means to Swift. You may as well call it R<T> and MR<T>. This seems like a name that will simply lend weight to the argument that Swift is just C++ with Apple paint. Plus, don't we already have Ref as a common suffix from C++ and CoreFoundation, but which means something different? Is there no other word that actually has some inherent meaning that is even close to this usage?

3 Likes

The abbreviation "ref" appears as either a keyword or as a type or function in the standard library in C#, F#, Ruby, Rust, OCaml, Perl, Python, and yes C++, so it feels like accusations this is "a C++ thing" are retrofitted to a predisposition to believe that. There's no point pandering to that prejudice at the expense of using what is a succinct and common term of art.

"Borrow" on the other hand is a pretty enigmatic notion specific to a smaller set of languages. And "a borrow" with this meaning is not really a word (the noun for something borrowed is a loan). While the fact that this is a borrowed reference is important to fully understand how it works, the "reference" part of the term is the more useful. And as soon as you start wanting to add more words (BorrowedReference? MutableBorrowedReference?) things have clearly got out of hand.

And yes, to fully understand the feature when encountering it for the first time, you have to do more than just see the word in source. The nice thing about this being a type is documentation via a click in your IDE is readily available.

None that the LSG can think of – but any ideas we've missed would be very helpful.

14 Likes

It may not matter to Apple, but it matters a lot in the community. Perception of the language as limited to Apple platforms is bad enough, perception that its designers are recreating C++ is worse still. So while you may think things like this have no impact, or that the impact doesn't matter, it's real all the same. It affects adoption and, ultimately, jobs. So even if you don't feel people should care about things like this, they obviously do.

If Ref is a term of art (is something still a term of art when the meaning differs?), then why doesn't "borrow" get the same consideration? Technical jargon uses verbs as nouns and nouns as verbs all the time, so any awkwardness around the grammatical status of "a borrow" seems relatively unimportant. Especially when we're talking about "ref", which isn't even a word in the first place (it's an abbreviation).

And the reasoning here seems backwards anyway. This is a rather specific type of reference, so isn't the fact that it's used in the same way in specific languages a good thing, rather than being slightly different variations amongst many? That way we can speak about it precisely, rather than always having to clarify exactly what semantics we mean.

Grammar aside, can you explain how "reference" is the more important part of the name here? I was under the impression that the semantics of the reference were the important part, not just that it's some kind of reference in the first place. Besides, aren't all borrows references anyway? But since not all references are borrows, aren't we exchanging a more precise term for a less precise term? Rust calls it the "borrow checker", not the "reference checker".

As type names, BorrowedReference and BorrowedMutableReference don't seem that bad to me, especially for something that will be used rather rarely, and typed even less. Besides, the nice thing about this being a type is that auto complete in your IDE is readily available, so making a little longer shouldn't matter. And besides, this seems like something where verbosity is actually a good thing.

I was fine with Borrow<T> and MutableBorrow<T>. The slightly ungrammatical spelling is more than compensated for by the precise nature of the name, and it will be used so rarely (in general) that it doesn't really harm readability.

Ultimately I think this a difference in perspective between someone usually operating top down (me) who won't use this type much, aside from dropping down when I need to, and someone (Swift designers) usually operating bottom up, designing fundamental language abstractions. From my perspective, clarity in the name is important, so when I read it I can immediately grasp what it is without having to disambiguate it from every other type with "ref" in the name. From the other, it will be a pain to write all the time, especially without a functional IDE, and obviously they'd know what "ref" meant since they're using it constantly.

So who are we designing Swift for?

1 Like

From the proposal

Two new standard library types, ... and ..., represent safe references to another value, with shared (immutable) or exclusive (mutable) access respectively.

‘Safe reference’ is the way these types are introduced. ‘Safe’ is redundant in Swift (where it’s the default), so Reference or Ref seems like the most straightforward name. The only alternatives to Ref/MutableRef would be SharedRef/ExclusiveRef, if you wanted to emphasise that property of the type. However, that distinction is also shared with other immutable/mutable references, so it’s probably better to stick with mutability in the name.

4 Likes

I think deciding between “Ref” and “Reference” boils down to what syntactic sugar we’re willing to admit into the language. For instance, if we add borrow and inout reference bindings as sugar for [Mutable]Reference, users will rarely have to spell out the full word “Reference.” Further, if we generalize this borrow/inout syntax to return types and type properties (as I illustrated above), users will almost never have to type out “Reference.”

I prefer “Reference” because I think the sugar I proposed enhances readability while “Ref” detracts from it. I would argue “Ptr” is also widely understood to mean “Pointer” but Swift still opted for the latter to avoid confusion. Correct me if I’m wrong, but I think we only gave shorthand names to incredibly common types like “Int.” I am worried that “Ref” is already very prevalent with users sometimes naming their container box-like classes “Ref.” Rust, C++ and others accept this tradeoff because their focus on manual memory management exposes beginners to such types. On the other hand, Swift values progressive disclosure and even intermediate users may be unaware of the proposed types. “Reference” minimizes this risk for confusion in Swift.

4 Likes

I'd concur with that.. My vote would be for either Ref and Ptr or Reference and Pointer (whichever we end up choosing at the end of the day - I'm ambivalent) otherwise it's inconsistent.

1 Like

I think the spelled-out “Pointer” is, in part, to make those types less attractive since Swift discourages usage of pointer types. The same would not apply to these types, so more convenient names feel more appropriate here to me.

1 Like

We do not have bare "Pointer" names without "Unsafe" in them IIRC, and that's the Unsafe prefix in those types that does the job of making those types less attractive, methinks.

1 Like

Ref (as in reference) is a good name in my view—less obscure than Borrow. In the real world "borrow" implies exclusivity: for instance, two people can't borrow the same book at the same time. But that's not how it works here and so I find it's sort of a counterintuitive term.

That said, I'm a bit concerned by the proliferation of different terms to mean the same concept. Can we replace the term "borrow"/"borrowing" for "ref" or similar elsewhere too? Obviously that'd have to be another proposal, but I feel like choosing Ref here without any intent to revise the naming in other places would be strange.

5 Likes

Since I made this argument pretty strongly previously, I’d like to note that I’ve since changed my mind about this. In trying to write about this type and how it works, I found that…

Well, have you ever seen the infamous example of a homophone-filled English sentence?

Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo.

Talking about how you borrow a value to form a Borrow is kind of like that. The terminology turns straightforward discussions into a confusing muddle; everything is a "borrow" and nothing makes sense.

But if you start using distinct terms in place of the ambiguous ones:

Upstate bison upstate bison bully bully upstate bison.

Then suddenly—even without any other attempt to rephrase the sentence—the meaning comes across much more clearly. And so it is with Ref: having a distinct term for the noun just makes it a whole lot easier to talk and think about.

I do still have some reservations about Ref. In particular, I don't like how "Ref" sounds much more closely related to a "reference type" than it really is; I worry that might be confusing. But I no longer think Borrow is a good answer and I haven't seen any other suggestions that I think work.

6 Likes

I think Ref and MutableRef is fine, it at least makes sense with Span and MutableSpan.

It just means that the accepted borrow/inout spelling for accessors is extraordinarily confusing. (Not that it wasn't anyway, because it is a true borrow and not a copy-in-copy-out like an inout parameter anyway). Could we at least retcon inout -> mutableborrow for the previous proposal, so that there's a little consistency?

The accessor is called mutate, not inout. inout is only used on parameters; everywhere else we use some grammatical variant of "mutate".

1 Like

oops, my bad sorry!

OCaml doesn't focus on manual memory management, and in fact doesn't support manual management at all IIRC. The default memory management approach it takes is GC. And yet, the first section of Chapter 6: Mutability of "OCaml Programming" book starts with refs:

A ref is like a pointer or reference in an imperative language. It is a location in memory whose contents may change. Refs are also called ref cells , the idea being that there’s a cell in memory that can change.

2 Likes

OCaml’s “ref” feels more like an escape hatch from functional programming which changes mutation semantics, rather than an ownership modifier. So while programmers indeed understand that “Ref” means reference, my main concern is that they won’t understand it refers to ownership. They might, for instance, mistake it for a class reference, similar to OCaml’s ref (if I understand that feature correctly).

Either way, I think both names are acceptable.

I don’t expect that we’ll abandon the term “borrow”, and the inconsistency between this and the existing uses of “borrow” in the language definitely makes me uncomfortable. We’ve been really struggling to find terms that don’t have some major problem, though.

3 Likes

Ref Love it. Let's go!

1 Like

If we're not going to revise the use of the term "borrow" elsewhere in the language, then I think it needs to be part of the type name. Instead of using Pointer and MutablePointer as the template, why not use the modifiers for parameters and methods as a modifier for Ref? Like this:

BorrowingRef<T>  // based on `borrowing` parameters
MutatingRef<T>   // based on `mutating` methods, could also be InoutRef
6 Likes