But as soon as I do this, I start getting errors related to missing lifetime specifier. At this stage of learning Rust, I know next to little about lifetimes...
The other thing is that at the end of the day, both Task and Job will actually have to be created and used in separate threads. Meaning they will be structs across thread boundary that will have to make use of Utility. And I have a feeling dealing with shared things across threads has its own solution that might not involve having to fiddle with lifetimes.
So my question is, how do one go about this? Is sorting the lifetime errors the solution? or there are other ways, especially given that sharing across threads will be involved?
You are correct. wrapping in Arc seems to have worked. I am just now curious in what situations do one use lifetime annotations and in what situations to things like Arc come in handy?
Normal references (& and &mut) are primarily used for short-term access, like function arguments, because they have a slightly lower runtime overhead than something like Arc.
This brings severe restrictions on how they can be used, however, so storing them longer-term in a lifetime-annotated structure is more of an advanced, niche technique.
For completeness, to make the compiler happy with a reference in a case like this, you would have to write something like:
struct Job<'a> {
utility: &'a Utility
}
Note the added "lifetime parameter" 'a (where a can be any name, of course), that a caller may now provide. The meaning is that the Utility referenced by this Job will be alive for some lifetime, and it must be at least as long as as the lifetime provided for the type parameter 'a. The compiler will infer a value from usage, or you can provide a lifetime parameter from a function (with much the same syntax: fn name<'a>() {}), but either way different lifetimes will result in different types, the same as a type parameter T on Vec<T> being different makes a different type
The result is that these types are generally tricky to use across multiple functions, as you need to be careful to ensure they all end up with the same lifetime. This is not for the faint hearted, and generally doesn't result in faster code anyway, but sometimes it is useful to implement some nice easy to use API like MutexGuard, or in a library that's going to extra effort trying to avoid requiring allocations where it might matter to some users, such as serde
There's one other option, where the data will live for the entire program (not just as long as even main()!), where you can instead use 'static as the lifetime for the reference. This is mostly for literal strings, eg "foo" has the type &'static str, which can be named and used anywhere, since the string will never be destroyed.