Why the compiler didn't catch this one? (pointer vs lifetime)

Hi,

consider this:

pub trait AsStr {
    fn as_str(&self) -> &str;

    fn to_utf16(&self) -> Vec<u16> {
        self.as_str().to_utf16()
    }

    fn to_pcwstr(&self) -> PCWSTR {
        PCWSTR(self.to_utf16().as_ptr())
    }
}

In to_pcwstr It seems that the Vec created by to_utf16() doesn't live long enough for the pointer created by as_ptr() to be valid.

I'm encountering non systematic failure calling some Windows API and I've tracked down the cause to this.

Did I found a bug or (more likely), what am I missing?

The lifetime of pointers is not checked by the compiler.

The docs for as_ptr mention this:

The caller must ensure that the vector outlives the pointer this function returns, or else it will end up pointing to garbage.

1 Like

In Rust, a str always stores UTF-8 data. Therefore, whatever crate provides the str::to_utf16() function must translate it into a new buffer. As a consequence, you cannot return a reference to the to_utf16() output from to_pcwstr(); you'll have to provide some kind of free function:

pub fn slice_as_pcwstr(slice: &[u16]) -> PCWSTR {
    PCWSTR(slice.as_ptr())
}

pub fn example(s: impl AsStr) {
    let buf: Vec<u8> = s.to_utf16();
    let pcwstr: PCWSTR = slice_as_pcwstr(&buf);
    some_api(pcwstr);
}
2 Likes

Silly me, thanks!

For things such as pointers which are not lifetime-tracked, I personally recommend using a scoped / callback pattern, so that programmers can easily view the area / scope of usability of the pointer:

    fn with_pcwstr<R>(&self, scope: impl FnOnce(PCWSTR) -> R) -> R {
        scope(PCWSTR(self.to_utf16().as_ptr()))
    }

so as to:

    let buf: Vec<u8> = s.to_utf16();
    buf.with_pcwstr(|pcwstr| {
        some_api(pcwstr);
    }) // <- `pcwstr` valid until here; beyond this point it's a dangling pointer
1 Like