4 个版本

0.1.3 2021 年 7 月 13 日
0.1.2 2020 年 5 月 13 日
0.1.1 2020 年 5 月 11 日
0.1.0 2020 年 5 月 11 日

#1051 in Rust 模式

Download history 2207/week @ 2024-03-14 2036/week @ 2024-03-21 1355/week @ 2024-03-28 2226/week @ 2024-04-04 2096/week @ 2024-04-11 2270/week @ 2024-04-18 1731/week @ 2024-04-25 1867/week @ 2024-05-02 1591/week @ 2024-05-09 3221/week @ 2024-05-16 2451/week @ 2024-05-23 1627/week @ 2024-05-30 2016/week @ 2024-06-06 1892/week @ 2024-06-13 1588/week @ 2024-06-20 1274/week @ 2024-06-27

6,918 每月下载量
用于 12 个 Crates (5 个直接使用)

MIT 许可证

52KB
651

Crate tuple_list

Rust Crates 用于无宏的变长元组元编程。

提供了一种递归定义任意大小元组特质的途径。

有关详细信息和方法,请参阅 Crates 文档

受 MIT 许可证保护。


lib.rs:

无宏的变长元组元编程的 Crates。

原理

截至编写此 Crates,Rust 不支持变长泛型,并且不允许对元组进行一般性推理。

最重要的是,Rust 不允许为所有实现了该特质的元素的所有元组泛型实现一个特质。

此 Crates 尝试通过提供一种递归定义元组特质的方式来填补这一空白。

元组列表

元组 (A, B, C, D) 可以明确地映射到递归元组 (A, (B, (C, (D, ())))).

在每一级上,它由一对 (Head, Tail) 组成,其中 Head 是元组元素,Tail 是列表的其余部分。对于最后一个元素,Tail 是一个空列表。

与常规平坦元组不同,这种递归元组可以在 Rust 中有效地进行推理。

此 Crates 将此类结构称为“元组列表”,并提供了一组特性和宏,使人们可以方便地使用它们。

示例 1:PlusOne 递归特质

让我们创建一个特质,它将向任意长度元组列表的每个元素加一,根据元素类型表现不同。

// `TupleList` is a helper trait implemented by all tuple lists.
// Its use is optional, but it allows to avoid accidentally
// implementing traits for something other than tuple lists.
use tuple_list::TupleList;

// Define trait and implement it for several primitive types.
trait PlusOne {
    fn plus_one(&mut self);
}
impl PlusOne for i32    { fn plus_one(&mut self) { *self += 1; } }
impl PlusOne for bool   { fn plus_one(&mut self) { *self = !*self; } }
impl PlusOne for String { fn plus_one(&mut self) { self.push('1'); } }

// Now we have to implement trait for an empty tuple,
// thus defining initial condition.
impl PlusOne for () {
    fn plus_one(&mut self) {}
}

// Now we can implement trait for a non-empty tuple list,
// thus defining recursion and supporting tuple lists of arbitrary length.
impl<Head, Tail> PlusOne for (Head, Tail) where
    Head: PlusOne,
    Tail: PlusOne + TupleList,
{
    fn plus_one(&mut self) {
        self.0.plus_one();
        self.1.plus_one();
    }
}

// `tuple_list!` as a helper macro used to create
// tuple lists from a list of arguments.
use tuple_list::tuple_list;

// Now we can use our trait on tuple lists.
let mut tuple_list = tuple_list!(2, false, String::from("abc"));
tuple_list.plus_one();

// `tuple_list!` macro also allows us to unpack tuple lists
let tuple_list!(a, b, c) = tuple_list;
assert_eq!(a, 3);
assert_eq!(b, true);
assert_eq!(&c, "abc1");

示例 2:CustomDisplay 递归特质

让我们创建一个类似于 Display 的 trait,该 trait 对所有实现了它的元组列表进行实现。

// Define the trait and implement it for several standard types.
trait CustomDisplay {
    fn fmt(&self) -> String;
}
impl CustomDisplay for i32  { fn fmt(&self) -> String { self.to_string() } }
impl CustomDisplay for bool { fn fmt(&self) -> String { self.to_string() } }
impl CustomDisplay for &str { fn fmt(&self) -> String { self.to_string() } }

// Now we have to implement trait for an empty tuple,
// thus defining initial condition.
impl CustomDisplay for () {
    fn fmt(&self) -> String { String::from("<empty>") }
}

// In order to avoid having trailing spaces, we need
// custom logic for tuple lists of exactly one element.
//
// The simplest way is to use `TupleList::TUPLE_LIST_SIZE`
// associated constant, but there is also another option.
//
// Instead of defining initial condition for empty tuple list
// and recursion for non-empty ones, we can define *two*
// initial conditions: one for an empty tuple list and
// one for tuple lists of exactly one element.
// Then we can define recursion for tuple lists of two or more elements.
//
// Here we define second initial condition for tuple list
// of exactly one element.
impl<Head> CustomDisplay for (Head, ()) where
    Head: CustomDisplay,
{
    fn fmt(&self) -> String {
        return self.0.fmt()
    }
}

