Following the discussion about pointer type sugar (Int*
which would be sugar for UnsafePointer<Int>
etc. here: Swift Pointer Syntatic Sugar), Slava mentioned "unsafe" blocks + function annotation where such sugar could be utilized. This is a feature that is found in languages like Rust and C# where functions can be marked unsafe which allows their whole body to use things like pointer arithmetic or dereferencing raw pointers, etc. In order to call an "unsafe" function, one must either call it in an unsafe function, or within an unsafe block. Rust, for example, has "safe" Rust and "unsafe" Rust. Safe Rust guarantees there is no undefined behavior whereas unsafe Rust does not hold guarantee. Swift on the other hand is considered a "safe" language, but includes many unsafe features. Many of these features include the pointer types whose names all include an Unsafe prefix, or the withUnsafe family of functions.
One of the reasons why this feature could make sense going forward is that it might allow for twiddling with pointers in a way that might make working with them more concise like C. It might also allows us to make some bold claims like Rust and say safe Swift does not introduce undefined behavior. Currently you can mess around with unsafe pointers, but there are some operations that are inherently safe and some that aren't, so it's difficult to really understand which APIs have the ability to introduce UB and those that don't. I've fiddled with this idea enough to come up with an early implementation to play around with. Below are some examples of this in action:
// No pointer type sugar in this pitch/discussion
// just simply unsafe functions
unsafe func add1(to ptr: UnsafeMutableRawPointer) {
// Unsafe and probably wrong
// Add 1 to the first byte to this pointer
ptr.assumingMemoryBound(to: UInt8.self).pointee &+= 1
}
var x = 0
// error: call is unsafe but is not marked with 'unsafe'
add1(to: &x)
// Nothing unsafe or worthy of throws, but for the sake
// of displaying why this was a good fit for do statements
unsafe func zero() throws -> Int {
return 0
}
// Unsafe do block
//
// This flows nicely with throwing unsafe functions
unsafe do {
// ok because this is inside an unsafe context
add1(to: &x)
// ok because this is inside an unsafe context
let x = try zero()
} catch {
// It is also acceptable to use unsafe functions without
// marking them unsafe in the catch block of an 'unsafe' do
}
print(x) // 1 (if little endian)
// Unsafe expression
//
// This differs from Rust and C#, but allows for
// staying in the same scope while also ensuring
// the caller understands this is an unsafe operation.
unsafe add1(to: &x)
print(x) // 2 (if little endian)
// warning: no calls to unsafe functions occur within 'unsafe do'
// statement
unsafe do {
print(x) // 2 (if little endian)
}
// Adding 1 to enums
enum ABC {
case a, b, c
}
var y = ABC.c
unsafe add1(to: &y) // this is probably very unsafe
print(y) // ()
switch y {
case .a:
print(1)
case .b:
print(2)
case .c:
print(3)
}
// 1
// stdlib function to get ptr to value
unsafe prefix func &<T>(_ value: inout T) -> UnsafeMutablePointer<T> {
// implementation here
}
var a = 0
let b = unsafe &a
For this feature to really make senes, it would require some stdlib inclusion (auditing unsafe apis and possibly marking them 'unsafe'), or something like Rust's model with FFIs. FFIs (Foreign Function Interface) in Rust are all treated as unsafe functions and must be used within unsafe blocks because Rust can't guarantee that calling them won't introduce UB, thus 'unsafe'. Because this is a simple annotation, we could mark some stdlib functions as unsafe without affecting ABI, but might affect source stability if we produced an error (we may be able to emit a warning for the stdlib to preserve SS).
This is intended to be more of a "pre-pitch"/discussion post regarding the merits of such an annotation in Swift rather than an actual proposal ready to go. I'm very interested in hearing thoughts and opinions about exploring ideas regarding unsafe Swift and whether this could make sense going forward. (I can also see a world where we may introduce inline assembly () and it would make sense to only allow such statements within unsafe contexts)