Help with 'static lifetime caused by fn pointer

Hi

I have a problem which I suppose is caused by the higher rank trait bounds. It's the beginning of a debug console for an embedded system. The commands (CommandItem and MenuDef) would be defined statically and readonly. Each command has a function pointer, whose argument is a more volatile generic Context. This is passed in by reference at runtime.

My problem is that as soon as the context contains a reference, I'm getting a complaint that the context reference is too short lived and needs to be 'static

I'm pretty sure (from looking at similar posts, like Argument requires that ... is borrowed for `'static` ) that this is caused by the bounds on the function pointer, but cannot see how to fix it.

Any help would be appreciated!

#![allow(unused_variables)]
#![allow(unused_imports)]
#![allow(dead_code)]
// #![deny(elided_lifetimes_in_paths)]

use std::marker::PhantomData;

type PhantomBound<'big, 'small> = PhantomData<&'small &'big ()>;

struct CommandItem<'a,'b,X> {
    pub function: fn(ctx: &'b mut X),
    pub name: &'a str
}
struct MenuDef<'a,'b,X> {
    pub name: &'a str,
    pub commands: &'a [&'a CommandItem<'a,'b,X>],
    pub submenus: &'a [MenuDef<'a,'b,X>]
}



impl<'a,'b,X> MenuDef<'a,'b,X> {

    pub fn run(self: &'a Self, text: &str, ctx: &'b mut X) -> Option<()> {
       // ...
       Option::Some(())
    }


}


fn main() {
    println!("Hello, world!");
}

#[cfg(test)]
mod tests {
    use std::marker::PhantomData;

    use crate::{CommandItem, MenuDef};

    #[test]
    fn test3() {
        struct MenuContext<'a> {
            stuff:&'a String
        }

        fn item1func(ctx: &mut MenuContext) { 
            println!("{}",ctx.stuff); 
        }

        const ITEM1: CommandItem<MenuContext> = CommandItem { 
            function: item1func, 
            name: "Item1"
        };

        const MENU1: MenuDef<MenuContext> = MenuDef { 
            name: "root", 
            commands: &[&ITEM1], 
            submenus: &[] 
        };

        let s1: String = "hello".into();

        let mut c: MenuContext = MenuContext { stuff: &s1 };
        assert_eq!(Option::Some(()),MENU1.run("Item1",&mut c));
        assert_eq!(Option::None,MENU1.run("Item2", &mut c));
    }


}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `s1` does not live long enough
  --> src/lib.rs:66:55
   |
64 |         let s1: String = "hello".into();
   |             -- binding `s1` declared here
65 |
66 |         let mut c: MenuContext = MenuContext { stuff: &s1 };
   |                                                       ^^^ borrowed value does not live long enough
67 |         assert_eq!(Option::Some(()),MENU1.run("Item1",&mut c));
   |                                     ------------------------- argument requires that `s1` is borrowed for `'static`
68 |         assert_eq!(Option::None,MENU1.run("Item2", &mut c));
69 |     }
   |     - `s1` dropped here while still borrowed

error[E0597]: `c` does not live long enough
  --> src/lib.rs:67:55
   |
66 |         let mut c: MenuContext = MenuContext { stuff: &s1 };
   |             ----- binding `c` declared here
67 |         assert_eq!(Option::Some(()),MENU1.run("Item1",&mut c));
   |                                     ------------------^^^^^^-
   |                                     |                 |
   |                                     |                 borrowed value does not live long enough
   |                                     argument requires that `c` is borrowed for `'static`
68 |         assert_eq!(Option::None,MENU1.run("Item2", &mut c));
69 |     }
   |     - `c` dropped here while still borrowed

error[E0499]: cannot borrow `c` as mutable more than once at a time
  --> src/lib.rs:68:52
   |
67 |         assert_eq!(Option::Some(()),MENU1.run("Item1",&mut c));
   |                                     -------------------------
   |                                     |                 |
   |                                     |                 first mutable borrow occurs here
   |                                     argument requires that `c` is borrowed for `'static`
