Skip to content

Latest commit

 

History

History
97 lines (69 loc) · 3.03 KB

passing-strings.md

File metadata and controls

97 lines (69 loc) · 3.03 KB

传递字符串

描述

当向FFI函数传递字符串时,应该遵循四个原则:

  1. 使拥有的字符串的生命周期尽可能长。
  2. 在转换过程中尽量减少unsafe代码。
  3. 如果C代码可以修改字符串数据,使用Vec而不是CString
  4. 除非外部函数API要求,否则字符串的所有权不应该转移给被调用者。

动机

Rust内置了对C风格字符串的支持,有CStringCStr类型。 然而,对于从Rust函数中发送字符串到外部函数调用,我们可以采取不同的方法。

最好的做法很简单:用CString的方式来减少unsafe的代码。 然而,次要的注意事项是,对象必须活得足够长,这意味着生命周期应该最大化。 此外,文档解释说,CString进行"round-tripping"修改是未定义行为,所以在这种情况下需要额外的工作。

代码示例

pub mod unsafe_module {

    // other module content

    extern "C" {
        fn seterr(message: *const libc::c_char);
        fn geterr(buffer: *mut libc::c_char, size: libc::c_int) -> libc::c_int;
    }

    fn report_error_to_ffi<S: Into<String>>(
        err: S
    ) -> Result<(), std::ffi::NulError>{
        let c_err = std::ffi::CString::new(err.into())?;

        unsafe {
            // SAFETY: calling an FFI whose documentation says the pointer is
            // const, so no modification should occur
            seterr(c_err.as_ptr());
        }

        Ok(())
        // The lifetime of c_err continues until here
    }

    fn get_error_from_ffi() -> Result<String, std::ffi::IntoStringError> {
        let mut buffer = vec![0u8; 1024];
        unsafe {
            // SAFETY: calling an FFI whose documentation implies
            // that the input need only live as long as the call
            let written: usize = geterr(buffer.as_mut_ptr(), 1023).into();

            buffer.truncate(written + 1);
        }

        std::ffi::CString::new(buffer).unwrap().into_string()
    }
}

优势

这个例子的编写方式是为了确保:

  1. unsafe块尽可能小。
  2. CString存活得足够久。
  3. 类型转换的错误被尽可能传播。

一个常见的错误(常见到在文档中)是不在第一个块中使用变量:

pub mod unsafe_module {

    // other module content

    fn report_error<S: Into<String>>(err: S) -> Result<(), std::ffi::NulError> {
        unsafe {
            // SAFETY: whoops, this contains a dangling pointer!
            seterr(std::ffi::CString::new(err.into())?.as_ptr());
        }
        Ok(())
    }
}

这段代码将导致一个悬垂指针,因为CString的生命周期并没有因为指针的创建而延长,这与创建引用的情况不同。

另一个经常提出的问题是,初始化1k个零的向量是“慢”的。 然而,最近的Rust版本实际上将这个特殊的宏优化为对zmalloc的调用,这意味着它的速度和操作系统返回零内存的能力一样快(这相当快)。

劣势

没有?

Latest commit 606bcff on 26 Feb 2021