是否有更惯用的方法来防止释放可选参数字符串?

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 以避免移动。