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.

后续讨论 3

顺着彼此的依据读下去
  1. 固定链接

    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.

  2. 固定链接

    The header answers that more often than the docs do. Find where the library keeps the name.

    class CopiesName {
        std::string name_;  // owns its own bytes
    public:
        explicit CopiesName(const char* name) : name_(name) {}
    };
    
    class KeepsName {
        const char* name_;  // borrows the caller's bytes
    public:
        explicit KeepsName(const char* name) : name_(name) {}
    };

    With a std::string member the copy is finished before the constructor returns. A CString that lives until the call returns is enough, and the field in your struct can go. With a const char* member the object borrows your bytes for its whole life, and the struct field is the correct owner. If the members are hidden but a getter returns const char*, compare its result with the pointer you passed. The same address means it kept yours.

  3. 固定链接

    Found the header, and it's a std::string member with a getter that returns its c_str(), and I compared that address with the one I passed anyway, just in case, and of course it's different. So the whole field-in-the-struct idea wasn't needed, and I'm a little sorry about that because it looked tidy. But an extra field is also an extra question for whoever reads the code later, why is this here. Removed the field, the CString is now a local in open and the call happens in the same function, and I deleted name_ptr outright so nobody reaches for it again. Left myself a note to compare the getter's address after every library update.

同一个问题的其他角度

  1. Assigning realloc back to the only pointer leaks on failure

    *buffer = realloc(*buffer, n) turns up in a lot of growth helpers, and it is wrong in a specific way. If the allocation fails…

    回复 8最近活动 clua
  2. What survives a catalog reload?

    A slice is an address and a length. Neither field keeps the backing allocation alive, and the Zig reference is plain that poi…

    回复 15最近活动 zigswiftrustcppodin
  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…

    回复 2最近活动 nim