the view would never have exclusive ownership of the original allocation because self would still be alive while RandomNumberGenerator.fill(_:) is running. it would be possible if we had generalized yield & or some other way of moving self out of the function and reinitializing it after. but that’s the same as having @noncopyable.
Perhaps we could introduce a warning suggesting using Data's / Array's "withUnsafeMutableBytes" as the majority of users would want the latter call. With some ability to suppress the warning for those few who actually meant to use the first call.
Thanks for the pointer. It looks like it could have used any other numeric type there, or even an arbitrary non numeric type (ideally with some restriction to a safe subset of POD types):
public mutating func next<T>(ofType: T.Type) -> T {
var random: T!
_withUnprotectedUnsafeMutablePointer(to: &random) {
swift_stdlib_random($0, MemoryLayout<T>.size)
}
return random
}
We could, and should. But note that the error only occurs for Data: Array silently does the right thing. This is part of why the pattern is so dangerous: if you're working with Array it does what you mean, for (almost) anything else it doesn't.
No-one means to use the first call. In the extraordinarily rare event that someone really did want the bytes of the Data struct, they can be forced to type out withUnsafeBytes(of:).
Please clarify. The following test shows both Array and Data behaving exactly the same way with the calls being discussed.
Test
func testUnsafe() {
var array = [1 as UInt8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
var data = Data(array)
// MARK: array.withUnsafeBytes test
array.withUnsafeBytes { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("array.withUnsafeBytes", terminator: "\t\t\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: array.withUnsafeMutableBytes test
array.withUnsafeMutableBytes { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("array.withUnsafeMutableBytes", terminator: "\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: data.withUnsafeBytes test
data.withUnsafeBytes { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("data.withUnsafeBytes", terminator: "\t\t\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: data.withUnsafeMutableBytes test
data.withUnsafeMutableBytes { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("data.withUnsafeMutableBytes", terminator: "\t\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: withUnsafeBytes(of: array) test
withUnsafeBytes(of: array) { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("withUnsafeBytes(of: array)", terminator: "\t\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: withUnsafeBytes(of: &array) test
withUnsafeBytes(of: &array) { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("withUnsafeBytes(of: &array)", terminator: "\t\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: withUnsafeMutableBytes(of: &array) test
withUnsafeMutableBytes(of: &array) { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("withUnsafeMutableBytes(of: &array)", terminator: "\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: withUnsafeBytes(of: data) test
withUnsafeBytes(of: data) { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("withUnsafeBytes(of: data)", terminator: "\t\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: withUnsafeBytes(of: &data) test
withUnsafeBytes(of: &data) { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("withUnsafeBytes(of: &data)", terminator: "\t\t\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
// MARK: withUnsafeMutableBytes(of: &data) test
withUnsafeMutableBytes(of: &data) { p in
let r = p.baseAddress!.assumingMemoryBound(to: UInt8.self)
print("withUnsafeMutableBytes(of: &data)", terminator: "\t")
for i in 0 ..< p.count { print(String(format: "%02x", r[i]), terminator: " ") }
print()
}
print("done")
}
Outputs:
array.withUnsafeBytes 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
array.withUnsafeMutableBytes 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
data.withUnsafeBytes 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
data.withUnsafeMutableBytes 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14
withUnsafeBytes(of: array) 00 98 f5 01 00 60 00 00
withUnsafeBytes(of: &array) 00 98 f5 01 00 60 00 00
withUnsafeMutableBytes(of: &array) 00 98 f5 01 00 60 00 00
withUnsafeBytes(of: data) 00 00 00 00 14 00 00 00 40 b8 96 02 00 60 00 40
withUnsafeBytes(of: &data) 00 00 00 00 14 00 00 00 40 b8 96 02 00 60 00 40
withUnsafeMutableBytes(of: &data) 00 00 00 00 14 00 00 00 40 b8 96 02 00 60 00 40
Yup, good catch: my original post was intending to just directly use the & operator, which does behave differently between the two types.