I'm exposing some rust code with a c interface. If any method return an error, the consumer can call get_last_error and pass in an empty char buffer which gets populated with an full error description. This seems like a better idea than returning a pointer and relying on the consumer to free it later. This way the caller allocates the memory.
I'm aware that I have to use a CString, but how should I copy it into err_string while ensuring that I don't copy in more than err_string_len characters?
#[no_mangle]
pub extern "C" fn get_last_error(err_string: *mut c_char, err_string_len: i32) {
// need to copy a CString into err_string...
}
By the way, in your provided code example, if a string is longer than err_string_len, this won't provide a NUL terminator, which can cause subtle bugs. This may or may not be what the thread author wanted, however.
From my experience with C language, strncpy and strlcpy (you are essentially doing strncpy here, but without zero-fill) are pretty much always wrong, using those identifiers in a program pretty much always indicates bugs.
If you can, I would prefer to allocate a buffer (with malloc or provide a function to free that buffer), and allow the caller to deallocate it. Use naming conventions to point out that the function allocates (yes, I'm suggesting Hungarian notation, but honestly C is so primitive that you may as well need it). The design with err_string_len is very error-prone. With malloc you will have at worst a memory leak, which can be found with tools like Valgrind.
In this case, given that it is an error message, it could be acceptable to truncate te string with a err_string.add(err_string_len - 1).write(b'\0') at the end of the function (and an assertion that the len is > 0).
That being said, out pointers are only useful if they can provide a way to return a dynamically sized element while avoiding an allocation. So indeed, if the chars are being copied from a CString, you'd better be returning a(n owning) pointer to the CString chars.
And of course, if you have the "return value" slot already used by what your function returns when it does not fail, you can still use an out-pointer: *mut *mut c_char