Borrowing Local Variables In Function To Compute Result

I'm trying to break my code up into smaller functions. Here's one such function where I'm initializing a struct, but I'm using local variables as part of the process to build my resulting value:

fn build_app_info<'a>() -> vk::ApplicationInfoBuilder<'a> {
    let application_name = CString::new("vkFire").unwrap();
    let engine_name = CString::new("UnknownGameEngine").unwrap();

    vk::ApplicationInfo::builder()
        .application_name(&application_name)
        .application_version(vk::make_version(0, 0, 1))
        .engine_name(&engine_name)
        .engine_version(vk::make_version(0, 42, 0))
        .api_version(vk::make_version(1, 0, 106))
}

I end up getting an error because I can't borrow my local application_name or engine_name variables to build my result variable that I intend on returning. That does make sense in a degree, but how else should I go about this? I could pass those variables in as inputs to my function, but I'd prefer to avoid that at this point in time. I intentionally want that logic hard-coded.

Is there are way that I can declare lifetime specifiers on local variables?

No, you do not declare lifetimes. Lifetimes are deduced from the shape of the program, so to change lifetimes, you must change the shape of the program.

In this case, since the strings are hard-coded, you could use a reference into a compile-time constant.

fn build_app_info() -> vk::ApplicationInfoBuilder<'static> {
    let application_name = CStr::from_bytes_with_nul(b"vkFire\0").unwrap();
    let engine_name = CStr::from_bytes_with_nul(b"UnknownGameEngine\0").unwrap();

    vk::ApplicationInfo::builder()
        .application_name(application_name)
        .application_version(vk::make_version(0, 0, 1))
        .engine_name(engine_name)
        .engine_version(vk::make_version(0, 42, 0))
        .api_version(vk::make_version(1, 0, 106))
}

If the variables are not constants, you have two options:

  1. Pass them as arguments to the function.
  2. Use Box::leak to leak the memory containing them and produce a 'static builder.

I would probably prefer an API that takes ownership of the strings.

2 Likes

Ah, right. The compiler typically deduces lifetime relationships between a given function's inputs and outputs, but sometimes it needs help from us by explicitly providing lifetimes. Is that correct?

I tried making those local variables constants, but I was unable to since there's no way of knowing the exact size at compile-time. Ideally, I'd want to pass string slices into my function, but I have no idea how to convert a &str to a CStr like so:

fn build_app_info(app_name: &str, engine_name: &str) -> vk::ApplicationInfoBuilder<'static> {
    // ...
}

I tried Googling thinking that this would be a common need, but I couldn't find anything.

Lifetimes in the function arguments are deduced based on a set of rules called lifetime elision. In your case, the elision rules don't apply, so you should manually specify them:

fn build_app_info<'a>(app_name: &'a str, engine_name: &'a str) -> vk::ApplicationInfoBuilder<'a> {

The above lifetimes say that the returned builder contains references into both arguments. You could remove the 'a on one of them to say that it only contains references into one of them, or use 'static to say that the output doesn't point into any of them. This ambiguity is why you have to specify them in this case. The specification you posted with 'static is incorrect, because the builder does contain non-static references, namely references into the arguments.

However in your case there is a problem because there is no way to turn an &str into a CStr without allocating. This is because a &str does typically not have a null-byte at the end, which a CStr requires. This is why I explicitly included it in my earlier example, as the CStr cannot be constructed without the null byte.

I'll re-read through the elision rules again. Rust's data ownership model was beginning to click for me earlier this year when I started to really get into Rust, but then I dropped off of the project I was working on because of the day job... I was starting to understand lifetimes better too.

In your earlier post, you mentioned that I had 2 options if my variables aren't constants. By constants, did you mean the literal const declaration or did you mean immutable declaration via the let keyword like it is above? Does the solution you provided leak memory at all, or do application_name and engine_name release their internal vectors after they drop out of scope?

With constant string i mean hardcoded in the Rust source code.

The solution I provided doesn't allocate any memory in the first place, so it can't leak memory. Only a CString has an internal vector, not a CStr.