68 |         assert_eq!(Option::None,MENU1.run("Item2", &mut c));
   |                                                    ^^^^^^ second mutable borrow occurs here

Some errors have detailed explanations: E0499, E0597.
For more information about an error, try `rustc --explain E0499`.
error: could not compile `playground` (lib test) due to 3 previous errors
warning: build failed, waiting for other jobs to finish...

fn(ctx: &'b mut X) is a function that can accept a mutable reference of exactly one lifetime, 'b. What you likely want is a function that can accept mutable references with multiple different lifetimes: for<'b> fn(ctx: &'b mut X).

The types of consts can only have 'static lifetimes, so you are constraining X to be : 'static. Define these with let instead.

Putting it all together:

#![allow(dead_code, unused_imports, unused_variables)]
#![warn(rust_2018_idioms)]

use std::marker::PhantomData;

type PhantomBound<'big, 'small> = PhantomData<&'small &'big ()>;

struct CommandItem<'a, X> {
    pub function: for<'b> fn(ctx: &'b mut X),
    pub name: &'a str,
}
struct MenuDef<'a, X> {
    pub name: &'a str,
    pub commands: &'a [&'a CommandItem<'a, X>],
    pub submenus: &'a [MenuDef<'a, X>],
}

impl<'a, X> MenuDef<'a, X> {
    pub fn run(self: &'a Self, text: &str, ctx: &mut X) -> Option<()> {
        // ...
        Option::Some(())
    }
}

fn main() {
    println!("Hello, world!");
}

#[cfg(test)]
mod tests {
    use std::marker::PhantomData;

    use crate::{CommandItem, MenuDef};

    #[test]
    fn test3() {
        struct MenuContext<'a> {
            stuff: &'a String,
        }

        fn item1func(ctx: &mut MenuContext<'_>) {
            println!("{}", ctx.stuff);
        }

        let ITEM1: CommandItem<'_, MenuContext<'_>> = CommandItem {
            function: item1func,
            name: "Item1",
        };

        let MENU1: MenuDef<'_, MenuContext<'_>> = MenuDef {
            name: "root",
            commands: &[&ITEM1],
            submenus: &[],
        };

        let s1: String = "hello".into();

        let mut c: MenuContext<'_> = MenuContext { stuff: &s1 };
        assert_eq!(Option::Some(()), MENU1.run("Item1", &mut c));
        assert_eq!(Option::None, MENU1.run("Item2", &mut c));
    }
}

