Skip to content

Commit

Permalink
Address some comments
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r committed Sep 28, 2020
1 parent a4dba69 commit 91df9aa
Showing 1 changed file with 139 additions and 23 deletions.
162 changes: 139 additions & 23 deletions text/2978-stack_vec.md → text/2978-stack_based_vec.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
- Feature Name: `stack_vec`
- Feature Name: `stack_based_vec`
- Start Date: 2020-09-27
- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)

# Summary
[summary]: #summary

This RFC proposes the creation of a new "growable" vector named `StackVec` that manages stack memory and can be seen as an alternative for the built-in structure that handles heap-allocated memory, aka `alloc::vec::Vec<T>`.
This RFC, which depends and takes advantage of the upcoming stabilization of constant generics (min_const_generics), tries to propose the creation of a new "growable" vector named `ArrayVec` that manages stack memory and can be seen as an alternative for the built-in structure that handles heap-allocated memory, aka `alloc::vec::Vec<T>`.

# Motivation
[motivation]: #motivation

`core::collections::StackVec<T>` has several use-cases and should be conveniently added into the standard library due to its importance.
`core::collections::ArrayVec<T>` has several use-cases and should be conveniently added into the standard library due to its importance.

### Unification

Expand All @@ -23,15 +23,12 @@ Stack-based allocation is generally faster than heap-based allocation and can be

### Building block

Just like `Vec`, `StackVec` is a primitive vector where high-level structures can use it as a building block. For example, a stack-based matrix or binary heap.
Just like `Vec`, `ArrayVec` is also a primitive vector where high-level structures can use it as a building block. For example, a stack-based matrix or binary heap.

### Useful in the real world

`arrayvec` is one of the most downloaded project of `crates.io` and is used by thousand of projects, including Rustc itself.

### Compiler internals

Unstable features can be used internally to make operations more efficient, e.g., `specialization`.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
Expand Down Expand Up @@ -64,23 +61,23 @@ Instead of relying on a heap-allocator, stack-based memory area is added and rem
---------
```

`StackVec` 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.
`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.

```rust
fn main() {
// `stack_vec` has a pre-allocated memory of 2048 bits that can store up to 64 decimals.
let mut stack_vec: StackVec<i32, 64> = StackVec::new();
// `array_vec` has a pre-allocated memory of 2048 bits that can store up to 64 decimals.
let mut array_vec: ArrayVec<i32, 64> = ArrayVec::new();

// Although reserved, there isn't anything explicitly stored yet
assert_eq!(stack_vec.len(), 0);
assert_eq!(array_vec.len(), 0);

// Initializes the first 32 bits with a simple '1' decimal or
// 00000000 00000000 00000000 00000001 bits
stack_vec.push(1);
array_vec.push(1);

// Our vector memory is now split into a 32/2016 pair of initialized and
// uninitialized memory respectively
assert_eq!(stack_vec.len(), 1);
assert_eq!(array_vec.len(), 1);
}
```

Expand All @@ -89,21 +86,140 @@ Of course, fixed buffers lead to some inflexibility because unlike `Vec`, the un
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

The most natural module for `StackVec` is `core::collections` with an API that basically mimics most of the current `Vec<T>` surface.
`ArrayVec` is a contiguous memory block where elements can be collected, threfore, a collection by definition and even though `core::collections` doesn't exist, it is the most natural modulo placement.

The API basically mimics most of the current `Vec` surface with some tweaks and additional methods to manage capacity. Notably, these additional methods are fallible versions of some well-known functions like `insert` that will return `Result` instead of panicking at run-time.

