From b0c07d3bf6c1fa9030807c8503a8f9ec288ad3be Mon Sep 17 00:00:00 2001 From: Evans Jahja Date: Sun, 20 Apr 2025 10:27:49 +0900 Subject: [PATCH 1/4] modify singletons.md --- src/peripherals/singletons.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index 1f3a556e..d6748135 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -23,6 +23,8 @@ fn main() { 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 internal mutability. + ## How do we do this in Rust? Instead of just making our peripheral a global variable, we might instead decide to make a structure, in this case called `PERIPHERALS`, which contains an `Option` for each of our peripherals. @@ -128,6 +130,26 @@ fn main() { } ``` +But what is `SerialPort` anyway? Surely `SerialPort` needs to be a type that is public so that other parts of the program can import it. If we let `SerialPort` to just be an empty struct to mark ownership of data, other parts of the program might mistakenly create instance of it, which we don't want. Therefore, we must store at least one private field in it to ensure no one else could construct it, like so: + +```rust, ignore + +struct SerialPort(()); + +struct Peripherals { + serial: Option, +} +impl Peripherals { + fn take_serial(&mut self) -> SerialPort { + let p = replace(&mut self.serial, None); + p.unwrap() + } +} +static mut PERIPHERALS: Peripherals = Peripherals { + serial: Some(SerialPort(())), +}; +``` + ## Treat your hardware like data Additionally, because some references are mutable, and some are immutable, it becomes possible to see whether a function or method could potentially modify the state of the hardware. For example, From b2f2ebb39fbed9cf569a2535a212aaf702840e0d Mon Sep 17 00:00:00 2001 From: Evans Jahja Date: Sun, 20 Apr 2025 11:40:08 +0900 Subject: [PATCH 2/4] Add about using interior mutability --- src/peripherals/singletons.md | 2 +- src/peripherals/using-interior-mutability.md | 96 ++++++++++++++++++++ 2 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/peripherals/using-interior-mutability.md diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index d6748135..b278a7d6 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -23,7 +23,7 @@ fn main() { 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 internal mutability. +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. ## How do we do this in Rust? diff --git a/src/peripherals/using-interior-mutability.md b/src/peripherals/using-interior-mutability.md new file mode 100644 index 00000000..59791fd0 --- /dev/null +++ b/src/peripherals/using-interior-mutability.md @@ -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 = 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(UnsafeCell); + +unsafe impl Sync for SyncUnsafeCell {} + +``` + +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. + +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). From d918f02c37d2a487610f3f1aecb22e2f20209b9a Mon Sep 17 00:00:00 2001 From: Evans Jahja <1199566+EvansJahja@users.noreply.github.com> Date: Thu, 24 Jul 2025 12:57:16 +0900 Subject: [PATCH 3/4] Explain that references to static mutable reference is discouraged --- src/peripherals/singletons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/peripherals/singletons.md b/src/peripherals/singletons.md index b278a7d6..3dfddb02 100644 --- a/src/peripherals/singletons.md +++ b/src/peripherals/singletons.md @@ -23,7 +23,7 @@ fn main() { 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. +Since Rust 2024, references to static mutable variables are lint-denied by default. 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. ## How do we do this in Rust? From 49c57d637c1373df558cf6cb4f247d14dae74599 Mon Sep 17 00:00:00 2001 From: Evans Jahja <1199566+EvansJahja@users.noreply.github.com> Date: Sat, 3 Jan 2026 15:05:06 +0900 Subject: [PATCH 4/4] Apply suggestions (wording, links) Co-authored-by: Diego Barrios Romero --- src/peripherals/using-interior-mutability.md | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/peripherals/using-interior-mutability.md b/src/peripherals/using-interior-mutability.md index 59791fd0..29cac2fe 100644 --- a/src/peripherals/using-interior-mutability.md +++ b/src/peripherals/using-interior-mutability.md @@ -12,7 +12,7 @@ One consequence of a `static` variable is that it cannot be moved, as it lives _ 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: +The problem occurs when we try to borrow it as mutable. Consider the following: ```rust,ignore static mut flag: bool = false; @@ -35,15 +35,15 @@ fn 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. +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. -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. ## 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.". +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.". -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. The usage looks like this: @@ -73,7 +73,7 @@ Compared to `static mut`, now we make it obvious that what we are trying to do i 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: +We could ignore all of the safety in Rust 2024 by using a `SyncUnsafeCell`. If you want to implement it yourself, this is all you need to write: ```rust,ignore @@ -84,13 +84,13 @@ unsafe impl Sync for SyncUnsafeCell {} ``` -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)]`. +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. -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). -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). -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).