-
Notifications
You must be signed in to change notification settings - Fork 194
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
Open
EvansJahja
wants to merge
2
commits into
rust-embedded:master
Choose a base branch
from
EvansJahja:static_muts
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# Interior Mutability | ||
|
||
What's the matter of using `static mut` variables anyway? | ||
|
||
In Rust, whenever we have a variable `foo`, only one of the following may happen: | ||
|
||
* the variable `foo` is moved to another place. Subsequent reference of `foo` would be a compile failure as the value has been moved. | ||
* some number of `&foo` borrows, which allow read-only access. | ||
* a single (exclusive) `&mut foo` mutable borrow, which allow read/write. | ||
|
||
One consequence of a `static` variable is that it cannot be moved, as it lives _statically_ in memory, meaning it is in a fixed place. | ||
|
||
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: | ||
|
||
```rust,ignore | ||
static mut flag: bool = false; | ||
|
||
|
||
fn first() { | ||
if !flag { | ||
flag = true; | ||
println!("first"); | ||
} | ||
} | ||
|
||
fn second() { | ||
if !flag { | ||
flag = true; | ||
println!("second"); | ||
} | ||
} | ||
``` | ||
|
||
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. | ||
|
||
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. | ||
|
||
## 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.". | ||
|
||
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. | ||
|
||
The usage looks like this: | ||
|
||
```rust,ignore | ||
static flag: UnsafeCell<bool> = UnsafeCell::new(false); | ||
|
||
fn some_usage() { | ||
// Here we take a mutable __pointer__ out of `flag`, even though | ||
// it is a normal static variable without `mut`. | ||
|
||
let my_mutable_flag_ref: *mut bool = flag.get(); | ||
|
||
// what we have now is a mutable __pointer__, not a reference. | ||
// Now, it is obvious that we are dealing with unsafe behavior. | ||
|
||
unsafe { | ||
if *flag == false { | ||
*flag = true; | ||
println!("set the flag to true"); | ||
} | ||
} | ||
} | ||
|
||
``` | ||
|
||
Compared to `static mut`, now we make it obvious that what we are trying to do is unsafe and we promise to the compiler to be careful. | ||
|
||
One small lie here is that you cannot use `UnsafeCell` in a static variable that way because it does not implement `Sync`, which means it is not thread-safe (or interrupt safe for that matter). | ||
|
||
We could ignore all of the safety in Rust 2024 by using SyncUnsafeCell. If you want to implement it yourself, this is all you need to write: | ||
|
||
```rust,ignore | ||
|
||
#[repr(transparent)] | ||
pub struct SyncUnsafeCell<T>(UnsafeCell<T>); | ||
|
||
unsafe impl<T: Sync> Sync for SyncUnsafeCell<T> {} | ||
|
||
``` | ||
|
||
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)]`. | ||
|
||
|
||
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 commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this example a bit too specific and complicated? |
||
|
||
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). |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back 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.
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."?