有没有更惯用的方法来保持可选参数字符串被释放?

时间:2017-03-02 19:38:58

标签: c rust command-line-arguments ffi

我想在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;
}

但这并没有奏效。 传递&#34; foo&#34;:

的参数时,代码会输出以下内容
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

有更好的方法吗?

1 个答案:

答案 0 :(得分:7)

问题是你的代码正在创建一个临时的 string str = ("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:/Users/charlyn_dale/Documents/Visual Studio 2010/Projects/LMS/WindowsFormsApplication2/Accounts.accdb;Persist Security Info=False"); OleDbCommand conn = new OleDbCommand(str); con.Open(); string query = "insert into Account ([Username],[Password],FirstName,MiddleName,LastName,Age,Section,Gender,Address,AccountStatus) values('" + txt1.Text + "','" + txt2.Text + "','" + txt4.Text + "','" + txt5.Text + "','" + txt6.Text + "','" + txt7.Text + "','" + txt8.Text + "','" + cmb2.Text + "','" + txt9.Text + "','" + cmb1.Text + "')"; OleDbCommand cmd = new OleDbCommand(query, con); conn.ExecuteNonQuery(); MessageBox.Show("Registration Success!"); con.Close(); ,但只保留一个指针。实际的CString被删除,而悬空指针被传递给C函数。要了解发生了什么,将模式匹配扩展为更详细的形式非常有用:

CString

Safe Rust通过仅通过引用支持指针间接来防止悬空指针,引用的生命周期由编译器仔细跟踪。在编译时会自动拒绝使用超过对象的引用。但是您使用原始指针和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(), }; 块来阻止这些检查发生,因此您需要手动确保适当的生命周期。实际上,第二个片段通过创建一个局部变量来解决问题,该变量将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>的存储空间可以超过传递给C函数的指针;
  • CString用于阻止Option::as_ref() 移动进入arg_cstring,这将在指针实际使用之前再次释放它;
  • map用于替代模式匹配,如果您想表达“Option::map()如果Option执行某些操作,则只需将其保留为Some”。< / LI>
  • 如果在程序中多次使用模式,None模式可以并且可能应该移动到实用程序函数中。请注意,该函数会引用x.as_ref().map(|x| x.as_ptr().unwrap_or(null())以避免移动。