2013年1月19日星期六

Rust中的泛型

Rust中的泛型

根据Rust 0.6 tutorial整理。

这部分内容鄙人很喜欢,Rust的泛型和Haskell非常类似,所以我将参照着Haskell来叙述。(推荐一下Haskell,看了Haskell之后很多“新鲜”概念就不那么神秘了)

1 泛型函数

Rust是静态类型语言,因此定义函数时要提供参数和返回值的类型,提供了类型之后,函数就只能作用于给定的类型了。如果我们不提供具体类型,而是提供类型参数(类似Haskell中的类型变量),那么函数就能作用于很多类型了,所以叫泛型:
fn map(vector: &[T], function: fn(v: &T) -> U) -> ~[U] {
    let mut accumulator = ~[];
    for vec::each(vector) |element| {
        accumulator.push(function(element));
    }
    return accumulator;
}
如上例所示,map就是一个泛型函数,类型参数为<T, U>。它接受一个向量参数vector和一个函数参数function,其中vector的元素类型为T;function参数类型&T(为了保证通用性,因为有些类型的拷贝代价高昂,甚至不允许拷贝),返回类型为U;map返回类型为~[U]。map函数对vector中的每个元素调用函数function,然后将得到的结果装入新的向量,最后返回该向量。

我们看一下Haskell中类似的实现,就会发现两者很类似,只不过Haskell中类型变量惯例用小写。

my_map :: [t] -> (t -> u) -> [u]
my_map [] _ = []
my_map (x:xs) f = (f x):(my_map xs f)

2 泛型类型

我们之前定义的type、struct和enum中,每个数据域的类型都是固定的。Rust允许数据域的类型不固定,而由类型参数给出,如下所示:
type Set = HashMap;

struct Stack {
    elements: ~[mut T]
}

enum Option {
    Some(T),
    None
}
和泛型函数类似,类型参数写在<>中。这些泛型就可以实例化为Set<int>,Stack<int>和Option<int>这样的具体类型。

而在Haskell中,上述类型定义对应为:
type Set t = HashMap t () 

data Stack t = Stack [t]

data Option t = Some t
              | None

熟悉Haskell的估计已经看出来,Rust中的Option就是Haskell中的Maybe。而Rust的stuct和enum在Haskell中都是data。

Option在Rust中使用频率很高。因为Rust中没有null指针(除了unsafe code),所以需要用Option来应对不存在的情况。例如:
fn radius(shape: Shape) -> Option {
   match shape {
       Circle(_, radius) => Some(radius),
       Rectangle(*)      => None
   }      
}
Rust编译器可以高效地编译泛型函数,通过对泛型函数进行monomorphizing。Monomorphization是一种简单的方法:在每个调用点为泛型函数生成单独的拷贝,该拷贝根据参数类型对函数进行实例化,所以可以针对参数类型进行优化。因此,Rust的泛型有着与C++模板类似的性能。

3 Traits

泛型函数中能对类型参数进行的操作是很有限的,毕竟不知道具体的类型,所以不能安全地对它们进行修改或者查询它们的值。这就是为什么需要trait的原因。Traits是Rust中编写多态代码的强大工具。Java程序员会发现traits很像Java的interface。Haskeller也会发现它们很像type classes(确实如此)。Rust的traits是一种bounded polymorphism:trait用来限定类型参数所能代表的类型种类。

我们看一下trait的具体应用。在Rust中,不是所有类型都有copy操作。用户自定义析构函数是不能copy的一个原因:拷贝一个带有析构函数的类型可能会使析构函数被多次调用。因此,有用户自定义析构函数的类型不能拷贝(无论是隐式拷贝还是显式拷贝),而其它类型也不能包含有着析构函数的类型。

这就复杂化了对泛型函数的处理。如果类型参数为T,我们可以拷贝它的值吗?在Rust中,你不能,如果试图运行下面的代码,编译器会发出抱怨:
// This does not compile
fn head_bad(v: &[T]) -> T {
    v[0] // error: copying a non-copyable value
}
然而,如果我们告诉编译器head函数只作用于能够copy的类型:也就是那些实现了Copy trait的类型:
// This does
fn head(v: &[T]) -> T {
    v[0]
}
这就意味着head函数只能作用于实现了Copy trait的类型。当实例化泛型函数时,你只能用实现了正确trait的类型来进行实例化,所以不能将head函数应用于有析构函数的类型(Copy是一个特殊的trait,编译器自身内置,使得编译器能够强制实行该限制)。

