#type-level #error-message #control-flow

no-std type_level_values

用于使用类型级别值和函数的Crate

5个版本

使用旧的Rust 2015

0.1.2 2018年11月23日
0.1.1 2018年11月20日
0.1.0 2018年11月20日
0.0.21 2018年10月3日
0.0.20 2018年10月3日

#31 in #type-level


type_level_examples 中使用

MIT/Apache

710KB
17K SLoC

Build Status

声明和使用类型级别值和函数的包。

库特性

此库提供以下(非详尽列表)类型级别特性

  • 从枚举和结构体派生类型级别性,并为此提供一些核心数据类型。

  • 声明类型级别函数,包括一些核心数据类型方法的等价函数。

  • 集合特质/函数。

  • 其他控制流操作:恐慌/断言/If/模式匹配。

  • 字段特质/函数(设置/获取/映射字段)。

  • 整数操作(除了typenum提供的那些)。

  • 转换特质/函数,包括类型级别值和运行时值之间的转换。

  • 包装操作:unwrap/AndThen/OrElse/IntoInner等。

  • 函数适配器/组合子。

此库为使用类型级别值参数的类型提供以下功能

  • 使用(受类型限制的类型级别函数)修改类型级别值参数,即使类型是引用。

文档

有关API文档,请访问此处。如果您已将此包含在crate中,请使用cargo doc --open

有关除单个项和模块的文档之外的其他文档,包括对TypeLevelMutConstValue派生宏的文档,请转到docs子模块(在type_level_values的文档中)。

示例crate

有关使用type_level库的示例,请参阅指南(也可使用cargo doc --open访问),或查看type_level_examples crate。

最低支持的Rust版本

此包支持Rust 1.20及以后版本。使用构建脚本启用Rust 1.20之后的功能。

此库支持Rust 1.20及以后版本,因为其他基本库也支持,如果这没有必要,请搜索/创建一个问题,争论支持哪个版本。

无std支持

要在无std上下文中使用type_level_values,请禁用默认功能。

这个crate几乎不需要标准库(而非核心库)的功能,默认情况下需要它,以便用户不知道核心库时不需要传递功能来启用需要std的功能项。

Cargo功能

"std": 启用标准库支持,否则使用核心库。默认启用。

"serde": 启用serde支持。默认启用。

"large_tlist": 启用固定大小为32个元素的类型列表的实现,而不是16个元素。

示例

假设我们要实现一个特殊的俄罗斯方块游戏,其中我们有限数量的俄罗斯方块(tetrominos),目标是要获得最高分。

这里我们实现了一个类型安全的构建器,该构建器跟踪类型系统中每个字段的初始化。

这只是一个示例,此库不提供为构建器提供derives(尽管依赖项仓库可以做到这一点)。

Cargo.toml

type_level_values={version = "0.1"}
derive_type_level={version = "0.1"}

main.rs


// For the 2018 edition uncomment the next lines.
// use derive_type_level::{TypeLevel,MutConstValue};
// use type_level_values::{tlist,mutator_fn};

use type_level_values::prelude::*;
use type_level_values::field_traits::{SetField,SetField_};

fn main(){    
    let pieces=TetrisBuilder::new()
        .l_pieces(10)
        .i_pieces(20)
        .z_pieces(30)
        .s_pieces(40)
        .o_pieces(50)
        .build();

    assert_eq!(
        pieces,
        TetrisPieces{
            l_pieces:10,
            i_pieces:20,
            z_pieces:30,
            s_pieces:40,
            o_pieces:50,
        }
    )
}

///////////////////////////////////////////////////////////////////

