mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 16:48:10 -05:00
First draft of const
This commit is contained in:
parent
71c0be652d
commit
2ca50b0d24
@ -118,6 +118,7 @@ Each one is 4 bytes, for a total of 12 bytes. We can temporarily reserve space f
|
|||||||
variables because we know exactly how much space is needed.
|
variables because we know exactly how much space is needed.
|
||||||
- If you're looking at the assembly: `millis` is stored in `edi`,
|
- If you're looking at the assembly: `millis` is stored in `edi`,
|
||||||
`micros` is stored in `eax`, and `nanos` is stored in `ecx`.
|
`micros` is stored in `eax`, and `nanos` is stored in `ecx`.
|
||||||
|
The `eax` register is re-used to store the final result.
|
||||||
2. Because `MICROS_PER_MILLI` and `NANOS_PER_MICRO` are constants, the compiler never
|
2. Because `MICROS_PER_MILLI` and `NANOS_PER_MICRO` are constants, the compiler never
|
||||||
allocates memory, and just burns the constants into the final program.
|
allocates memory, and just burns the constants into the final program.
|
||||||
- Look for the `mov edi, 1000` and `mov ecx, 1000`.
|
- Look for the `mov edi, 1000` and `mov ecx, 1000`.
|
||||||
@ -128,22 +129,142 @@ was a bit silly though, so let's talk about the more interesting details.
|
|||||||
|
|
||||||
## **static** and **const**: Program Allocations
|
## **static** and **const**: Program Allocations
|
||||||
|
|
||||||
The first memory type we'll look at is pretty special; when Rust can prove that
|
The first memory type we'll look at is pretty special: when Rust can prove that
|
||||||
certain *references* are valid for the lifetime of the program (`static`,
|
a *reference* is valid for the lifetime of the program (`static`, not specifically
|
||||||
not specifically `'static`), and when certain *values* are the same for the lifetime
|
`'static`), and when a *value* is the same for the lifetime of the program (`const`).
|
||||||
of the program (`const`). Understanding the distinction between reference and value
|
Understanding the distinction between reference and value is important for reasons
|
||||||
is important; **`static` forces the Rust compiler to guarantee a unique reference
|
we'll go into below. The
|
||||||
to the declared expression, while `const` allows the compiler to make copies of the
|
[full specification](https://github.com/rust-lang/rfcs/blob/master/text/0246-const-vs-static.md)
|
||||||
expression wherever it chooses.**
|
for these two memory types is available, but I'd rather take a hands-on approach to the topic.
|
||||||
|
|
||||||
You can take a look at [the specification](https://github.com/rust-lang/rfcs/blob/master/text/0246-const-vs-static.md)
|
### **const**
|
||||||
if you want, but I'd rather take a hands-on approach to the topic.
|
|
||||||
|
The quick summary is this: `const` declares a read-only block of memory that is loaded
|
||||||
|
as part of your program binary (during the call to [exec(3)](https://linux.die.net/man/3/exec)).
|
||||||
|
Any `const` value resulting from calling a `const fn` is guaranteed to be materialized
|
||||||
|
at compile-time (meaning that access at runtime will not invoke the `const fn`),
|
||||||
|
even though the function is available at run-time as well. The compiler can choose to
|
||||||
|
copy the constant value wherever it is deemed practical. Getting the address of a `const`
|
||||||
|
value is legal, but not guaranteed to be the same even when referring to the same
|
||||||
|
named identifier.
|
||||||
|
|
||||||
|
The first point is a bit strange - "read-only memory". *Typically* in Rust you can use
|
||||||
|
"inner mutability" to modify things that aren't declared `mut`.
|
||||||
|
[`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) provides an API
|
||||||
|
to guarantee at runtime that some consistency rules are enforced:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
fn my_mutator(cell: &RefCell<u8>) {
|
||||||
|
// Even though we're given an immutable reference,
|
||||||
|
// the `replace` method allows us to modify the inner value.
|
||||||
|
cell.replace(14);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let cell = RefCell::new(25);
|
||||||
|
// Prints out 25
|
||||||
|
println!("Cell: {:?}", cell);
|
||||||
|
my_mutator(&cell);
|
||||||
|
// Prints out 14
|
||||||
|
println!("Cell: {:?}", cell);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8e4bea1a718edaff4507944e825a54b2)
|
||||||
|
|
||||||
|
When `const` is involved though, modifications are silently ignored:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
const CELL: RefCell<u8> = RefCell::new(25);
|
||||||
|
|
||||||
|
fn my_mutator(cell: &RefCell<u8>) {
|
||||||
|
cell.replace(14);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// First line prints 25 as expected
|
||||||
|
println!("Cell: {:?}", &CELL);
|
||||||
|
my_mutator(&CELL);
|
||||||
|
// Second line *still* prints 25
|
||||||
|
println!("Cell: {:?}", &CELL);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=88fe98110c33c1b3a51e341f48b8ae00)
|
||||||
|
|
||||||
|
And a second example using [`Once`](https://doc.rust-lang.org/std/sync/struct.Once.html):
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::sync::Once;
|
||||||
|
|
||||||
|
const SURPRISE: Once = Once::new();
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// This is how `Once` is supposed to be used
|
||||||
|
SURPRISE.call_once(|| println!("Initializing..."));
|
||||||
|
// Because `Once` is a `const` value, we never record it
|
||||||
|
// having been initialized the first time, and this closure
|
||||||
|
// will also execute.
|
||||||
|
SURPRISE.call_once(|| println!("Initializing again???"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Rust Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c3cc5979b5e5434eca0f9ec4a06ee0ed)
|
||||||
|
|
||||||
|
[Clippy](https://github.com/rust-lang/rust-clippy) will treat this behavior as an error if attempted,
|
||||||
|
but it's still something to be aware of.
|
||||||
|
|
||||||
|
The next thing to mention is that `const` values are loaded into memory *as part of your program binary*.
|
||||||
|
Because of this, any `const` values declared in your program will be "realized" at compile-time;
|
||||||
|
accessing them may trigger a main-memory lookup, but that's it.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
const CELL: RefCell<u32> = RefCell::new(24);
|
||||||
|
|
||||||
|
pub fn multiply(value: u32) -> u32 {
|
||||||
|
value * (*CELL.get_mut())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Compiler Explorer](https://godbolt.org/z/ZMjmdM)
|
||||||
|
|
||||||
|
The compiler only creates one `RefCell`, and uses it everywhere. However, that value
|
||||||
|
is fully realized at compile time, and is fully stored in the `.L__unnamed_1` section.
|
||||||
|
|
||||||
|
If it's helpful though, the compiler can choose to copy `const` values.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const FACTOR: u32 = 1000;
|
||||||
|
|
||||||
|
pub fn multiply(value: u32) -> u32 {
|
||||||
|
value * FACTOR
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn multiply_twice(value: u32) -> u32 {
|
||||||
|
value * FACTOR * FACTOR
|
||||||
|
}
|
||||||
|
```
|
||||||
|
-- [Compiler Explorer](https://godbolt.org/z/Qc7tHM)
|
||||||
|
|
||||||
|
In this example, the `FACTOR` value is turned into the `mov edi, 1000` instruction
|
||||||
|
in both the `multiply` and `multiply_twice` functions; the "1000" value is never
|
||||||
|
"stored" anywhere, as it's small enough to use directly.
|
||||||
|
|
||||||
|
Finally, getting the address of a `const` value is possible but not guaranteed
|
||||||
|
to be unique (given that the compiler can choose to copy values). In my testing
|
||||||
|
I was never able to get the compiler to copy a `const` value and get differing pointers,
|
||||||
|
but the specifications are clear enough: *don't rely on pointers to `const`
|
||||||
|
values being consistent*. To be frank, I have no idea why you'd ever care about
|
||||||
|
a pointer to `const`.
|
||||||
|
|
||||||
|
### **static**
|
||||||
|
|
||||||
Final note: `thread_local!()` is always a heap allocation.
|
Final note: `thread_local!()` is always a heap allocation.
|
||||||
|
|
||||||
## **push** and **pop**: Stack Allocations
|
## **push** and **pop**: Stack Allocations
|
||||||
|
|
||||||
The first
|
|
||||||
Example: Why doesn't `Vec::new()` go to the allocator?
|
Example: Why doesn't `Vec::new()` go to the allocator?
|
||||||
|
|
||||||
Questions:
|
Questions:
|
||||||
|
Loading…
Reference in New Issue
Block a user