虽然大部分traits都可以由用户自定义和实现,但是有三个trait是由编译器自动推导和实现的,用户不能覆盖:
  • Copy——可以被拷贝的类型:通过隐式拷贝,或者copy运算符显式拷贝。除了有析构函数的类型和包含了有析构函数类型的类型外,所有的类型都是可拷贝的。
  • Owned——Owned types。除了包含有managed boxes, managed closures,和borrowed pointers的类型外,其他类型都属于owned。Owned类型可能可以拷贝,也可能不能拷贝。
  • Const——常量(不可变的)类型。指没有包含可变域的类型。
另外,Drop trait用来定义析构函数。该trait定义了finalize函数,该函数在实现了Drop的类型的值被销毁时自动调用(当值超出作用域,或者垃圾回收时)。
struct TimeBomb {
    explosivity: uint,
}

impl TimeBomb : Drop {
    fn finalize(&self) {
        for iter::repeat(self.explosivity) {
            io::println("blam!");
        }
    }
}
直接调用finalize是非法的。只有由编译器插入的代码才可以调用它。

4 声明和实现traits

trait包含了一组方法,这些方法没有函数体,或者为空。例如,我们声明一个名为Printable的trait,有一个print方法:
trait Printable {
    fn print(&self);
}
这在Haskell中对应于type class:
class Printable a where
    my_print :: a -> IO ()
Traits由具体类型来实现,实现由impl关键字引出,和为类型定义方法类似,不过多了trait相关说明:

impl int: Printable {
    fn print(&self) { io::println(fmt!("%d", *self)) }
}

impl &str: Printable {
    fn print(&self) { io::println(*self) }
}
对应于Haskell中对typeclass的实例化:
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}  
 
instance Printable Int where
    my_print i = print i
  

instance Printable String where
    my_print str = print str

Trait的实现中定义的方法调用起来和其他的方法一样,使用点记法,如1.print()。Traits也可以包含类型参数。带有类型参数的trait如下所示:
trait Seq {
    fn len(&self) -> uint;
    fn iter(&self, b: fn(v: &T));
}

impl ~[T]: Seq {
    fn len(&self) -> uint { vec::len(*self) }
    fn iter(&self, b: fn(v: &T)) {
        for vec::each(*self) |elt| { b(elt); }
    }
}

上述实现中必须明确声明类型参数(impl<T>)。声明后才能使用T来指明trait类型(Seq<T>)。Rust要求这么做是因为:impl也可以用来定义Seq<int>这样的实现。trait的类型(冒号后的部分,Seq<T>)参考了一个类型(T),而不是定义了一个类型。

Trait绑定的类型参数对于trait的每一个方法都是可见的。所以,对于trait中的方法,不管是trait中声明,还是impl中实现 ,都不需要再明确指明类型参数(比如:fn len<T>(&self) -> uint),否则将引起编译错误。

在trait定义中,self是一个特殊的类型,你可以认为它是一个类型参数。一个针对类型T的trait实现会把self替换为T。简单地说,在trait中,self是一个类型,而在impl中,self是一个值。下面的trait描述了支持相等操作的类型:
// In a trait, `self` refers both to the self argument
// and to the type implementing the trait
trait Eq {
  fn equals(&self, other: &self) -> bool;
}

// In an impl, `self` refers just to the value of the receiver
impl int: Eq {
  fn equals(&self, other: &int) -> bool { *other == *self }
}
注意在上面trait定义中,equals第二个参数的类型为self。相应的,在impl中,equals第二个参数类型为int,只使用self作为接收者的名字。

Traits也可以定义静态方法,然后通过trait名字前缀来调用。编译器通过类型推导来判断调用哪个实现。
struct Circle { radius: float }
struct Square { length: float }

impl Circle: Shape {
    static fn new(area: float) -> Circle { Circle { radius: sqrt(area / pi) } }
}
impl Square: Shape {
     static fn new(area: float) -> Square { Square { length: sqrt(area) } }
}

let area = 42.5;
let c: Circle = Shape::new(area);
let s: Square = Shape::new(area);

5 类型参数限定和静态方法分配

