Rust 如果ffi函数修改指针,那么所属结构是否应该被可变引用?

Rust 如果ffi函数修改指针,那么所属结构是否应该被可变引用?,rust,Rust,我目前正在试验Rust的FFI功能,并使用libcurl作为练习实现了一个SimbleHTTP请求。考虑下面的独立例子: use std::ffi::c_void; #[repr(C)] struct CURL { _private: [u8; 0], } // Global CURL codes const CURL_GLOBAL_DEFAULT: i64 = 3; const CURLOPT_WRITEDATA: i64 = 10001; const CURLOPT_URL: i

我目前正在试验Rust的FFI功能,并使用libcurl作为练习实现了一个SimbleHTTP请求。考虑下面的独立例子:

use std::ffi::c_void;

#[repr(C)]
struct CURL {
    _private: [u8; 0],
}

// Global CURL codes
const CURL_GLOBAL_DEFAULT: i64 = 3;
const CURLOPT_WRITEDATA: i64 = 10001;
const CURLOPT_URL: i64 = 10002;
const CURLOPT_WRITEFUNCTION: i64 = 20011;

// Curl types
type CURLcode = i64;
type CURLoption = i64;

// Curl function bindings
#[link(name = "curl")]
extern "C" {
    fn curl_easy_init() -> *mut CURL;
    fn curl_easy_setopt(handle: *mut CURL, option: CURLoption, value: *mut c_void) -> CURLcode;
    fn curl_easy_perform(handle: *mut CURL) -> CURLcode;
    fn curl_global_init(flags: i64) -> CURLcode;
}

// Curl callback for data retrieving
extern "C" fn callback_writefunction(
    data: *mut u8,
    size: usize,
    nmemb: usize,
    user_data: *mut c_void,
) -> usize {
    let slice = unsafe { std::slice::from_raw_parts(data, size * nmemb) };

    let mut vec = unsafe { Box::from_raw(user_data as *mut Vec<u8>) };
    vec.extend_from_slice(slice);
    Box::into_raw(vec);
    nmemb * size
}

type Result<T> = std::result::Result<T, CURLcode>;

// Our own curl handle
pub struct Curl {
    handle: *mut CURL,
    data_ptr: *mut Vec<u8>,
}

impl Curl {
    pub fn new() -> std::result::Result<Curl, CURLcode> {
        let ret = unsafe { curl_global_init(CURL_GLOBAL_DEFAULT) };
        if ret != 0 {
            return Err(ret);
        }

        let handle = unsafe { curl_easy_init() };
        if handle.is_null() {
            return Err(2); // CURLE_FAILED_INIT according to libcurl-errors(3)
        }

        // Set data callback
        let ret = unsafe {
            curl_easy_setopt(
                handle,
                CURLOPT_WRITEFUNCTION,
                callback_writefunction as *mut c_void,
            )
        };
        if ret != 0 {
            return Err(2);
        }

        // Set data pointer
        let data_buf = Box::new(Vec::new());
        let data_ptr = Box::into_raw(data_buf);
        let ret = unsafe {
            curl_easy_setopt(handle, CURLOPT_WRITEDATA, data_ptr as *mut std::ffi::c_void)
        };
        match ret {
            0 => Ok(Curl { handle, data_ptr }),
            _ => Err(2),
        }
    }

    pub fn set_url(&self, url: &str) -> Result<()> {
        let url_cstr = std::ffi::CString::new(url.as_bytes()).unwrap();
        let ret = unsafe {
            curl_easy_setopt(
                self.handle,
                CURLOPT_URL,
                url_cstr.as_ptr() as *mut std::ffi::c_void,
            )
        };
        match ret {
            0 => Ok(()),
            x => Err(x),
        }
    }

    pub fn perform(&self) -> Result<String> {
        let ret = unsafe { curl_easy_perform(self.handle) };
        if ret == 0 {
            let b = unsafe { Box::from_raw(self.data_ptr) };
            let data = (*b).clone();
            Box::into_raw(b);
            Ok(String::from_utf8(data).unwrap())
        } else {
            Err(ret)
        }
    }
}

