Reputation: 1010
I want to take command-line arguments in a Rust program and pass them to a C function. However, these arguments are optional and the program should behave differently if no arguments are supplied. I have read the docs for CString::as_ptr
but I had hoped that keeping a local variable containing an Option
containing the argument, (if it exists,) would keep that String
from being freed as in the following example.
This Rust code:
extern crate libc;
use std::ffi::CString;
extern "C" {
fn print_in_c(opt_class: *const libc::c_char) -> libc::c_int;
}
fn main() {
let mut args = std::env::args();
//skip execuatble name
args.next();
let possible_arg = args.next();
println!("{:?}", possible_arg);
let arg_ptr = match possible_arg {
Some(arg) => CString::new(arg).unwrap().as_ptr(),
None => std::ptr::null(),
};
unsafe {
print_in_c(arg_ptr);
};
}
Along with this C code:
#include <stdio.h>
int
print_in_c(const char *bar)
{
puts("C:");
puts(bar);
return 0;
}
But this didn't work. The code prints out the following when passed an argument of "foo":
Some("foo")
C:
Followed by a blank line.
I got the program to print the correct text if I change the Rust code to the following:
extern crate libc;
use std::ffi::CString;
extern "C" {
fn print_in_c(opt_class: *const libc::c_char) -> libc::c_int;
}
fn main() {
let mut args = std::env::args();
//skip execuatble name
args.next();
let possible_arg = args.next();
println!("{:?}", possible_arg);
let mut might_be_necessary = CString::new("").unwrap();
let arg_ptr = match possible_arg {
Some(arg) => {
might_be_necessary = CString::new(arg).unwrap();
might_be_necessary.as_ptr()
}
None => std::ptr::null(),
};
unsafe {
print_in_c(arg_ptr);
};
}
When run, this prints
Some("foo")
C:
foo
as expected.
This method technically works, but it is awkward to extend to multiple arguments and results in a compiler warning:
warning: value assigned to `might_be_necessary` is never read
--> src/main.rs:19:9
|
19 | let mut might_be_necessary = CString::new("").unwrap();
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: #[warn(unused_assignments)] on by default
Is there a better way to do this?
Upvotes: 3
Views: 905
Reputation: 155246
The problem is that your code is creating a temporary CString
but holding on to just a pointer. The actual CString
is dropped, while the dangling pointer is passed to the C function. To understand what's going on, it is useful to expand the pattern match to a more verbose form:
let arg_ptr = match possible_arg {
Some(arg) => {
let tmp = CString::new(arg).unwrap();
tmp.as_ptr()
} // <-- tmp gets destructed here, arg_ptr is dangling
None => std::ptr::null(),
};
Safe Rust prevents dangling pointers by only supporting pointer indirection through references, whose lifetimes are carefully tracked by the compiler. Any use of a reference that outlives the object is automatically rejected at compile time. But you are using raw pointers and an unsafe
block which prevents those checks from taking place, so you need to manually ensure proper lifetimes. And indeed, the second snippet fixes the problem by creating a local variable that stores the CString
for long enough for its value to outlive the pointer.
The prolonged lifetime comes at the cost of an additional local variable. But fortunately it can be avoided - since you already have a local variable that holds the pointer, you can modify that to store the actual CString
, and extract the pointer only when actually needed:
let arg_cstring = possible_arg.map(|arg| CString::new(arg).unwrap());
unsafe {
print_in_c(arg_cstring.as_ref()
.map(|cs| cs.as_ptr())
.unwrap_or(std::ptr::null()));
}
There are several things to notice here:
arg_cstring
holds an Option<CString>
, which ensures that CString
has storage that can outlive the pointer passed to the C function;Option::as_ref()
is used to prevent arg_cstring
from being moved into map
, which would again free it before the pointer was actually used;Option::map()
is used as an alternative to pattern matching when you want to express "do something with Option
if Some
, otherwise just leave it as None
".x.as_ref().map(|x| x.as_ptr().unwrap_or(null())
can and probably should be moved into a utility function if it is used more than once in the program. Be careful that the function takes reference to Option
to avoid a move.Upvotes: 8