I am currently learning rust and I have a problem I do not know how to solve.
I have a struct containing several owned members and a reference, which is supposed to point to one of these members. Here is some example code (minimal example with no real world purpose):
error[E0502]: cannot borrow `test` as immutable because it is also borrowed as mutable
--> src\main.rs:29:5
|
28 | test.set_a();
| ------------ mutable borrow occurs here
29 | test.print()
| ^^^^^^^^^^^^
| |
| immutable borrow occurs here
| mutable borrow later used here
I noticed, that if I would not have to specify the lifetime annotation in:
This is known as a self-referential struct, and there is no good way to do it in Rust so far -- it does not play well with the ownership and borrowing semantics. If you search this forum for the term, you'll find many threads.
In the first case, you're mutably (i.e. exclusively) borrowing the struct for the entire lifetime of the struct. This makes it useless -- unusable -- after the call.
In the second case, you can't create a &'long mut T by going through a &'short mut U, as you can create memory unsafety by doing so.
Finding some approach that doesn't involve a direct self-reference is recommended (an enum, an index or Range...).
No, it shouldn't. Where do you think the reference would point after the struct (with all of its fields, including the referenced one) is moved? Since moving doesn't change the lifetime parameters, if this kind of code were allowed to compile, it would trivially result in use-after-free. However, it's – correctly – rejected by the compiler, exactly because the lifetime annotations simply can't be assigned in a general enough way for borrowck to allow this. This is fundamental.
You should instead consider what you are trying to achieve (at the higher level). Likely, there is a correct (i.e., memory-safe) solution for that problem.
This couldn't ever work as this cite from Rustonomicon explains:
Every type must be ready for it to be blindly memcopied to somewhere else in memory. This means pure on-the-stack-but- still-movable intrusive linked lists are simply not happening in Rust (safely).
I just wish it was somewhere in more accessible place, coz deep investigation of complex parts of Rustonomicon is not something newbies read and this they make mistake #3 insanely often.
Note that Rustonomicon, basically, tells you, tantalizingly, that it is possible if you use unsafe, and, indeed, std::pinmakes it possible, but… that's just not something that you want to use when you are still learning Rust.
No, it's not problem of ownership and borrowing. They could have been adjusted, probably, if the whole thing made sense. But thing about function new in the topicstarter example: it first creates Test struct and the moves it to some other place. At this point you either have introduce move constructor and allow move to be non-trivial (like C++ did) or move object to the heap (like most GC-based language do).
First approach was deemed unfeasible (it's really hard to deal with constructors since they couldn't just return error and panic in the middle of function call is just too surprising) and box everything is not something you do in a system language. If you really need that, there's pinned box, but it requires unsafe and, in general, most definitely not something you try to learn Rust with.