Lifetime issue of Option<&'static str> method, Vec<[&str; 2]> parameter

I'm building an app that uses dialog boxes.
Most button press results are static, including menu boxes,
but I've recently been wanting a more dynamic menu box,
that brings one to a dialog box of choice.

const LIST_MAIN_MENU: [[&str; 2]; 3] = [
["Start installation", DBOX_USERNAME],
["Change keyboard layout", DBOX_KEYMAP_HOST],
["Quit", QUIT],
];

...
pub fn run(&mut self) -> Result<(), Error> {
    let mut current_box = Some(MAIN_MENU);
    let mut menu: Option<String>;
    loop {
        match current_box {
            Some(QUIT) => return Self::exit(self),
            ...
            Some(USERNAME) => current_box = Self::handle_dbox_username(self),
            ...
            Some(MAIN_MENU) => current_box = Self::handle_dbox_menu_config(self),
            ...
            }
        };
    }
...

fn handle_dbox_username(&mut self) -> Option<&'static str> {
    match Self::input_box(&TEXT_USERNAME, DEFAULT_USERNAME) {
        (Choice::yes, Some(input_text)) => {
             self.username = input_text;
             Some(DBOX_FULLNAME)
        }
        _ => Some(QUIT),
    }
}

...

fn handle_dbox_main_menu() -> Option<&'static str> {
    match Self::menu_box(&TEXT_MAIN_MENU, Self::get_list_menu(LIST_MAIN_MENU.to_vec())) {
    (Choice::Yes, Some(choice)) => {
        Self::get_menu_choice(LIST_MAIN_MENU.to_vec(), choice)        
    },
    _ => Some(QUIT),
}

...

fn get_menu_choice(list: Vec<[&str; 2]>, choice: String) -> Option<&'static str> {
    match list.iter().find(|[x,_]| x == &choice) {
        Some([_,y]) => Some(y),
        _ => None,
    }
}

Because I'm using 'linux dialog' for my app,
I've only been able to get the button press value and the value of the item label,
so I'm using the get_menu_choice method in order to look up the real result.
This results however into an issue:

error: lifetime may not live long enough
   --> src/app.rs:699:28
    |
