I recently fixed a bug in some code handing off a string to a C library function. The C function takes a plain char * (no const in either position): void use_str(char * s); so we need an UnsafeMutablePointer<CChar> on the Swift side. Our Swift code was basically this:
import Foundation
let s = "abcde"
let sPtr = UnsafeMutablePointer(mutating: s.cString(using: .utf8))
use_str(sPtr)
Running this, if you inspect sPtr and sPtr.pointee before the use_str call, you can see that the pointer is bad: it does not point to the contents of the string.* Until Xcode 10/Swift 4.2, it had consistently pointed to zeroed-out memory and the original implementer "worked around" what they thought was a bug in the C library. When we upgraded, the pointed-to values started being garbage, and there was breakage.
Storing the C string in a local variable first, var sChars = s.cString(using: .utf8)!, fixes the issue. (That done, we can apparently also skip the explicit call to UnsafeMutablePointer(mutating:) and just write use_str(&sChars).)
So it looks like the return value of cString(using:) is invalid immediately unless explicitly copied. Shouldn't it live as long as the String that produced it? If I understand correctly what's happening, this ObjC is equivalent to the original code:
NSString * s = @"abcde";
use_str([s cStringUsingEncoding:NSUTF8StringEncoding]);
which is perfectly valid, as far as I know (use_str may need to copy the bytes, of course, but that's a separate issue).
Alternatively, can/should there be a warning about UnsafeMutablePointer(mutating:) pointing directly to the result of a method call like this? Maybe our original Swift code is more equivalent instead to this, taking the address of a message send expression, which is illegal:
void update_char(char * c);
//...
update_char(&[s characterAtIndex:0]);
Please help me correct errors in my understanding; I'm trying to better grasp the situation/how Swift pointers operate. We should have caught this bug in our code, but I'm not sure why it occurred in the first place.
*In fact if you add another string/pointer pair, in Swift 4.2 the two pointers consistently hold the same address!