2013年1月14日星期一

Rust中的Modules和Crates

Rust中的Modules和Crates

整理自Rust 0.6 tutorial。

这部分主要说明Rust的模块和Crate(怎么翻译?集装箱?),我一直觉得这部分内容应该放在最前面,而不是最后面。一开始就说明Rust程序如何组织会比较好。

1 模块

Rust的名字空间按照模块层次组织。每个源文件(.rs文件)代表一个模块,同时可以包含附加模块:
mod farm {
    pub fn chicken() -> &str { "cluck cluck" }
    pub fn cow() -> &str { "mooo" }
}

fn main() {
    io::println(farm::chicken());
}
use关键字将模块的内容导入当前作用域,同时也可以对模块起别名。use可以出现在crates、modS、fnS、以及其它block结构的开头。
// Bring `chicken` into scope
use farm::chicken;

fn chicken_farmer() {
    // The same, but name it `my_chicken`
    use my_chicken = farm::chicken;
    ...
}
farm模块中的函数定义开头有一个新关键字:pub。关键字pub修改一个项的可见性,使得它在本模块之外依然可见。通过::可以在模块外访问模块中的项,例如farm::chicken。诸如以fn、struct、enum、type、以及const声明的项,缺省都是模块内私有的(模块外不可见)。

Rust中的可见性控制只存在与模块级别。这和大多数面向对象语言不同,它们的可见性控制还作用于对象级别。这并不是意味着Rust不支持封装:struct的数据域和方法可以是私有的。只是这种封装是模块级别,而不是struct级别罢了。注意struct的数据项和方法默认是public的。
mod farm {
    pub struct Farm {
        priv mut chickens: ~[Chicken],
        priv mut cows: ~[Cow],
        farmer: Human
    }

    // Note - visibility modifiers on impls currently have no effect
    impl Farm {
        priv fn feed_chickens(&self) { ... }
        priv fn feed_cows(&self) { ... }
        fn add_chicken(&self, c: Chicken) { ... }
    }

    pub fn feed_animals(farm: &Farm) {
        farm.feed_chickens();
        farm.feed_cows();
    }
}

fn main() {
     let f = make_me_a_farm();
     f.add_chicken(make_me_a_chicken());
     farm::feed_animals(&f);
     f.farmer.rest();
}

2 Crates

Rust中的独立编译单位是crate:rustc每次编译一个crate,生成一个库或者可执行文件。

当编译单个.rs文件时,文件自身充当crate。编译时可以通过开关 --lib 来创建共享库,或者没有用--lib开关,若文件中包含fn main函数,则生成可执行文件。

大型crate通常有多个.rs文件,从后缀为.rc的文件开始编译,.rc文件叫做crate文件。.rc文件后缀用来区分代表crates文件和源文件,除此之外,源文件和crate文件是一样的。

crate文件声明crate相关属性,这些属性用来影响编译器编译。crate属性包括:metadata(用来定位和链接crate)、crate的类型(库还是可执行文件)、控制warning和error行为、以及一些别的东东。crate文件也常常声明该crate依赖的外部crates以及从其他文件加载的模块。
// Crate linkage metadata
#[link(name = "farm", vers = "2.5", author = "mjh")];

// Make a library ("bin" is the default)
#[crate_type = "lib"];

// Turn on a warning
#[warn(non_camel_case_types)]

// Link to the standard library
extern mod std;

// Load some modules from other files
mod cow;
mod chicken;
mod horse;

fn main() {
    ...
}
编译上述文件时,编译器将在.rc文件所做目录中查找cow.rs、chicken.rs、以及horse.rs文件,将它们一起编译,然后根据crate_type = "lib"这个属性,生成一个共享库或者可执行文件(如果#[crate_type = "lib"];不存在,rustc将生成可执行文件)。

#[link(...)]属性提供了关于模块的元信息,这些信息供其他crate正确加载模块使用,后面会进一步说明。

想要使用目录嵌套结构来安排源文件的话,可以使用嵌套模块:
mod poultry {
    mod chicken;
    mod turkey;
}
编译器将查找poultry/chicken.rs和poultry/turkey.rs,同时将内容导出到poultry::chicken和poultry::turkey名字空间中。你也可以提供一个poultry.rs文件来加入poultry模块自身的内容。

3 使用其他crates

extern mod声明使得我们可以在一个crate中使用另一个crate(该crate已被编译为库)。extern mod可以出现在crate文件的顶部或者模块的顶部,这将使得编译器在查找路径(可以通过-L开关追加路径)中搜寻已编译好的、名字对应的库,然后在当前作用域中加入一个以crate名字为名模块。

例如,extern mod std链接标准库

当extern mod后出现逗号分隔的名字/值列表时,编译器前端将用这些来匹配crate文件中link属性提供信息。只有两者匹配时编译器才会使用相应地crate。我们也可以通过提供name属性来覆盖用来查找crate的名字。

举个例子,crate声明的link属性为:
#[link(name = "farm", vers = "2.5", author = "mjh")];
我们可以通过下面的任一种方式链接该crate:
extern mod farm;
extern mod my_farm (name = "farm", vers = "2.5");
extern mod my_auxiliary_farm (name = "farm", author = "mjh");
如果任一个指明的元数据不匹配,那么crate就不能编译成功。

4 一个小例子

下面是两个文件:
// world.rs
#[link(name = "world", vers = "1.0")];
pub fn explore() -> &str { "world" }
// main.rs
extern mod world;
fn main() { io::println(~"hello " + world::explore()); }
现在通过下述命令来编译:
> rustc --lib world.rs  # compiles libworld-94839cbfe144198-1.0.so
> rustc main.rs -L .    # compiles main
> ./main
"hello world"
注意生成的库文件中包含版本号以及一串由数字和字母组成的诡异字符。这两个都是Rust库版本机制的组成部分。数字字母串是一个表示crate元数据的hash值。

5 core库

Rust的core库是Rust的运行时系统,包含了内存管理、task调度以及基础类型相关的模块。比如vectorsstrings相关的方法,大部分比较运算及数学运算的实现,以及像OptionResult这种无所不在的核心类型。

只要crate的开头出现下面的代码,所有的Rust程序都将链接core库,导入core的内容:
extern mod core;
use core::*;


没有评论:

发表评论