Hi,i tried to compile that code:
let mut x = 10;
let x_ref = &mut x;
let y = x;
*x_ref = 5;
i got an error while compiling that code.
my question is, why i got that error? this is single thread code, what bad can happen?
Hi,i tried to compile that code:
let mut x = 10;
let x_ref = &mut x;
let y = x;
*x_ref = 5;
i got an error while compiling that code.
my question is, why i got that error? this is single thread code, what bad can happen?
Unfortunately the text quality of your post is not too good, so it was a bit difficult for me to read parts of it. But I partly agree, Rust's borrowing rules and restrictions of exclusive mutable access even in single-threaded code can initially appear a bit strong and restricting. When I started with Rust I had similar doubts -- later I summarizes some of the motivation here: Borrowing and references - Rust for C-Programmers
im sorry for my bad english m8, thanks for the answer im gonna read that, In short, my question is that it seems to me that the borrow checker is too biased in some points, and code that would run safely "from my point of view" will not compile because one of the borrow checker's rules is violated.
My feeling is, that typically this is not true. When Rust's borrow checker complains, it often had good reasons. And the borrow checker was relaxed already, e.g. with "non lexical lifetimes" allowing more freedom and re-borrowing, and I read that a new borrow checker is in development allowing even more freedom. But I agree, the fact that Rust does not like mutable shared access makes some kind of software a bit hard to write, e.g. GUIs -- I am watching the hard work of the Xilem authors.
The compiler would have to know or detect that you're running in a single threaded context though. That's not currently the case.
And if the compiler accepts or not a program based on this there needs to be some well specified rule it follows that can then be documented in a specification and checked for correctness, in the sense that someone would have to prove that if a program is accepted according to this rule then it's surely safe. That's tricky and a lot of work to do.
This is really a question of what program transformations are allowed. If you want to rule out this particular transformation while still allowing other useful transformations ("optimizations") that are enabled by the semantics of mutable references then you need some reasonable rule for that.
Note: this is a different concern than the multithreaded/singlethreaded one and both are valid ones.
The mutable reference used for each push call is no longer valid once the next push happens, so the program transformation you're thinking of cannot happen here.
You seem to think the compiler/borrow checker is concerned with code that will be unsafe to compile/run but that's the wrong mental model. They care about which programs are provably safe to run. Or in the other words they accept programs that follow some criteria and reject all the others. So you need to ask yourself why the compiler cannot prove your program to be ok, rather than what issue the compiler thinks is in your program.
I really liked what I read from your link, the chapter about borrowing answered several of my other questions, thank you very much!
Here. I have corrected your phrase for you. Question “can this program do something” is non-answerable for almost any something. It doesn't even matter what we ask — as long as question is non-trivial (in mathematical sense, not colloquial sense), the answer becomes undecidable, impossible to answer. For some programs answer would be “yes, this program can do something”, for some programs answer would be “no, this program can not do something”, but no matter what we would do any checker would have to have third class “I have no idea if that program would do something or not”.
That's where the core Rust philosophy begins: we very much want our program to do something “good” (like: work correctly) and not to do something “bad” (like: access memory after it's freed, read memory before it's written to, etc) – but it's not possible to write a program that may check if our program is “good” or “bad”! Not reliably, at least.
If compiler would “try to see if anything bad may ever happen” then it would, sometimes, accept programs that are “bad”, but compiler couldn't see it! That's not what people want. They want a compiler that accepts “good” programs and rejected “bad” programs, but such thing is simply impossible.
Thus the next best thing is to split our program in two:
unsafe Rust where thing that you want to do is trivial: just use mutable pointer and not mutable reference and compiler would accept your code — but, on the flip side, compiler no longer checks whether what you wrote is correct or not, that's your responsibility to guarantee that your program is “good”It's not “easier”. It's the only possibility.
I think that for your special case, i.e., copying the value of a Copy object when an exclusive reference exists, it could be possible to add a special exception to the compiler. But possible does not mean useful: how much would that complicate the compiler and for what? Reordering two lines of your program allow it to run and more complex code would be outside of the special casing anyway. What you perceive as a lack in the compiler is just a trade-off: not all safe code is accepted by the compiler but we don't need that: we need it to accept just enough safe code to make programming in Rust useful and practical.
Your answer sounds logical, i think you're right. thanks!
The &Cell type is the single-threaded &mut you want.
It doesn't even need the whole program to be single threaded. It just can't be moved between threads.
&mut in Rust means exclusive access (think of it as mutual exclusion, not mutability). It doesn't allow reading from elsewhere because it doesn't allow any other access, period.
In Rust it just happens that when you have exclusive access to something then you can do whatever you want with it, including mutation.
When access is shared, then usage is restricted and mutation may be possible in a limited way (Cell, Atomic) or not (like &[u8]).
The TL;DR to what is below: &mut _ being exclusive is a fundamental property of the language. Depending on that property goes deep into the compiler (including optimizations, which aren't necessarily predictable), and into the ecosystem (assumptions of unsafe). Making exceptions to such a fundamental property soundly is very difficult.
One way to think about this is to start from a higher level. Rust wants to prove things about your code and to be able to optimize it too. And one of the fundamental building blocks it has chosen for the language is that &mut _s are exclusive references. That building block is so fundamental that it is considered instant UB if you create an aliased &mut _ or manage to access the referent in some other way while the &mut _ is active.
Then all the parts of the compiler rely on that building block.[1] And the way they do so may surface in unpredictable, emergent ways. So thinking you can reason about what the hardware or compiler will or won't do when you violate the rules is almost always incorrect. Once you have UB (e.g. by violating the invariants of a &mut), all bets are off. (For example optimizations are not stupid, but they do get to assume the fundamental promises of the language are being upheld.)
With this viewpoint, one can still have conversations about if the building block was the best choice or not, et cetera. But those are then more conversations about language design, as opposed to reasoning about a specific code blurb.
Changing or creating an exception to how the fundamental building block works without creating UB in the ecosystem or introducing unsoundness in the language is
unsafe codeFor example, it's possible for unsafe code to temporarily violate a library invariant and then restore it later. The code can be written in such a way to use the exclusivity of &mut _ to ensure no uses that rely on the invariant happen before it is restored. Your suggested change would blow a hole in that approach.
unsafe code relies on it too ↩︎
Here's the canonical link:
If you want "look, it's single-threaded it's fine", use Cell in std::cell - Rust : it has zero overhead, and allows reading and writing Copy values through a &Cell<_>.