macro_rules! declare_setter {( $($field:ident),* $(,)* ) => {


    /// This is the type we are trying to build,
    /// it represents the ammount of tetris pieces left in a special form of tetris. 
    #[derive(Clone, Debug,PartialEq)]
    pub struct TetrisPieces{
        $( $field:usize, )*
    }

    // This creates the type-level equivalent of FieldInitialization in the 
    // type_level_FieldInitialization module,requiring us to reexport what we need.
    #[derive(TypeLevel)]
    //This reexports the type-level equivalents of InitField/UninitField
    #[typelevel(reexport(Variants))]
    pub enum FieldInitialization{
        InitField,  
        UninitField,
    }

    // This derive macro creates the type-level equivalent of InitializedFields in the 
    // type_level_InitializedFields module,requiring us to reexport what we need.
    #[derive(TypeLevel)]
    #[typelevel(
        derive(ConstEq,ConstOrd),
        //This reexports ConstInitializeFields
        reexport(Struct), 
    )]
    pub struct InitializedFields{
        $( pub $field:FieldInitialization, )*
    }

    /// We manually reexport the field accessors submodule 
    /// as if_field to avoid name colisions
    pub use self::type_level_InitializedFields::{
        fields as if_field,
    };

    /**
    This is the ConstInitializedFields we start with.
    
    `InitializedFields_Uninit` is the uninitialized version of ConstInitializedFields,
    which we must initialize to use.

    We use the special field accessor `All` to 
    initialize all the fields with the passed value (which in this case is `UninitField`).
    */
    pub type AllUninitialized=SetField<
        InitializedFields_Uninit,
        if_field::All,
        UninitField
    >;

    /**
    This is the ConstInitializedFields required to build the TetrisPieces.

    This is an alternate way to initialize all the fields,
    allowing us to pass any value for fields
    while ensuring that all fields are initialized.
    */
    pub type AllInitialized=Construct<
        InitializedFields_Uninit,
        tlist!(
            $( (if_field::$field , InitField) ,)*
        )
    >;


    /**
    We declare a datatype which uses a ConstValue-parameter `C` (type-level-value=ConstValue).
    
    Note that the type we use in the rest of the example is 
    `TetrisBuilder` not `__TetrisBuilder`.

    In the future MutConstValue might be superceded (leaving it for pre Rust 1.30 users) 
    by a proc-macro attribute so as to not require a dummy type declaration.
    */
    #[derive(MutConstValue)]
    #[mcv(
        doc="These are the docs for TetrisBuilder_Ty.",
        derive(Clone, Debug),
        Type = "TetrisBuilder",
        ConstValue = "C",
    )]
    pub struct __TetrisBuilder<C>{
        $( $field:Option<usize>, )*
        initialization:ConstWrapper<C>,
    }

    impl TetrisBuilder< AllUninitialized >{
        fn new()->Self {
            TetrisBuilder::default()
        }
    }

    // implementing this on TetrisBuilder<AllUninitialized> caused an internal compiler error,
    // so I just use TypeIdentity to alias AllUninitialized into I.
    impl<I> Default for TetrisBuilder< I >
    where AllUninitialized:TypeIdentity<Type=I>
    {
        fn default()->Self{
            Self{
                $( $field:None, )*
                initialization:ConstWrapper::NEW,
            }
        }
    }

    mod builder_internal{
        
        use super::*;
        
        /// This declares a Type-level function which is allowed to mutate the 
        /// ConstValue-parameter `C` of `TetrisBuilder<C>`.
        mutator_fn!{
            type This[C]=(TetrisBuilder<C>)

            // The AllowedSelf type here determines whether the function is allowed to 
            // mutate C for a value/reference/mutable-reference of TetrisBuilder<C>.
            // For some functions on some types it may be valid to 
            // use some combination of the 3.
            type AllowedSelf=(allowed_self_constructors::ByVal)

            /**
            This is the function,note that we must declare generic parameters 
            for the function inside `[..]`instead of `<..>`,
            this is mostly for implementation simplicity.
            */
            fn InitializeField[I,Field](I,Field)
            // The `[..]` is here to make this easier to parse.
            where [ I:SetField_<Field,InitField,Output=Out>, ]
            { 
                // `let` here declares a type variable,which can be initialized anywhere. 
                let Out;
                // This is the return value of the function,like in regular Rust.
                Out 
            }
        }

        impl<C> TetrisBuilder< C >{
            $(
                // Here we initialize the field and set the 
                // same field on the `C` type parameter (a `ConstInitializedFields<..>`) 
                // as initialized.
                //
                // The `__OutSelf` here is how we emulate "output" types,
                // this is simpler than many alternatives.
                pub fn $field<__OutSelf>(mut self,value:usize)->__OutSelf
                where 
                    Self:MCPBounds<InitializeField,if_field::$field,NextSelf=__OutSelf>
                {
                    self.$field=Some(value);
                    // The `::T` is an associated constant defined in core_extensions::SelfOps,
                    // which allows us to emulate passing types as regular parameters.
                    self.mutparam(InitializeField::NEW,if_field::$field::T)
                }

            )*
        }
    }


    // If this impl block were on TetrisBuilder<AllInitialized>
    // it would just say that the build method does not exist
    impl<C> TetrisBuilder< C >{
        fn build(self)->TetrisPieces
        // TypeIdentity is used here to assert that C and AllInitialized are the same type.
        where C:TypeIdentity<Type= AllInitialized >
        {
            TetrisPieces{
                $( $field:self.$field.unwrap(), )*
            }
        }

    }
}}


declare_setter!{
    l_pieces,
    i_pieces,
    z_pieces,
    s_pieces,
    o_pieces 
}


阅读错误信息

如果我们取消注释 .o_pieces(50) 并尝试构建代码,我们将得到一个相当难以解码的错误信息,使用 [^\(\)\[\]{}>`<,= ]+:: 正则表达式来删除错误信息中的垃圾信息,它应该产生如下内容

error[E0271]: type mismatch resolving `<ConstInitializedFields<InitField, InitField, InitField, InitField, UninitField<o_pieces>> as SetField_<o_pieces, InitField>>::Output == ConstInitializedFields<InitField, InitField, InitField, InitField, UninitField>`
  --> type_level_examples\src\playground_01.rs:11:10
   |
11 |         .build();
   |          ^^^^^ expected struct `InitField`, found struct `UninitField`
   |
   = note: expected type `ConstInitializedFields<_, _, _, _, InitField>`
              found type `ConstInitializedFields<_, _, _, _, UninitField>`
   = note: required because of the requirements on the impl of `TypeFn_<(ConstInitializedFields<InitField, InitField, InitField, InitField, UninitField<o_pieces>>, (o_pieces, InitField))>` for `SetFieldValuePair`
   <some notes elided to shorten this example error message>

如果我们阅读第一则提示,我们会看到它说最后一个字段未初始化,这意味着我们忘记初始化o_pieces(我们声明的最后一个字段),所以请在构建之前添加对o_pieces方法的调用,它应该可以正常编译。

错误是由以下约束引起的 C:TypeIdentity<Type= AllInitialized >,它断言C和AllInitialized必须是同一类型。

许可

type_level的许可采用以下之一

Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

任选一项。

贡献

除非你明确表示,否则你提交给type_level的任何贡献,根据Apache-2.0许可定义,将双重许可如上所述,不附加任何额外条款或条件。

依赖项

~6MB
~120K SLoC