error: `line` does not live long enough (but I kno

2019-08-01 14:30发布

问题:

I am trying to make some kind of ffi to a library written in C, but got stuck. Here is a test case:

extern crate libc;
use libc::{c_void, size_t};

// this is C library api call
unsafe fn some_external_proc(_handler: *mut c_void, value: *const c_void,
                             value_len: size_t) {
    println!("received: {:?}" , std::slice::from_raw_buf(
             &(value as *const u8), value_len as usize));
}

// this is Rust wrapper for C library api
pub trait MemoryArea {
    fn get_memory_area(&self) -> (*const u8, usize);
}

impl MemoryArea for u64 {
    fn get_memory_area(&self) -> (*const u8, usize) {
        (unsafe { std::mem::transmute(self) }, std::mem::size_of_val(self))
    }
}

impl <'a> MemoryArea for &'a str {
    fn get_memory_area(&self) -> (*const u8, usize) {
        let bytes = self.as_bytes();
        (bytes.as_ptr(), bytes.len())
    }
}

#[allow(missing_copy_implementations)]
pub struct Handler<T> {
    obj: *mut c_void,
}

impl <T> Handler<T> {
    pub fn new() -> Handler<T> { Handler{obj: std::ptr::null_mut(),} }

    pub fn invoke_external_proc(&mut self, value: T) where T: MemoryArea {
        let (area, area_len) = value.get_memory_area();
        unsafe {
            some_external_proc(self.obj, area as *const c_void,
                               area_len as size_t)
        };
    }
}

// this is Rust wrapper user code
fn main() {
    let mut handler_u64 = Handler::new();
    let mut handler_str = Handler::new();

    handler_u64.invoke_external_proc(1u64); // OK
    handler_str.invoke_external_proc("Hello"); // also OK
    loop  {
        match std::io::stdin().read_line() {
            Ok(line) => {
                let key =
                    line.trim_right_matches(|&: c: char| c.is_whitespace());

                //// error: `line` does not live long enough
                // handler_str.invoke_external_proc(key)
            }
            Err(std::io::IoError { kind: std::io::EndOfFile, .. }) => break ,
            Err(error) => panic!("io error: {}" , error),
        }
    }
}

Rust playpen

I get "line does not live long enough" error if I uncomment line inside the loop. In fact, I realize that Rust is afraid that I could store short-living reference to a slice somewhere inside Handler object, but I quite sure that I wouldn't, and I also know, that it is safe to pass pointers to the external proc (actually, memory is immidiately copied at the C library side).

Is there any way for me to bypass this check?

回答1:

The problem is that you are incorrectly parameterizing your struct, when you really want to do it for the function. When you create your current Handler, the struct will be specialized with a type that includes a lifetime. However, the lifetime of line is only for the block, so there can be no lifetime for Handler that lasts multiple loop iterations.

What you want is for the lifetime to be tied to the function call, not the life of the struct. As you noted, if you put the lifetime on the struct, then the struct is able to store references of that length. You don't need that, so put the generic type on the function instead:

impl Handler {
    pub fn new() -> Handler { Handler{obj: std::ptr::null_mut(),} }

    pub fn invoke_external_proc<T>(&mut self, value: T) where T: MemoryArea {
        let (area, area_len) = value.get_memory_area();
        unsafe {
            some_external_proc(self.obj, area as *const c_void,
                               area_len as size_t)
        };
    }
}

Amended answer

Since you want to specialize the struct on a type, but don't care too much about the lifetime of the type, let's try this:

#[allow(missing_copy_implementations)]
pub struct Handler<T: ?Sized> {
    obj: *mut c_void,
}

impl<T: ?Sized> Handler<T> {
    pub fn new() -> Handler<T> { Handler{ obj: std::ptr::null_mut() } }

    pub fn invoke_external_proc(&mut self, value: &T) where T: MemoryArea {
        let (area, area_len) = value.get_memory_area();
        unsafe {
            some_external_proc(self.obj, area as *const c_void,
                               area_len as size_t)
        };
    }
}

Here, we allow the type to be unsized. Since you can't pass an unsized value as a parameter, we now have to take a reference instead. We also have to change the impl:

impl MemoryArea for str {
    fn get_memory_area(&self) -> (*const u8, usize) {
        let bytes = self.as_bytes();
        (bytes.as_ptr(), bytes.len())
    }
}


标签: rust