前面说了,在泛型函数中,我们能对类型参数所做的事情很有限。因为不知道类型参数的具体类型,也就不知道它所能支持的操作。有了traits之后,我们就能使用traits来限定类型参数,指明其具有的操作或者抽象属性。
fn print_all(printable_things: ~[T]) {
    for printable_things.each |thing| {
        thing.print();
    }
}
上例中,我们声明T为实现了Printable trait的类型,因此类型T的值就可以调用Printable中声明的方法。同时,如果对以未实现Printable的类型的值为元素的向量调用print_all时,就会引发编译错误。
类型参数也可以由多个traits限定,用空格分隔trait即可,比如下面版本的print_all:
fn print_all(printable_things: ~[T]) {
    let mut i = 0;
    while i < printable_things.len() {
        let copy_of_thing = printable_things[i];
        copy_of_thing.print();
        i += 1;
    }
}
调用含有限定类型的方法时使用的是静态分配和普通的函数调用相比没有额外开销,因此也是最受欢迎的traits多态方式。(注:因为类型已经被trait限定,而trait中声明的方法也已经被实现,所以调用时不需要编译器动态生成什么东西,效率很高,是为静态分配。) 这种使用traits的方式类似于Haskell种的type classes:
-- 限定了Printable的print_all
print_all :: (Printable t) => [t] -> IO ()
print_all xs = mapM_ my_print xs

-- 限定了Printable和Copy的print_all
-- 这里只写出函数签名,Copy在Haskell中无对应 
print_all :: (Printable t, Copy t) => [t] -> IO ()

6 Traits对象和动态方法分配

上述的方法使得我们能够定义具有多态行为的函数,该函数作用于被trait限定的未知类型。然而,考虑下面这个函数:
trait Drawable { fn draw(&self); }

fn draw_all(shapes: ~[T]) {
    for shapes.each |shape| { shape.draw(); }
}
我们可以在circles数组上调用draw_all,也可以在squares数组上调用draw_all(假定它们都实现了Drawable trait),但是不能在一个包含了circles和squares的数组上调用draw_all。如果我们需要这么做,那么可以把trait的名字当作类型,叫做object
fn draw_all(shapes: &[@Drawable]) {
    for shapes.each |shape| { shape.draw(); }
}
在上例中,没有类型参数。取而代之的是,用@Drawable类型来表示任何实现了Drawable trait的managed box值。为了构造这样的值,我们可以使用as运算符来将一个值转换成一个object。
impl Circle: Drawable { fn draw(&self) { ... } }

impl Rectangle: Drawable { fn draw(&self) { ... } }

let c: @Circle = @new_circle();
let r: @Rectangle = @new_rectangle();
draw_all([c as @Drawable, r as @Drawable]);
这里省略了new_circle和new_rectangle的代码;假设它们返回缺省大小的CircleS和RectangleS。注意,和字符串及向量类似,objects具有动态大小,因此只能通过指针访问。将类型强转为traits时指针必须兼容,所以,不能把@Circle强转为~Drawable。
// A managed object
let boxy: @Drawable = @new_circle() as @Drawable;
// An owned object
let owny: ~Drawable = ~new_circle() as ~Drawable;
// A borrowed object
let stacky: &Drawable = &new_circle() as &Drawable;
在traits类型的对象上调用方法时使用的是动态分配。因为编译器编译时不知道调用哪个方法,而在运行时通过lookup table(也叫vtable或者dictionary)才知道调用哪个方法。 这种使用Traits的方式类似Java interfaces。

7 Trait 继承

我们可以声明一个trait继承自其他traits(supertraits,父trait),一个类型如果实现了一个trait,也必须同时实现它的supertraits。例如:我们定义一个Circle trait继承了Shape。
trait Shape { fn area(&self) -> float; }
trait Circle : Shape { fn radius(&self) -> float; }
现在,我们要用一个类型实现Circle,同时也必须实现Shape。
struct CircleStruct { center: Point, radius: float }
impl CircleStruct: Circle {
     fn radius(&self) -> float { sqrt(self.area() / pi) }
}
impl CircleStruct: Shape {
     fn area(&self) -> float { pi * square(self.radius) }
}   
注意,Circle的方法可以调用Shape的方法,比如我们的radius实现中调用了area方法。这种求圆半径的方法很蠢,纯粹用来帮我们明白继承的含义。 trait继承在Haskell对应的概念是subclassing。上述例子在Haskell中对应为:
class Shape a where
    area :: a -> Float
  
class (Shape a) => Circle a where
    getRadius :: a -> Float
  
data CircleStruct = CircleStruct { center :: Point
                                 , radius :: Float }  
                    
instance Circle CircleStruct where
    getRadius c = sqrt $ (area c) / pi
  
instance Shape CircleStruct where
    area c = pi * (radius c)^2
在具有类型参数的函数中,supertrait的方法可以被由subtrait(子trait)限定的参数类型的值调用。回到之前的例子:trait Circle : Shape。
fn radius_times_area(c: T) -> float {
    // `c` is both a Circle and a Shape
    c.radius() * c.area()
}
上述例子在Haskell中对应为:
radius_times_area :: (Circle t) => t -> Float
radius_times_area c = getRadius c * area c
类似的,supertrait方法也可以在trait object上调用。
let mycircle: Circle = @mycircle as @Circle;
let nonsense = mycircle.radius() * mycircle.area();
注意:上述trait objects相关的继承暂时未实现好。
 注: Rust的trait本质上类似Haskell的type class。但是混合了Java的interface特性,即trait可以当作类型使用,可以构造某个trait类型的对象。而这部分偏OO的能力在纯函数式的Haskell中没有对应。

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::*;


