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

Won't there be a conflict here with "reference types" (classes)?

I bet we'll soon witness conversations with: "... ah, no, I meant the other kind of reference!"

Even this topic title: if you read it cursory you may incorrectly assume it's something about ".....class references" :sweat_smile:

My two agurot.

No matter what names are chosen, people will need to learn what they represent and how to use them. Even if people have to learn what a "Borrow" type does, at least the term is unlikely to be confused in their mind with anything else they already know about Swift. If someone is already familiar with ownership and the concept of "borrowing" a (reference to a) value, they will find the name even less confusing, and they will also have a head start on understanding the new type.

These feel informative enough without being too weighty. I like the idea of “Ref” being a specialized term for the reified concept of a reference, which is a more general term that extends to “reference types” and “reference semantics.”

In the future, maybe those terms would change to give “Ref” more breathing room. (“Reference type” is already difficult to distinguish from “type with reference semantics.” Perhaps “reference-counted type”?)

1 Like

Borrowing isn't the opposite of mutating though. The mutating one is also borrowing. What value does Borrowing add here? This is a case we fall into commonly where there is a temptation to stuff in another word at an attempt at clarification that adds no practical benefit: people who already know don't need it, and for people who don't, it doesn't provide enough extra info to fix that – you really do need to read the docs to understand what the implications of borrowing mean (but at the same time... you don't actually have to read the docs if you're just browsing code that already exists – that this is a reference not a copy is the key info).

5 Likes

It’s a valid criticism, but the point is to associate the term with the borrow keyword.

It’s already kind of a bummer that the inout keyword is so distinct from the constellation of mutating keywords that it is related to. Without Borrowing in the name, you wind up with yet another case where Ref and borrow are two standalone words for heavily overlapping concepts.

Unless it’s fair game to go back and push the term ref where there is currently an ocean of owned, consuming, borrowing, inout, and mutating?

4 Likes