Thanks, but the const definitions are quite important to me, I need to find a way to make it work with those. (It's a constrained ram system, and I might have quite a few menus in the end, and I want them in flash)

The compiler is free to duplicate consts as much as it likes. You should consider using a static instead.

In any case, you will need to get non-'static lifetimes out of the MenuDef and CommandItem types. In your code so far, the X type parameter is only ever set to MenuContext; to find a solution, it would help to understand all the potential types that X could be.

Yes, I suppose I mean static there for MENU1 and ITEM1.

The X generic could contain anything. I'm looking for a way for the MENU1 and ITEM1 to have static lifetime, but the context argument, which is passed into ...::run to have a shorter lifetime. It should only have to last as long as the run invocation,

&mut X is invariant in X, and your static needs to have a WhateverLifetimeCarrier<'static>. If you need to take something non-'static as the ctx parameter, it can't be &mut X. You need some other type -- a type that differs in (at least) the lifetime parameter.

It's not very meaningful to take "anything", you're presumably going to do something with that ctx parameter, so you need a way to know that's possible with the type of ctx. And because it can't be X, you'll presumably also need something to relate X to the input type.

A trait may be what you need to fill both of those roles.

trait Useful {
    type This<'a>;
}

impl<'a, X: Useful> MenuDef<'a, X> {
    pub fn run(self: &'a Self, text: &str, ctx: &mut X::This<'_>) -> Option<()> {
       Option::Some(())
    }
}

I'm still not sure if this will actually help you or not.

1 Like

By anything I mean that X is opaque to this code, its implementation is between whatever calls run and the functions pointed to by the CommandItem::function pointers. All this code needs to do is transport the mut reference between the two. The only reason X is a type parameter is because it has to be part of the signature of the function pointer, its never instantiated except by the caller, and the reference only needs to last through the call to run()

I'm not really getting why it matters that &mut X is invariant in X - this code doesn't actually use X other than to carry the mut reference.

It seems to me to be a similar problem to Argument requires that ... is borrowed for `'static` and to the scoped_threads thing mentioned there, but my understanding isn't great enough yet to be sure.

I suspect I'm still carrying too much C++ baggage in my head! Sorry if I'm being dense.

If X were covariant, &MENU1 -- which is a

&'any MenuDef<'static, MenuContext<'static>>
// This part is `X`    ^^^^^^^^^^^^^^^^^^^^

could coerce to a &'any MenuDef<'any, MenuContext<'any>>, and then you could call the run method with your locally borrowing mut c. But X is invariant because it's unsound to shorten a lifetime behind a &mut.

After walking through this again, it sort of feels like you really want X to be a "higher-ranked generic" (which is not something Rust has). You want CommandItem::function to function on "X<'_> with any lifetime" and the ctx parameter to similarly take "X<'_> with any lifetime"... I think.

That can be emulated, but it's not the most ergonomic thing ever.

Thanks. That solution is actually doing what I trying to get to in the first place. You are right, it doesn't seem very elegant, but it'll hold me until my understanding grows and I find a better overall pattern for doing it.

If all commands are defined statically, why are you taking parameterized lifetime references to them?

Command names should all be 'static str, the menu def should hold &'static CommandItem<'ctx>, and similarly for the submenus.

the issue with the first two compiler errors:

   Compiling playground v0.0.1 (/playground)
error[E0597]: `s1` does not live long enough
  --> src/lib.rs:66:55
   |
64 |         let s1: String = "hello".into();
   |             -- binding `s1` declared here
65 |
66 |         let mut c: MenuContext = MenuContext { stuff: &s1 };
   |                                                       ^^^ borrowed value does not live long enough
67 |         assert_eq!(Option::Some(()),MENU1.run("Item1",&mut c));
   |                                     ------------------------- argument requires that `s1` is borrowed for `'static`
68 |         assert_eq!(Option::None,MENU1.run("Item2", &mut c));
69 |     }
   |     - `s1` dropped here while still borrowed

error[E0597]: `c` does not live long enough
  --> src/lib.rs:67:55
   |
66 |         let mut c: MenuContext = MenuContext { stuff: &s1 };
   |             ----- binding `c` declared here
67 |         assert_eq!(Option::Some(()),MENU1.run("Item1",&mut c));
   |                                     ------------------^^^^^^-
   |                                     |                 |
   |                                     |                 borrowed value does not live long enough
   |                                     argument requires that `c` is borrowed for `'static`
68 |         assert_eq!(Option::None,MENU1.run("Item2", &mut c));
69 |     }
   |     - `c` dropped here while still borrowed

Is caused by trying to keep a reference to variables that are not static and dropped. in the case of s1, the string literal "hello" is static although its of type &str. if you defined the  string variable with the const or static keyword instead of the let keyword that should work. when you create the menucontext struct you pass in a &String which is a shared reference or barrow. it doesn't necessarily have to be static, but it does need to live longer than the menucontext. if the menucontext is going to live for a long time then you might want to give it a reference to a string labeled with the const or static keyword instead of the let keyword. further reading: https://doc.rust-lang.org/reference/items/static-items.html

&'static CommandItem<'ctx> is a type that cannot exist except when 'ctx is equal to 'static, which makes it useless.

2 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.