2013年1月10日星期四

Rust中的方法(Methods)

Rust中的方法(Methods)

整理自Rust 0.6 tutorial。

Methods和函数类似,只不过第一个参数总是self,用来表明方法的接收者。self参数类似C++中的this指针,以及其它语言中的self。方法调用使用点符号,比如:my_vec.len()。

Implementations,以关键字impl开头,用来为大多数Rust类型定义方法,包括structs和enums。例如,我们为enum Shape定义一个draw方法。
struct Point {
    x: float,
    y: float
}

enum Shape {
    Circle(Point, float),
    Rectangle(Point, Point)
}

impl Shape {
    fn draw(&self) {
        match *self {
            Circle(p, f) => draw_circle(p, f),
            Rectangle(p1, p2) => draw_rectangle(p1, p2)
        }
    }
}

let s = Circle(Point { x: 1f, y: 2f }, 3f);
s.draw();
这样就为Shape定义了一个implementation,包含一个方法draw。draw方法定义和函数定义很类似,除了self参数。

self的类型是该方法接收者的类型,或者指针。self可以写成self, &self, @self,或者~self。调用者必须使用相容的指针类型来调用方法:
impl Shape {
    fn draw_borrowed(&self) { ... }
    fn draw_managed(@self) { ... }
    fn draw_owned(~self) { ... }
    fn draw_value(self) { ... }
}

let s = Circle(Point { x: 1f, y: 2f }, 3f);

(@s).draw_managed();
(~s).draw_owned();
(&s).draw_borrowed();
s.draw_value();
self通常使用borrowed pointer类型,所以编译器会尽可能地把方法接收者转换为borrowed pointer。

// As with typical function arguments, managed and unique pointers
// are automatically converted to borrowed pointers

(@s).draw_borrowed();
(~s).draw_borrowed();

// Unlike typical function arguments, the self value will
// automatically be referenced ...
s.draw_borrowed();

// ... and dereferenced
(& &s).draw_borrowed();

// ... and dereferenced, and borrowed, and
(&@~s).draw_borrowed();
Implementations也可以定义static方法,静态方法不需要self参数。关键字static将静态方法和需要self的方法区分开来。
impl Circle {
    fn area(&self) -> float { ... }
    static fn new(area: float) -> Circle { ... }
} 
注:后续将移除static关键字,以是否存在self参数来区分静态方法和非静态方法。
构造函数是静态方法最常使用的地方,比如上面的new。调用静态方法需要提供类型名前缀:
struct Circle { radius: float }
impl Circle {
    static fn new(area: float) -> Circle { Circle { radius: sqrt(area / pi) } }
}
let c = Circle::new(42.5);


2013年1月9日星期三

Rust中的匿名函数与闭包

Rust中的匿名函数与闭包(Closures)

根据Rust 0.6的tutorial整理。

“闭包”这个概念来自于抽象代数,是指一个元素的集合在某个运算之下封闭,即将该运算应用于这个集合的元素,所得到的结果仍然是该集合的元素。不幸的是,Lisp社区将“闭包”用来表示另一种含义:闭包是一种为表示带有自由变量的过程而用的实现技术。简单说就是过程中可以使用自由变量(自由变量是相对于约束变量而言,约束变量即过程的形参,以及过程中定义的变量),严格地讲是指能捕获上下文环境。在编程语言中,“闭包”这个概念很不幸指的是Lisp社区的概念。

先使用“闭包”概念的源头——Lisp(Scheme)——来说明简单说明闭包。
我们定义一个函数make-add-n。该函数接受一个数值参数n,返回一个匿名函数。 该匿名函数接受一个数值参数i,返回i+n的值。

(define (make-add-n n)
  (lambda (i)
    (+ i n)))

lambda中的n对于lambda来讲就是自由变量。我们使用make-add-n生成一个add-3函数:

(define add-3 (make-add-n 3))

> (add-3 5)
8

可以看出,make-add-n的参数n在make-add-n调用结束后就已经离开了作用域,不存在了。但是add-3依然可以访问n(这里n=3),也就是说add-3(或者说make-add-n中的lambda)捕获了n(n=3)。

下面回到Rust。在Rust中,具名函数(用fn定义的那些给出了名字的函数)是不具备闭包能
力的,Rust中的闭包由匿名函数来完成。

