Description
how to reproduce
pub fn leak() {
let nsstrig = NSString::from_str("aaabbb");
nsstrig.as_str();
}
repo with examples and tests https://github.com/Lurk/nsstring_leak
why it is leaking
INSString.as_str() internaly uses UTF8String property of NSString. Apple doc says that the memory behind this pointer has a lifetime shorter than a lifetime of an NSString itself. But apparently, this is not entirely true. At least, this statement is not valid for strings that contain characters outside the ASCI range. And sometimes for strings that do not.
Sadly, I did not find any reason for that.
So, in the end, the actual leak occurs not in INSString.as_str() but, I guess, in objc runtime.
Is there a workaround?
Yes. NSString::getCString (Apple doc) and we can use it like this:
pub fn nsstring_to_rust_string(nsstring: Id<NSString>) -> String {
unsafe {
let string_size: usize = msg_send![nsstring, lengthOfBytesUsingEncoding: 4];
// + 1 is because getCString returns null terminated string
let buffer = libc::malloc(string_size + 1) as *mut c_char;
let is_success: bool = msg_send![nsstring, getCString:buffer maxLength:string_size+1 encoding:4];
if is_success {
// CString will take care of memory from now on
CString::from_raw(buffer).to_str().unwrap().to_owned()
} else {
// In case getCString failed there is no point in creating CString
// So we must free memory
libc::free(buffer as *mut c_void);
// Original NSString::as_str() swallows all the errors.
// Not sure if that is the correct approach, but we also don`t have errors here.
"".to_string()
}
}
}
In the repo I mentioned above, you will find a test and benchmark for that.
The only problem I see with this solution is that it has a different return type (String instead of &str).
If you know how to fix that, or any other idea on how to do things better - please let me know.