本文来自喵哥的知乎:https://zhuanlan.zhihu.com/p/105733343
上次我们说到Rust里有十四种条目。还记得它们都是什么嘛,来试着默背一遍?不记得了的话,快翻回前一篇复习一下。好了,这次我们来说说条目的一些小伙伴们。
在十四种条目中,除了宏条目以外,其他十三种可以指定可见性(visibility)。宏条目有自己独特的一套规则,在这里先不说。
可见性(visibility)可以类比其他语言的“访问控制”,即,我在代码里的什么地方可以访问这个条目。上次我们说到盒(crate)具有一个最外层的匿名模块(module)条目,模块条目可以包含条目,形成一棵条目树。在这里,可见性就是在划定在条目树上的。
默认的,也是最严格的是“私有”,pub(in self)
,可以不写出来,意思是“以当前模块为根的子树可以访问”;最宽松的是“公有”,pub
,意思是“无限制,哪里都可以访问”。
其他选择就是当前模块的父节点条目、父节点的父节点条目,等等,一直到盒最外层的匿名模块条目。这条通路上的每个模块条目都可以被选定,意思是“以这个模块为根的子树可以访问”,语法是pub(in <path>)
,例如pub(in crate::xxx::yyy)
就是“以crate::xxx::yyy
为根的子树可以访问”。
另外Rust也提供了几个简写形式:pub(crate)
相当于pub(in crate)
,pub(super)
相当于pub(in super)
,pub(self)
相当于pub(in self)
。这里的pub(crate)
是比较常用的。
能写在pub(in …)
里的部分,就是Rust里的路径的一种,称为简单路径。形式是aaa::bbb::ccc
这样,中间用双冒号隔开。开头的aaa
可以用几个关键字:crate
(当前盒),super
(父级模块),self
(当前模块),$crate
(当前宏所在的盒)。aaa
不写,直接从双冒号开头则是表示全局空间,从这里可以访问其他盒的内容。在pub(in ...)
语法中使用简单路径时有额外语义限制:这里路径表示的必须是当前模块条目或者当前模块的一个祖先模块条目;被标记的条目的可见性会限制到路径指定的这个模块条目对应作用域的子树范围内。
要注意,你要想能够从代码访问某个条目,光是目标条目本身的可见性符合要求是不够的。而是要求你有一条路径,并且这条路径上的每个中间节点和最终节点的可见性都满足要求才行。让我们举一反三实践一下,理解一下下面这句话:私有模块里的公有条目是被“藏起来”的,需要有一条可访问路径才能访问。怎么样,理解了吗?那么,该怎么访问这种私有模块里的公有条目呢?答案是要用到上次我们说到的“引用声明条目”,它可以用类似“快捷方式”的办法“跳过”中间那些私有模块之类的无法访问的条目,直达目的地。这个我们以后会讲到。
属性可以标注在很多地方,而最最常用的场景还是标注在条目上。属性属于一种元数据,会被编译器处理,编译器没法处理的话,就会报错。
从语法上,属性分为外侧属性(Outer Attribute)和内侧属性(Inner Attribute)两大类,含义是相同的,只不过标注的位置不同。日常用到的绝大多数都是外侧属性,原因很简单:很多条目和语法构造没有“内侧”。下面让我举个栗子:
/// OA1
/** OA2 */#[OA3(A, B="C")]struct Foo { #![IA1] /*! IA2 */ //! IA3
}
上面代码是无法编译通过的伪代码,其中的OA1
、OA2
、OA3
是外侧属性,IA1
、IA2
、IA3
是内侧属性。
OA1
、OA2
、IA2
、IA3
周围的注释形式是一种特殊的语法糖,会被编译器转化为#[doc="xxx"]
的属性,内容作为markdown处理。
需要提到一下的是,之前说到盒(crate)具有一个最外层的匿名模块(module)条目。这个条目是用源码文件本身表示的,所以它只能添加内侧属性(写在文件的开头),而无法添加外侧属性。
编译器提供了很多内置属性,而用户还可以通过属性过程宏对属性进行扩充。属性除了可以修饰、修改条目本身的含义以外,还可以用来额外产生新的条目,替换当前条目,在特定条件下移除当前条目等等。最最常用的derive属性就是额外自动产生新的条目的功能,它也可以由derive过程宏进行功能扩充。
好啦,这次我们就说到这里。下次我们下沉层级,继续向Hello world程序进发!