1 Rust中的匿名函数

我们由一个例子引出匿名函数。下例中,我们定义一个匿名函数,该匿名函数接受一个参数,并将参数的值打印出来。然后我们将该匿名函数赋值给变量closure。
let closure = |arg| io::println(fmt!("arg=%d", arg));
然后closure就可以像函数一样调用:

    closure(10);

从上例中可以看出,Rust的匿名函数由双竖线| |引出。双竖线中是匿名函数的参数列表,后面跟着匿名函数的表达式。匿名函数的参数类型以及返回值类型通常可以忽略,交由编译器推断。有时编译器推断不出来,则需要我们加上类型说明:

let square = |x: int| -> uint { x * x as uint };
上面的例子中,匿名函数所做的操作都是很简单的,如果要做一点复杂的事情怎么办?比如要用if判断?用下面这个例子说明:
 
    let abs = |x: int| {
        if x > 0 {
            x
        } else { -x }
    };

到目前为止一切似乎都很简单,哦,等等,复杂的来了——“闭包”。

闭包本身不复杂,那个scheme的例子基本说明了闭包是怎么一回事。不过呢,到了Rust中,事情变得有趣了很多。我们还记得Rust的内存模型吧?Rust中的数据有三处存储地可选:栈上、托管堆上、以及交换堆上。所以呢,Rust中有三种闭包,你没听错,是三种。分别对应于三种存储地:stack closure、managed closure、owned closure
:tutorial中是把closure和匿名函数合在一起讲的,重点在强调closure特性,但我觉得分开说好一些,匿名函数可以使用闭包,也可以不使用闭包。不使用闭包特性的匿名函数照样很有用。使用stack closure、managed closure、owned closure术语是重点强调这几种匿名函数对于捕获的变量的要求,所以我也沿用这几个术语。

2 Stack closures

Stack closures的类型为&fn,可以直接访问它上下文环境中的局部变量:
let mut max = 0;
[1, 2, 3].map(|x| if *x > max { max = *x });
Stack closures非常高效,因为它们的环境是分配在栈上,同时通过指针访问捕获的变量(不用拷贝)。为了保证stack closures的生命周期不会比它们捕获的变量还长,stack closures不能作为第一等级函数使用。就是说:它们不能存储在数据结构中,也不能当作函数的返回值;它们通常用作某些高阶函数的参数,如上例中作为map的参数。除了这些限制,stack closure在Rust中被普遍使用(匿名简短小函数)。

 

3 Managed closures

Managed closures对应于managed boxes,即在托管堆上存储的closures。因此生存周期没有stack closures那样的限制。类型为@fn。是第一等级函数,可以存储,可以被当作返回值返回。当然,继承自managed boxes的限制,managed closures无法跨越tasks边界。

另外,managed closure不直接访问它的上下文环境,而是将它捕获的值拷贝为私有数据结构。因此它不能对这些捕获的变量进行赋值,也无法检测到外部值得变化。

来看一个例子:
fn mk_appender(suffix: ~str) -> @fn(~str) -> ~str {
    // The compiler knows that we intend this closure to be of type @fn
    return |s| s + suffix;
}

fn main() {
    let shout = mk_appender(~"!");
    io::println(shout(~"hey ho, let's go"));
}

4 Owned closures

Owned closures,写作~fn,类似于owned boxes,存储于交换堆,因此可以在tasks之间传递。和managed boxes类似,也拷贝它们所捕获的值,不同的是,owned closures拥有这些值的所有权,别的代码无法访问这些捕获的值。Owned closures用于并发代码,特别是生成tasks。

 

5 匿名函数的通用性

 Rust的匿名函数有个很便利的子类型化特性:你可以将任何类型的匿名函数(只要参数和返回值类型匹配)传递给接受fn()为参数的函数。因此,如果写了一个高阶函数,仅仅调用其的函数参数,而不对参数作别的事,那么应该将其参数声明为fn()类型。这样的话,调用者就可以传入任意类型的函数(包括具名函数)。
fn call_twice(f: fn()) { f(); f(); }
let closure = || { "I'm a closure, and it doesn't matter what type I am"; };
fn function() { "I'm a normal function"; }
call_twice(closure);
call_twice(function); 

6 Do 记法