Given that the (one and only, if I'm seeing right) initializer would be Ref<Value>(_: borrowing Value) and MutableRef<Value>(_: inout Value), respectively, I don't know if I'd say that these terms stand alone. Rather, the relationship between the ownership modifier and the type is going to be pretty explicit no matter how they're named, right from the moment you instantiate one.

In fact, I'd wager one reason folks started wondering about the point of having these types at all was immediately seeing the redundant redundancy in Borrow<Value>(_: borrowing Value) and Inout<Value>(_: inout Value).

4 Likes

Having Ref in the name solves this first problem:

And qualifying it as a BorrowingRef solves this other problem:

If people start to think of Ref as a "borrowed reference" (and it's certainly going to be explained that way), it would sort of imply that a MutableRef is a "mutable borrowed reference". Not good if we want to avoid phrasing things as "mutable borrow".

On the other hand, BorrowingRef and MutatingRef can both stand side by side as equals with no implication that one is a subset of the other. People will also naturally call them "borrowing references" and "mutating references" which is pretty much self-explanatory since it mirrors existing terminology.

1 Like

To my mind, this example actually makes it extremely clear why both the types and keywords exist, in a way the original proposal really didn't. Makes them feel a lot like Result to me. It also makes it clear to me that there's no obvious connection between the Ref naming and the actual semantics of the capture, so you could replace it with really any other non-descriptive term.

I'd still appreciate an explanation why a second definition of "reference" is valuable for these sorts of captures, rather than exactly describing their semantics. I also share @michelf's concerns about how these types will be commonly described if their naming appears to have a hierarchy.

As I start to integrate this type into my head, I find that I’m increasingly thinking of it as the single-value equivalent of a Span. We don’t have BorrowingSpan and MutatingSpan, do we?

(Actually, BufferRef isn’t a bad name for Span. The road not taken…)

1 Like

I’m generally ok with Ref / MutableRef, but the naming does feel somewhat like an echo from the past. For those of us familiar with C-family languages, these terms are easy to adopt, but I’m not sure they communicate the intended semantics as clearly to a broader Swift audience.

If I had a preference, I would lean toward something like Borrow / Mutable (MutableBorrow). That terminology feels more aligned with the underlying ownership model being introduced, and it emphasizes the temporary, non-owning nature of these values. While I understand there has been an effort to avoid the term “borrow,” it still seems like the most semantically direct option, especially for users trying to build an accurate mental model.

By contrast, Ref carries a fair amount of historical and conceptual baggage. In many contexts, “reference” implies shared identity, indirection or reference semantics tied to heap allocation. Even if those associations are only partially accurate here, they may still lead to confusion, particularly when the implementation may optimize storage (e.g. copying small values). In that sense the name risks overemphasizing representation rather than semantics.

I do see the argument that aligning with existing “reference type” terminology could be helpful. However, this connection might also blur important distinctions, especially since these constructs are not simply “reference types” in the traditional Swift sense.

Another concern is that Ref is a very general term, and it might be more valuable to reserve it for more explicitly reference-like abstractions such as RefBox (which I would personally prefer to see named something like UniqueHeapRef, to make its ownership and allocation semantics more explicit).

Finally, while the MutableXYZ pattern is well established, MutableRef reads a bit awkwardly to me. It describes mutability of the reference itself rather than the referent, which could introduce ambiguity. Alternatives that express the borrowing relationship more directly might avoid that confusion.

Overall, I think Ref / MutableRef is workable, but I’m not fully convinced it’s the most expressive or least confusing option available.

3 Likes

"first class references" vs "first-class references"

1 Like

This two are the best option for me in the context where we have long debates choosing between Borrow and Ref

Do you mean existing Span and MutableSpan or something else that you'd expect hypothetical BorrowingSpan and MutatingSpan to do?

For that matter, did anyone here mention Ref and MutableRef as counterparts to Span and MutableSpan yet?

2 Likes

This topic is long, so easy for things to fade away.

I like this naming as well, mostly due to the match with the Span API.

2 Likes

Can you explain why this might be helpful? I can't see a connection that would be very useful. Given what Swift devs know about reference types in the language, which parts actually apply here?

Reference types are generally about shared mutable state. These new types do not offer any sort of shared mutable state since they follow the rules of value semantics.

Both class- and Ref- references can be understood as links that provide access to something, not the thing itself:

  • Passing them around doesn’t imply copying the underlying value. This aligns well with existing mental model Swift developers have for passing class references.
  • Multiple Ref values can refer to the same underlying value (region of memory), just as multiple class references can point to the same instance.

I agree the similarity is quite narrow:

  • Swift reference types (class / actor) have identity (e.g. via ObjectIdentifier), ARC-managed lifetimes and are typically heap-allocated. None of these capabilities apply to Ref.
  • A Ref does not imply shared ownership – it is non-owning and temporary which is fundamentally different from class references.
  • The compiler may optimize away indirection for small values, so the “reference” aspect is more of an abstraction than a guarantee about representation.

Nevertheless, at the concept level we have different kinds of references. Today, developers mostly associate that idea with classes and actors, but this mental model will be broaden to include more kinds of references with different capabilities.
That's why I've said "Ref / MutableRef is workable".

However, I would personally prefer BorrowingRef<T> / MutatingRef<T>, which makes the borrowing semantics explicit and reduces the risk of importing assumptions from class-based reference semantics.

4 Likes

I mean that when we were designing Span, we did not think it was necessary to have a name that stated it was borrowing its storage, and I think the same is true for Ref.

2 Likes

I feel like, to some extent, this kind of naming collision is inevitable, and usually unproblematic. Such as "an Unmanaged value contains an unmanaged reference", "an Array contains a reference to an allocated array", "a Box boxes a value to the heap, and stores a pointer to the box", and so on. "A Borrow represents a borrow of a value" doesn't seem much worse to me.