openlang

The dangling pointer warning went away, the pointer did not

Passing a name into a C++ library through a wrapper, and the first version was one line, CString::new(name).unwrap().as_ptr(). rustc warned right away that the temporary CString dies at the end of the statement and the pointer will dangle. Fine, fair. So I moved it into a small function that builds the CString in a local and returns as_ptr(), and the warning went away, and I almost relaxed, and then it hit me that nothing changed, the string now just dies at the end of the function instead of the statement.

use std::ffi::{c_char, CString};

fn name_ptr(s: &str) -> *const c_char {
    let c = CString::new(s).unwrap();
    c.as_ptr()
}

That compiles without a single warning. The lint catches the temporary in one expression and not a bound local that leaves the function, even though the help text on the warning itself says binding inside the function isn't enough when the pointer is returned. And if the C++ side also stores the pointer rather than just reading it during the call, then even a variable in the caller isn't enough, and I don't know yet which one it does, there's barely any documentation. For now I'm keeping the CString in the wrapper struct next to the handle so they live the same length, and I'll see what people say.

The conversation continues 1

Follow the reasoning
  1. Permalink

    Count the owners and it gets simpler. Storing it in the struct gives you one owner with the same lifetime as the handle, which is right if the library reads the pointer only while that handle is alive. It's wrong if the library keeps the pointer after the handle is closed, or frees it itself.

    If the library keeps it, hand over ownership explicitly. CString::into_raw gives up the Rust side, and one CString::from_raw on that same pointer, never two, takes it back when the library is done. One call out, one call back, and the pointer must never reach C's free, since the allocation came from Rust's allocator. If nothing in the library tells you when it's done, that's the question to answer before picking either.

Other sides of the same question

  1. getline hands you the buffer even when it fails

    POSIX getline allocates or grows the buffer through the pointer you pass, and the buffer belongs to the caller from the first…

    Replies 4c
  2. Interface references and object references to the same instance

    The lifetime mistake I meet most often in older Delphi code comes from mixing two ownership rules on one instance. A class th…

  3. Same sink call, a copy on one line and a move on the next

    Nim 2 decides copy or move per call site. Not per proc. Same argument, same proc. The first call copies, because a is still n…

    Replies 2nim