-
Notifications
You must be signed in to change notification settings - Fork 201
Explain why we should avoid static mutable variable and introduce interior mutability #390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
src/peripherals/singletons.md
Outdated
|
|
||
| But this has a few problems. It is a mutable global variable, and in Rust, these are always unsafe to interact with. These variables are also visible across your whole program, which means the borrow checker is unable to help you track references and ownership of these variables. | ||
|
|
||
| Up until Rust 2024, the usage of static **mutable** variable has been discouraged. Since Rust 2024, static mutable variable results in an error. Of course, there are legitimate use case of having a shared mutable state, it's just that Rust want you to really think about the scope of the mutability. We'll talk about this later when we talk about interior mutability. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When read literally, "Since Rust 2024, static mutable variable results in an error." is wrong.
A static mutable variable in itself is not an issue at all, if you are comfortable with unsafe code: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=a9bb1d76aa1ef7cb07442d890438cd7d
What's discouraged is using references to static mutable variables:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=7b96c2c8023526d86d8f9b619eda9846
And even there, it's not a hard error, but a deny lint, that can be overridden (#[allow(static_mut_refs)])
Perhaps make that "Since Rust 2024, references to static mutable variables are denied by default."?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in d918f02, thank you for the correction
|
|
||
| Of course, none of the above are safe. In order to get a proper interior mutability that is safe, we should implement `Sync` ourself and put in our synchronization primitive specific to the hardware such as locking the resource with atomic swap and guarding the code with interrupt-free section (disable interrupt, run some code, enable interrupt), also known as critical section. | ||
|
|
||
| You could check how [rust-console gba](https://github.com/rust-console/gba/blob/6a3fcc1ee6493a499af507f8394ee542500721b7/src/gba_cell.rs#L63) implement it for reference. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this example a bit too specific and complicated?
I don't have a better example at hand though. Not a big deal, if someone has a better suggestion, it can easily be replaced later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree, let's keep this for now
|
I very much like what you wrote, it explains the topic well and is easily understandable! What I did not yet check is how well this section fits into the book. I only read the diff in isolation, and the last time I read the book was a long time ago, so that should be judged by someone more familiar with the book. |
|
Bumping this as there doesn't seem to be any activity. Perhaps @jannic could pitch in if there's anything else we need? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work! Thank you so much!
I just did some wording adjustments and added some links.
Once that is settled, I would merge it.
|
|
||
| A `static` variable can be borrowed as `&foo` without much issue as it is just reading. | ||
|
|
||
| The problem occurs when we try to borrow is as mutable. Consider the following: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| The problem occurs when we try to borrow is as mutable. Consider the following: | |
| The problem occurs when we try to borrow it as mutable. Consider the following: |
|
|
||
| The question is, which one would run? It's not obviousy just looking at a code. Perhaps it depends on the order or execution, which would be guaranteed _if_ there's only one active execution at a time. | ||
|
|
||
| You may think that because you're in an embedded programming land and you only have single core, you don't have to think about race condition, but even with a single core CPU without making any thread, you may have interrupts and those interrupt requests may access the shared mutable variable `flag`. The issue here is that the compiler cannot prohibit you from taking multiple mutable reference to the shared mutable variable at compile time, especially since it does not know about the order of executions of threads or interrupts, for example. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| You may think that because you're in an embedded programming land and you only have single core, you don't have to think about race condition, but even with a single core CPU without making any thread, you may have interrupts and those interrupt requests may access the shared mutable variable `flag`. The issue here is that the compiler cannot prohibit you from taking multiple mutable reference to the shared mutable variable at compile time, especially since it does not know about the order of executions of threads or interrupts, for example. | |
| You may think that because you're in an embedded programming context and you only have a single core, you don't have to think about race conditions, but even with a single core CPU without making any threads, you may have interrupts and those interrupt requests may access the shared mutable variable `flag`. The issue here is that the compiler cannot prohibit you from taking multiple mutable reference to the shared mutable variable at compile time, especially since it does not know about the order of execution of threads or interrupts, for example. |
|
|
||
| You may think that because you're in an embedded programming land and you only have single core, you don't have to think about race condition, but even with a single core CPU without making any thread, you may have interrupts and those interrupt requests may access the shared mutable variable `flag`. The issue here is that the compiler cannot prohibit you from taking multiple mutable reference to the shared mutable variable at compile time, especially since it does not know about the order of executions of threads or interrupts, for example. | ||
|
|
||
| Also, the use of `static mut` variables _might_ be deprecated in the future. There's a strong indication of this and having the usage `deny` by default since Rust 2024 is one of them. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Also, the use of `static mut` variables _might_ be deprecated in the future. There's a strong indication of this and having the usage `deny` by default since Rust 2024 is one of them. | |
| Also, the use of `static mut` variables _might_ be deprecated in the future. An indication is that since Rust 2024, references to static mutable variables are lint-denied by default. |
Let's keep it more vague, since we do not know what will happen.
|
|
||
| Interior mutability is a way to "trick" the borrow checker to mutate a borrowed reference `&foo` (notice the lack of `&mut`). This is basically us saying to the compiler "I'm going to mutate the shared reference anyway, please don't stop me.". | ||
|
|
||
| Of course, mutating something that is borrowed in other places are undefined behaviors (UB), yet there is a way of doing so, using an `UnsafeCell`. As the name implies, you need to use the `unsafe` keyword when using it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Of course, mutating something that is borrowed in other places are undefined behaviors (UB), yet there is a way of doing so, using an `UnsafeCell`. As the name implies, you need to use the `unsafe` keyword when using it. | |
| Of course, mutating something that is borrowed in other places is undefined behavior (UB), yet there is a way of doing so, using an [`UnsafeCell`](https://doc.rust-lang.org/core/cell/struct.UnsafeCell.html). As the name implies, you need to use the `unsafe` keyword when using it. |
|
|
||
| ## So what is interior mutability? | ||
|
|
||
| Interior mutability is a way to "trick" the borrow checker to mutate a borrowed reference `&foo` (notice the lack of `&mut`). This is basically us saying to the compiler "I'm going to mutate the shared reference anyway, please don't stop me.". |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Interior mutability is a way to "trick" the borrow checker to mutate a borrowed reference `&foo` (notice the lack of `&mut`). This is basically us saying to the compiler "I'm going to mutate the shared reference anyway, please don't stop me.". | |
| Interior mutability is a way around the borrow checker to mutate a borrowed reference `&foo` (notice the lack of `&mut`). This is basically us saying to the compiler "I'm going to mutate the shared reference anyway, please don't stop me.". |
I think "trick" sounds a bit too alluring :) Since we do not want people to use unsafe more that really strictly necessary, I would make the wording slightly more neutral.
| ``` | ||
|
|
||
| where Sync does absolutely nothing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| where Sync does absolutely nothing. | |
| where `Sync` does absolutely nothing. |
|
|
||
| where Sync does absolutely nothing. | ||
|
|
||
| Another way is to use Rust's `SyncUnsafeCell` which is currently only available in nightly under the flag `#![feature(sync_unsafe_cell)]`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Another way is to use Rust's `SyncUnsafeCell` which is currently only available in nightly under the flag `#![feature(sync_unsafe_cell)]`. | |
| Another way is to use Rust's [`SyncUnsafeCell`](https://doc.rust-lang.org/core/cell/struct.SyncUnsafeCell.html) which is currently only available in nightly under the flag `#![feature(sync_unsafe_cell)]` as of this writing. |
| Another way is to use Rust's `SyncUnsafeCell` which is currently only available in nightly under the flag `#![feature(sync_unsafe_cell)]`. | ||
|
|
||
|
|
||
| Of course, none of the above are safe. In order to get a proper interior mutability that is safe, we should implement `Sync` ourself and put in our synchronization primitive specific to the hardware such as locking the resource with atomic swap and guarding the code with interrupt-free section (disable interrupt, run some code, enable interrupt), also known as critical section. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Of course, none of the above are safe. In order to get a proper interior mutability that is safe, we should implement `Sync` ourself and put in our synchronization primitive specific to the hardware such as locking the resource with atomic swap and guarding the code with interrupt-free section (disable interrupt, run some code, enable interrupt), also known as critical section. | |
| Of course, none of the above is safe. In order to get proper interior mutability that is safe, we should implement `Sync` ourselves and put in our synchronization primitives specific to the hardware such as locking the resource with atomic swap and guarding the code with an interrupt-free section (disable interrupt, run some code, enable interrupt), also known as [critical section](https://github.com/rust-embedded/critical-section). |
|
|
||
| Of course, none of the above are safe. In order to get a proper interior mutability that is safe, we should implement `Sync` ourself and put in our synchronization primitive specific to the hardware such as locking the resource with atomic swap and guarding the code with interrupt-free section (disable interrupt, run some code, enable interrupt), also known as critical section. | ||
|
|
||
| You could check how [rust-console gba](https://github.com/rust-console/gba/blob/6a3fcc1ee6493a499af507f8394ee542500721b7/src/gba_cell.rs#L63) implement it for reference. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| You could check how [rust-console gba](https://github.com/rust-console/gba/blob/6a3fcc1ee6493a499af507f8394ee542500721b7/src/gba_cell.rs#L63) implement it for reference. | |
| You could check how [rust-console gba](https://github.com/rust-console/gba/blob/6a3fcc1ee6493a499af507f8394ee542500721b7/src/gba_cell.rs#L63) implement it for reference (this example if fairly complex, though). |
|
|
||
| You could check how [rust-console gba](https://github.com/rust-console/gba/blob/6a3fcc1ee6493a499af507f8394ee542500721b7/src/gba_cell.rs#L63) implement it for reference. | ||
|
|
||
| Interior mutability and static mutability is a rather deep topic. I strongly recommend reading more about static mutable references from [the rust edition guide here](https://github.com/rust-lang/edition-guide/blob/master/src/rust-2024/static-mut-references.md). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Interior mutability and static mutability is a rather deep topic. I strongly recommend reading more about static mutable references from [the rust edition guide here](https://github.com/rust-lang/edition-guide/blob/master/src/rust-2024/static-mut-references.md). | |
| Interior mutability and static mutability is a rather deep topic. I strongly recommend reading more about static mutable references from [The Rust Edition Guide here](https://doc.rust-lang.org/edition-guide/rust-2024/static-mut-references.html). |
Closes #389