3 releases
0.0.3 | Aug 9, 2023 |
---|---|
0.0.2 | Jul 25, 2023 |
0.0.1 | Jul 24, 2023 |
#1352 in Data structures
Used in 3 crates
(via liter)
34KB
477 lines
construe
Compile-Time Growable Array: Vec
& String
for const
!
Construe
& StrConstrue
allow you to write const
functions that behave as though though they can collect items into a Vec
or &str
s into a String
, returning an array or &str
, whose length does not need to be known before the function is called.
The one caveat is that this is only possible for deterministic functions which, given the same inputs, will always return the same amount of items. This is because they will be invoked twice: once to determine the size of the buffer needed and once to collect its contents.
Some other restrictions (currently) apply:
- can't overwrite previous items or assign directly to an index
- items can only be added to the end (only
.push()
) - can't remove items from the buffer (e.g. no
.pop()
) - it's not possible to inspect the buffer during construction
- no fallback mode for use outside of
const
contexts
Examples
Simple compile-time &str
concatenation using StrConstrue
:
use construe::{StrConstrue, construe};
/// Concatenate all `&str`s into a single `&str`
const fn concat<const N: usize>(mut slice: &[&str]) -> StrConstrue<N> {
let mut strc = StrConstrue::new();
// no `for` in const
while let [s, rest @ ..] = slice {
slice = rest;
// by-value since there's no `&mut` in const
strc = strc.push_str(s);
}
strc
}
construe!(const HELLO_WORLD: &str = concat(&["Hello", " ", "World", "!"]));
assert_eq!(HELLO_WORLD, "Hello World!");
And a slightly more complicated example, using Construe
:
use construe::{Construe, construe};
/// Multiply each item in `slice` `x` times
const fn multiply<const N: usize, T: Copy>(mut slice: &[T], x: usize)
-> Construe<T, N>
{
let mut c = Construe::new();
while let [item, rest @ ..] = slice {
slice = rest;
let mut i = 0;
while i < x {
// see Construe::push docs on why we need `.0` at the end
c = c.push(*item).0;
i += 1;
}
}
c
}
// as slice:
construe!(const NUMBERS: &[u8] = multiply(&[1, 2, 3], 2));
assert_eq!(NUMBERS, [1, 1, 2, 2, 3, 3]);
// as array:
construe!(const NUMBERS_ARRAY: [u8; _] = multiply(&[1, 2, 3], 2));
assert_eq!(NUMBERS, &NUMBERS_ARRAY);
// or with `&str`s:
construe!(const WORDS: &[&str] = multiply(&["hey", "you"], 2));
assert_eq!(WORDS, &["hey", "hey", "you", "you"]);
Possible Improvements
Some of the restrictions mentioned above may or may not be remedied in future versions (roughly in the order given).
However, if a specialized version of these types is required, it would be easiest for you to just write it yourself.
For instance, if your function will need to inspect, say, the last 4 items of the buffer during construction, it would be fairly easy to write a Construe
that holds a buffer of size 4 for the first run too.
Implementing pop()
where you don't need the result would be trivial, and if you do, it would be analogous to the previous case, as long as you can guarantee that a buffer of size N
can keep up (e.g. "pop()
won't be called more than once between push()
es").
If you can't make this guarantee it might still be possible to implement: you could run your function thrice, once to determine the size of the look-back buffer, then twice like a normal Construe
.
Generally, I think the method used by this crate can be extended to make any computation work, given that you could implement an allocator: it would abort the computation prematurely if it runs out of space and ask for twice as much.
Then you need to invoke the computation at most log2(available_memory) times.
Probably better to just wait until const
support is better though.