mirror of
https://github.com/bspeice/speice.io
synced 2024-11-14 22:18:10 -05:00
114 lines
6.6 KiB
Markdown
114 lines
6.6 KiB
Markdown
---
|
|
layout: post
|
|
title: "Allocations in Rust"
|
|
description: "An introduction to the memory model."
|
|
category:
|
|
tags: [rust, understanding-allocations]
|
|
---
|
|
|
|
There's an alchemy of distilling complex technical topics into articles and videos that change the
|
|
way programmers see the tools they interact with on a regular basis. I knew what a linker was, but
|
|
there's a staggering amount of complexity in between
|
|
[the OS and `main()`](https://www.youtube.com/watch?v=dOfucXtyEsU). Rust programmers use the
|
|
[`Box`](https://doc.rust-lang.org/stable/std/boxed/struct.Box.html) type all the time, but there's a
|
|
rich history of the Rust language itself wrapped up in
|
|
[how special it is](https://manishearth.github.io/blog/2017/01/10/rust-tidbits-box-is-special/).
|
|
|
|
In a similar vein, this series attempts to look at code and understand how memory is used; the
|
|
complex choreography of operating system, compiler, and program that frees you to focus on
|
|
functionality far-flung from frivolous book-keeping. The Rust compiler relieves a great deal of the
|
|
cognitive burden associated with memory management, but we're going to step into its world for a
|
|
while.
|
|
|
|
Let's learn a bit about memory in Rust.
|
|
|
|
# Table of Contents
|
|
|
|
This series is intended as both learning and reference material; we'll work through the different
|
|
memory types Rust uses, and explain the implications of each. Ultimately, a summary will be provided
|
|
as a cheat sheet for easy future reference. To that end, a table of contents is in order:
|
|
|
|
- Foreword
|
|
- [Global Memory Usage: The Whole World](/2019/02/the-whole-world.html)
|
|
- [Fixed Memory: Stacking Up](/2019/02/stacking-up.html)
|
|
- [Dynamic Memory: A Heaping Helping](/2019/02/a-heaping-helping.html)
|
|
- [Compiler Optimizations: What It's Done For You Lately](/2019/02/compiler-optimizations.html)
|
|
- [Summary: What Are the Rules?](/2019/02/summary.html)
|
|
|
|
# Foreword
|
|
|
|
Rust's three defining features of
|
|
[Performance, Reliability, and Productivity](https://www.rust-lang.org/) are all driven to a great
|
|
degree by the how the Rust compiler understands memory usage. Unlike managed memory languages (Java,
|
|
Python), Rust
|
|
[doesn't really](https://words.steveklabnik.com/borrow-checking-escape-analysis-and-the-generational-hypothesis)
|
|
garbage collect; instead, it uses an
|
|
[ownership](https://doc.rust-lang.org/book/ch04-01-what-is-ownership.html) system to reason about
|
|
how long objects will last in your program. In some cases, if the life of an object is fairly
|
|
transient, Rust can make use of a very fast region called the "stack." When that's not possible,
|
|
Rust uses
|
|
[dynamic (heap) memory](https://en.wikipedia.org/wiki/Memory_management#Dynamic_memory_allocation)
|
|
and the ownership system to ensure you can't accidentally corrupt memory. It's not as fast, but it
|
|
is important to have available.
|
|
|
|
That said, there are specific situations in Rust where you'd never need to worry about the
|
|
stack/heap distinction! If you:
|
|
|
|
1. Never use `unsafe`
|
|
2. Never use `#![feature(alloc)]` or the [`alloc` crate](https://doc.rust-lang.org/alloc/index.html)
|
|
|
|
...then it's not possible for you to use dynamic memory!
|
|
|
|
For some uses of Rust, typically embedded devices, these constraints are OK. They have very limited
|
|
memory, and the program binary size itself may significantly affect what's available! There's no
|
|
operating system able to manage this
|
|
["virtual memory"](https://en.wikipedia.org/wiki/Virtual_memory) thing, but that's not an issue
|
|
because there's only one running application. The
|
|
[embedonomicon](https://docs.rust-embedded.org/embedonomicon/preface.html) is ever in mind, and
|
|
interacting with the "real world" through extra peripherals is accomplished by reading and writing
|
|
to [specific memory addresses](https://bob.cs.sonoma.edu/IntroCompOrg-RPi/sec-gpio-mem.html).
|
|
|
|
Most Rust programs find these requirements overly burdensome though. C++ developers would struggle
|
|
without access to [`std::vector`](https://en.cppreference.com/w/cpp/container/vector) (except those
|
|
hardcore no-STL people), and Rust developers would struggle without
|
|
[`std::vec`](https://doc.rust-lang.org/std/vec/struct.Vec.html). But with the constraints above,
|
|
`std::vec` is actually a part of the
|
|
[`alloc` crate](https://doc.rust-lang.org/alloc/vec/struct.Vec.html), and thus off-limits. `Box`,
|
|
`Rc`, etc., are also unusable for the same reason.
|
|
|
|
Whether writing code for embedded devices or not, the important thing in both situations is how much
|
|
you know _before your application starts_ about what its memory usage will look like. In embedded
|
|
devices, there's a small, fixed amount of memory to use. In a browser, you have no idea how large
|
|
[google.com](https://www.google.com)'s home page is until you start trying to download it. The
|
|
compiler uses this knowledge (or lack thereof) to optimize how memory is used; put simply, your code
|
|
runs faster when the compiler can guarantee exactly how much memory your program needs while it's
|
|
running. This series is all about understanding how the compiler reasons about your program, with an
|
|
emphasis on the implications for performance.
|
|
|
|
Now let's address some conditions and caveats before going much further:
|
|
|
|
- We'll focus on "safe" Rust only; `unsafe` lets you use platform-specific allocation API's
|
|
([`malloc`](https://www.tutorialspoint.com/c_standard_library/c_function_malloc.htm)) that we'll
|
|
ignore.
|
|
- We'll assume a "debug" build of Rust code (what you get with `cargo run` and `cargo test`) and
|
|
address (pun intended) release mode at the end (`cargo run --release` and `cargo test --release`).
|
|
- All content will be run using Rust 1.32, as that's the highest currently supported in the
|
|
[Compiler Exporer](https://godbolt.org/). As such, we'll avoid upcoming innovations like
|
|
[compile-time evaluation of `static`](https://github.com/rust-lang/rfcs/blob/master/text/0911-const-fn.md)
|
|
that are available in nightly.
|
|
- Because of the nature of the content, being able to read assembly is helpful. We'll keep it
|
|
simple, but I [found](https://stackoverflow.com/a/4584131/1454178) a
|
|
[refresher](https://stackoverflow.com/a/26026278/1454178) on the `push` and `pop`
|
|
[instructions](http://www.cs.virginia.edu/~evans/cs216/guides/x86.html) was helpful while writing
|
|
this.
|
|
- I've tried to be precise in saying only what I can prove using the tools (ASM, docs) that are
|
|
available, but if there's something said in error it will be corrected expeditiously. Please let
|
|
me know at [bradlee@speice.io](mailto:bradlee@speice.io)
|
|
|
|
Finally, I'll do what I can to flag potential future changes but the Rust docs have a notice worth
|
|
repeating:
|
|
|
|
> Rust does not currently have a rigorously and formally defined memory model.
|
|
>
|
|
> -- [the docs](https://doc.rust-lang.org/std/ptr/fn.read_volatile.html)
|