```rust
// core::collections

pub struct StackVec<T, const N: usize> {
data: MaybeUninit<[T, N]>,
pub struct ArrayVec<T, const N: usize> {
data: MaybeUninit<[T; N]>,
len: usize
}

impl<T, const N: usize> StackVec<T, N> {
// Much of the `Vec` API goes here
impl<T, const N: usize> ArrayVec<T, N> {
// Constructors

pub unsafe fn from_raw_parts(_ptr: *mut T, _len: usize) -> Self;

#[inline]
pub const fn new() -> Self;

// Methods

#[inline]
pub fn as_mut_ptr(&mut self) -> *mut T;

#[inline]
pub fn as_mut_slice(&mut self) -> &mut [T];

#[inline]
pub fn as_ptr(&self) -> *const T;

#[inline]
pub fn as_slice(&self) -> &[T];

#[inline]
pub const fn capacity(&self) -> usize;

pub fn clear(&mut self);

pub fn dedup(&mut self);

pub fn dedup_by<F>(&mut self, same_bucket: F)
where
F: FnMut(&mut T, &mut T) -> bool;

pub fn dedup_by_key<F, K>(&mut self, key: F)
where
F: FnMut(&mut T) -> K,
K: PartialEq<K>;

pub fn drain<R>(&mut self, range: R)
where
R: RangeBounds<usize>;

pub fn extend_from_cloneable_slice(&mut self, other: &[T])
where
T: Clone; // Panics at run-time

pub fn extend_from_copyable_slice(&mut self, other: &[T])
where
T: Copy; // Panics at run-time

pub fn insert(&mut self, idx: usize, element: T); // Panics at run-time

#[inline]
pub const fn is_empty(&self) -> bool;

#[inline]
pub const fn len(&self) -> usize;

pub fn pop(&mut self) -> T; // Panics at run-time

pub fn push(&mut self, element: T); // Panics at run-time

pub fn remove(&mut self, idx: usize) -> T; // Panics at run-time

pub fn retain<F>(&mut self, f: F)
where
F: FnMut(&mut T) -> bool;

#[inline]
pub unsafe fn set_len(&mut self, len: usize);

pub fn splice<R, I>(&mut self, range: R, replace_with: I)
where
I: IntoIterator<Item = T>,
R: RangeBounds<usize>;

pub fn split_off(&mut self, at: usize) -> Self;

pub fn swap_remove(&mut self, idx: usize) -> T; // Panics at run-time

pub fn truncate(&mut self, len: usize);

pub fn try_extend_from_cloneable_slice(&mut self, other: &[T]) -> Result<(), ArrayVecError>
where
T: Clone;

pub fn try_extend_from_copyable_slice(&mut self, other: &[T]) -> Result<(), ArrayVecError>
where
T: Copy;

pub fn try_insert(&mut self, _idx: usize, element: T) -> Result<(), ArrayVecError>;

pub fn try_pop(&mut self) -> Result<(), ArrayVecError>;

pub fn try_push(&mut self, element: T) -> Result<(), ArrayVecError>;

pub fn try_remove(&mut self, idx: usize) -> Result<(), ArrayVecError>;

pub fn try_swap_remove(&mut self, idx: usize) -> Result<(), ArrayVecError>;
}

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
pub enum ArrayVecError {
CapacityOverflow,
NoElement
}

impl fmt::Display for ArrayVecError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match *self {
Self::CapacityOverflow => "It is not possible to add more elements",
Self::NoElement => "There are no elements in the vector",
};
write!(f, "{}", s)
}
}
```

Meaningless, unstable and deprecated methods like `reserve` or `drain_filter` weren't considered and in general, everything that includes or removes elements have a fallible version: `extend_from_cloneable_slice`, `extend_from_copyable_slice`, `pop`, `remove` and `swap_remove`.

A concrete implementation is available at https://github.com/c410-f3r/stack-based-vec.

# Drawbacks
[drawbacks]: #drawbacks

Expand Down Expand Up @@ -132,7 +248,7 @@ As seen, there isn't an implementation that stands out among the others because

### Nomenclature

`StackVec` will probably avoid conflicts with existing crates but another name might be a better option.
`ArrayVec` will conflict with `arrayvec::ArrayVec` and `tinyvec::ArrayVec`.

### Prelude

Expand All @@ -142,10 +258,10 @@ Should it be included in the prelude?

```rust
// Instance with 1i32, 2i32 and 3i32
let _: StackVec<i32, 33> = stack_vec![1, 2, 3];
let _: ArrayVec<i32, 33> = array_vec![1, 2, 3];

// Instance with 1i32 and 1i32
let _: StackVec<i32, 64> = stack_vec![1; 2];
let _: ArrayVec<i32, 64> = array_vec![1; 2];
```

# Future possibilities
Expand All @@ -168,7 +284,7 @@ impl<T, const N: usize> DynVec<T, N> {
// This is just an example. `Vec<T>` could be `Box` and `enum` an `union`.
enum DynVecData<T, const N: usize> {
Heap(Vec<T>),
Inline(StackVec<T, N>),
Inline(ArrayVec<T, N>),
}
```

Expand All @@ -181,5 +297,5 @@ Many structures that use `alloc::vec::Vec` as the underlying storage can also us
```rust
type DynString = GenericString<DynVec<u8>>;
type HeapString = GenericString<Vec<u8>>;
type StackString = GenericString<StackVec<u8>>;
type StackString = GenericString<ArrayVec<u8>>;
```

0 comments on commit 91df9aa

Please sign in to comment.