在Rust中实现类型安全的printf

2020-08-14 23:59:18

我将展示如何使用异构列表和特征在Rust中实现类型安全的printf。这些机制可以确保两个可变参数列表共享重要属性,如格式字符串孔的数量与printf参数的数量相匹配。

关于Rust中的类型级编程的正在进行的系列文章的一部分。考虑先读第一部分!此节点中的所有代码都可以在此要点中找到。

铁锈有一个很棒的printf功能,println!它有丰富的格式化语言,但也能在编译时捕获错误。例如,println!将检查参数数量是否与格式字符串中的空洞数量匹配:

错误:格式字符串中有2个位置参数,但有1个参数-->;src/printf.rs:36:13|36|println!(";{}{}";,";Hello";);

铁锈编译器如何进行此检查?因为格式字符串和参数都在宏中,所以Rust将检查空洞的数量是否与参数的数量匹配。因此,格式字符串必须在宏内。如果我们写道:

我将向您展示如何在不使用过程性宏的情况下实现类型安全的printf。更一般地,本说明包含函数的锈蚀配方,其中:

多个可变输入共享一个并行属性,例如,参数的数量应与格式孔的数量匹配。

首先,我们需要了解主要的类型级机制:异构列表(或H列表)。H列表是潜在不同类型的值序列。例如,[1,";a";,true]是H列表,但不是有效的锈蚀矢量。H列表在Rust中使用链表样式实现:

Struct HNil;struct HCons<;Head,Tail>;{Head:Head,Tail:Tail}let示例:HCons<;I32,HCons<;bool,HNil>;>;=HCons{Head:1,Tail:HCons{Head:True,Tail:HNil}};

关键思想是H列表的类型在您每次对其进行更改时都会更改。相比之下,如果推送到VEC<;T&>,矢量的类型保持不变。

就像铁锈有了vc一样![],我们可以用破碎的板条箱来得到一份hlist!宏。

让我们回到printf的成分上。我们需要格式字符串和参数列表。关键思想是用H列表来表示两者,并仔细使用Rust的特性来确保我们想要的特性:参数的数量应该与空洞的数量相匹配。

首先,为了表示格式化字符串,我们将使用一系列结构来表示字符串的每个部分。

Pub struct FString(&;#39;static str);pub struct fvar;//假设我们编译了";Hello{}!代码中的第一个素数是{}";。//这将是一个简单的语法转换。让Example=hlist![FString(";Hello";),fvar,FString(";!第一个素数是";),fvar];

这里,self是format指令的H列表,args是可变参数的H列表。Format需要将args作为类型参数,因为当我们从args列表中删除元素时,它的类型将会改变。

现在,我们继续按案例实现格式特征。首先,到达格式列表HNil末尾的基本情况:

这个impl说明当我们到达格式列表的末尾时,只需返回空字符串。我们将接受的唯一参数是一个空参数列表。与下一个隐式相结合,这会归纳地确保不接受额外的参数。

接下来,我们将实现FString。此实现应使用包含在FString结构中的字符串常量,并递归地将其与格式列表的其余部分组合在一起。我们没有对FString使用可变参数,因此它们被传递。在Rust中,此英文规范变为:

实施<;ArgList、FmtList&>;Format<;ArgList&>;用于HCons<;FString、FmtList>;的FmtList:Format<;ArgList>;其中FmtList:Format<;ArgList>;{FN Format(&;Self,Args:ArgList)->;String{sel.head.。0.To_Owner()+&;self.ail.format(Args)}}。

请注意,我们必须添加FmtList:format<;ArgList>;以确保对self.tai.format的递归调用正常工作。还要注意,我们不是直接在FString上实现Format,而是在包含FString的H列表上实现Format。

最后是最复杂的情况,fvar。我们希望这个iml从ArgList中获取一个参数,然后用剩余的参数格式化剩余的格式列表。

实施<;T,ArgList,FmtList>;Format<;HCons<;T,ArgList>;对于HCons<;fvar,FmtList>;,其中FmtList:Format<;ArgList>;,T:ToString,{FN Format(&;self,args:HCons<;T,ArgList&Gt;)。Self.ail.format(args.ail)}}。

注意观察头部和尾部正在访问哪个H列表。这里,args H列表通过args.head提供填补漏洞的数据。

让Example=hlist![FString(";Hello";),fvar,FString(";!第一个素数是";),fvar];assert_eq!(示例.format(hlist![";world";,2]),";Hello world!第一个素数是2";);

错误[E0308]:类型不匹配-->;src/printf.rs:48:18|48|example.format(hlist。

虽然错误是神秘的,但我们的错误至少在编译时被正确捕获。这是因为Rust推导出example.format()期望一个形状为HCons<;_、HCons<;_、HNil>;>;的H列表,但是它在我们的1元素H列表中发现HNil太快了。当提供太多参数时,也会出现类似的错误。

现在,我们的format函数只检查格式列表和参数列表是否相同长度。例如,我们可以扩展我们的格式结构,以确保fvar必须是特定类型,或者必须使用Debug vs Display。下面是这样一个战略的草图:

使用std::marker::PhantomData;//为是否使用显示或调试pub struct FDisplay;pub struct FDebug;添加标志//使用带有PhantomData的类型参数来表示所需的类型pub struct fvar<;T,Flag>;(PhantomData<;(T,Flag)>;);//现在,格式列表和参数列表之间的T必须相同//还有,FDisplay。T,ArgList>;适用于HCons<;fvar<;T,FDisplay>;,FmtList:Format<;ArgList>;,T:Display,{FN Format(&;self,args:HCons<;T,ArgList>;)->;string{//使用格式!是作弊,但会得到IDEA格式!(";{}&34;,args)+&;self.ail.format(args.ail)}}//使用`FDebug`时,`t:Debug`的IMPL类似。

Haskell的仆人框架中的这篇博客文章让我开始思考如何将他们的策略应用到Rust中。