是否有更惯用的方法来防止释放可选参数字符串?
Is there a more idiomatic way to keep an optional argument string from being freed?
我想在 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);
};
}
连同此 C 代码:
#include <stdio.h>
int
print_in_c(const char *bar)
{
puts("C:");
puts(bar);
return 0;
}
但这没有用。
当传递 "foo" 的参数时,代码打印出以下内容:
Some("foo")
C:
后跟一个空行。
如果我将 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);
};
}
当 运行 时,打印
Some("foo")
C:
foo
符合预期。
这个方法在技术上是可行的,但扩展到多个参数会很尴尬并导致编译器警告:
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
有更好的方法吗?
问题是您的代码正在创建一个临时 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(),
};
Safe Rust 仅通过 references 支持指针间接来防止悬挂指针,编译器会仔细跟踪其生命周期。在编译时自动拒绝任何使用比对象还长的引用。但是您正在使用原始指针和一个 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()));
}
这里有几点需要注意:
arg_cstring
持有一个 Option<CString>
,这确保 CString
的存储空间可以比传递给 C 函数的指针更长久;
Option::as_ref()
用于防止 arg_cstring
被 移动 到 map
,这将在实际使用指针之前再次释放它;
当你想表达 "do something with Option
if Some
, otherwise just leave it as None
". 时,Option::map()
被用作模式匹配的替代方法
- 如果模式
x.as_ref().map(|x| x.as_ptr().unwrap_or(null())
在程序中多次使用,则可以而且可能应该将其移至实用程序函数中。请注意该函数引用 Option
以避免移动。
我想在 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);
};
}
连同此 C 代码:
#include <stdio.h>
int
print_in_c(const char *bar)
{
puts("C:");
puts(bar);
return 0;
}
但这没有用。 当传递 "foo" 的参数时,代码打印出以下内容:
Some("foo")
C:
后跟一个空行。
如果我将 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);
};
}
当 运行 时,打印
Some("foo")
C:
foo
符合预期。
这个方法在技术上是可行的,但扩展到多个参数会很尴尬并导致编译器警告:
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
有更好的方法吗?
问题是您的代码正在创建一个临时 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(),
};
Safe Rust 仅通过 references 支持指针间接来防止悬挂指针,编译器会仔细跟踪其生命周期。在编译时自动拒绝任何使用比对象还长的引用。但是您正在使用原始指针和一个 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()));
}
这里有几点需要注意:
arg_cstring
持有一个Option<CString>
,这确保CString
的存储空间可以比传递给 C 函数的指针更长久;Option::as_ref()
用于防止arg_cstring
被 移动 到map
,这将在实际使用指针之前再次释放它;
当你想表达 "do something with Option::map()
被用作模式匹配的替代方法- 如果模式
x.as_ref().map(|x| x.as_ptr().unwrap_or(null())
在程序中多次使用,则可以而且可能应该将其移至实用程序函数中。请注意该函数引用Option
以避免移动。
Option
if Some
, otherwise just leave it as None
". 时,