fn main() -> Result<()> {
    let my_curl = Curl::new().unwrap();
    my_curl.set_url("https://www.example.com")?;
    my_curl.perform().and_then(|data| Ok(println!("{}", data)))
    // No cleanup code in this example for the sake of brevity.
}
使用std::ffi::c_void;
#[报告员(C)]
结构旋度{
_私人:[u8;0],
}
//全局旋度码
const CURL\u GLOBAL\u默认值:i64=3;
常数CURLOPT_WRITEDATA:i64=10001;
const CURLOPT_URL:i64=10002;
const CURLOPT_WRITEFUNCTION:i64=20011;
//卷曲类型
类型代码=i64;
类型选项=i64;
//Curl函数绑定
#[链接(name=“curl”)]
外部“C”{
fn curl_easy_init()->*mut curl;
fn curl_easy_setopt(句柄:*mut curl,选项:curlpoption,值:*mut c_void)->CURLcode;
fn curl\u easy\u perform(句柄:*mut curl)->CURLcode;
fn curl\u global\u init(标志:i64)->CURLcode;
}
//用于数据检索的Curl回调
外部“C”fn回调函数\u writefunction(
数据:*mut u8,
大小:usize,
nmemb:使用,
用户数据:*多个c_void,
)->使用{
let slice=unsafe{std::slice::from_raw_parts(数据,大小*nmemb)};
设mut-vec=unsafe{Box::from_raw(user_data as*mut-vec)};
向量从_切片(切片)扩展_;
框::输入未加工(vec);
nmemb*大小
}
类型Result=std::Result::Result;
//我们自己的卷发手柄
pub结构旋度{
手柄:*mut CURL,
数据提示:*mut Vec,
}
内旋{
pub fn new()->std::result::result{
设ret=unsafe{curl\u global\u init(curl\u global\u DEFAULT)};
如果ret!=0{
返回错误(ret);
}
让handle=unsafe{curl\u easy\u init()};
if handle.is_null(){
返回Err(2);//CURLE_失败_根据libcurl错误初始化(3)
}
//设置数据回调
让ret=不安全{
卷曲?轻松?设置选项(
手柄
CURLOPT_WRITEFUNCTION,
回调_writefunction为*mut c_void,
)
};
如果ret!=0{
返回错误(2);
}
//设置数据指针
让data_buf=Box::new(Vec::new());
将data_ptr=Box::放入_raw(data_buf);
让ret=不安全{
curl_easy_setopt(句柄、CURLOPT_WRITEDATA、数据_ptr as*mut std::ffi::c_void)
};
火柴网{
0=>Ok(Curl{handle,data_ptr}),
_=>错误(2),
}
}
pub fn set_url(&self,url:&str)->结果{
让url_cstr=std::ffi::CString::new(url.as_bytes()).unwrap();
让ret=不安全{
卷曲?轻松?设置选项(
自我处理,
卷发URL,
url_cstr.as_ptr()as*mut std::ffi::c_void,
)
};
火柴网{
0=>Ok(()),
x=>Err(x),
}
}
发布fn执行(&self)->结果{
让ret=unsafe{curl\u easy\u perform(self.handle)};
如果ret==0{
设b=unsafe{Box::from_raw(self.data_ptr)};
让数据=(*b.clone();
盒子::放入未加工(b);
Ok(字符串::from_utf8(data).unwrap())
}否则{
错误(ret)
}
}
}
fn main()->结果{
让我的_curl=curl::new().unwrap();
my_curl.set_url(“https://www.example.com")?;
my_curl.perform()和_then(| data | Ok(println!(“{}”,data)))
//为了简洁起见,本例中没有清理代码。
}
虽然这样做有效,但我发现令人惊讶的是
my_curl
不需要声明
mut
,因为没有任何方法使用
&mut self
,即使它们将
mut*
指针传递给FFI函数 美国

由于内部缓冲区被修改,我是否应该将
perform
的声明更改为使用
&mut self
而不是
&self
(出于安全考虑)?Rust并不强制执行这一点,但是Rust当然不知道缓冲区会被libcurl修改


这个小例子运行得很好,但我不确定在较大的程序中是否会遇到任何类型的问题,当编译器可能优化
Curl
结构上的不可变访问时,即使该结构的实例正在被修改,或者至少是指针指向的数据。

与流行的观点相反,在传递
*const
/
*mut
指针时,绝对没有任何由借阅检查程序引起的锈蚀限制。不需要这样做,因为取消引用指针本身是不安全的,只能在这样的块中完成,程序员手动验证所有必要的不变量。在您的情况下,您需要告诉编译器这是一个可变引用,正如您已经怀疑的那样


感兴趣的读者一定要读一读这本书,找出一些有趣的方法,用它射自己的脚。

传递指针总是正确的-取消引用需要
不安全的
。是的,你必须在这里自己标记为可变。@L.Riemer如果你将其作为答案发布,我将很乐意接受。