do记法提供了将高阶函数(以函数为参数的函数)转换为控制结构的方法。
例如,each函数对一个vector的元素进行迭代,以一个vector和一个函数为参数,将函数应用于vector的每个元素上:
fn each(v: &[int], op: fn(v: &int)) {
   let mut n = 0;
   while n < v.len() {
       op(&v[n]);
       n += 1;
   }
}
调用each时,如果我们使用匿名函数作为参数,那么我们可以写成一种很好看的形式:
each([1, 2, 3], |n| {
    do_some_work(n);
});
这种形式很常用,因此Rust提供了一个特殊的函数调用形式,使得上面的调用看起来更像内置的控制结构:
do each([1, 2, 3]) |n| {
    do_some_work(n);
}
上面的方式以关键字do开头,不将最后的匿名函数参数写入括号内,而是写在括号外,这样看起来更像一个常见的代码块(很像Ruby的Block)。
 
使用do来调用task::spawn创建task很方便。spawn函数的类型签名为spawn(fn: ~fn())。也就是说,spawn是一个函数,接受一个无参数的ownend closure为参数。
use task::spawn;

do spawn() || {
    debug!("I'm a task, whatever");
}
上面的括号和竖线中什么都没有,所以可以省略:
do spawn {
   debug!("Kablam!");
} 

7 For 循环

Rust中最常用的进行迭代的方式是使用for循环。和do类似,for也有着将closures描述成控制结构的漂亮语法。另外,在for循环中,break、loop、以及return的作用和它们在while和loop中相同。

还是用each函数这个例子,这次我们当函数参数返回false时break:
fn each(v: &[int], op: fn(v: &int) -> bool) {
   let mut n = 0;
   while n < v.len() {
       if !op(&v[n]) {
           break;
       }
       n += 1;
   }
}
使用该函数对vector进行迭代:
each([2, 4, 8, 5, 16], |n| {
    if *n % 2 != 0 {
        println("found odd number!");
        false
    } else { true }
});
有了for,each这样的函数就可以更像内建循环结构。在for循环中调用each,不需要通过返回false来跳出循环,直接使用break即可。如果需要调制下一次迭代开头,使用loop。
for each([2, 4, 8, 5, 16]) |n| {
    if *n % 2 != 0 {
        println("found odd number!");
        break;
    }
}
另外,你还可以在作为for循环体的block中使用return,这在closures中通常是不允许的,这时return将从使用for的函数中返回,而不仅仅是循环体。
fn contains(v: &[int], elt: int) -> bool {
    for each(v) |x| {
        if (*x == elt) { return true; }
    }
    false
}
注意,each将每个值通过borrowed pointer传递,所以每次使用值时需要解引用。我们可以使用Rust的参数模式匹配来直接获取值,而不是指针:
    for each(v) |&x| {
        if (x == elt) { return true; }
    }
for记法只能用于stack closures。

2013年1月8日星期二

Rust中的vector和字符串

Rust中的vector和字符串

根据Rust 0.6的tutorial整理。

一个vector就是一段相邻的内存,其中包含零个或者多个同一类型的值。和Rust的其他类型一样,vectors可以存储于栈,本地堆,和交换堆上。vectors的borrowed pointers也称为“slices”。
// A fixed-size stack vector
let stack_crayons: [Crayon * 3] = [Almond, AntiqueBrass, Apricot];

// A borrowed pointer to stack allocated vector
let stack_crayons: &[Crayon] = &[Aquamarine, Asparagus, AtomicTangerine];

// A local heap (managed) vector of crayons
let local_crayons: @[Crayon] = @[BananaMania, Beaver, Bittersweet];

// An exchange heap (owned) vector of crayons
let exchange_crayons: ~[Crayon] = ~[Black, BlizzardBlue, Blue];
运算符 + 作用于vector类型时表示连接。
let my_crayons = ~[Almond, AntiqueBrass, Apricot];
let your_crayons = ~[BananaMania, Beaver, Bittersweet];

// Add two vectors to create a new one
let our_crayons = my_crayons + your_crayons;

// += will append to a vector, provided it lives in a mutable slot
let mut my_crayons = my_crayons;
my_crayons += your_crayons;
注意: 上面的例子中使用的是owned vectors。一些对slices和stack vectors的操作还没有很好地实现。Owned vectors通常是最可用的。
方括号表示对vector进行索引:
let crayons: [Crayon * 3] = [BananaMania, Beaver, Bittersweet];
match crayons[0] {
    Bittersweet => draw_scene(crayons[0]),
    _ => ()
}
vector的元素继承vector的可变性,因此,如果vector是不可变的,那么元素也不能重新赋值。
let crayons: ~[Crayon] = ~[BananaMania, Beaver, Bittersweet];

crayons[0] = Apricot; // ERROR: Can't assign to immutable vector
将其转移至可变变量,就可以对元素赋值。
let crayons: ~[Crayon] = ~[BananaMania, Beaver, Bittersweet];

// Put the vector into a mutable slot
let mut mutable_crayons = move crayons;

