From 5082adc83d83be77985c49cc5e2aaa9a71148056 Mon Sep 17 00:00:00 2001 From: Caio Date: Mon, 28 Sep 2020 17:47:11 -0300 Subject: [PATCH] Expand guide-level --- text/2978-stack_based_vec.md | 96 +++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/text/2978-stack_based_vec.md b/text/2978-stack_based_vec.md index 0b3a4ebb38d..cb344c0ad1d 100644 --- a/text/2978-stack_based_vec.md +++ b/text/2978-stack_based_vec.md @@ -33,56 +33,76 @@ Just like `Vec`, `ArrayVec` is also a primitive vector where high-level structur # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Instead of relying on a heap-allocator, stack-based memory area is added and removed on-demand in a last-in-first-out (LIFO) order according to the calling workflow of a program. Let's illustrate an imaginary function `super()` that allocates 32 bits before calling another function named `sub()` that also allocates 32 bits of auxiliary memory: +`ArrayVec` is a container that encapsulates fixed size buffers. -```txt -0 bits 32 bits 64 bits +```rust +let mut v: ArrayVec = ArrayVec::new(); +let _ = v.push(1); +let _ = v.push(2); + +assert_eq!(v.len(), 2); +assert_eq!(v[0], 1); + +assert_eq!(v.pop(), Some(2)); +assert_eq!(v.len(), 1); + +v[0] = 7; +assert_eq!(v[0], 7); +v.extend([1, 2, 3].iter().copied()); -| | -> | | -> | | ---------- --------- --------- - | super | | sub | - --------- --------- - | super | - --------- +for element in &v { + println!("{}", element); +} +assert_eq!(v, [7, 1, 2, 3]); ``` -* Now `super()` returns and the exactly same thing happens in reverse order, dropping everything when out of scope. +Instead of relying on a heap-allocator, stack-based memory area is added and removed on-demand in a last-in-first-out (LIFO) order according to the calling workflow of a program. `ArrayVec` takes advantage of this predictable behavior to reserve an exactly amount of uninitialized bytes up-front and these bytes form a buffer where elements can be included dynamically. -```txt -64 bits 32 bits 0 bits +```rust +// `array_vec` can store up to 64 elements +let mut array_vec: ArrayVec = ArrayVec::new(); +``` +Of course, fixed buffers lead to inflexibility because unlike `Vec`, the underlying capacity can not expand at run-time and there will never be more than 64 elements in the above example. -| | -> | | -> | | ---------- --------- --------- -| sub | | super | ---------- --------- -| super | ---------- +```rust +// This vector can store up to 0 elements, therefore, nothing at all +let mut array_vec: ArrayVec = ArrayVec::new(); +let push_result = array_vec.push(1); +// Ooppss... Our push operation wasn't successful +assert!(push_result.is_err()); ``` -`ArrayVec` takes advantage of this predictable behavior to reserve an exactly amount of uninitialized bytes up-front and these bytes form a buffer where elements can be included dynamically. +A good question is: Should I use `core::collections::ArrayVec` or `alloc::collections::Vec`? Well, `Vec` is already good enough for most situations while stack allocation usually shines for small sizes. + +* Do you have a known upper bound? + +* How much memory are you going to allocate for your program? The default values of `RUST_MIN_STACK` or `ulimit -s` might not be enough. + +* Are you using nested `Vec`s? `Vec>` might be better than `Vec>`. + +Each use-case is different and should be pondered individually. In case of doubt, stick with `Vec`. + +For a more technical overview, take a look at the following operations: ```rust -fn main() { - // `array_vec` has a pre-allocated memory of 2048 bits that can store up to 64 decimals. - let mut array_vec: ArrayVec = ArrayVec::new(); +// `array_vec` has a pre-allocated memory of 2048 bits (32 * 64) that can store up +// to 64 decimals. +let mut array_vec: ArrayVec = ArrayVec::new(); - // Although reserved, there isn't anything explicitly stored yet - assert_eq!(array_vec.len(), 0); +// Although reserved, there isn't anything explicitly stored yet +assert_eq!(array_vec.len(), 0); - // Initializes the first 32 bits with a simple '1' decimal or - // 00000000 00000000 00000000 00000001 bits - array_vec.push(1); +// Initializes the first 32 bits with a simple '1' decimal or +// 00000000 00000000 00000000 00000001 bits +array_vec.push(1); - // Our vector memory is now split into a 32/2016 pair of initialized and - // uninitialized memory respectively - assert_eq!(array_vec.len(), 1); -} +// Our vector memory is now split into a 32/2016 pair of initialized and +// uninitialized memory respectively +assert_eq!(array_vec.len(), 1); ``` -Of course, fixed buffers lead to some inflexibility because unlike `Vec`, the underlying capacity can not expand at run-time and there will never be more than 64 elements in the above example. - # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -189,8 +209,6 @@ impl ArrayVec { } ``` -Since it is known at compile-time the upper capacity bound, the compiler is likely going to remove the conditional bounding checking of the newly - Meaningless, unstable and deprecated methods like `reserve` or `drain_filter` weren't considered. A concrete implementation is available at https://github.com/c410-f3r/stack-based-vec. # Drawbacks @@ -230,11 +248,11 @@ Should it be included in the prelude? ### Macros ```rust - // Instance with 1i32, 2i32 and 3i32 - let _: ArrayVec = array_vec![1, 2, 3]; +// Instance with 1i32, 2i32 and 3i32 +let _: ArrayVec = array_vec![1, 2, 3]; - // Instance with 1i32 and 1i32 - let _: ArrayVec = array_vec![1; 2]; +// Instance with 1i32 and 1i32 +let _: ArrayVec = array_vec![1; 2]; ``` # Future possibilities