// Recursion step is defined for all tuple lists
// longer than one element.
impl<Head, Next, Tail> CustomDisplay for (Head, (Next, Tail)) where
    Head: CustomDisplay,
    (Next, Tail): CustomDisplay + TupleList,
    Tail: TupleList,
{
    fn fmt(&self) -> String {
        return format!("{} {}", self.0.fmt(), self.1.fmt());
    }
}

// Ensure `fmt` is called for each element.
let tuple_list = tuple_list!(2, false, "abc");
assert_eq!(
    tuple_list.fmt(),
    "2 false abc",
);

// Since tuple lists implement `CustomDisplay` themselves, they can
// be elements in other tuple lists implementing `CustomDisplay`.
let nested_tuple_list = tuple_list!(2, false, "abc", tuple_list!(3, true, "def"));
assert_eq!(
    nested_tuple_list.fmt(),
    "2 false abc 3 true def",
);

示例 3: SwapStringAndInt 递归 trait

让我们实现一个 trait,该 trait 将 i32 转换为 String,反之亦然。

这个示例比其他示例要复杂得多,因为它将一个元组列表映射到另一个元组列表。

// Let's define and implement a trait for `i32` and `String`
// so that it converts `String` to `i32` and vice versa.
trait SwapStringAndInt {
    type Other;
    fn swap(self) -> Self::Other;
}
impl SwapStringAndInt for i32 {
    type Other = String;
    fn swap(self) -> String { self.to_string() }
}
impl SwapStringAndInt for String {
    type Other = i32;
    fn swap(self) -> i32 { self.parse().unwrap() }
}

// Now we have to implement trait for an empty tuple,
// thus defining initial condition.
impl SwapStringAndInt for () {
    type Other = ();
    fn swap(self) -> () { () }
}

// Now we can implement trait for a non-empty tuple list,
// thus defining recursion and supporting tuple lists of arbitrary length.
impl<Head, Tail, TailOther> SwapStringAndInt for (Head, Tail) where
    Head: SwapStringAndInt,
    Tail: SwapStringAndInt<Other=TailOther> + TupleList,
    TailOther: TupleList,
{
    type Other = (Head::Other, Tail::Other);
    fn swap(self) -> Self::Other {
        (self.0.swap(), self.1.swap())
    }
}

// Tuple lists implement `SwapStringAndInt` by calling `SwapStringAndInt::swap`
// on each member and returning tuple list of resulting values.
let original = tuple_list!(4, String::from("2"), 7, String::from("13"));
let swapped  = tuple_list!(String::from("4"), 2, String::from("7"), 13);

assert_eq!(original.swap(), swapped);

示例 4: 预先添加和附加函数

让我们为元组列表实现附加和预先添加函数。

// Prepend is a trivial operation for tuple lists.
// We just create a new pair from prepended element
// and the remainder of the list.
fn prepend<T, Tail: TupleList>(value: T, tail: Tail) -> (T, Tail) {
    (value, tail)
}

// Append is a bit more comples. We'll need a trait for that.
trait Append<T>: TupleList {
    type AppendResult: TupleList;

    fn append(self, value: T) -> Self::AppendResult;
}

// Implement append for an empty tuple list.
impl<T> Append<T> for () {
    type AppendResult = (T, ());

    // Append for an empty tuple list is quite trivial.
    fn append(self, value: T) -> Self::AppendResult { (value, ()) }
}

// Implement append for non-empty tuple list.
impl<Head, Tail, T> Append<T> for (Head, Tail) where
    Self: TupleList,
    Tail: Append<T>,
    (Head, Tail::AppendResult): TupleList,
{
    type AppendResult = (Head, Tail::AppendResult);

    // Here we deconstruct tuple list,
    // recursively call append on the
    // tail of it, and then reconstruct
    // the list using the new tail.
    fn append(self, value: T) -> Self::AppendResult {
        let (head, tail) = self;
        return (head, tail.append(value));
    }
}

// Now we can use our append and prepend functions
// on tuple lists.
let original  = tuple_list!(   1, "foo", false);
let appended  = tuple_list!(   1, "foo", false, 5);
let prepended = tuple_list!(5, 1, "foo", false);

assert_eq!(original.append(5), appended);
assert_eq!(prepend(5, original), prepended);

示例 5: 反转函数

我们还可以实现一个函数,该函数可以反转元组列表中的元素。

// Rewind is a helper trait which maintains two tuple lists:
// `Todo` (which is `Self` for the trait) is the remainder of a tuple list to be reversed.
// `Done` is already reversed part of it.
trait Rewind<Done: TupleList> {
    // RewindResult is the type of fully reversed tuple.
    type RewindResult: TupleList;

    fn rewind(self, done: Done) -> Self::RewindResult;
}

// Initial condition.
impl<Done: TupleList> Rewind<Done> for () {
    type RewindResult = Done;

    // When nothing is left to do, just return reversed tuple list.
    fn rewind(self, done: Done) -> Done { done }
}

