An API for bulk random bytes

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.

1 Like

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.