&mut references are exclusive and non-copyable, so the hot potato approach can even be used within their scope.
But the problem in Rust is that threads can unwind/exit at any time, invalidating buffers living on the stack, and io_uring may use the buffer for longer than the thread lives.
The borrow checker only checks what code is doing, but doesn't have power to alter runtime behavior (it's not a GC after all), so it only can prevent io_uring abstractions from getting any on-stack buffers, but has no power to prevent threads from unwinding to make on-stack buffer safe instead.
In my case, I have code that essentially looks like this:
struct Parser {
state: ParserState
}
struct Subparser {
state: ParserState
}
impl Parser {
pub fn parse_something(&mut self) -> Subparser {
Subparse { state: self.state } // NOTE: doesn't work
}
}
impl Drop for Subparser {
fn drop(&mut self) {
parser.state = self.state; // NOTE: really doesn't work
}
}
Okay, I can make the first line work by changing Parser.state to be an Option<ParserState> instead and using Option::take (or std::mem::replace on a custom enum; going from an &mut T to a T is possible in a number of ways). But how do I give Subparser the ability to give its ParserState back to the original parser? If I could make Subparser take a lifetime and just have a pointer to Parser.state, I wouldn't even bother with half of this setup because I would just reach into the Parser directly, but that's not an option in this case. (The safe Rust option I eventually reached for is a oneshot channel, which is actually a lot of overhead for this case).It's the give-back portion of the borrow-to-give-back pattern that ends up being gnarly. I'm actually somewhat disappointed that the Rust ecosystem has in general given up on trying to build up safe pointer abstractions in the ecosystem, like doing use tracking for a pointed-to object. FWIW, a rough C++ implementation of what I would like to do is this:
template <typename T> class HotPotato {
T *data;
HotPotato<T> *borrowed_from = nullptr, *given_to = nullptr;
public:
T *get_data() {
// If we've given the data out, we can't use it at the moment.
return given_to ? nullptr : data;
}
std::unique_ptr<HotPotato<T>> borrow() {
assert(given_to == nullptr);
auto *new_holder = new HotPotato();
new_holder->data = data;
new_holder->borrowed_from = this;
given_to = new_holder;
}
~HotPotato() {
if (given_to) {
given_to->borrowed_from = borrowed_from;
}
if (borrowed_from) {
borrowed_from->given_to = given_to;
} else {
delete data;
}
}
};
Damage is a funny word here. Yes - money was lost, but no building were destroyed, nor people physically harmed. “Actual damage” makes it sound like a lot more than lost time and a few extra contracts paid out.
> “Do you understand what I'm saying?" shouted Moist. "You can't just go around killing people!"
> "Why Not? You Do." The golem lowered his arm.
> "What?" snapped Moist. "I do not! Who told you that?"
> "I Worked It Out. You Have Killed Two Point Three Three Eight People," said the golem calmly.
> "I have never laid a finger on anyone in my life, Mr Pump. I may be–– all the things you know I am, but I am not a killer! I have never so much as drawn a sword!"
> "No, You Have Not. But You Have Stolen, Embezzled, Defrauded And Swindled Without Discrimination, Mr Lipvig. You Have Ruined Businesses And Destroyed Jobs. When Banks Fail, It Is Seldom Bankers Who Starve. Your Actions Have Taken Money From Those Who Had Little Enough To Begin With. In A Myriad Small Ways You Have Hastened The Deaths Of Many. You Do Not Know Them. You Did Not See Them Bleed. But You Snatched Bread From Their Mouths And Tore Clothes From Their Backs. For Sport, Mr Lipvig. For Sport. For The Joy Of The Game.”