// Now it's mutable to the bone
mutable_crayons[0] = Apricot;
这个简单的例子展示了Rust中数据结构的双模式:冻结和解冻。

字符串被实现为以u8为元素的vector,虽然它们有明确的类型(str)。字符串基本上支持和vectors相同的分配方式,不过没有明确存储标识的字符串(例如,"foo")和相应的vector([foo])的处理方式不同。Plain vectors是栈上分配的大小固定的vectors,plain strings是指向只读内存的region pointers(borrowed pointers)。所有的字符串都是不可变的。
// A plain string is a slice to read-only (static) memory
let stack_crayons: &str = "Almond, AntiqueBrass, Apricot";

// The same thing, but with the `&`
let stack_crayons: &str = &"Aquamarine, Asparagus, AtomicTangerine";

// A local heap (managed) string
let local_crayons: @str = @"BananaMania, Beaver, Bittersweet";

// An exchange heap (owned) string
let exchange_crayons: ~str = ~"Black, BlizzardBlue, Blue";
Vectors和字符串支持许多有用的方法,定义在模块core::vec和core::str中。下面是一些例子:
let crayons = [Almond, AntiqueBrass, Apricot];

// Check the length of the vector
assert crayons.len() == 3;
assert !crayons.is_empty();

// Iterate over a vector, obtaining a pointer to each element
for crayons.each |crayon| {
    let delicious_crayon_wax = unwrap_crayon(*crayon);
    eat_crayon_wax(delicious_crayon_wax);
}

// Map vector elements
let crayon_names = crayons.map(|v| crayon_to_str(*v));
let favorite_crayon_name = crayon_names[0];

// Remove whitespace from before and after the string
let new_favorite_crayon_name = favorite_crayon_name.trim();

if favorite_crayon_name.len() > 5 {
   // Create a substring
   println(favorite_crayon_name.substr(0, 5));
}

2013年1月7日星期一

Rust中的盒子和指针

Rust中的盒子和指针

根据Rust 0.5的tutorial整理,指针这部分内容应该不会变化了。

大多数现代语言对于聚合类型(如class, struct, enum)都采用一种“uniform representation”方式表示,将这些类型缺省表示为分配在堆上的内存的指针。Rust则不同,类似于C和C++,Rust直接表示这些类型。也就是说聚合数据在Rust中是未打包的(unboxed)。这意味着当你写下let x = Point {x: 1f, y: 1f};时,你是在栈上创建了一个struct。如果你把它拷贝到一个数据结构中,你拷贝的是整个struct,而不是指针。

对于Point这样的小型结构来说,直接拷贝要比先分配内存后再通过指针访问高效。但是对于大型结构,或者那些有可变数据域的结构,只在栈上或堆上保留一份数据,然后通过指针引用会更好一些。

Rust支持好几种指针(三种)。安全指针@T代表分配在本地堆上的managed boxes;指针~T代表分配在交换堆上的uniquely-owned boxes;而指针&T,代表
borrowed pointers,这种指针可以指向任何内存,同时它们的生命周期由调用栈管理。

所有的指针类型都由一元运算符 * 来解除引用。

1 Managed Boxes 

Managed Boxes是一种指针,指向堆上分配,具有GC的内存。将一元运算符@ 应用于表达式就建立了一个managed box。生成的盒子包含了表达式的结果。拷贝一个managed box,只是拷贝了指针,而不是盒子中的内容。
let x: @int = @10; // New box
let y = x; // Copy of a pointer to the same box

// x and y both refer to the same allocation. When both go out of scope
// then the allocation will be freed.  
一个managed type要么形如@T,要么是任何包含了managed boxes或者其他managed types的类型。
// A linked list node
struct Node {
    mut next: MaybeNode,
    mut prev: MaybeNode,
    payload: int
}

enum MaybeNode {
    SomeNode(@Node),
    NoNode
}

let node1 = @Node { next: NoNode, prev: NoNode, payload: 1 };
let node2 = @Node { next: NoNode, prev: NoNode, payload: 2 };
let node3 = @Node { next: NoNode, prev: NoNode, payload: 3 };

// Link the three list nodes together
node1.next = SomeNode(node2);
node2.prev = SomeNode(node1);
node2.next = SomeNode(node3);
node3.prev = SomeNode(node2);
Managed Boxes绝不跨越task边界。

2 Owned Boxes

和managed boxes不同的是,owned boxes具有内存独享性,因此两个owned boxes不会指向同一内存。所有跨越全部tasks边界的owned boxes都在一个exchange heap上分配。而它们的唯一所有权特性使得tasks可以高效地交换它们。