// Recursion step.
impl<Done, Next, Tail> Rewind<Done> for (Next, Tail) where
    Done: TupleList,
    (Next, Done): TupleList,
    Tail: Rewind<(Next, Done)> + TupleList,
{
    type RewindResult = Tail::RewindResult;

    // Strip head element from `Todo` and prepend it to `Done` list,
    // then recurse on remaining tail of `Todo`.
    fn rewind(self, done: Done) -> Self::RewindResult {
        let (next, tail) = self;
        return tail.rewind((next, done));
    }
}

// Helper function which uses `Rewind` trait to reverse a tuple list.
fn reverse<T>(tuple: T) -> T::RewindResult where
    T: Rewind<()>
{
    // Initial condition, whole tuple list is `Todo`,
    // empty tuple is `Done`.
    tuple.rewind(())
}

// Now `reverse` is usable on tuple lists.
let original = tuple_list!(1, "foo", false);
let reversed = tuple_list!(false, "foo", 1);

assert_eq!(reverse(original), reversed);

元组列表和元组互操作性

此 crate 定义了 TupleTupleList trait,这些 trait 可以自动实现,允许您将元组转换为元组列表,反之亦然。

处理互操作性的最佳方式是将数据存储为元组列表,并在必要时将其转换为元组。

或者,可以创建一个辅助函数,该函数接受一个元组,将其转换为元组列表,调用 trait 方法,然后返回结果。

以下是一个从上一个示例中的 Append trait 创建的此类函数的示例

// `Tuple` trait is needed to access conversion function.
use tuple_list::Tuple;

fn append<T, AppendedTupleList, Elem>(tuple: T, elem: Elem) -> AppendedTupleList::Tuple where
    T: Tuple,                                                   // input argument tuple
    T::TupleList: Append<Elem, AppendResult=AppendedTupleList>, // input argument tuple list
    AppendedTupleList: TupleList,                               // resulting tuple list
{
    // Convert tuple into tuple list, append the element
    // and convert the result back into tuple.
    tuple.into_tuple_list().append(elem).into_tuple()
}

// Unlike `Append` trait which is defined for tuple lists,
// `append` function works on regular tuples.
let original = (1, "foo", false);
let appended = (1, "foo", false, 5);

assert_eq!(append(original, 5), appended);

请注意,元组/元组列表转换是破坏性的,它会消耗原始数据,这看起来阻止您修改原始元组的内容。

为了减轻这个问题,tuple_list crate 引入了 AsTupleOfRefs trait,该 trait 允许将引用转换为引用的元组。

想法是,如果您可以将引用转换为引用的元组,然后可以将引用的元组转换为元组列表,然后像往常一样使用递归 trait。

让我们修改 PlusOne trait 示例,以便它可以用于修改常规元组

// Define trait and implement it for several primitive types.
trait PlusOne {
    fn plus_one(&mut self);
}
impl PlusOne for i32    { fn plus_one(&mut self) { *self += 1; } }
impl PlusOne for bool   { fn plus_one(&mut self) { *self = !*self; } }
impl PlusOne for String { fn plus_one(&mut self) { self.push('1'); } }

// Now we have to define a new trait
// specifically for tuple lists of references.
//
// Unlike the original, it accepts `self` by value.
trait PlusOneTupleList: TupleList {
    fn plus_one(self);
}

// Now we have to implement trait for an empty tuple,
// thus defining initial condition.
impl PlusOneTupleList for () {
    fn plus_one(self) {}
}

// Now we can implement trait for a non-empty tuple list,
// thus defining recursion and supporting tuple lists of arbitrary length.
//
// Note that we're implementing `PlusOneTupleList` for
// *tuple list of mutable references*, and as a result
// head of the list is a mutable reference, not a value.
impl<Head, Tail> PlusOneTupleList for (&mut Head, Tail) where
    Self: TupleList,
    Head: PlusOne,
    Tail: PlusOneTupleList,
{
    fn plus_one(self) {
        self.0.plus_one();
        self.1.plus_one();
    }
}

// Now let's define a helper function operating on regular tuples.
fn plus_one<'a, T, RT>(tuple: &'a mut T) where
    T: AsTupleOfRefs<'a, TupleOfMutRefs=RT>,
    RT: Tuple + 'a,
    RT::TupleList: PlusOneTupleList,

{
    tuple.as_tuple_of_mut_refs().into_tuple_list().plus_one()
}

// Now we can use this helper function on regular tuples.
let mut tuple = (2, false, String::from("abc"));
plus_one(&mut tuple);

assert_eq!(tuple.0, 3);
assert_eq!(tuple.1, true);
assert_eq!(&tuple.2, "abc1");

如您所见,使用元组需要很多样板代码。除非您需要支持现有的代码,否则通常最好直接使用元组列表,因为它们更容易处理。

为常规元组实现递归 trait

为常规元组实现递归 trait 会带来某些问题。到目前为止,在 tuple_list crate 中是可能的,但使用外部它时,会迅速导致孤儿规则违规。

您可以在 tuple_list::test::all_features 中看到一个为常规元组实现 trait 的工作示例,但它过于复杂,基本上是实验性的。

一旦 Rust 中实现了特征专业化功能,就可以在常规元组上定义递归特征。

无运行时依赖

功能