#vec #compile-time #const #array-vec #array #constants

无std construe

编译时增长数组:Vec & String for const!

3个版本

0.0.3 2023年8月9日
0.0.2 2023年7月25日
0.0.1 2023年7月24日

#1866 in 数据结构


liter 中使用

GPL-3.0-or-later

34KB
477

construe

编译时增长数组:Vec & String for const

Construe & StrConstrue 允许你编写 const 函数,这些函数的行为就像它们可以将项目收集到一个 Vec 或将 &str 放入一个 String 中,返回一个数组或 &str,其长度在函数调用之前不需要知道。

唯一的缺点是,这仅适用于确定性函数,这些函数给定相同的输入,将始终返回相同数量的项目。这是因为它们将被调用两次:一次是为了确定所需的缓冲区大小,一次是为了收集其内容。

(目前)还有一些限制适用

  • 不能覆盖先前的项目或直接赋值给索引
  • 只能将项目添加到末尾(只有 .push()
  • 不能从缓冲区中删除项目(例如,没有 .pop()
  • 在构建期间无法检查缓冲区
  • 没有用于 const 上下文之外的回退模式

示例

使用 StrConstrue 的简单编译时 &str 连接

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!");

以下是一个稍微复杂一点的示例,使用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"]);

可能的改进

上述提到的某些限制可能在未来的版本中得到修复(大致按照给出的顺序)。

然而,如果需要这些类型的专用版本,最好的方法可能是自己编写。例如,如果您的函数在构造期间需要检查缓冲区的最后4个项目,那么编写一个在首次运行时也包含大小为4的缓冲区的Construe就相当简单。
在不需要结果的情况下实现pop()将是微不足道的,如果需要,它将与上一个案例类似,只要您可以保证大小为N的缓冲区可以跟上(例如,“pop()不会在两次push()之间调用”)。如果您无法做出这个保证,仍然可能实现:您可以将您的函数运行三次,一次用于确定回溯缓冲区的大小,然后像正常的Construe一样运行两次。

通常,我认为这个crate使用的方法可以扩展到实现任何计算,前提是您可以实现一个分配器:如果空间不足,它将提前终止计算并请求两倍的空间。然后您最多需要调用计算log2(可用内存)次。
不过,可能还是等到const支持更好再说。

没有运行时依赖