Value does not live enough or cannot return value referencing local variable `desktop_entry` returns a value referencing data owned by the current function

I'm struggling to compile this file.

The whole project can be viewed here:

There are 2 points of error, but first I want to solve the get_app_browser_ini() function.

If I remove the lifetime, it says on the return Some(entry);:

error[E0515]: cannot return value referencing local variable `desktop_entry`
  --> src/providers.rs:71:12
   |
61 |     let name: &str = desktop_entry.get("Name").unwrap().clone();
   |                      ------------- `desktop_entry` is borrowed here
...
71 |     return Some(entry);
   |            ^^^^^^^^^^^ returns a value referencing data owned by the current function

Even though I closed the name, icon and exec string.

If I specify a static lifetime (the compiler suggested, so I don't fully understand it), I get the error:

error[E0597]: `desktop_entry` does not live long enough
  --> src/providers.rs:61:22
   |
48 |     let desktop_entry = ini
   |         ------------- binding `desktop_entry` declared here
...
61 |     let name: &str = desktop_entry.get("Name").unwrap().clone();
   |                      ^^^^^^^^^^^^^ borrowed value does not live long enough
...
65 |     let entry: BrowserEntry<'static> = BrowserEntry {
   |                --------------------- type annotation requires that `desktop_entry` is borrowed for `'static`
...
72 | }
   | - `desktop_entry` dropped here while still borrowed

I don't know how to proceed or why the clone still maintains the ownership of from desktop_entry.

Here is the file providers.rs

use std::path::PathBuf;
use std::{fs, path::Path};

use ini::Ini;

const APP_DIR_SYSTEM: &str = "/usr/share/applications/";
const APP_DIR_USER: &str = "~/.local/share/applications/";
const DESKTOP_EXTENSION: &str = "desktop"; // Files that ends with .desktop
const WEB_BROWSER_CATEGORY: &str = "WebBrowser";
const USE_USER_APPS: bool = false;

#[derive(Debug)]
pub struct BrowserEntry<'a> {
    name: &'a str,
    icon: &'a str,
    exec: Option<&'a str>,
}

pub const CLIPBOARD_ENTRY: BrowserEntry<'_> = BrowserEntry {
    name: "Copy to clipboard",
    icon: "edit-copy-symbolic",
    exec: None,
};

fn get_app_desktop_paths(app_dir: &Path) -> Vec<PathBuf> {
    assert!(app_dir.is_dir());

    let mut desktop_entries: Vec<PathBuf> = Vec::new();

    for entry_result in fs::read_dir(app_dir).unwrap() {
        let entry: fs::DirEntry = entry_result.unwrap();
        let path = entry.path();

        if let Some(extension) = path.extension() {
            if extension == DESKTOP_EXTENSION {
                desktop_entries.push(path);
            }
        }
    }

    desktop_entries
}

fn get_app_browser_ini(path: &PathBuf) -> Option<BrowserEntry<'static>> {
    // println!("FILE: {:?}", path);
    let ini = Ini::load_from_file(path).expect("Error parsing desktop file");

    let desktop_entry = ini
        .section(Some("Desktop Entry"))
        .expect("No desktop.entry")
        .clone();
    // println!("{:#?}", desktop_entry);

    let _categories = desktop_entry.get("Categories").unwrap_or_default();
    let categories: Vec<&str> = _categories.split(';').collect();

    if categories.contains(&WEB_BROWSER_CATEGORY) {
        let name: &str = desktop_entry.get("Name").unwrap().clone();
        let icon: &str = desktop_entry.get("Icon").unwrap().clone();
        let exec: &str = desktop_entry.get("Exec").unwrap().clone();
    
        let entry: BrowserEntry = BrowserEntry {
            name: name,
            icon: icon,
            exec: Some(exec),
        };
        return Some(entry);
    }

    None // Not a Web Browser
}

fn get_browser_desktop_list() -> Vec<BrowserEntry<'static>> {
    let app_dir = Path::new(APP_DIR_SYSTEM);
    let mut file_paths = get_app_desktop_paths(&app_dir);

    if USE_USER_APPS {
        let app_dir_user = Path::new(APP_DIR_USER);
        let file_paths_user = get_app_desktop_paths(&app_dir_user);
        file_paths.extend(file_paths_user);
    }

    let browsers: Vec<BrowserEntry> = file_paths
        .iter()
        .map(get_app_browser_ini)
        .flatten()
        .collect();

    browsers
}

pub fn get_browsers_list() -> Vec<BrowserEntry<'static>> {
    let mut browsers: Vec<BrowserEntry> = get_browser_desktop_list();
    browsers.insert(0, CLIPBOARD_ENTRY);

    browsers
}