697 | fn get_menu_choice(list: Vec<[&str; 2]>, choice: String) -> Option<&'static str> {
    |                               - let's call the lifetime of this reference `'1`
698 |           match list.iter().find([|x,_]| x == &choice) {
699 |                          Some([_,y]) => Some(y),
    |                                         ^^^^^^^ returning this value reguires that `'1 must outlive `'static`

I know I can solve this with using Option,
but I'd like to be able to return my dialog choice results with Option<&'static str>,
since all other dialog results do so as well.
How to solve this dilemma?

You can't get a &'static T out of an arbitrary &'a T. That is the very definition of a dangling pointer or a use-after-free. I don't know why you would even expect it to be possible in the first place.

If you want to get a reference valid for the static lifetime, then you can only get it from another such reference.

You have to either explicitly declare both the return type and the input as containing &'static str, or leave off the explicit 'static annotation from both (ie., the return type as well), and let type inference infer the lifetime at the call site. The latter solution would be considered more idiomatic, since there's nothing in the body of your get_menu_choice() function that would specifically require the 'static lifetime.


I am also puzzled as to why you are using Vec as the argument type; it's completely unnecessary. Just use a slice.

3 Likes

I'm still a beginner. It's my first rust app I'm writing.
Vec was the first thing I've heard of.
I'll look into slices first
and then try figuring out what you've said about static lifetimes.

Coming from python and other easy languages,
it's difficult to wrap my head around that concept.

Alright, so if I understand correctly,
since I'm not using 'static values only' for my main menu,
Option<String> must be used for both get_menu_choice()
and handle_dbox_main_menu(). There's no way around it.
I'm assuming that this is the correct course of action:

...
pub fn run(&mut self) -> Result<(), Error> {
    let mut current_box = Some(MAIN_MENU);
    let mut menu: Option<String>;
    loop {
        match current_box {
            Some(QUIT) => return Self::exit(self),
            ...
            Some(USERNAME) => current_box = Self::handle_dbox_username(self),
            ....
            Some(MAIN_MENU) => {
                menu = Self::handle_dbox_menu_config(self);
                current_box = menu.as_deref();
            }
        };
    }
...
fn handle_dbox_main_menu() -> Option<String> {
    match Self::menu_box(&TEXT_MAIN_MENU, Self::get_list_menu(LIST_MAIN_MENU))) {
    (Choice::Yes, Some(choice)) => {
        Self::get_menu_choice(LIST_MAIN_MENU, choice)        
    },
    _ => Some(String::from(QUIT)),
}

...

fn get_menu_choice(list: &[[&str; 2]]>, choice: String) -> Option<String> {
    match list.iter().find(|[x,_]| x == &choice) {
        Some([_,y]) => Some(y.to_string()),
        _ => None,
    }
}

Edit: crossed out stuff about const references. See quinedot's post further down in this thread for details.

A few notes on some of the other aspects of your code:

Don't put big arrays in constants. You should assume that a constant's value is copy+pasted in everywhere it is used[1]. For cases like this, you want to store a reference to the array.

// EDIT: Don't do this.  See top of this post.
// const LIST_MAIN_MENU: &[[&str; 2]] = &[ ... ];

Also note that you don't need to specify the length of slices. That leads me to my second point:

Don't use "symbolic strings". That is, don't do this:

const QUIT: &str = "QUIT";

In idiomatic Rust, you would instead do this:

enum Pages { DBoxUsername, DBoxKeymapHost, Quit }

Then, your list of menu items would look like:

const LIST_MAIN_MENU: &[(&str, Page)] = &[
    ("Start installation", Page::DBoxUsername),
    ("Change keyboard layout", Page::DBoxKeymapHost),
    ("Quit", Page::Quit),
];

This will take less memory, be faster, and when you match on a Page value, the compiler will warn you if you forget one.

Third, there's no reason I can see to use Self::exit(self) instead of the idiomatic self.exit().

Fourth: when you are taking something like a string or an array as an input to a function, you generally want to take it by-reference instead of by-value. What I mean is that instead of:

fn get_menu_choice(list: Vec<[&str; 2]>, choice: String) -> ... { ... }

You want to use:

fn get_menu_choice(list: &[[&str; 2]], choice: &str) -> ... { ... }

In the case of choice, the latter will work for any "string" type including &str, String, Box<str>, Rc<str>, and Arc<str>. The former will only work for exactly String, and if you don't have that, you'll have to do a conversion.


  1. The thing is, consts don't have any backing storage. So const X: i32 = 42; doesn't store 42 somewhere in memory, it inserts it literally everywhere you use X. However, if you do something like const X: &i32 = &i32; the use of a reference forces the compiler to store the value somewhere so it can take a reference to it. When doing this with big structures, this ensures the structure is defined and stored once, as opposed to once everywhere it's used. At least, that's how I understand it. Edit: the preceding is wrong. Top of this post has a link you should follow. ↩︎

3 Likes

No. That doesn't seem to be true at all, based on your first code snippet in your original post. You are only using consts and string literals. There seems to be no need for dynamically allocating Strings.

You can make either of the following two trivial modifications to your get_menu_choice() function, both of which compile:

/// with explicit lifetimes
fn get_menu_choice(list: Vec<[&'static str; 2]>, choice: String) -> Option<&'static str> {
    match list.iter().find(|[x,_]| x == &choice) {
        Some([_,y]) => Some(y),
        _ => None,
    }
}

/// with elided lifetimes
fn get_menu_choice(list: Vec<[&str; 2]>, choice: String) -> Option<&str> {
    match list.iter().find(|[x,_]| x == &choice) {
        Some([_,y]) => Some(y),
        _ => None,
    }
}
1 Like

No such thing is ensured. If you want to ensure only one copy anywhere, put it in a static. Otherwise the compiler can create a "backing storage" per use.[1]


  1. In reality it's probably less... but again, not ensured. ↩︎

4 Likes

While you’re of course absolutely right in correcting this point, I’d like to also add that there should still be a significant benefit to using a reference (to an array) in a constant: in the case of a const …: &T, even pessimistically, your one creation “per use” would count only the number of syntactic occurrences in code, not the number of executions of a/all usage(s) at run-time as it’s the case (at least before compiler optimizations) for a const …: T; and “creation” of the object (populating the array’s data, in this case) in its respective memory would happen at compile-time, not run-time.

Thank you very much for the correction. I've edited my post to hopefully not do any further damage.

Alright, so if I understand correctly this time around,
is that it's best to use enums instead of strings or strs
for symbolic values.

// dboxes, mboxes and exits
#[derive(Clone)]
enum Page {
    ..., NoBoxFound, ...
    ..., MainMenu, ...
    ..., Quit, ...
    ..., Username, ...
}

const LIST_MAIN_MENU: &[(&str, Page)] = &[
    ("Start installation", Page::Username), 
    ("Change keyboard layout", Page::KeymapHost),
    ("Quit", Page::Quit)
];
...
pub fn run(&mut self) -> Result<(), Error> {
    let mut current_box = Page::MainMenu;
    loop {
        match current_box {
            Some(QUIT) => return Self::exit(self),
            ...
            Some(USERNAME) => current_box = Self::handle_dbox_username(self),
            ...
            Some(MAIN_MENU) => current_box = Self::handle_dbox_menu_config(self),
            ...
            }
        };
    }
...

fn handle_dbox_main_menu(&mut self) -> Page {
        match Self::menu_box(BoxTypeMenu::Main, &TEXT_BOX_MAIN_MENU, 
            Self::get_list_menu(&LIST_MAIN_MENU)) {
            (Choice::Yes, Some(choice)) => {
                Self::get_menu_choice(&LIST_MAIN_MENU, &choice)
            },
            (Choice::Escape, Some(msg_txt)) => {
                match msg_txt.as_str() {
                    EMPTY => Page::Escape,
                    EMPTY_MENU_ERROR => Page::EmptyMenu,
                    _ => {
                        self.error_msg = msg_txt;
                        Page::UnknownError
                    },
                }
            },
            (Choice::Cancel, _) => Page::Quit,
            _ => Page::NoBoxFound,
        }
    }
...
    fn handle_dbox_username(&mut self) -> Page {
        ...
    }
...

    fn get_menu_choice(list: &[(&str, Page)], choice: &str) -> Page {
        match list.into_iter().find(|(x,_)| x == &choice) {
            Some((_,y)) => {
                y.clone()
            },
            _ => Page::NoBoxFound,
        }
    }

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.