Rya*_*729 3 c ffi command-line-arguments rust
我想在Rust程序中获取命令行参数并将它们传递给C函数.但是,这些参数是可选的,如果没有提供参数,程序应该表现不同.我已经阅读了文档,CString::as_ptr但是我曾希望保留一个Option包含参数的局部变量(如果存在)将使其String不被释放,如下例所示.
这个Rust代码:
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);
};
}
Run Code Online (Sandbox Code Playgroud)
与此C代码一起:
#include <stdio.h>
int
print_in_c(const char *bar)
{
puts("C:");
puts(bar);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
但这没效果.传递参数"foo"时,代码打印出以下内容:
Some("foo")
C:
Run Code Online (Sandbox Code Playgroud)
后跟一个空白行.
如果我将Rust代码更改为以下内容,我得到了打印正确文本的程序:
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);
};
}
Run Code Online (Sandbox Code Playgroud)
运行时,会打印出来
Some("foo")
C:
foo
Run Code Online (Sandbox Code Playgroud)
正如所料.
此方法在技术上有效,但扩展到多个参数并导致编译器警告很难:
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
Run Code Online (Sandbox Code Playgroud)
有一个更好的方法吗?
问题是你的代码正在创建一个临时的,CString但只是一个指针.实际CString被删除,而悬空指针被传递给C函数.要了解发生了什么,将模式匹配扩展为更详细的形式非常有用:
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(),
};
Run Code Online (Sandbox Code Playgroud)
Safe Rust通过仅通过引用支持指针间接来防止悬空指针,引用的生命周期由编译器仔细跟踪.在编译时会自动拒绝使用超过对象的引用.但是您使用原始指针和unsafe阻止这些检查发生的块,因此您需要手动确保适当的生命周期.实际上,第二个片段通过创建一个局部变量来解决问题,该变量存储的CString时间足够长,使其值超过指针.
延长的寿命是以额外的局部变量为代价的.但幸运的是可以避免 - 因为你已经拥有一个保存指针的局部变量,你可以修改它来存储实际值CString,并仅在实际需要时提取指针:
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()));
}
Run Code Online (Sandbox Code Playgroud)
这里有几点需要注意:
arg_cstring持有一个Option<CString>,确保CString存储能够比传递给C函数的指针更长;Option::as_ref()用于防止arg_cstring被感动到map,这将再次释放它实际上是用指针之前;Option::map()当你想表达"用Optionif 做什么Some,否则只是留下它None"时,它被用作模式匹配的替代品.x.as_ref().map(|x| x.as_ptr().unwrap_or(null())如果在程序中多次使用该模式,则该模式可以并且可能应该移动到实用程序函数中.请注意该功能需要参考Option以避免移动.