#iterator #enums #macro #zerocost #union-types #no-alloc

no-std sumtype

生成迭代器或闭包的零成本 sumtype

2 个版本

新版本 0.1.1 2024 年 8 月 25 日
0.1.0 2024 年 8 月 24 日

883Rust 模式

Download history · Rust 包仓库 267/week @ 2024-08-19 · Rust 包仓库

每月 267 次下载

MIT 许可证

25KB
382

sumtype 包 最新版本 文档

在 Rust 中,当函数需要根据其参数返回不同类型的迭代器时,这是一个挑战,因为即使迭代器返回相同类型的元素,它们也被认为是不同类型。这使得无法简单地使用返回语句直接返回它们。解决这个问题的常见方法是用 Box<dyn Iterator>,但这种方法有两个缺点:它会产生额外的堆内存使用,并且不提供零成本抽象。此外,迭代器返回的元素必须具有 'static 生存期。

以下是一个示例来阐述这一点

fn conditional_iterator(flag: bool) -> Box<dyn Iterator<Item = i32>> {
    if flag {
        Box::new(0..10) as Box<dyn Iterator<Item = i32>> // Returns an iterator over the range 0 to 10
    } else {
        Box::new(vec![1, 2, 3].into_iter()) as Box<dyn Iterator<Item = i32>> // Returns an iterator over a vector
    }
}

在此代码中,根据标志的值,该函数返回一个范围 (0..10) 的迭代器或一个向量 (vec![1, 2, 3]) 的迭代器。这两个迭代器都产生 i32 元素,但它们的类型不同(分别是 std::ops::Range<i32>std::vec::IntoIter<i32>)。使用 Box<dyn Iterator<Item = i32>> 允许我们从同一函数中返回它们,但它引入了上述缺点。

此软件包通过为带有 #[sumtype] 属性的装饰上下文生成单个匿名和类型来解决上述问题。通过使用宏 sumtype!([expr]),您可以将给定表达式包装在这个和类型中。由于包装的类型在同一个 #[sumtype] 上下文中成为相同的类型,它使像从单个函数中返回不同迭代器这样的场景成为可能。内部,此和类型使用简单的枚举,这意味着它不消耗额外的堆内存。此外,它提供零成本抽象。例如,在前面的示例中,如果编译器可以静态地确定 flag,则还可以确定返回的迭代器,从而可能消除由和类型引入的任何额外的抽象成本。

以下是它的样子

use sumtype::sumtype;
#[sumtype]
fn conditional_iterator(flag: bool) -> impl Iterator<Item = i32> {
    if flag {
        sumtype!((0..10)) // Wraps the range iterator
    } else {
        sumtype!(vec![1, 2, 3].into_iter()) // Wraps the vector iterator
    }
}

在此示例中,#[sumtype] 属性生成了一个可以包装这两种迭代器类型的和类型。使用 sumtype! 宏来包装表达式,允许它们在函数内被视为同一类型。因为和类型在内部使用简单的枚举,所以避免了额外的堆分配。如果 flag 在编译时已知,编译器可以在不产生额外抽象成本的情况下优化返回的迭代器。

此外,sumtype 可以不仅用于函数,还可以用于其他上下文。例如,通过在数学公式中的表达式块中使用 #[sumtype],可以根据某些条件初始化不同类型的迭代器并将它们分配给特定变量。

以下是一个示例来说明这一点

# use sumtype::sumtype;
# let some_condition = true;
#[sumtype]
let mut iter =  {
    if some_condition {
        sumtype!((0..5)) // Wraps the range iterator
    } else {
        sumtype!(vec![10, 20, 30].into_iter()) // Wraps the vector iterator
    }
};

// Now `iter` can be used as a unified iterator type in the rest of the code
for value in iter {
    println!("{}", value);
}

在这个例子中,#[sumtype] 属性被应用于一个表达式块,允许根据条件初始化不同类型的迭代器。然后使用 sumtype! 宏对这些迭代器进行封装,并将它们分配给 iter 变量,使得在整个代码中可以统一地使用它们。遗憾的是,这个特性需要 nightly Rust 和 #![feature(proc_macro_hygiene)],参见 过程宏和“hygiene 2.0”跟踪问题

此外,#[sumtype] 可以在定义特质以及实现它们时使用。以下是每个情况的示例

使用 #[sumtype] 与特质定义

# use sumtype::sumtype;
#[sumtype]
trait MyTrait {
    fn get_iterator(&self, flag: bool) -> impl Iterator<Item = i32> {
        if flag {
            sumtype!((0..5)) // Wraps the range iterator
        } else {
            sumtype!(vec![10, 20, 30].into_iter()) // Wraps the vector iterator
        }
    }
}

在这个例子中,#[sumtype] 被应用于一个特质定义。这可能有助于创建能够表示特质的不同实现的 sum 类型。

使用 #[sumtype] 与结构定义

# use sumtype::sumtype;
#[sumtype]
trait MyTrait {
    fn get_iterator(&self, flag: bool) -> impl Iterator<Item = i32> {
        if flag {
            sumtype!((0..5)) // Wraps the range iterator
        } else {
            sumtype!(vec![10, 20, 30].into_iter()) // Wraps the vector iterator
        }
    }
}
struct StructA;

#[sumtype]
impl MyTrait for StructA {
    fn get_iterator(&self, _flag: bool) -> impl Iterator<Item = i32> {
        sumtype!((0..5)) // Wraps a range iterator
    }
}

在这里,MyStruct 包含一个字段,该字段包含一个迭代器。根据条件,它使用 sumtype! 封装不同的迭代器。

使用 #[sumtype] 与模块定义

# use sumtype::sumtype;
#[sumtype]
mod my_module {
    pub struct MyStruct {
        iter: sumtype!(),
    }

    impl MyStruct {
        pub fn new(flag: bool) -> Self {
            let iter = if flag {
                sumtype!(0..5, std::ops::Range<u32>) // Wraps a range iterator
            } else {
                sumtype!(vec![10, 20, 30].into_iter(), std::vec::IntoIter<u32>) // Wraps a vector iterator
            };
            MyStruct { iter }
        }

        pub fn iterate(self) {
            for value in self.iter {
                println!("{}", value);
            }
        }
    }
}

依赖关系

~320–780KB
~17K SLoC