I'm relatively new to Rust and still wrapping my head around the borrow checker. I've come across an interesting pattern that I'd like to get your thoughts on.
I know a common issue is that pure getters can be limiting because they take an &self reference, which can prevent further borrowing of the main struct. I've found a workaround where you return a tuple containing both the field you want and self.
This seems to make the borrow checker treat them as independent references. While the original self is locked, you get a new &self that isn't.
Here's a simplified example I made with linked lists:
(Note: This code is intentionally simplified and incomplete - it's just to demonstrate the concept.)
My question is: Is this a legitimate pattern in Rust, or am I essentially creating a hack that could lead to issues down the line?
I'd appreciate any insights or feedback on this approach. Thanks!
&mut can also be read as &exclusive; that is, if there exists a &mut to a value, it must be the only reference anywhere in the program. By returning (&mut Self, &mut LinkedList<T>), you make it possible to reach the new node by multiple paths. That is undefined behaviour, and the compiler is within its rights to summon nasal demons.
It's not even hard to use this to absolutely wreck your program: mem::replace(new_self, LinkedList::new(0)) and your new_node pointer is now invalid.
Using unsafe to avoid the borrow checker is one of those things where you pretty much need to have a comprehensive understanding of the rules before you even start. It's really easy to mess up, and even people who have worked on the compiler and standard library get it wrong some times. It's very much a "don't commit this without a full-page, iron-clad mathematical proof of correctness and 110% test coverage" situation.
And running Miri on the playground shows this is indeed undefined behaviour:
error: Undefined Behavior: trying to retag from <2418> for Unique permission at alloc1139[0x0], but that tag does not exist in the borrow stack for this location
--> /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mut_ptr.rs:740:57
|
740 | if self.is_null() { None } else { unsafe { Some(&mut *self) } }
| ^^^^^^^^^^
| |
| trying to retag from <2418> for Unique permission at alloc1139[0x0], but that tag does not exist in the borrow stack for this location
| this error occurs as part of retag at alloc1139[0x0..0x18]
|
= help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
= help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information
Let me tell you a tale first. You are not the first guy who invents something like this. There's another guy who did something similar, Nikolay Kim, author of very popular (still very popular!) package, Actix-Web who did something similar.
And if someone uses unsafe to “hide the crime”… that's just shouldn't be done. Period.
Mistakes do happen, but to deliverately break the core rust promise just like that… that's not something people tolerate.
Unique reference is unique. Period. Both compiler and, more importantly, readers of your program rely on your upholding that property. If you want to access something via two references simultaneously there's interior mutability (and safe wrappers around that).
Hey, I don't mean to sidetrack this since my question was already answered, but I have to say, that kind of behavior is pretty disheartening.
As engineers, our primary goal is to write code that meets specific requirements. Languages like Rust are just tools in our toolbox. We shouldn't be adhering to dogmas blindly. In my experience, following dogma without considering the context often leads to trouble.
If someone wrote a library that performs well, that's something to be praised, not criticized for not adhering strictly to Rust's conventions.
And being kicked out for that? This is the kind of elitism you don't want in a community...
The reaction was very much over the top from what I remember, but I agree that a huge part of Rust's attraction is the whole-hearted rejection of "function first, correctness later". Rust exists primarily to do better than what came before, and achieving that requires everyone to make an effort.
I once wrote a single line involving unsafe manipulation of a mutable reference in a project that no one other than myself was ever going to see. I had a half-page comment going over every possible way it could fail that I could find, and how I had made sure it wouldn't happen. Basically, when I work in Rust, I hold myself to a higher standard because I know everyone else is also holding themselves to a higher standard. I don't want to "let the side down".
An allegory: if an engineer builds a bridge and then talks about how they don't bother with safety standards (because they're elitist or too much trouble), then you bet your ass I'm not crossing any of their bridges. And I don't think they should be allowed to make bridges without big warning signs on them saying "caution: potential death trap".
Edit: to be clear, none of the above makes abuse or harassment okay, even if someone is deliberately writing broken unsafe code while twirling their moustache and cackling. I shouldn't need to say it, but it's the internet...
Edit 2: regarding the use of "allowed" above. I don't want that to come across as directly translating to Rust. I don't endorse people being denied access to compilers or other tools because they done a naughty with unsafe. What I mean is that if you write a library that for some reason has to expose unsafe interfaces, then those interfaces absolutely should be marked as unsafe. And if for some reason that isn't possible (though I can't think of where that would be the case), then the library needs to come with very prominent warnings.
The short answer is that you shouldn't use unsafe to circumvent the lifetime system. If you think you found a legitimate use case or a "correct solution", then you didn't.
As a beginner, you don't need unsafe. Just no. Forget it. Don't. All of the simple tasks you may possibly need to perform are already in the standard library or 3rd-party crates.