A thing used as an index, or a thing that stores a thing used as an index, usually has value semantics. A thing has value semantics if it has a value that's independent of that of any other instance of which it is not a part. The index's value does not have any relationship to the value of the thing at that index.
I'm not certain this is what Nevin meant, but I understood that the table lookups would be part of the implementation of CachedValue
, so that the natural result of comparisons via Comparable
could change based on external changes to the lookup table.
In that case I agree that CachedValue
has reference semantics.
(Much-delayed response, since I've now just gotten back to addressing feedback from this thread in the vision document).
Unlike with concurrency, there is no expectation here that strict safety will be enabled in a new language mode. The updated vision document lists some reasons, but for me the salient point is that Swift's current (non-strict) memory safety is the right default for the vast majority of Swift users, and that's not likely to change.
The strict memory safety is also much simpler than the concurrency model, because the use of unsafe constructs can always be dealt with locally: @unsafe
doesn't make its way into the type signatures anywhere, so it's not viral in the way that (say) async
or @Sendable
have to be. And at any point, you can take the use of an unsafe construct and "encapsulate" it so that the unsafety doesn't propagate further. The vision talks about a couple different syntaxes for it (unsafe { ... }
blocks, @safe(unchecked)
), but this is something we didn't have in concurrency world: you can't just "wrap up" and async operation and forget that it was async without it having systemic effects.
Checking for unsafe constructs is a simpler problem: if your code isn't building with strict safety checking, we ignore the @unsafe
annotations and don't produce any diagnostics, so it doesn't matter if the modules you depend on have enabled strict safety checking. With data-race safety, we could only approximate this through things like @preconcurrency
and minimal checking, but the implementation of those is very difficult because they are effectively trying to work around fundamental changes in the type system. @unsafe
doesn't impact the type system, so the problem is far easier.
One data point to support my claim above: development toolchains have had @unsafe
on the standard library APIs mentioned in the vision document for a few months now, and it's had zero impact on anyone because nobody has enabled the strict memory safety mode outside of compiler tests.
To be clear, we are working on improving the concurrency story as well. You wrote this before the vision on improving the approachability of data-race safety was posted, and I hope that helps. We can handle more than one thing at a time, and the strict memory safety vision here is significantly smaller in scope and impact.
It does not require annotations, because those libraries can continue to be compiled without strict memory safety checking. Clients of those libraries that choose to enable strict memory safety checking will still be able to use them, and the compiler will assume that every API is safe unless its declaration involves some @unsafe
type. For example ContiguousBytes.withUnsafeBytes
traffics in UnsafeRawBufferPointer
, so it is considered @unsafe
.
There will be an audit trail that states that Foundation has not enabled strict memory safety checking, and that may create social pressure on Foundation to do so. That exercise will make the implicit @unsafe
based on type information explicit, and require annotation for all other unsafe uses. It's likely that they'll want to add safe counterparts to some of their APIs, such as a Span
-based version of the withUnsafeBytes
mentioned above. However, this is additive and will not break existing clients.
Nothing is unusable in this subset. One can locally acknowledge and encapsulate any use of unsafe constructs.
I said this above as well, but to restate it here: if you don't have strict memory safety checking enabled, you will not get any warnings from it.
Some of the types we depend on to provide safe counterparts to unsafe APIs, for example the newly-introduced Span
type, may have back-deployment issues. The changes described in this vision have zero impact on the ABI and no back-deployment concerns.
I'd expect that most app-level code won't care to enable this checking, unless the app is in a domain where the security requirements are such that strict memory safety is required. Low-level modules are more likely to enable this checking both because they are more likely to be doing the kind of work that benefits from stricter memory safety checking, and because they're more likely to have clients asking for it.
Sure, here are some examples:
- Libraries that are parsing untrusted data (HTML, XML, images, fonts, messages, cryptographic libraries, etc.) and want to be sure that memory-safety issues don't turn into security problems
- Clients of those libraries, such as messaging apps, web browsers, anyone that handles confidential personal information
- Low-level systems software that cannot afford to go wrong, such as firmware or OS kernels
Yes, this vision is distilled from many discussions with folks who care deeply about security and the impact of memory (un)safety. The auditability section I've recently added is in direct response to requests from security-minded folks who are responsible for ensuring that projects and organizations can make deliberate incremental, forward progress on memory safety.
Those projects that have adopted this strict checking should be able to show that there were no memory-safety issues introduced within those places that are covered by the strict checking, and that it was possible to enable this checking over large swaths of code without sacrificing other goals of the project (whether performance, developer productivity, or whatever).
Doug
Can this option be used / compatible with embedded-Swift?
Will it be possible to produce a warning instead of error?
In some projects warning will be preferred. As Swift evolving, usage of unsafe constructs become less needed. It can typically handle that developer is doing something in a wrong way, don't have enough knowledge or time to express the idea using safe constructs or solution was copy-pasted from stackoverflow for Swift 3 (and now we can do better using Swift 6).
So project goal can be not to entirely eliminate unsafe constructs, but use them as few as possible. In this scenario error can completely block the release, but warning encourage developers to update their knowledge or better express what they want.
There are different kinds of unsafety. What about @unsafeMemory
for this attribute instead of @unsafe
? (or @unsafeMemoryAccess
)
It is not clear why this initializer is treated as unsafe in aspect of memory access. Range.init(uncheckedBounds:)
trap at runtime when incorrect arguments are passed. It is as safe as forced unwrapping of Optional or Array subscript.
One interesting example is delegate methods from UIKit:
func scrollViewWillEndDragging(_: UIScrollView,
withVelocity _: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>)
Of course targetContentOffset
should better be done as inout parameter. But in this case usage of UnsafeMutablePointer
seems to be safe if it will ba marked as nonEscapable
. In such a way this UnsafeMutablePointer
can only be used during function call and can not be saved in a global variable, captured by closure, etc...
If it was done in such a manner, are there any unsafely pitfalls?
Update: it trap only in debug builds, din't know it.
It would be great to have this info in documentation.
One cannot make a parameter non escapable, it's a property of the type itself. Pointers in Swift are not nonescapable at all ever. Even if you couldn't "escape" it outside of where you got it from, it is still a pointer; you can still move around the memory given to you for reading/writing random junk where you shouldn't (assuming the system doesn't protect said other regions of memory and ends your process). Everything about pointers are inherently super unsafe and the only way to make this particular interface "safe" is like you said, offering an inout CGPoint
or some safer new nonescapable Inout<CGPoint>
(which would be a pointer underneath the hoods, but it wouldn't have any actual pointer operations on it besides altering the pointee).
Yes! I should mention this in the vision, thank you.
That's how the prototype is implemented: it's emitting warnings, but I added a specific warning group so they can be escalated to errors based on SE-0443.
I think, from the language perspective, "safety" is memory safety. We use "unsafe" in the pointer types and in things like unowned(unsafe)
because they don't provide memory safety.
This is a place where, for example, the additional header annotations could make Swift treat the API as safe. The C noescape
attribute and __counted_by(1)
together could cause this to be imported as a MutableSpan<CGPoint>
with one element. The Clang -fbounds-safety
also has a __single
for this purpose, which (with noescape
and non-null) could be treated as inout
on the Swift side. There are implementation complexities here, and protocols are especially hard, but it works conceptually.
Alejandro is correct here for Swift. However, the C "noescape" attribute is intended to be used to help translate C interfaces into safer Swift interfaces, as noted above.
Doug
Sure, in majority of cases it is true.
One more tangential aspect is for things like Range.init(uncheckedBounds:)
. I personally was very surprised that it traps only in debug builds. I just remembered that invalid bounds caused runtime crash.
What do you think about additional annotation of such unsafe constructs with @_spiOnly
or something like it besides proposed @unsafe
, making it available only in special mode.
Unsafe(Mutable)(Raw)(Buffer)Pointer
is not easy to use for average Swift developer, and when using it everyone is ready for pitfalls.
The ability to initialize completely invalid range is very unexpected (because it violates Swift's valid after init
idiom) and felt to be better not exposed to public API. It looks like something for standard library developers or SDK developers, and in addition to @unsafe
is likely ti be annotated with something else.
I don't think we need another special mode here. Whether it is @unsafe
or not is independent of who has access to this API.
I mean, sure, but it's there and we can't take it away. It needs to be marked @unsafe
because violating this invariant causes bounds-safety issues. Perhaps we should deprecate it, but IMO the @unsafe
and scary name are sufficient.
Doug
This vision has been accepted.