When you pass &mut value.as_mut_ptr(), you're passing a reference to the temporary pointer. The FFI may set that to its allocated memory, but you have no way to see that update in your temporary pointer after it returns. It won't be pointing to your value anymore.
You could use an uninitialized pointer:
let mut buffer = MaybeUninit::<*mut libc::c_char>::uninit();
let ret = get_string(buffer.as_mut_ptr());
But it's also easy to just initialize it as null:
let mut buffer: *mut libc::c_char = std::ptr::null_mut();
let ret = get_string(&mut buffer);
The first version has the issue that if the function does not write into the argument, you get an uninitialized pointer.
Given essentially zero cost for the second version, I don't think the first version should ever be considered.
You can create a CStr from the pointer via CStr in std::ffi - RustCStr::from_ptr and then to_str.
Then convert that &str into a String and free the original pointer via the appropriate function call (probably free in libc - Rust)