Hi folks,
I'm a mentee participating in the 2026 Swift Mentorship program. I have interest in the ownership system but I'm fairly new to the compiler implementation. While I'm investigating Borrowed value with `as` casting, I found that as operator might not have been updated with the new ownership system, and I would love to work on it. My goal of this post is to discuss
- what's the intended behavior when
asis used with borrowed values? - how should I approach adding this to the language, if this was needed?
Expected Behavior?
Casting with as operator on borrowed values has very limited support. For instance, the following snippet looks reasonable, but will fail during compilation. I have seen people raised similar issues [1] [2] in the past, so it does seem to be helpful to have such feature in the language.
protocol Prot {
borrowing func test()
}
struct Value: Prot {
borrowing func test() {
// do something
}
}
class Klass: Prot {
borrowing func test() {
// do something
}
}
func run(on v: borrowing Value) {
// implicitly casting from struct to existential (v as any Prot)
runProt(v)
}
func run(on k: borrowing Klass) {
// implicitly casting from class to existential (k as any Prot)
runProt(k)
}
func run<T: Prot>(v: borrowing T) {
// implicitly casting from generic to existential (v as any Prot)
runProt(v)
}
func runProt(_ v: borrowing any Prot) {
v.test()
}
Error message:
❯ swift-frontend-dev -c existential.swift
existential.swift:17:13: error: 'v' is borrowed and cannot be consumed
15 | }
16 |
17 | func run(on v: borrowing Value) {
| `- error: 'v' is borrowed and cannot be consumed
18 | // implicitly casting from struct to existential (v as any Prot)
19 | runProt(v)
| `- note: consumed here
20 | }
21 |
existential.swift:22:13: error: 'k' is borrowed and cannot be consumed
20 | }
21 |
22 | func run(on k: borrowing Klass) {
| `- error: 'k' is borrowed and cannot be consumed
23 | // implicitly casting from class to existential (k as any Prot)
24 | runProt(k)
| `- note: consumed here
25 | }
26 |
existential.swift:27:19: error: 'v' is borrowed and cannot be consumed
25 | }
26 |
27 | func run<T: Prot>(v: borrowing T) {
| `- error: 'v' is borrowed and cannot be consumed
28 | // implicitly casting from generic to existential (v as any Prot)
29 | runProt(v)
| `- note: consumed here
30 | }
31 |
The as operation can carry out many duties. Some of the casting cases seem to make sense to work with borrowed values:
- existential casting:
- from struct to existential
- from class to existential
- from generic to existential
anyP as any QwherePrefinesQ(already works)
- class casting
- from class to
AnyObject - casting to the same type (a
Klassinstance casted toKlass:k as Klass, already works) - class upcast (already works)
- from class to
- tuple conversion:
(Sub, Int)to(Super, Int)
Some of the casting cases don't seem to make sense:
StringtoNSString,ArraytoNSArray,NSStringtoCFString, etc.- optional inject (
value as Value?) - optional widen (
optionalSub as Optional<Super>)
It looks like similar cases for types conforming to ~Copyable have been considered, and yield different error messages. For instance, the following snippet
protocol Prot: ~Copyable {
borrowing func foo() -> Int
}
struct Value: ~Copyable, Prot {
borrowing func foo() -> Int {
return 42
}
}
func run(_ v: borrowing Value) {
runProt(v)
runOptional(v)
v as any Prot
}
func runProt(_ p: borrowing any Prot) {
p.foo()
}
func runOptional(_ p: borrowing Value?) {
p?.foo()
}
will give error message:
❯ swift-frontend-dev -c typeof.swift
typeof.swift:12:13: error: argument type 'Value' does not conform to expected type 'Copyable'
10 |
11 | func run(_ v: borrowing Value) {
12 | runProt(v)
| `- error: argument type 'Value' does not conform to expected type 'Copyable'
13 | runOptional(v)
14 | v as any Prot
typeof.swift:13:17: error: implicit conversion to 'Value?' is consuming
11 | func run(_ v: borrowing Value) {
12 | runProt(v)
13 | runOptional(v)
| |- error: implicit conversion to 'Value?' is consuming
| `- note: add 'consume' to make consumption explicit
14 | v as any Prot
15 | }
typeof.swift:14:5: error: cannot convert value of type 'Value' to type 'any Prot' in coercion
12 | runProt(v)
13 | runOptional(v)
14 | v as any Prot
| `- error: cannot convert value of type 'Value' to type 'any Prot' in coercion
15 | }
16 |
Potential Approach
New explicit syntax
An additional syntax can be introduced, as discussed in a previous pitch, to make borrow casting specific: borrow x as any P. This would allow us to surface more clear error messages, for example when users try to do borrow str as NSString.
New SIL support
For the first few examples of copyable types, the root cause seems to be that the SIL does a init_existential_addr, unwraps the @moveOnly wrapper of the borrowed value by owning the value, then does a store or copy_addr of the unwrapped value. It doesn't seem like the SIL has the capability to express placing a borrowed loadable value into a existential buffer without copying it.
For the existential casting cases, it seems we may need a new kind of SIL instruction that could work with borrowed address into existential.
We could lift the restriction of the existing store_borrow, so that it would work with init_existential_addr besides alloc_stack.
%1 = copyable_to_moveonlywrapper [owned] %0 : $S
%2 = move_value [lexical] %1 : $@moveOnly S
%3 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2 : $@moveOnly S
%4 = alloc_stack $any P
%5 = init_existential_addr %4 : $*any P, $S
%6 = begin_borrow %3
%7 = moveonlywrapper_to_copyable [guaranteed] %6
%8 = store_borrow %7 to %5 // currently not possible
%9 = open_existential_addr immutable_access %4 to $*@opened Self
%10 = witness_method $@opened Self, #P.f, %9
apply %10<@opened Self>(%9)
end_borrow %8
deinit_existential_addr %4
end_borrow %6
dealloc_stack %4
I'd appreciate guidance on a few things before I go further:
- Is existential erasure of borrowed values unimplemented, or unresolved?
- Is a new SIL instruction the right direction?
- Would the new surface syntax (
borrow x as any P, per the earlier pitch) be a reasonable candidate?
Thanks in advance!