#array-index #array #index #indexing #wrapper #indexed

andex

无需依赖的 Rust 安全强类型数组索引和包装器

16 个版本

0.0.18 2024 年 6 月 21 日
0.0.17 2024 年 6 月 17 日
0.0.14 2022 年 6 月 3 日
0.0.13 2022 年 4 月 30 日
0.0.9 2021 年 8 月 22 日

#249 in Rust 模式

MIT 许可证

34KB
337

CI coveralls crates.io doc.rs

andex

andex (数组索引) 是一个零依赖的 Rust crate,帮助我们创建具有提供大小的强类型、无成本的数值数组索引和相应的数组类型。索引是安全的,这意味着不能创建越界的值,数组类型也不能被任何其他类型索引。

这在以下场景中很有用,即我们在 struct 中有不同类型的数组,并希望引用成员而不需要持有可能会“锁定”整个 struct 的正确引用。在编写 实体组件系统 时也可能很有用。

而且这一切都无需使用任何宏。

用法

创建 andex 类型

Andex 是索引类型,AndexableArray 是数组包装器的类型。

使用 andex 的推荐方法是

  • 创建一个唯一的空类型
    enum MyIdxMarker {}
    
  • Andex 类型创建一个类型别名,该类型使用该类型参数化
    type MyIdx = Andex<MyIdxMarker, 12>;
    
  • AndexableArray 类型创建一个类型别名,该类型由上面创建的 Andex 别名索引
    type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
    // There is also a helper macro for this one:
    type MyOtherU32 = andex::array!(MyIdx, u32);
    

创建 andex 实例

当创建 andex 时,它知道在编译时索引的数组的大小,并且所有实例都假定在范围内。

因此,限制创建 Andex 的方式是有用的。获取实例的方法包括

  • 通过 new,传递值作为泛型常量参数

    const first : MyIdx = MyIdx::new::<0>();
    

    这将在编译时检查值是否有效,只要您使用它创建 const 变量。

  • 通过 try_from,它返回一个 Result<Andex, Error>,必须进行检查或显式忽略

    if let Ok(first) = MyIdx::try_from(0) {
        // ...
    }
    
  • 通过 FIRSTLAST

    const first : MyIdx = MyIdx::FIRST;
    let last = MyIdx::LAST;
    
  • 通过迭代

    for idx in MyIdx::iter() {
        // ...
    }
    

假设实例只能存储有效值,这使我们能够在索引器实现中使用 get_unsafeget_unsafe_mut,从而在索引时防止边界检查,提供了一定的优化。

创建可索引数组

AndexableArray 实例的约束较少。它们可以通过多种方式创建

  • 如果基础类型支持,可以使用 Default
    type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;
    
    let myu32 = MyU32::default();
    
    // We also have a helper macro that avoids repeating the size:
    type MyOtherU32 = andex::array!(MyIdx, u32);
    
  • 使用适当的数组与 From
    let myu32 = MyU32::from([8; MyIdx::SIZE]);
    
  • 收集具有适当元素和大小的迭代器
    let myu32 = (0..12).collect::<MyU32>();
    
    注意:如果迭代器返回不同数量的元素,则 collect 会引发恐慌。

使用可索引数组

除了使用配对的 Andex 实例进行索引外,我们还可以通过使用 as_ref 来访问内部数组,在 for 循环中迭代(使用 IntoIterator 的实现之一)或者通过消耗 AndexableArray 来获取内部数组。

完整示例

use std::convert::TryFrom;
use std::error::Error;
use andex::*;

// Create the andex type alias:
//   First, we need an empty type that we use as a marker:
enum MyIdxMarker {}
//   The andex type takes the marker (for uniqueness)
//   and the size of the array as parameters:
type MyIdx = Andex<MyIdxMarker, 12>;

// Create the array wrapper:
type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;

// We can create other arrays indexable by the same Andex:
type MyF64 = AndexableArray<MyIdx, f64, { MyIdx::SIZE }>;

fn main() -> Result<(), Box<dyn Error>> {
    let myu32 = MyU32::default();

    // We can now only index MyU32 using MyIdx
    const first : MyIdx = MyIdx::new::<0>();
    println!("{:?}", myu32[first]);

    // Trying to create a MyIdx with an out-of-bounds value
    // doesn't work, this won't compile:
    // const _overflow : MyIdx = MyIdx::new::<30>();

    // Trying to index myu32 with a "naked" number
    // doesn't work, this won't compile:
    // println!("{}", myu32[0]);

    // We can create indexes via try_from with a valid value:
    let second = MyIdx::try_from(2);
    // ^ Returns a Result, which Ok(MyIdx) if the value provided is
    // valid, or an error if it's not.

    // We can also create indexes at compile-time:
    const third : MyIdx = MyIdx::new::<1>();

    // The index type has an `iter()` method that produces
    // all possible values in order:
    for i in MyIdx::iter() {
        println!("{:?}", i);
    }
    Ok(())
}

编译时保证

这就是为什么在第一次使用时使用 Andex 而不是普通数组的原因,对吧?以下是我们在编译时获得的一些编译时限制列表。

  • 我们无法使用 usize 索引 AndexableArray

    以下代码无法编译

use andex::*;
enum MyIdxMarker {}
type MyIdx = Andex<MyIdxMarker, 12>;
type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;

fn main() {
    let myu32 = MyU32::default();

    // Error: can't index myu32 with a usize
    println!("{}", myu32[0]);
}
  • 我们无法使用越界值创建 const Andex

    以下代码无法编译

use andex::*;
enum MyIdxMarker {}
type MyIdx = Andex<MyIdxMarker, 12>;

fn main() {
    // Error: can't create out-of-bounds const:
    const myidx : MyIdx = MyIdx::new::<13>();
}
  • 即使它们具有相同的大小,我们也无法使用不同的 Andex 来索引 AndexableArray。这就是使用不同标记所得到的结果。

    以下代码无法编译

use andex::*;

enum MyIdxMarker {}
type MyIdx = Andex<MyIdxMarker, 12>;
type MyU32 = AndexableArray<MyIdx, u32, { MyIdx::SIZE }>;

enum TheirIdxMarker {}
type TheirIdx = Andex<TheirIdxMarker, 12>;
type TheirU32 = AndexableArray<TheirIdx, u32, { TheirIdx::SIZE }>;

fn main() {
    let myu32 = MyU32::default();
    let theirIdx = TheirIdx::FIRST;

    // Error: can't index a MyU32 array with TheirIdx
    println!("{}", myu32[theirIdx]);
}

替代方案

这些替代方案可能更适合需要无界索引的情况(也许用于向量)

无运行时依赖