swiftpointers

Why UnsafeMutablePointer is deallocated after first read from buffer?


This is my code:

let string = "aaabbbdccccc"
var pointer = string.withCString {
    UnsafeMutablePointer<Int8>(mutating: $0)
}
var currentChar = pointer.pointee
var count = 1
pointer = pointer.successor()

var values = [String: Int]()

while currentChar != 0 {
    print(currentChar)
    if currentChar == pointer.pointee {
        count += 1
    } else {
        let charString = String(UnicodeScalar(UInt8(currentChar)))
        values[charString] = count
        count = 1
        currentChar = pointer.pointee
    }
    pointer = pointer.successor()
}

All I need is to create values which gives an output:

["a": 3, "b": 4, "c": 5]

Why does it only prints the following?

["a": 1]

Solution

  • The documentation says,

    The pointer passed as an argument to body is valid only during the execution of withCString(_:). Do not store or return the pointer for later use.

    By returning the pointer from the closure and assigning it to pointer, you are doing exactly what you should not do.

    You should instead put all your code inside the closure. You can return the dictionary values at the end for later use, if you like.

    let values = string.withCString {
        var pointer = $0
        var currentChar = pointer.pointee
        var count = 1
        pointer = pointer.successor()
    
        var values = [String: Int]()
    
        while currentChar != 0 {
            print(currentChar)
            if currentChar == pointer.pointee {
                count += 1
            } else {
                let charString = String(UnicodeScalar(UInt8(bitPattern: currentChar)))
                values[charString] = count
                count = 1
                currentChar = pointer.pointee
            }
            pointer = pointer.successor()
        }
        return values
    }
    print(values)
    

    Note that I have also changed UInt8(currentChar) to UInt8(bitPattern: currentChar). The initialiser without an argument label will crash if currentChar is negative.


    Actually, this particular operation you are doing doesn't make much sense. You are taking the UTF-8 code units of a string and counting them, but you are treating each UTF-8 code unit as a Unicode scalar (which is conceptually a completely different thing).

    If you just want to count the occurrences of each character and produce a [String: Int], you can do

    let values = Dictionary(string.map { (String($0), 1) }, uniquingKeysWith: +)
    

    If you actually want to count the occurrences of each UTF-8 code unit, you can simply do

    let values = Dictionary(string.utf8.map { ($0, 1) }, uniquingKeysWith: +)
    

    This produces a [UInt8: Int].