pub fn main_dev() {
    let browsers = get_browsers_list();
    println!("BROWSERS:\n{:#?}", browsers);
}

The compiler is trying to be helpful here... but the suggestion is a bit misleading. Basically, the only way to return a string from a function that fulfills the 'static lifetime is to return an owned string.

1 Like

Like, using String instead of &str? I started with it but it created a lot of errors.

The root cause of the problem is a design mistake of using temporary loans inside structs. Your BrowserEntry should contain String, rather than try to forbid storing any strings in it and instead hang on to local function variables, which will get destroyed.

By using &str in a return type you're saying "I'm not going to create any new strings, I'm not going to load any strings from a file, I'll just return you the strings that you already have and have loaned to me as arguments to my function". Which in your case is false. You're creating new strings, and String is for newly-created strings, &str is for giving temporary view permission to String that has already been stored somewhere in a larger scope.

1 Like

If I use Strings, I get an error declaring the constant:

pub const CLIPBOARD_ENTRY: BrowserEntry<'_> = BrowserEntry {
    name: "Copy to clipboard",
    icon: "edit-copy-symbolic",
    exec: None,
};

How would I fix it then?

name: String::from("Copy to clipboard").

You can also use Cow<'_, str> as a type that can hold either a string literal or a newly-created string.

In Rust string literals " " are a different type, because string literals are hardcoded into your program, so they don't need to be created or destroyed, because they exist from the beginning to the very end of your program. String can be created and destroyed. Rust uses types to track which values need to be destroyed and which must not be.

1 Like

This gives this error:

error[E0015]: cannot call non-const fn `<std::string::String as From<&str>>::from` in constants
  --> src/providers.rs:20:11
   |
20 |     name: String::from("Copy to clipboard"),
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: calls in constants are limited to constant functions, tuple structs and tuple variants

That was one of the reasons to avoid using String in the first place

Oh sorry, I've overlooked that it's a global constant. In that case use std::borrow::Cow::Borrowed("str"). This is an enum that also has Owned variant that can be assigned a newly-allocated String.

In the code Cow ends up acting as a boolean that tells Rust whether to deallocate the string or not when the Cow is dropped or overwritten.

1 Like

Like this?:

pub const CLIPBOARD_ENTRY: BrowserEntry<'_> = BrowserEntry {
    name: std::borrow::Cow::Borrowed("Copy to clipboard"),
    icon: std::borrow::Cow::Borrowed("edit-copy-symbolic"),
    exec: None,
};

It got the error:

error[E0308]: mismatched types
  --> src/providers.rs:20:11
   |
20 |     name: std::borrow::Cow::Borrowed("Copy to clipboard"),
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^- help: try using a conversion method: `.to_string()`
   |           |
   |           expected `String`, found `Cow<'_, str>`
   |
   = note: expected struct `std::string::String`
                found enum `Cow<'_, str>`

Use Cow<'static, str> in the struct definition. This way BrowserEntry will be a normal struct, without restriction of a a temporary lifetime attached to it.

2 Likes

Thanks @kornel . This solved, I think.

I'm still learning rust. This seems a little bit hackish. Is this usual for using strings or more like a hack?

Using owned types like String isn't hackish; learning what references are actually useful for is a common phase when learning Rust. Since you need dynamic values, you need String, or something that can hold a String, like Cow<'_, str>.[1]

Cow<'static, str> isn't a hack when used to represent string data that might be in a literal[2] or might be dynamically allocated, either. It's a reasonable choice when you want to support both of those in a single type for whatever reason.

In your case, you have the const and you can't allocate to create Strings in const context. Since you need Strings for other reasons, your reasonable choices are roughly...

  • Use the Cow<'static, str>
  • Give up on the const value and have a function to generate the value dynamically
  • Use a static LazyLock<_> instead of the const
    • and clone it where needed (perhaps using Arc<str> instead of String)
  • Have different types (perhaps via generic parameter) so you can keep the const
    • with dynamic conversion to the String case where needed

If you dislike the Cow, you can try out the alternatives.


  1. Or some other type that can hold dynamic data, like an Arc<str>. ↩︎

  2. or a leaked String ↩︎

3 Likes

When you have just started learning Rust, you are expected to use the owned version of strings: String. As you progress, you can move to the string slice (&str) and the Cow (Cow<'_, str>) versions of strings.

The limitation you faced today is that the usual ways to construct a String (Using the From and ToOwned traits) are non-const, therefore cannot be used in constants. That's why @kornel suggested to use the Borrowed variant of Cow instead.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.