Lifetime variable declaration problem

The 1.23.0 (766bd11c8 2018-01-01) compiler on Debian Sid has forced me to have lifetime variable on a couple of structs, which is fine per se, except that it requires me to put one on a thread local static declaration:

static CONTROL_WINDOW: RefCell<Option<ControlWindow<'a>>> = RefCell::new(None)

but, of course, now complains that the lifetime variable is undefined. I suspect I have just missed something very simple, but how can I define this lifetime variable? (I have tried putting <'a> in random places but they all result in a parse error.)

Does it work with 'static?

In general annotations describe where the value was borrowed from. Since you're creating a new value, and not borrowing from anything that existed before, then you don't have a lifetime to declare.

It compiles with 'static but then there is no resolution of the lifetime constraints as the ControlWindow instance and the relate type instances. At least I don't think there is: I am still having understanding issues with lifetimes and the borrow checker and the needs of the application design.

Lifetimes in Rust need to be statically known at compile time from the syntax. It looks like you're trying to make one up later — that won't work.

RefCell upgrades temporary read-only access to temporary write access, but does nothing about the temporary scope.

If you can't know the lifetime ahead of time, then Rc/Arc is the construct for creating and destructing objects whenever. However, I'm afraid that the ControlWindow struct itself is a borrowed object, not something you can own, so it can't be put in Rc, and probably can't be safely put in thread-local storage either.

You may have to keep it on stack and pass in arguments. Or use unsafe raw pointer in thread-local storage.

1 Like

@kornel Thanks for the comments. If I can impose on you a little more, I think I am learning something significant for me here.

There will only ever be one instance of ControlWindow but this is a multi-threaded, but message passing, not shared data situation. It is seemingly not possible to pass around a reference to the ControlWindow object as that would require passing between threads and because a GTK type is involved, the necessary types are not implemented. Using thread local static data seemed wrong, and yet seems the only way of handling this, or maybe just drop the thread local stuff?

In the current code, the thread local static ControlWindow object should be an owned instance, it should not be borrowed. That you say it would be perhaps indicates something wrong with what I have here.

Control window<'a> is a type that contains a borrowed reference due to the lifetime attached. If that is your type, then try using owned types inside it (like Box, if you need a pointer).

If it's supposed to be on one thread, then you could use a dedicated thread for it and use mpsc channel to send messages to it.

Global objects can be put in a static field wrapped in Arc and Mutex.

Separate threads with mpsc channels is exactly what I have. :slight_smile:

The messages come in to a listener which then puts function calls on the GTK thread. The listener cannot know about the ControlWindow instance which is created in response to the start-application event. The ControlWindow instance has zero or more ControlWindowButton instances which have references to the ControlWindow. It is the backreference that started all this lifetime stuff.

I should put the code on GitHub or GitLab shouldn't I – I hadn't done this as yet as I suspect the code is really not very good Rust (it is me rewriting a C++ application).

Two-way references in Rust unfortunately require Rc to be manageable. Otherwise you'll run into a larger-scale version of "self-referential struct" limitation of the borrow checker.

Generally you should use references and lifetimes only for things that are limited to a stack frame. If the buttons aren't created and destroyed within a scope of some function call, then they shouldn't be stored as borrowed.

The GTK containers and widgets are effectively global data. I had assumed that having them on the heap would be a good idea in Rust as it is in C++. It seems though that managed pointers in Rust are very different to those in C++.

Global is fine. Just keep in mind that reference in Rust conceptually isn't a pointer. For heap pointers use Box or Rc.

I was trying to use Rc but then it got into lifetime issues. It sounds though as if move into thread local static is a good option, and Rc without loops another.

You shouldn't have got any lifetime issues with Rc. You can cheaply clone it instead of referencing it.

You may had issues with Rc<Somestruct<'a>>, and that is most likely because the struct uses a reference instead of Box or Rc. Structs with internal references are a very rare optimization.

When in doubt, don't use references for anything other than function arguments. Think of references not as pointers, but as temporary read/write locks on data.

The online book doesn't really cover Box and Rc that well. I'll try again with Rc and see if I can avoid the reverse reference – it was when I had Rc references in the structs that lifetimes appears to become an issue.

Thanks for the comments, most helpful in helping me think through this.