由于owned boxes的唯一所有特性,拷贝它们则需要分配一个新的owned box然后复制内容。然而,owned boxes默认使用moved,交换内存所有权,反初始化前一个owning变量。任何企图在变量被move后访问该变量的操作都将触发编译错误。
let x = ~10;
// Move x to y, deinitializing x
let y = x;
如果你打算拷贝owned box,你必须明确指出:
let x = ~10;
let y = copy x;

let z = *x + *y;
assert z == 20;
当owned boxes不包含任何managed boxes时,可以发送给其他task。发送方task将交出box的所有权,发送后将再不能访问它。接收方task将成为box的唯一所有者。

3 Borrowed Pointers

Rust的borrowed pointers是通用的引用/指针类型,类似C++的引用类型,但是保证指向有效内存。和owned boxes不同的是,borrowed pointers从不隐含内存所有权。指针可以从任意类型借来,同时保证指针不会比它指向的值生存得更久。

举个例子,比如一个简单的结构类型:Point
struct Point {
    x: float, y: float
}
我们使用这个简单的类型来展示指针的多种分配方式。例如,在下面的代码中,三个局部变量都包含一个point,但是分配在不同的位置上。
let on_the_stack : Point  =  Point {x: 3.0, y: 4.0};
let managed_box  : @Point = @Point {x: 5.0, y: 1.0};
let owned_box    : ~Point = ~Point {x: 7.0, y: 9.0};
假如我们想要写一个计算两个点距离的函数,要求不管两个点存储在什么位置都能用。例如,我们可能计算on_the_stack和managed_box之间的距离,或者managed_box和owned_box之间的距离。一种方法是定义两个参数都是Point类型的函数,该函数使用point的值。这就会造成我们调用函数时拷贝point的值。对于points而言,问题还不严重,但是一般情况下拷贝都是代价高昂的,甚至更糟的是,存在可变数据域,这些将改变程序的语意。所以我们一般定义接受指向points的指针的函数。我们使用borrowed_pointers来达成目标:
fn compute_distance(p1: &Point, p2: &Point) -> float {
    let x_d = p1.x - p2.x;
    let y_d = p1.y - p2.y;
    sqrt(x_d * x_d + y_d * y_d)
}
现在我们可以以各种方式调用compute_distance():
compute_distance(&on_the_stack, managed_box);
compute_distance(managed_box, owned_box);
这里的&运算符用来取得变量on_the_stack的地址;我们称这个为borrowing局部变量on_the_stack,因为我们创建了一个别名:也就是访问同一数据的另一种方式。

而对于像managed_box和owned_box这样的盒子,则不需要什么操作。编译器能自动将类似@point或~point这样的盒子转换为类似&point这样的borrowed pointer。这是另一种形式的borrowing;这种情况下,managed/owned box的内容被借出。

不论一个值何时被借出,对于原始值得操作都存在一些限制。例如,如果一个变量的内容已经被借出,那么我们不能将该变量发送给别的task,也不能执行任何会使得借出的值被释放,或者类型改变的操作。请牢记这条原则:你必须等借出的值被归还后(也就是等borrowed pointer离开作用域),才能再次对它为所欲为。

对于borrowed pointers更进一步的解释,请参考borrowed pointer tutorial

4 Dereferencing pointers

Rust使用一元运算符 * 来获取box或者pointer的值,这点和C类似。
let managed = @10;
let owned = ~20;
let borrowed = &30;

let sum = *managed + *owned + *borrowed;
对可变指针解引用可以出现在赋值式的左边。这样的赋值将修改指针指向的值。
let managed = @mut 10;
let owned = ~mut 20;

let mut value = 30;
let borrowed = &mut value;

*managed = *owned + 10;
*owned = *borrowed + 100;
*borrowed = *managed + 1000;
指针相关操作符具有高优先级,但是比取数据域和调用方法的点操作优先级低。
let start = @Point { x: 10f, y: 20f };
let end = ~Point { x: (*start).x + 100f, y: (*start).y + 100f };
let rect = &Rectangle(*start, *end);
let area = (*rect).area();
为了消除上面那丑陋的星号括号,点操作符对接受者(点号的左边)自动解引用,所以大多数情况下,不需要对接受者显式解引用。
let start = @Point { x: 10f, y: 20f };
let end = ~Point { x: start.x + 100f, y: start.y + 100f };
let rect = &Rectangle(*start, *end);
let area = rect.area();
来个极端的例子,让编译器对任意重指针自动解引用:
let point = &@~Point { x: 10f, y: 20f };
io::println(fmt!("%f", point.x));
索引操作符([ ])也具有自动解引用的特性。