├── graphs ├── Cargo.toml ├── src │ ├── mod.rs │ ├── rc_graph.rs │ ├── ref_graph.rs │ └── ref_graph_generic_mod.rs └── README.md ├── LICENSE.md ├── primitives.md ├── hello world.md ├── why rust.md ├── unique.md ├── rc raw.md ├── README.md ├── control flow.md ├── destructuring.md ├── borrowed.md ├── destructuring 2.md ├── arrays.md └── data types.md /graphs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "graphs" 3 | version = "0.0.1" 4 | authors = ["nrc "] 5 | 6 | [[bin]] 7 | name = "graphs" 8 | path = "src/mod.rs" 9 | -------------------------------------------------------------------------------- /graphs/src/mod.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private)] 2 | 3 | extern crate arena; 4 | 5 | mod rc_graph; 6 | mod ref_graph; 7 | 8 | fn main() { 9 | println!("\nRc>:"); 10 | rc_graph::main(); 11 | println!("\n&Node and UnsafeCell:"); 12 | ref_graph::main(); 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2015 The Rust for C++ programmers Developers. 2 | 3 | Licensed under the Apache License, Version 2.0 or the MIT license 5 | , at your 6 | option. This file may not be copied, modified, or distributed 7 | except according to those terms. 8 | -------------------------------------------------------------------------------- /graphs/src/rc_graph.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::rc::Rc; 3 | use std::cell::RefCell; 4 | use std::collections::HashSet; 5 | 6 | struct Node { 7 | datum: &'static str, 8 | edges: Vec>>, 9 | } 10 | 11 | impl Node { 12 | fn new(datum: &'static str) -> Rc> { 13 | Rc::new(RefCell::new(Node { 14 | datum: datum, 15 | edges: Vec::new(), 16 | })) 17 | } 18 | 19 | fn traverse(&self, f: &F, seen: &mut HashSet<&'static str>) 20 | where F: Fn(&'static str) 21 | { 22 | if seen.contains(&self.datum) { 23 | return; 24 | } 25 | f(self.datum); 26 | seen.insert(self.datum); 27 | for n in &self.edges { 28 | n.borrow().traverse(f, seen); 29 | } 30 | } 31 | 32 | fn first(&self) -> Rc> { 33 | self.edges[0].clone() 34 | } 35 | } 36 | 37 | fn foo(node: &Node) { 38 | println!("foo: {}", node.datum); 39 | } 40 | 41 | fn init() -> Rc> { 42 | let root = Node::new("A"); 43 | 44 | let b = Node::new("B"); 45 | let c = Node::new("C"); 46 | let d = Node::new("D"); 47 | let e = Node::new("E"); 48 | let f = Node::new("F"); 49 | 50 | { 51 | let mut mut_root = root.borrow_mut(); 52 | mut_root.edges.push(b.clone()); 53 | mut_root.edges.push(c.clone()); 54 | mut_root.edges.push(d.clone()); 55 | 56 | let mut mut_c = c.borrow_mut(); 57 | mut_c.edges.push(e.clone()); 58 | mut_c.edges.push(f.clone()); 59 | mut_c.edges.push(root.clone()); 60 | } 61 | 62 | root 63 | } 64 | 65 | pub fn main() { 66 | let g = init(); 67 | let g = g.borrow(); 68 | g.traverse(&|d| println!("{}", d), &mut HashSet::new()); 69 | let f = g.first(); 70 | foo(&*f.borrow()); 71 | } 72 | -------------------------------------------------------------------------------- /primitives.md: -------------------------------------------------------------------------------- 1 | # 基本数据类型和运算符 2 | 3 | Rust有很多跟C++基本相同的算术和逻辑运算符。其中`bool`类型在两个语言中完全一样。Rust也有与C++中相似的整数、无符号整数和浮点数。不过语法略有不同。Rust用`int`来表示整形,`uint`用来表示无符号整形。这些类型的大小与指针大小相同,比如在32为系统中,`uint`表示32位无符号整数。Rust也支持显式的长度标记,就是在`u`或`i`后面跟上8,16,32,64等数:`u8`表示长度为8位的无符号整数,而`i32`表示长度为32位的有符号整数。对于浮点数,Rust中有`f32`和`f64`两种类型。 4 | 5 | 数值字面量后面可以写后缀来指示他们的类型。如果没有给出后缀,Rust会试图推断它的类型。在无法推断时,会假定位`int`(值为整数时)和`f64`(值为小数时) 6 | 7 | ```rust 8 | fn main() { 9 | let x: bool = true; 10 | let x = 34; // 类型:int 11 | let x = 34u; // 类型:uint 12 | let x: u8 = 34u8; 13 | let x = 34i64; 14 | let x = 34f32; 15 | } 16 | ``` 17 | 18 | 上面的例子可以看出,Rust允许你重新定义同名的变量。每个`let`语句创建一个新的`x`并且隐蔽之前的一个。由于变量本身默认是不可变的,允许重新定义就显得尤其有用。 19 | 20 | 除了十进制外,数字字面值可以用二进制、八进制、十六进制来给出。他们分别对应`0b`、`0o`和`0x`前缀。你可以在字面值中间加入下划线来方便阅读,他们将在编译时被忽略。 21 | 22 | ```rust 23 | fn main() { 24 | let x = 12; 25 | let x = 0b1100; 26 | let x = 0o14; 27 | let x = 0xe; 28 | let y = 0b_1100_0011_1011_0001; 29 | } 30 | ``` 31 | 32 | Rust中有字符和字符串,不过由于他们都是Unicode,因此与C++中的对应物略有不同。我将在介绍完指针引用和数组之后着重说这点。 33 | 34 | Rust不会进行数值的隐式类型转换。总的来说,Rust相比C++只有很少几个隐式类型转换。如果需要显式类型转换,Rust提供`as`关键字。通过`as`关键字可以在任意两个数值类型间转换,但是不能进行布尔型和数值之间的转换。 35 | 36 | ```rust 37 | fn main() { 38 | let x = 34u as int; 39 | let x = 10 as f32; 40 | let x = 10.45f64 as i8; // float to int (丢失小数部分) 41 | let x = 4u8 as u64; 42 | let x = 400u16 as u8; // 144, 丢失精度 43 | println!("`400u16 as u8` gives {}", x); 44 | let x = -3i8 as u8; // 253,丢失符号和精度 45 | println!("`-3i8 as u8` gives {}", x); 46 | //let x = 45u as bool; // 编译错误 47 | } 48 | ``` 49 | 50 | Rust有下面的如下数值运算符:`+`,`-`,`*`,`/`,`%`;位运算符有:`|`,`&`,`^`,`<<`,`>>`;比较运算符有:`==`,`!=`,`>`,`<`,`>=`,`<=`;具有求值短路的逻辑运算符:`||`,`&&`。所有运算符的行为与C++中完全相同,只不过Rust中对类型的限制更强:位运算符只能用于整数而逻辑运算符只能用于布尔值。Rust中有一元`-`运算符。`!`运算符对于布尔值取反,对整数则按位取反(与C++中的`~`相同)。Rust有与C++中完全一样的自操作运算符(`+=`等等),但没有自增自减运算符(如`++`)。 -------------------------------------------------------------------------------- /graphs/src/ref_graph.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::cell::UnsafeCell; 3 | use std::collections::HashSet; 4 | use arena::TypedArena; 5 | 6 | struct Node<'a> { 7 | datum: &'static str, 8 | edges: UnsafeCell>>, 9 | } 10 | 11 | impl<'a> Node<'a> { 12 | fn new<'b>(datum: &'static str, arena: &'b TypedArena>) -> &'b Node<'b> { 13 | arena.alloc(Node { 14 | datum: datum, 15 | edges: UnsafeCell::new(Vec::new()), 16 | }) 17 | } 18 | 19 | fn traverse(&self, f: &F, seen: &mut HashSet<&'static str>) 20 | where F: Fn(&'static str) 21 | { 22 | if seen.contains(&self.datum) { 23 | return; 24 | } 25 | f(self.datum); 26 | seen.insert(self.datum); 27 | unsafe { 28 | for n in &(*self.edges.get()) { 29 | n.traverse(f, seen); 30 | } 31 | } 32 | } 33 | 34 | fn first(&'a self) -> &'a Node<'a> { 35 | unsafe { 36 | (*self.edges.get())[0] 37 | } 38 | } 39 | } 40 | 41 | fn foo<'a>(node: &'a Node<'a>) { 42 | println!("foo: {}", node.datum); 43 | } 44 | 45 | fn init<'a>(arena: &'a TypedArena>) ->&'a Node<'a> { 46 | let root = Node::new("A", arena); 47 | 48 | let b = Node::new("B", arena); 49 | let c = Node::new("C", arena); 50 | let d = Node::new("D", arena); 51 | let e = Node::new("E", arena); 52 | let f = Node::new("F", arena); 53 | 54 | unsafe { 55 | (*root.edges.get()).push(b); 56 | (*root.edges.get()).push(c); 57 | (*root.edges.get()).push(d); 58 | 59 | (*c.edges.get()).push(e); 60 | (*c.edges.get()).push(f); 61 | (*c.edges.get()).push(root); 62 | } 63 | 64 | root 65 | } 66 | 67 | pub fn main() { 68 | let arena = TypedArena::new(); 69 | let g = init(&arena); 70 | g.traverse(&|d| println!("{}", d), &mut HashSet::new()); 71 | foo(g.first()); 72 | } 73 | -------------------------------------------------------------------------------- /hello world.md: -------------------------------------------------------------------------------- 1 | # 序章 - Hello world! 2 | 3 | 本章将介绍如何安装配置和做一些最简单的操作,也许你可以在官方教程和参考手册上找到更好的资源 4 | 5 | 首先你需要安装Rust。可以从[http://www.rust-lang.org/install.html](http://www.rust-lang.org/install.html)下载一个nightly版本(我个人推荐Nightly版本而不是所谓的稳定版,因为nightly版本并不会比那些所谓的稳定版崩溃的更频繁,而且早晚我们将要使用那些Rust中的新特性)。假设你已经想办法把东西安装熬了,应该可以在命令行中运行`rustc`命令,可以使用 `rustc -v`来测试 6 | 7 | 现在我们来写第一个程序。先创建一个文件,并把下面的代码复制进去,再保存为`hello.rs`,或者其它一样有创意的名字 8 | 9 | ```rust 10 | fn main() { 11 | println!("Hello world!"); 12 | } 13 | ``` 14 | 15 | 用`rustc hello.rs`,之后运行`./hello`。就应该能显示出预想中的文字 \o/ 16 | 17 | 有两个需要知道的编译器选项,一个是`-o ex_name`用来指定输出文件名,`-g`用来在输出中加入调试信息;你可以用gdb或lldb进行调试。用`-h`选项可以用来显示其它选项的信息。 18 | 19 | 回到代码上来,有几点有趣的地方:我们使用`fn`来定义函数和方法。`main()`是默认的入口函数(我们把命令行参数留到后面介绍)。Rust中没有C++中头文件和前置声明的概念。`println!`是printf的Rust版本,其中`!`表示这是一个宏(macro),你可以把它当做一个标准函数来对待。Rust标准库的一些部分是不需要显式包含或引用的,`println!`宏就是其中之一。 20 | 21 | 让我们把例子稍微改一下 22 | 23 | ```rust 24 | fn main() { 25 | let world = "world"; 26 | println!("Hello {}!", world); 27 | } 28 | ``` 29 | 30 | `let`用来引入一个新的变量,`world`是变量名,而他是一个字符串(理论上说,它的类型是`&'static str`)。编译器会自动进行类型推断,因此我们不需要指定它的类型。 31 | 32 | 在`println!`中使用`{}`与`printf`中的`%s`很相似。如果没有明确指定[1](#1),Rust会尝试将变量转换为一个字符串,因此它比printf更加通用。你可以用其它的各种类型来试着填进去,试试多个字符串或用数字等等。 33 | 34 | 你也可以显式的指定`world`的类型 35 | 36 | ```rust 37 | let world: &'static str = "world"; 38 | ``` 39 | 40 | 在C++中我们把类型声明写成`T x`来声明一个类型为`T`的变量`x`。在Rust中,不论是用在`let`语句还是函数签名上,上面的声明会写为`x: T`。大多数情况下我们会在`let`语句中省略显式类型声明,而在函数参数中,显式类型声明确是必须的。下面的函数展示了怎么用它 41 | 42 | ```rust 43 | fn foo(_x: &'static str) -> &'static str { 44 | "world" 45 | } 46 | 47 | fn main() { 48 | println!("Hello {}!", foo("bar")); 49 | } 50 | ``` 51 | 52 | `foo`函数中有一个变量`_x`,而它是一个字符串字面量(我们将"bar"从`main`中通过它传递进来)。我们在`foo`中并没有用到这个参数,这通常会导致Rust给出一个警告。不过如果我们在变量名前加一个`_`,就可以避免出现这种警告。事实上我们甚至不需要给它一个名字,只需要用一个`_`就可以了。 53 | 54 | 函数的返回值类型在`->`之后给出,如果一个函数什么都不返回(就像C++中的void函数),那就不需要给出返回值类型(就像`main()`函数那样)。如果你想灰常显式地说明这一情况,你可以写成`->()` ,在Rust中`()`就是一个无类型类型。例子中`foo`返回一个字符串字面量。 55 | 56 | `return`关键字并不是必要的。如果函数体的最后一个表达式没有以`;`结尾,那它将作为函数的返回值。因此`foo`函数会返回"world"。为了实现提前返回,`return`关键字当然也是存在的。你可以用`return "world";`来替代`"world"`,效果是一样的。 57 | 58 | ##### 1 59 | 60 | 这里是指自定义的转换函数,需要使用`Display` trait,优点类似于`toString`函数。你可以使用`{:?}`来让编译器产生对调试更加有用的数据表示形式。相对于printf,`println`选择更多的选择。 61 | -------------------------------------------------------------------------------- /graphs/src/ref_graph_generic_mod.rs: -------------------------------------------------------------------------------- 1 | // Note that this one is hypothetical future Rust and will not compile today. 2 | 3 | use std::cell::UnsafeCell; 4 | use std::collections::HashSet; 5 | use arena::TypedArena; 6 | 7 | // Module is parameterised with the lifetime of the graph. 8 | mod graph<'a> { 9 | struct Node { 10 | datum: &'static str, 11 | // The module-level lifetime is used for the lifetime of each Node. 12 | edges: UnsafeCell>, 13 | } 14 | 15 | impl Node { 16 | fn new(datum: &'static str, arena: &'a TypedArena) -> &'a Node { 17 | arena.alloc(Node { 18 | datum: datum, 19 | edges: UnsafeCell::new(Vec::new()), 20 | }) 21 | } 22 | 23 | fn traverse(&self, f: &F, seen: &mut HashSet<&'static str>) 24 | where F: Fn(&'static str) 25 | { 26 | if seen.contains(&self.datum) { 27 | return; 28 | } 29 | f(self.datum); 30 | seen.insert(self.datum); 31 | for n in &self.edges { 32 | unsafe { 33 | for n in &(*self.edges.get()) { 34 | n.traverse(f, seen); 35 | } 36 | } 37 | } 38 | } 39 | 40 | fn first(&self) -> &Node { 41 | unsafe { 42 | (*self.edges.get())[0] 43 | } 44 | } 45 | } 46 | 47 | // It would be nice if we could rely on lifetime elision and remove the `'a` 48 | // on the `foo` and `init` functions. 49 | fn foo(node: &'a Node) { 50 | println!("foo: {}", node.datum); 51 | } 52 | 53 | fn init(arena: &'a TypedArena) -> &'a Node { 54 | let root = Node::new("A", arena); 55 | 56 | let b = Node::new("B", arena); 57 | let c = Node::new("C", arena); 58 | let d = Node::new("D", arena); 59 | let e = Node::new("E", arena); 60 | let f = Node::new("F", arena); 61 | 62 | unsafe { 63 | (*root.edges.get()).push(b); 64 | (*root.edges.get()).push(c); 65 | (*root.edges.get()).push(d); 66 | 67 | (*c.edges.get()).push(e); 68 | (*c.edges.get()).push(f); 69 | (*c.edges.get()).push(root); 70 | } 71 | 72 | root 73 | } 74 | } 75 | 76 | pub fn main() { 77 | let arena = TypedArena::new(); 78 | // The lifetime of the module is inferred here from the lifetime of the 79 | // reference to the arena, i.e., the scope of the main function. 80 | let g = graph::init(&arena); 81 | g.traverse(&|d| println!("{}", d), &mut HashSet::new()); 82 | foo(g.first()); 83 | } 84 | -------------------------------------------------------------------------------- /why rust.md: -------------------------------------------------------------------------------- 1 | I realise that in terms of learning Rust, I had jumped straight to the 'how' and 2 | skipped the 'why'. I guess I am in enough of a Rust bubble that I can't imagine 3 | why you wouldn't want to learn it. So, I will make a bit more of an effort to 4 | explain why things are how they are. Here I will try to give a bit of an 5 | overview/motivation. 6 | 7 | If you are using C or C++, it is probably because you have to - either you need 8 | low-level access to the system, or need every last drop of performance, or both. 9 | Rust aims to do offer the same level of abstraction around memory, the same 10 | performance, but be safer and make you more productive. 11 | 12 | Concretely, there are many languages out there that you might prefer to use to 13 | C++: Java, Scala, Haskell, Python, and so forth, but you can't because either 14 | the level of abstraction is too high (you don't get direct access to memory, 15 | you are forced to use garbage collection, etc.), or there are performance issues 16 | (either performance is unpredictable or it's simply not fast enough). Rust does 17 | not force you to use garbage collection, and as in C++, you get raw pointers to 18 | memory to play with. Rust subscribes to the 'pay for what you use' philosophy of 19 | C++. If you don't use a feature, then you don't pay any performance overhead for 20 | its existence. Furthermore, all language features in Rust have predictable (and 21 | usually small) cost. 22 | 23 | Whilst these constraints make Rust a (rare) viable alternative to C++, Rust also 24 | has benefits: it is memory safe - Rust's type system ensures that you don't get 25 | the kind of memory errors which are common in C++ - memory leaks, accessing un- 26 | initialised memory, dangling pointers - all are impossible in Rust. Furthermore, 27 | whenever other constraints allow, Rust strives to prevent other safety issues 28 | too - for example, all array indexing is bounds checked (of course, if you want 29 | to avoid the cost, you can (at the expense of safety) - Rust allows you to do 30 | this in unsafe blocks, along with many other unsafe things. Crucially, Rust 31 | ensures that unsafety in unsafe blocks stays in unsafe blocks and can't affect 32 | the rest of your program). Finally, Rust takes many concepts from modern 33 | programming languages and introduces them to the systems language space. 34 | Hopefully, that makes programming in Rust more productive, efficient, and 35 | enjoyable. 36 | 37 | I would like to motivate some of the language features from part 1. Local type 38 | inference is convenient and useful without sacrificing safety or performance 39 | (it's even in modern versions of C++ now). A minor convenience is that language 40 | items are consistently denoted by keyword (`fn`, `let`, etc.), this makes 41 | scanning by eye or by tools easier, in general the syntax of Rust is simpler and 42 | more consistent than C++. The `println!` macro is safer than printf - the number 43 | of arguments is statically checked against the number of 'holes' in the string 44 | and the arguments are type checked. This means you can't make the printf 45 | mistakes of printing memory as if it had a different type or addressing memory 46 | further down the stack by mistake. These are fairly minor things, but I hope 47 | they illustrate the philosophy behind the design of Rust. 48 | -------------------------------------------------------------------------------- /unique.md: -------------------------------------------------------------------------------- 1 | # 独占指针 2 | 3 | Rust是一个系统级语言,因此必须给予程序员访问原始内存的能力。如C++中一样,这是通过提供指针类型来做到的。不论从语法上还是语义上说,Rust和C++的指针都有较大的差别。Rust通过指针类型检查来强化内存安全性,而这也成为它相比于其他语言的重要优势。虽然类型系统略有些复杂,但是你可以籍此同时获得内存安全性和接近机器语言的性能。 4 | 5 | 我本想在一章中写完所有的Rust指针,只是这一部分篇幅实在过大,因此这一章只会写一种指针——独占指针。其他类型的指针将在后面的章节中详述。 6 | 7 | 先来看一个没有使用指针的例子 8 | ```rust 9 | fn foo() { 10 | let x = 75; 11 | 12 | // ... do something with `x` ... 13 | } 14 | ``` 15 | 16 | 当程序运行到`foo`的最后,`x`会退出作用域。这意味着这一变量将不会再被访问,它的内存因此可以安全的被回收和重用。 17 | 18 | 在Rust中,对于每一个类型`T`,我们可以写`Box`来一个获得一个独占地指向`T`类型对象的指针。我们使用`Box::new(...)`来在堆上分配空间,用提供的值来初始化那个空间。这与C++中的 `new`很像。下面是一个例子 19 | 20 | ```rust 21 | fn foo() { 22 | let x = Box::new(75); 23 | } 24 | ``` 25 | 26 | 这里`x`是一个指向堆中的指针,指向的位置包含一个值`75`。`x`的类型是`Box`,所以也可以写成`let x: Box = Box::new(75);`。这与C++中写 `int* x = new int(75);`很像。然而与C++中不同的是,Rust会自动地管理相应的内存空间,因此并不需要手动的调用`free`或`delete`。独占指针的特性与值相似——当独占指针退出作用域时就会被删除。在刚才的例子中,`x`在`foo`函数之后不会再被用到,因而`x`指向的位置也会被自动回收。 27 | 28 | 独占指针可以像C++中一样用`*`来解引用。 29 | 30 | ```rust 31 | fn foo() { 32 | let x = Box::new(75); 33 | println!("`x` points to {}", *x); 34 | } 35 | ``` 36 | 37 | 像Rust中的基本类型一样,独占指针和它指向的数据默认情况下是不可变的。与C中不同,Rust中不能出现一个指向不可变对象的可变独占指针,反之亦然。数据的可变性与指针相同。 38 | 39 | ```rust 40 | fn foo() { 41 | let x = Box::new(75); 42 | let y = Box::new(42); 43 | // x = y; // 不允许, x是不可变的 44 | // *x = 43; // 不允许,*x是不可变的 45 | let mut x = Box::new(75); 46 | x = y; // 允许, x是可变的OK 47 | *x = 43; // 允许, *x是可变的OK 48 | } 49 | ``` 50 | 51 | 独占指针可以从函数中返回,指向的地址不会被释放,从而继续存活。然而,它最终总会退出某个作用域从而被回收,因此Rust中不会出现悬挂指针和内存泄露。 52 | 53 | ```rust 54 | fn foo() -> Box { 55 | let x = Box::new(75); 56 | x 57 | } 58 | 59 | fn bar() { 60 | let y = foo(); 61 | // ... use y ... 62 | } 63 | ``` 64 | 65 | 上面的例子中,内存在`foo`中初始化,并返回到`bar`中。`x`从`foo`中返回并存储到`y`中,因此不会被删除。在`bar`的最后,`y`退出了作用域,它的内存就被回收了。 66 | 67 | 独占指针之所以称为独占(或成为线形),是因为同时只能有一个独占指针指向相应的内存区域。这是通过移动语义来实现的。当一个独占指针指向了一个值,之前的其他任何指针都没法访问这个值。 68 | 69 | ```rust 70 | fn foo() { 71 | let x = Box::new(75); 72 | let y = x; 73 | // x不再能被访问 74 | // let z = *x; // 错误 75 | } 76 | ``` 77 | 78 | 类似的,当一个独占指针被传递给另外一个函数或存储到一个域中,它也不能再被使用了。 79 | 80 | ```rust 81 | fn bar(y: Box) { 82 | } 83 | 84 | fn foo() { 85 | let x = Box::new(75); 86 | bar(x); 87 | // x不再能被访问 88 | // let z = *x; // 错误 89 | } 90 | ``` 91 | 92 | Rust中的独占指针与C++中的`std::unique_ptr`非常相似。只能有一个独占指针指向同一个值,而当独占指针退出作用域时,他们对应的值就会被回收。不过Rust在编译时而非运行时进行了大部分检查。所以在C++中访问一个值已经被移动的独占指针将会导致运行时错误,而在Rust中这将导致编译错误,所以在运行时绝不会出错。 93 | 94 | 我们之后将会看到,在Rust中你可以创建其他类型的指针指向一个独占指针所指向的值。这与C++中相似。然而在C++,你可能编写出一段代码使用一个指向已被释放区域的指针,因而导致运行时错误。而在Rust中,编译器不会允许这样的代码编译通过。 95 | 96 | 上面说过,独占指针必须通过解引用来访问其中的值,然而函数调用时自动解引用的,因此在进行函数调用时不需要使用`->`和`*`。从这方面讲,Rust中的指针与C++中的指针和引用都有相似之处。举个例子 97 | 98 | ```rust 99 | fn bar(x: Box, y: Box>>>) { 100 | x.foo(); 101 | y.foo(); 102 | } 103 | ``` 104 | 105 | 假设`Foo`类型有一个函数`foo()`,上述两个表达式都是合法的。 106 | 107 | 对一个已存在的值调用`Box::new()`不会获取它的引用,而会对值作复制。 108 | 109 | ```rust 110 | fn foo() { 111 | let x = 3; 112 | let mut y = Box::new(x); 113 | *y = 45; 114 | println!("x is still {}", x); 115 | } 116 | ``` 117 | 118 | 大多数情况下,相比于拷贝语义,Rust更倾向于移动语义。如上面的例子所示,基本数据类型有拷贝语义,而复杂的数据类型将会有移动语义。之后将详细解释这一点。 119 | 120 | 不过在编程时,我们有时需要不止一个对值的引用。Rust中有借出指针来应付这种情况。我们将在之后谈到。 121 | -------------------------------------------------------------------------------- /rc raw.md: -------------------------------------------------------------------------------- 1 | # 引用计数和原始指针 2 | 3 | 目前我们已经介绍了独占指针和借出指针。独占指针与C++中的`std::unique_ptr`很相似,而当你在C++中用了指针或引用的地方,在Rust中你可以默认考虑借出指针。Rust还有少数几个更罕见的内建和库中实现的指针类型。它们很像你在C++中用到的各种智能指针。 4 | 5 | 我花了不少时间来写这一章,然而仍然不太满意。不论在我的文章中还是在Rust的实现上,这部分都有很多虎头蛇尾,我希望这在之后的写作中和Rust语言开发的过程中能有所改善。你可以首先跳过这一部分,因为你大概也不怎么会用到它们。 6 | 7 | 看起来Rust中有很多指针类型,其实这就像C++在标准库中有很多智能指针一样。只不过在Rust中,你刚开始就会遇到他们。Rust中的指针是编译器支持的,因此相比C++,使用它们更不容易出错。 8 | 9 | I'm not going to cover these in as much detail as unique and borrowed references 10 | because, frankly, they are not as important. I might come back to them in more 11 | detail later on. 12 | 我不想像介绍独占指针那样介绍它们,因为说实话它们真的不那么重要。也许之后我会回头来完善他们。 13 | 14 | ## `Rc` 15 | 16 | 引用计数指针是Rust标准库德一部分,它位于`std::rc`模块中(我们将会在不远的将来说到模块,本章的`use`指令就是为了引入模块的)。一个指向`T`类型的引用计数指针的类型为`Rc`。你可以使用静态方法(可以暂时理解为C++ 中的静态函数,不过之后会讲它们之间的差异)——`Rc::new(...)`来创建一个引用计数指针,它可以用一个值来初始化这个指针所指向的位置。这一构造函数遵循Rust的移动/拷贝语义(如我们之前讨论独占指针所说)——不论何时,在调用Rc::new之后,你将只能通过这个指针来访问那个值。 17 | 18 | 像其他指针类型一样,`.`操作符可以自动地进行解引用,也可以用`*`来手动解引用。 19 | 20 | 21 | 如果需要传递引用计数指针,你需要使用`clone`函数。这有点难看,但愿之后可以修复它,但是也一定能做到。因为可以获得对于它的借出指针,所以通常你并不需要经常使用`clone`。Rust的类型系统保证引用计数所指向的变量在所有引用退出作用域之前不会被删除。取得一个引用计数指针的借出引用不需要增加或减少引用计数,从而得到更好的性能(不过性能提高很有限,因为引用计数对象被限制在一个单独的线程内,因此引用计数操作不需要是原子操作)。 22 | 23 | 一个Rc的例子 24 | ```rust 25 | use std::rc::Rc; 26 | 27 | fn bar(x: Rc) { } 28 | fn baz(x: &i32) { } 29 | 30 | fn foo() { 31 | let x = Rc::new(45); 32 | bar(x.clone()); // Increments the ref-count 33 | baz(&*x); // Does not increment 34 | println!("{}", 100 - *x); 35 | } // Once this scope closes, all Rc pointers are gone, so ref-count == 0 36 | // and the memory will be deleted. 37 | ``` 38 | 39 | 引用计数指针总是不可变的。如果你需要一个可变的引用计数对象,你必须用`RefCell`或`Cell`来包裹`Rc`内的类型 40 | 41 | ## Cell and RefCell 42 | 43 | Cell and RefCell are structs which allow you to 'cheat' the mutability rules. 44 | This is kind of hard to explain without first covering Rust data structures and 45 | how they work with mutability, so I'm going to come back to these slightly 46 | tricky objects later. For now, you should know that if you want a mutable, ref 47 | counted object you need a Cell or RefCell wrapped in an Rc. As a first 48 | approximation, you probably want Cell for primitive data and RefCell for objects 49 | with move semantics. So, for a mutable, ref-counted int you would use 50 | `Rc>`. 51 | Cell和RefCell是一个可以帮你绕过可变性规则的结构。在介绍Rust数据结构之前可能有点难以理解,所以我会之后再介绍这个比较tricky的对象。现在你只需要知道如果你需要一个可变的引用计数对象,你需要用`Cell`或`RefCell`来包裹Rc中的类型,`Cell`一般用于基本类型数据,而`RefCell`用于有引用语义的对象。所以,要得到一个可变的有引用计数的`int`你可以写成`Rc>`。 52 | 53 | ## `*T` - 原始指针 54 | 55 | Rust有两类原始指针(也叫非安全指针):`*const T`用于不可变原始指针;`*mut T`用于可变的原始指针。可以通过`&`和`&mut`来分别创建它们,由于`&`操作符默认用来创建借出指针,因此你需要显式指定类型来得到一个`*T`而不是`&T`。原始指针就像C中的指针,它只是一个指向内存区域的值,并且对于它的使用没有任何限制(你不能在不做转换的情况下进行指针算数,不过如果你非要这么做的话也是可以做到的)。原始指针是Rust中唯一可以是null的指针类型。原始指针没有自动地解引用,因此你需要用`(*x).foo()`来访问具体的数据。最重要的一个限制是,在普通的Rust代码中原始指针只能被传递,它们不能在`unsafe`块之外解引用,因此也就不能在那之外被使用。 56 | 57 | 那么究竟什么是`unsafe`块呢?Rust有很强的安全性承诺,而且它在很罕见的情况下会禁止你作一些你必须做的事。由于Rust致力于成为一门系统编程语言,因此即使编译器无法验证代码的安全性,也必须允许做任何可能做的事。为了做到这一点,Rust有一个`unsafe`块的概念。在`unsafe`代码中,你可以做一些不安全的事——对原始指针作解引用,对数组作没有边界检查的访问,通过FFI调用其它语言的代码,或者做变量类型转换。很显然,在编写非安全代码时你需要加倍小心。事实上,你应该在非常罕见的情况下才会用到`unsafe`代码,主要是用在库的非常小段的代码中,而不是客户端代码。在`unsafe`块中你必须做C++中你经常做的事来保证安全性。另外,你必须手动保证通常由编译器保证的不变性。`unsafe`块要求你手动的保证Rust的不变性,如果你没有做到,你将会在`unsafe`代码内外同时引入错误。 58 | 59 | 下面是一个使用原始指针的例子 60 | 61 | ```rust 62 | fn foo() { 63 | let mut x = 5; 64 | let x_p: *mut i32 = &mut x; 65 | println!("x+5={}", add_5(x_p)); 66 | } 67 | 68 | fn add_5(p: *mut i32) -> i32 { 69 | unsafe { 70 | if !p.is_null() { // 注意:原始指针没有自动解引用,所以is_null是一个*i32中的函数,而不是i32中的 71 | *p + 5 72 | } else { 73 | -1 // 这不是推荐的错误处理模式 74 | } 75 | } 76 | } 77 | ``` 78 | 79 | 以上介绍了所有的Rust指针。下一章将介绍Rust中的数据类型,之后再回到借出指针的问题。 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust For C++ Programmers 2 | 3 | 一个为C/C++程序员写的Rust教程 4 | 5 | 转到 [目录](#contents). 6 | 转到 [Contributing](#contributing). 7 | 8 | 本教程是为已经熟悉指针、引用和系统编程基本概念的程序员写的。我们将主要介绍Rust和C++中的区别,以帮助你更快的用Rust编程,而不用花时间去学你已经熟知的知识。 9 | 10 | 对于C++程序员而言,Rust是一个很直观的语言——它们的大部分语法很相似。而在我看来其间最大的不同在于Rust编译器将一些系统编程中容易混淆的概念进行了更强的限制。这起初看起来会有些令人恼火——因为有些你原本能做的事Rust编译器禁止你去做,即使这些事情确实是安全的,只是编译器不相信而已。不过很快你就可以培养出写可接受代码的直觉。为了在你和编译器之间就内存安全的问题建立共识,你需要学习一些新的,有时看起来过于繁复的类型标示。但当你对对象的生存期有更强的概念,并有一定经验后,它们就不会特别难学。 11 | 12 | 这一教程起先是[一系列博客文章](http://featherweightmusings.blogspot.co.nz/search/label/rust-for-c)。我撰写它们,一部分原因是想帮助我自己(@nick29581)学习Rust,另一部分原因是我发现网上现存的Rust学习资源很难令人满意:它们花了太多的时间介绍我已经知道的基础知识,并利用了更高层的知识来讨论Rust中的概念,而我认为使用底层知识介绍它们更能说明问题。过了一段时间,Rust的官方文档已经有了很大的进步,然而我仍然认为C++的程序员,作为最适合学习Rust语言的人,却没有被文档编写者重视。 13 | 14 | ## 安装Rust 15 | 16 | 安装Rust的简单方法(至少在Linux和OS X上)是运行`rustup`的脚本,只需要在命令行中输入以下命令 17 | 18 | ### Beta 版本(推荐) 19 | ``` 20 | curl -s https://static.rust-lang.org/rustup.sh | sudo sh 21 | ``` 22 | 23 | ### Nightly 版本 24 | 如果你需要尚未更新到rust-beta中的那些包,可以使用Nightly版本 25 | ``` 26 | curl -s https://static.rust-lang.org/rustup.sh | sudo sh -s -- --channel=nightly 27 | ``` 28 | 29 | 你可以从[rust-lang.org](http://www.rust-lang.org/install.html)下载运行于Linux, OS X和Windows的nightly二进制版本。 30 | 31 | 你也可以从[github.com/rust-lang/rust](https://github.com/rust-lang/rust)中下载或运行`git clone https://github.com/rust-lang/rust.git`来获取最新的源代码。要构建Rust编译器只需要运行`./configure && make rustc`。你可以在[building-from-source](https://github.com/rust-lang/rust#building-from-source)查看更详细的安装指南。 32 | 33 | 34 | ## 其它资源 35 | 36 | * [The Rust book/guide](http://doc.rust-lang.org/book/) - 全面学习Rust的最好去处,同时也是学习本文没介绍内容的最佳选择 37 | * [Rust API documentation](http://doc.rust-lang.org/std/index.html) - Rust库的详细文档 38 | * [The Rust reference manual](http://doc.rust-lang.org/reference.html) - 略微过时的文档,但是还不错 39 | * [Discuss forum](http://users.rust-lang.org/) - 讨论使用和学习Rust时各类问题的论坛 40 | * [#rust irc channel](https://chat.mibbit.com/?server=irc.mozilla.org&channel=%23rust) - 如果你热爱使用IRC,这可能是你要找到某个答案最快的地方 41 | * [Stackoverflow Rust questions](https://stackoverflow.com/questions/tagged/rust) - 寻找有关Rust的基础和高级问题的地方,不过请注意,Rust在这些年来已经有了*非常大*的变化,有些答案在今天可能已经不再适用。 42 | 43 | ## Contents 44 | 45 | 1. [Hello world!](hello%20world.md) 46 | 1. [花絮 - 为啥要用Rust?](why%20rust.md) 47 | 1. [控制流](control%20flow.md) 48 | 1. [基本数据类型和操作符](primitives.md) 49 | 1. [独占指针](unique.md) 50 | 1. [借出指针](borrowed.md) 51 | 1. [Rc与原始指针](rc%20raw.md) 52 | 1. [数据类型](data%20types.md) 53 | 1. [解构 I](destructuring.md) 54 | 1. [解构 II](destructuring 2.md) 55 | 1. [Array与vec](arrays.md) 56 | 1. [实例:图与内存预分配](graphs/README.md) 57 | 58 | 59 | ## Contributing 60 | 61 | 跪求! 62 | 63 | 如果你发现了错别字或者逻辑错误,赶紧提出Pull Request,千万别害羞!对于更大的变化或者你想看到的其他章节,欢迎提出新[issue](https://github.com/nick29581/r4cppp/issues/new)。如果重新组织已有的文章或者增加新的样例可以让这个教程变的更好,我会非常高兴。 64 | 65 | 如果你想贡献一个段落,一个部分或者整个章节,非常欢迎!如果你希望对某些方面有更多介绍,请看[list of issues](https://github.com/nick29581/r4cppp/issues),尤其是那些打了 [new material](https://github.com/nick29581/r4cppp/labels/new%20material)Tag的Issue。如果你不确定,欢迎与我联系(@nick29581)或者在IRC里提出 (nrc, 我在 #rust 和 #rust-internals里活动) 66 | 67 | 68 | ### 文档风格 69 | 70 | 既然本教程主要针对C++程序员,这里主要介绍的是那些对C++程序员来说不太熟悉的概念(不过并不需要你对最新版本的C++很了解)。我希望能避免介绍太多的基础知识,而且不想和其他资源,尤其是Rust官方文档有过多知识上的重叠。 71 | 72 | 欢迎对前沿问题的讨论(比如用除了Cargo以外的构建系统,编写语法扩展,使用非稳定的API)。 73 | 74 | 我希望避免写出像“如何将下面的C++代码写成Rust“之类的例子,不过如果有一小部分也是可以的。 75 | 76 | 欢迎使用不同的文档格式(比如FAQs或者更大的实例) 77 | 78 | 我不打算添加对迷你项目的练习或者建议,然而如果你有兴趣,请联系我 79 | 80 | 我希望能表现出一副很学术的样子,但也不是那么死板。所有的文字要用(英式)英语写成,当然也欢迎翻译为其它语言(你可以直接提交中文给我——译者),并使用GitHub markdown格式。就写作风格、语法、拼写等等的问题欢迎参看牛津风格手册或 [The Economist Style Guide](http://www.economist.com/styleguide/introduction) 81 | 82 | 不要强求你的作品必须完美,我很欢迎其他人一起来编辑和修改 83 | 84 | ##译者续貂 85 | 很早就想学Rust,苦于没有很好的入门教程。 Rust for C++ Programmers对于像我一样的 C 艹程序员来说还是非常有帮助的。网络上介绍Rust的资源本就稀少,中文资源是更不必说的极端稀缺。欢迎所有对Rust有兴趣的朋友来一起完善本文,一起帮助Rust在C++程序员,尤其是中国C++程序员中进一步的传播。 -------------------------------------------------------------------------------- /control flow.md: -------------------------------------------------------------------------------- 1 | # 控制流 2 | 3 | ## `if` 4 | 5 | 在Rust中,`if`语句的使用与C++几乎完全一致,有一个小区别是,不能省略大括号,而在测试表达式外侧的括号则可以省略。另外一个区别是在Rust中,`if`是一个表达式,如之前说过,大括号中最后一个不以`;`结尾的语句就是`if`表达式的值,所以你可以像C++中的`?`一样使用它(Rust中没有`?`)。下面两个代码做的事情完全相同。 6 | 7 | ```rust 8 | fn foo(x: i32) -> &'static str { 9 | let mut result: &'static str; 10 | if x < 10 { 11 | result = "less than 10"; 12 | } else { 13 | result = "10 or more"; 14 | } 15 | return result; 16 | } 17 | 18 | fn bar(x: i32) -> &'static str { 19 | if x < 10 { 20 | "less than 10" 21 | } else { 22 | "10 or more" 23 | } 24 | } 25 | ``` 26 | 27 | 28 | 第一种基本上是C++写法的翻译版,而第二种则更具Rust风格。 29 | 30 | 你也可以写`let x = if ...`一类的句子 31 | 32 | 33 | ## 循环 34 | 35 | Rust跟C++一样有while循环 36 | 37 | ```rust 38 | fn main() { 39 | let mut x = 10; 40 | while x > 0 { 41 | println!("Current value: {}", x); 42 | x -= 1; 43 | } 44 | } 45 | ``` 46 | 47 | 在Rust中没有`do..while`循环,不过我们可以使用`loop`语句来写一个死循环 48 | 49 | ```rust 50 | fn main() { 51 | loop { 52 | println!("Just looping"); 53 | } 54 | } 55 | ``` 56 | 57 | Rust has `break` and `continue` just like C++. 58 | Rust跟C++一样有`break`和`continue`指令。 59 | 60 | 61 | ## For循环 62 | 63 | Rust也有`for`循环,不过跟C++中的不太一样。假设现在有一个包含很多整数的向量,而你像把它们都打印出来。在讲vector和数组的那一章中会具体说怎么用迭代器和范型,这里我们只需要知道`Vec`是一个包含`T`类型的序列,而`iter()`函数可以返回一个迭代器用来获取vector中的值。一个简单的`for`循环可以这样写 64 | 65 | ```rust 66 | fn print_all(all: Vec) { 67 | for a in all.iter() { 68 | println!("{}", a); 69 | } 70 | } 71 | ``` 72 | 73 | 如果你希望用下标来遍历`all`(像在C++中遍历普通数组那样),你可以写成: 74 | 75 | ```rust 76 | fn print_all(all: Vec) { 77 | for i in ..all.len() { 78 | println!("{}: {}", i, all.get(i)); 79 | } 80 | } 81 | ``` 82 | 83 | `len()`函数的意思你懂的。 84 | 85 | 86 | ## Switch/Match 87 | 88 | Rust中有`match`语句,与C++中的`switch`语句相似,不过它更加强大。最简单的`match`用法看起来会很熟悉。 89 | 90 | ```rust 91 | fn print_some(x: i32) { 92 | match x { 93 | 0 => println!("x is zero"), 94 | 1 => println!("x is one"), 95 | 10 => println!("x is ten"), 96 | y => println!("x is something else {}", y), 97 | } 98 | } 99 | ``` 100 | 101 | 这里有一些语法上的区别:我们用`=>`来分割匹配模式与需要执行的表达式,每一个分支之间用`,`分隔。上面的例子中,`y`会匹配未与之前任何值匹配的值。另外有一些不那么明显的语义区别:`match`的分支必须是穷尽的,这意味着匹配模式必须覆盖所有可能的匹配值。可以尝试下如果从在上面的例子中移除`y => ..`分支会发生什么。我们也可以写成 102 | 103 | ```rust 104 | fn print_some(x: i32) { 105 | match x { 106 | x => println!("x is something else {}", x) 107 | } 108 | } 109 | ``` 110 | 111 | 分支中的`x`引入了一个新变量,因此在分支中会隐蔽掉函数参数中的`x`,这很像在内部作用域中变量对外部同名变量的隐蔽。 112 | 113 | 如果你不希望命名一个变量,可以用`_`来占位。如果我们什么都不需要做,可以写一个空的分支。 114 | 115 | ```rust 116 | fn print_some(x: i32) { 117 | match x { 118 | 0 => println!("x is zero"), 119 | 1 => println!("x is one"), 120 | 10 => println!("x is ten"), 121 | _ => {} 122 | } 123 | } 124 | ``` 125 | 126 | 另外一个语义区别是`match`分支之间不会有C++中那种执行穿透现象。 127 | 128 | 在之后的章节中我们会发现`match`是非常强大的。目前只介绍几个简单的特性——`or`操作符和`if`从句,它们都可以顾名思义。 129 | 130 | ```rust 131 | fn print_some_more(x: i32) { 132 | match x { 133 | 0 | 1 | 10 => println!("x is one of zero, one, or ten"), 134 | y if y < 20 => println!("x is less than 20, but not zero, one, or ten"), 135 | y if y == 200 => println!("x is 200 (but this is not very stylish)"), 136 | _ => {} 137 | } 138 | } 139 | ``` 140 | 141 | 与`if`表达式相同,`match`语句也是一个表达式,所以我们可以将最后一个例子改写成 142 | 143 | ```rust 144 | fn print_some_more(x: i32) { 145 | let msg = match x { 146 | 0 | 1 | 10 => "one of zero, one, or ten", 147 | y if y < 20 => "less than 20, but not zero, one, or ten", 148 | y if y == 200 => "200 (but this is not very stylish)", 149 | _ => "something else" 150 | }; 151 | 152 | println!("x is {}", msg); 153 | } 154 | ``` 155 | 156 | 注意由于前一句事实上是一个`let`语句,必须有`let msg = ...;`的形式,所以最后一个大括号后的分号必不可少。 157 | 158 | Rust中的`match`语句避免了C++中`switch`语句的不少常见Bug:你绝不为因为忘记写`break`得到不想要的执行穿越;如果写出了一些对匹配值的模式,Rust编译器会自动帮你检查是否覆盖了所有可能的情况。 159 | 160 | 161 | ## 方法调用 162 | 163 | 最终简单介绍一下Rust中的成员方法。与C++中类似,他们总是使用`.`操作符来调用(没有`->`操作符,这会在其他章节中讲到)。刚才我们看到了`len`和`iter`两个例子。之后我们会更深入的讲到如何定义和调用他们,不过在学习之前,你可以认为你在Java和C++中学到的经验都是适用的。 -------------------------------------------------------------------------------- /destructuring.md: -------------------------------------------------------------------------------- 1 | # 解构 2 | 3 | 我们在前一章当中重点关注了Rust中的数据结构,当有了一个数据结构,你可能会像将数据从里面取出来。对于struct而言,你可以像在C++中一样使用域名进行访问。对tuple,enum和元组结构体,则必须使用解构(Destructuring)来进行数据提取。在C++中不存在数据解构的问题,但是Python或者很多函数式语言都有解构的概念(在有的语言中它被称为元组拆包)。它的中心思想是,使用类似于对象创建的语句结构,将已有对象的数据填入到指定的本地变量中。换句话说,解构就是一种模式匹配和本地变量赋值的结合。解构已经成为了Rust最有力的语言特性。 4 | 5 | 解构通常使用`let`和`match`语句完成。当数据结构有多重变体时(如enum),你需要使用`match`语句。`let`表达式将各个变量放入到当前作用域中,而`match`语句则会引入新的作用域。如下面的代码所示 6 | 7 | ```rust 8 | fn foo(pair: (int, int)) { 9 | let (x, y) = pair; 10 | // 我们可以在foo中的任何地方使用x和y 11 | 12 | match pair { 13 | (x, y) => { 14 | // 只能在这个作用域内使用x和y 15 | } 16 | } 17 | } 18 | ``` 19 | 20 | 模式的语法(上例中`let`之后和`=>`之前的部分)在两个例子中是(几乎)完全一样的。你也可以在函数参数中使用相同的模式语法。 21 | 22 | ```rust 23 | fn foo((x, y): (int, int)) { 24 | } 25 | ``` 26 | (这种用法对struct和元组结构体更用,而如果参数是tuple就没那么有用了) 27 | 28 | 大多数初始化表达式都可以在解构模式中出现,而且它们可以任意的复杂。模式中也可以包含引用和原始类型的字面值。举个例子。 29 | 30 | ```rust 31 | struct St { 32 | f1: int, 33 | f2: f32 34 | } 35 | 36 | enum En { 37 | Var1, 38 | Var2, 39 | Var3(int), 40 | Var4(int, St, int) 41 | } 42 | 43 | fn foo(x: &En) { 44 | match x { 45 | &Var1 => println!("first variant"), 46 | &Var3(5) => println!("third variant with number 5"), 47 | &Var3(x) => println!("third variant with number {} (not 5)", x), 48 | &Var4(3, St { f1: 3, f2: x }, 45) => { 49 | println!("destructuring an embedded struct, found {} in f2", x) 50 | } 51 | &Var4(_, x, _) => { 52 | println!("Some other Var4 with {} in f1 and {} in f2", x.f1, x.f2) 53 | } 54 | _ => println!("other (Var2)") 55 | } 56 | } 57 | ``` 58 | 59 | 需要注意的是我们需要在模式中使用`&`对引用进行解构。另外可以看到允许混合使用字面值(`5`, `3`, `St { ... }`),下划线`_`,以及任意变量名(即使跟要匹配的值同名也可以)。 60 | 61 | 当有一个变量会出现而你又想忽略它时,可以使用`_`。比如在上面的例子中`&Var3(_)`表示匹配Var3变体,但是并不关心其中的具体值。在第一个`Var4`分支中我们将整个结构体都进行了绑定。当然也可以使用`..`来表示tuple和struct的所有域。因此当你想对enum的变体做某些事情而又不想管这个变体的具体结构时,可以这样写: 62 | 63 | ```rust 64 | fn foo(x: En) { 65 | match x { 66 | Var1 => println!("first variant"), 67 | Var2 => println!("second variant"), 68 | Var3(..) => println!("third variant"), 69 | Var4(..) => println!("fourth variant") 70 | } 71 | } 72 | ``` 73 | 74 | When destructuring structs, the fields don't need to be in order and you can use 75 | `..` to elide the remaining fields. E.g., 76 | 当解构struct时,可以不以声明时的顺序进行解构,同时也可以使用`..`忽略剩下的所有域。 77 | 78 | ```rust 79 | struct Big { 80 | field1: int, 81 | field2: int, 82 | field3: int, 83 | field4: int, 84 | field5: int, 85 | field6: int, 86 | field7: int, 87 | field8: int, 88 | field9: int, 89 | } 90 | 91 | fn foo(b: Big) { 92 | let Big { field6: x, field3: y, ..} = b; 93 | println!("pulled out {} and {}", x, y); 94 | } 95 | ``` 96 | 97 | struct解构的一个缩写版本可以直接创建域名字对应的本地变量,比如在上面的例子中命名了两个新的本地变量`x`和`y`,你也可以这样写: 98 | 99 | ```rust 100 | fn foo(b: Big) { 101 | let Big { field6, field3, .. } = b; 102 | println!("pulled out {} and {}", field3, field6); 103 | } 104 | ``` 105 | 106 | 这样就会自动创建两个跟域名字相同的本地变量,上面的例子中就是`field6`和`field3`。 107 | 108 | 还有几个Rust中解构的小问题。假如你想要得到模式中一个变量的引用,不能直接使用`&`符号,因为它们将匹配一个引用而非创建一个引用(而且会对对象进行一个解引用操作)。例如 109 | 110 | ```rust 111 | struct Foo { 112 | field: &'static int 113 | } 114 | 115 | fn foo(x: Foo) { 116 | let Foo { field: &y } = x; 117 | } 118 | ``` 119 | 120 | 这里`y`的类型是`int`而且时一个`x`的拷贝而非引用。 121 | 122 | 要创建一个到某个模式的引用,你需要使用`ref`关键字,如下所示 123 | 124 | 125 | ```rust 126 | fn foo(b: Big) { 127 | let Big { field3: ref x, ref field6, ..} = b; 128 | println!("pulled out {} and {}", *x, *field6); 129 | } 130 | ``` 131 | 132 | 这里`x`和`field6`的类型都是`&int`,而且它们都是对`b`对象中相应域的引用。 133 | 134 | 还有最后一个小问题,当你需要对复杂对象进行解构时,可能不仅对某个具体域,而对某个中间对象也感兴趣。在前面的例子中有一个模式`&Var4(3, s,45)`,在这个模式中我们将结构体的一个域命名为`x`。假如你想对整个`St`结构体进行命名,你可以写成`&Var4(3, s, 135 | 45)`,从而将结构体对象命名为`s`,然而如果之后还需要访问其中的一个域就需要再写一个域访问语句,另外如果只想匹配某个域为某个值的对象,就需要再写一个嵌套的`match`语句。这一点也不好玩。Rust允许在模式中使用`@`语法来命名模式中的一部分,比如我们可以写`&Var4(3, s @ St{f1: 3, f2: x }, 45)`,这样可以同时将特定域(`x`对应`f2`)和整个结构体(`s`)同时命名。 136 | 137 | 上面介绍了在Rust中使用模式匹配的几个方式,但愿能让你了解使用`match`和`let`能实现哪些强大的特性。另外还有一些特性(如向量匹配)还没有提到。下一章我会介绍一些匹配和借出之间微妙的相互关系。在我学Rust的时候它们让我困惑了好一阵子。 -------------------------------------------------------------------------------- /borrowed.md: -------------------------------------------------------------------------------- 1 | # 借出指针 2 | 3 | 上一章中介绍了独占指针,这次我会介绍另外一种指针——借出指针(又称借出引用或引用),它在大多数Rust程序中远比独占指针常见。 4 | 5 | 如果你希望获得指向一个已存在的值的引用(而不是在堆中创建一个新的值并用一个独占指针指向它),我们必须要使用`&`符号,一个借出引用。这是可能是Rust中最为常见的一类指针。它很大程度上跟C++中的指针和引用(在向函数传递参数方面)用法相似。 6 | 7 | 我们使用`&`操作符来创建一个借出引用,使用`*`来解引用。对成员函数的自动解引用规则与独占指针相同。举例来说 8 | 9 | ```rust 10 | fn foo() { 11 | let x = &3; // 类型为 &i32 12 | let y = *x; // 3, 类型为 i32 13 | bar(x, *x); 14 | bar(&y, y); 15 | } 16 | 17 | fn bar(z: &i32, i: i32) { 18 | // ... 19 | } 20 | ``` 21 | 22 | `&`操作符并不分配内存(我们只能对已存在的值创建借出引用),一个借出引用退出作用域,它的空间也不会被释放。 23 | 24 | 借出引用不一定是唯一的,你可以创建多个指向同一个值的借出引用。 25 | 26 | ```rust 27 | fn foo() { 28 | let x = 5; // 类型为 i32 29 | let y = &x; // 类型为 &i32 30 | let z = y; // 类型为 &i32 31 | let w = y; // 类型为 &i32 32 | println!("These should all 5: {} {} {}", *w, *y, *z); 33 | } 34 | ``` 35 | 36 | 与值相同,借出引用默认是不可变的。你可以用`&mut`来创建一个可变的借出引用(指向的值可变,而非引用本身可变),或者来表示一个可变的借出引用类型。可变借出引用是唯一的,也就是说只能每个值只能由一个可变借出引用指向。可以创建一个指向可变值的不可变引用,反之则不然。下面是一个例子 37 | 38 | ```rust 39 | fn bar(x: &i32) { ... } 40 | fn bar_mut(x: &mut i32) { ... } // &mut i32 是一个指向可变的i32的引用 41 | 42 | fn foo() { 43 | let x = 5; 44 | //let xr = &mut x; // 错误 —— 无法借创建指向不可变值的可变引用 45 | let xr = &x; // OK 46 | bar(xr); 47 | //bar_mut(xr); // 错误 —— 需要一个可变引用 48 | 49 | let mut x = 5; 50 | let xr = &x; // OK (创建指向可变值的不可变引用) 51 | //*xr = 4; // 错误 —— 修改不可变引用 52 | //let xr = &mut x; // 错误 —— 已有一个不可变引用,因而不能存在可变引用 53 | 54 | let mut x = 5; 55 | let xr = &mut x; // OK (创建一个可变引用) 56 | *xr = 4; // OK 57 | //let xr = &x; // 错误 —— 已经有一个可变引用,不能创建一个不可变引用 58 | //let xr = &mut x; // 错误 —— 只能由一个可变引用 59 | bar(xr); // OK 60 | bar_mut(xr); // OK 61 | } 62 | ``` 63 | 64 | 注意,一个引用的可变性与这个引用所指向值的可变性没有关系。这有点像C++中,指针的const属性与所指向数据的const属性无关。这一点与独占指针不同,独占指针的可变性与其值的可变性相同。 65 | 66 | ```rust 67 | fn foo() { 68 | let mut x = 5; 69 | let mut y = 6; 70 | let xr = &mut x; 71 | //xr = &mut y; // 错误 —— xr不可变 72 | 73 | let mut x = 5; 74 | let mut y = 6; 75 | let mut xr = &mut x; 76 | xr = &mut y; // OK 77 | 78 | let mut x = 5; 79 | let mut y = 6; 80 | let mut xr = &x; 81 | xr = &y; // OK —— 虽然xr所指向的值不可变,但xr可变 82 | } 83 | ``` 84 | 85 | 如果一个可变值被借出,那在被借出的这段时间它不可变。而一旦借出引用退出作用域,这个值又会变为可变。这与独占指针完全不同,借出指针一旦移动就无法继续使用。 86 | 87 | ```rust 88 | fn foo() { 89 | let mut x = 5; // 类型: i32 90 | { 91 | let y = &x; // 类型: &i32 92 | //x = 4; // 错误 —— x已经被借出 93 | println!("{}", x); // OK —— c可读 94 | } 95 | x = 4; // OK - 可变借出退出作用域,x重新可变 96 | } 97 | ``` 98 | 99 | 上例中如果是一个可变借出引用也是同样的道理。总的来说在Rust中,数据仅能通过*一个*变量或指针修改,更进一步的,如果已经有一个可变借出引用,就不能创建一个不可变借出引用。这限制了我们如何使用底层的值。 100 | 101 | ```rust 102 | fn foo() { 103 | let mut x = 5; // 类型: i32 104 | { 105 | let y = &mut x; // 类型: &mut i32 106 | //x = 4; // 错误 —— x已经被借出 107 | //println!("{}", x); // 错误 —— 需要借出x,而x已被可变借出 108 | } 109 | x = 4; // OK - y不再存在 110 | } 111 | ``` 112 | 113 | 与C++中不同,Rust不会自动地为值创建引用。如果一个函数的参数为引用,调用方必须显式的获取一个引用作为参数。不过指针类型可以自动转换为引用。 114 | 115 | ```rust 116 | fn foo(x: &i32) { ... } 117 | 118 | fn bar(x: i32, y: Box) { 119 | foo(&x); 120 | // foo(x); // 错误 —— 需要一个&i32, 传入的却是 i32 121 | foo(y); // Ok 122 | foo(&*y); // 同样ok, 不过不是很好的风格 123 | } 124 | ``` 125 | 126 | ## `mut` 与 `const` 127 | 128 | 到这里,很有必要对比一下Rust中的`mut`和C++中的`const`。粗略的来说它们是相反的。默认情况下在Rust中值是不可变的,而可以使用`mut`标记为可变。而C++中值默认是可变的,可以用`const`来标记为不可变。更为重要且微妙的区别在于,C++中的常量性只针对对值的本次用例,而在Rust中不可变性对值的所有用例都适用:在C++中如果我有一个`const`变量,其他人可能会有一个指向它的非`const`引用,并且可能在我不知情的情况下修改它,而在Rust中如果你有一个不可变变量,你可以确定它不会被改变。 129 | 130 | 如前面所说,可变借出是唯一的,因此如果你有一个可变量,就可以保证如果你不修改它,它就不会变。更进一步,由于不可能有依赖于这个值不变性的其他实例存在,所以你可以任意的修改这个值。 131 | 132 | ## 借出与生存期 133 | 134 | Rust的一个主要安全目标就是避免悬挂指针(指针的生存期比它指向的对象更长)。在Rust中,不可能创建一个悬挂的借出引用。只有借出引用指向的对象将会比借出引用本身的生存期长(至少相同),编译器才认为是合法的程序。换句话说引用的生存期必须比它所指向的值生存期短。 135 | 136 | 这可以通过本章的各个例子看出,由每个`{}`或函数引入的作用域都绑定到了一个生存期——当一个变量退出了作用域,它的生存期也就结束了。如果我们试图获得一个指向更短生存期值的引用,比如说一个更小的作用域,编译器就会提示错误。举个例子 137 | 138 | ```rust 139 | fn foo() { 140 | let x = 5; 141 | let mut xr = &x; // Ok - x和xr有相同的生存期 142 | { 143 | let y = 6; 144 | //xr = &y // 错误 - xr比y的生存期更长 145 | } // y 在这里释放 146 | } // x 和 xr 在这里释放 147 | ``` 148 | 149 | 上面的例子中,`x`和`xr`的生存期并不相同,因为`xr`晚于`x`生成,但是它们生存期的结束更有意思,因为你不能引用一个尚未存在的值。这是Rust增加限制而变得比C++更安全的又一个例子。 150 | 151 | ## 显式生存期 152 | 153 | 在使用借出指针一阵之后,你可能会偶然发现具有显式生存期的借出引用。它的语法是`&'a T`(区别于`&T`)。为了说明它还需要介绍生存期多态性,因此我还是单开一章来详述(还有几个不太常见的指针类型要先介绍)。目前为止,只需要知道`&T`是 `&'a T`的缩写形式,其中`a`表示这个类型被声明位置所在的作用域。 -------------------------------------------------------------------------------- /destructuring 2.md: -------------------------------------------------------------------------------- 1 | # 解构II——匹配与借出 2 | 3 | 当使用解构时如果有借出的情况,会有一些意想不到的现象。虽说理论上如果你透彻的理解了借出引用的原理,这应该算不上意想不到,但我认为还是值得花一章来讨论(我就花了很久很久才搞明白,比我预想中的还要长,我甚至是在搞砸了这篇文章的第一版之后才真正弄明白) 4 | 5 | 想象一下,如果你有一个`&Enum`类型的变量`x`,其中`Enum`是一个枚举类型。你有两种选择,一是你可以对`*x`做匹配,并列出所有的变体(`Variant1 =>...`, etc.,以格式论,请尽量用这种方式,因为句法噪声要小些)。`x`是一个借出引用,而对借出引用进行解引用有这样那样的限制,而这些限制与`match`表达式混在一起时会有一些(至少对我来说)奇怪的表现,比如你看似完全无罪的修改了一个已存在的枚举,而编译器在`match`的某个地方却报警了。 6 | 7 | 在介绍匹配表达式之前,我们先来回顾一下Rust的值传递规则。在C++中,将一个值赋给一个变量或传递给函数有两种方法:值传递或引用传递。前一种是默认选择,值被拷贝构造函数拷贝,或直接执行二进制拷贝;如果在函数参数前加上`&`标志,则会导致值以引用传递,只有一个指针被拷贝过去,而当在这个变量上做操作时,事实上是在操作旧的那个值 8 | 9 | Rust有按引用传递的选项,只是在Rust中源值和目的值都要以`&`修饰。而要在Rust中进行值传递,有两种方法:拷贝和移动。拷贝就像在C++中一样(只是Rust中没有拷贝构造函数)。移动则会拷贝值并将久的值销毁,Rust的类型系统保证你无法再次访问旧的值。比如,`int`具有拷贝语义,而`Box`使用移动语义。 10 | 11 | ```rust 12 | fn foo() { 13 | let x = 7i; 14 | let y = x; // x 被拷贝 15 | println!("x is {}", x); // OK 16 | 17 | let x = box 7i; 18 | let y = x; // x 被移动 19 | //println!("x is {}", x); // 错误:使用了已被移动的值x 20 | } 21 | ``` 22 | 23 | Rust通过查找销毁器(Desturctors)来决定使用移动语义还是拷贝语义。销毁器可能需要单开一章来详述,但是现在你只需要知道Rust中如果一个对象实现了`Drop` trait,他就会有一个销毁器。与C++中相同,销毁器在对象被回收之前执行。如果一个对象有销毁器,那它就有移动语义。否则就会检查其中所有的域,如果有一个有移动语义则整个对象有移动语义。这一检查一直递归到结构体中。如果对象中没有任何成员有移动语义,则它有拷贝语义。 24 | 25 | 被借出的对象不能移动,否则你就会得到一个指向已不存在旧对象的引用。这相当于持有一个指向被销毁的对象的引用,有点像悬挂指针。如果有一个指向对象的指针,那也可能有其它的引用指向它。因此如果一个对象有移动语义,而你又有一个指向它的指针,对这个指针执行解引用会不安全。 26 | 27 | OK,回到匹配表达式。如之前所说,如果你希望对一个类型为`&T`的值`x`进行模式匹配,可以首先对它进行解引用,或者在`match`的每个分支中都使用引用匹配表达式。如下面的例子所示 28 | 29 | ```rust 30 | enum Enum1 { 31 | Var1, 32 | Var2, 33 | Var3 34 | } 35 | 36 | fn foo(x: &Enum1) { 37 | match *x { // 第一个方法:先解引用 38 | Var1 => {} 39 | Var2 => {} 40 | Var3 => {} 41 | } 42 | 43 | match x { 44 | // 第二个方法:在分支中解引用 45 | &Var1 => {} 46 | &Var2 => {} 47 | &Var3 => {} 48 | } 49 | } 50 | ``` 51 | 52 | 因为`Enum1`有拷贝语义,上面的例子中可以使用任何一种方法。让我们仔细观察两种方法:第一个方法中我们对`x`进行解引用并放在一个类型为`Enum1`的临时变量中(而它是参数`x`的一个拷贝),之后对`Enum1`的三个变体进行匹配。这是一个单层match,因为我们没有深入到值的类型中;第二个方法中没有解引用,我们对`&Enum1`的每个变体进行了匹配。这个是一个深度为2的匹配——它首先对类型进行匹配(这个例子中永远是引用类型),之后对它指向的对象的类型进行匹配(这个例子中是`&Enum1`)。 53 | 54 | 不论如何,我们(或者说编译器)必须保证我们遵守Rust关于移动和引用的不变性规则:如果某个对象的任何一部分被引用,则它不能被移动。如果一个值有拷贝语义,那倒无所谓。如果它有移动语义,则我们必须确保在任何一个match粉之中都不发生移动。为了保证这一点,要么需要忽略所有可能会移动的数据,要么就对它取引用(从而得到一个引用传递而非移动传递)。 55 | 56 | ```rust 57 | enum Enum2 { 58 | // Box有销毁器因而Enum2有移动语义 59 | Var1(Box), 60 | Var2, 61 | Var3 62 | } 63 | 64 | fn foo(x: &Enum2) { 65 | match *x { 66 | // 忽略内嵌的数据,因此可行 67 | Var1(..) => {} 68 | // 其它分支中不能修改 69 | Var2 => {} 70 | Var3 => {} 71 | } 72 | 73 | match x { 74 | // 忽略内嵌的数据,因此可行 75 | &Var1(..) => {} 76 | // 其它分支中不能修改 77 | &Var2 => {} 78 | &Var3 => {} 79 | } 80 | } 81 | ``` 82 | 83 | 无论使用哪种方式,我们都不能产生对内嵌数据的引用,因而任何一个都不会移动。在第一个方法中,即使`x`被引用了,我们不会在解引用(也就是match表达式)的作用域中对它的内部做改动,因此没有什么问题。我们同样不对整个值进行绑定(也就是不把`*x`绑定到一个变量上),因此我们不能移动整个对象。 84 | 85 | 在第二个`match`中我们可以产生对任何一种变体的引用,然而第一个版本却不行。因此第二个方法的第二个分支可以写成`a @ &Var2 => {}`其中`a`时一个引用。然而在第一个方法中我们不能写成`a @ Var2 => {}`,因为这将导致将`*x`移动到`a`。我们可以写成`ref a @ Var2 => {}`(其中`a`也是一个引用),不过这种写法不是很常见。 86 | 87 | 但是,如果我们需要使用`Var1`种内嵌的数据怎么办?我们不能写成: 88 | 89 | ```rust 90 | match *x { 91 | Var1(y) => {} 92 | _ => {} 93 | } 94 | ``` 95 | 96 | 或者 97 | 98 | ```rust 99 | match x { 100 | &Var1(y) => {} 101 | _ => {} 102 | } 103 | ``` 104 | 105 | 因为这两种方式都会造成`x`的一部分移动到了`y`。我们可以用`ref`关键字来获取`Var1`中数据的引用:`&Var1(ref y) => {}`。这是可以的,因为我们没有在任何地方对它解引用,因而不会移动`x`的任何部分。相反我们创建了一个指向`x`内部的指针。 106 | 107 | 另外,我们可以解构`Box`(match的深度进而增加为3层):`&Var1(box y) => {}`。这是OK的,因为`int`有拷贝语义而`y`是`Var1`中的`Box`中的`int`的拷贝(也就是在一个借出引用的内部),因此不会导致`x`的任何部分移动。我们也可以创建一个到`int`的引用而不是拷贝它:`&Var1(box ref y) => {}`。这样也是可以的,因为我们没有做任何解引用,因而不会移动`x`的任何部分。如果`Box`的内容也有移动语义,就不能写成`&Var1(box y) => {}`,因此只能选择使用引用版本。同样的技巧可以应用于上面第一种写法,其它完全一样,只是没有第一个`&`,比如应写成 `Var1(box ref y) => {}` 108 | 109 | 现在看看更复杂的例子,假如你想对一对枚举的引用做匹配。我们将完全不能用第一种方法: 110 | 111 | ```rust 112 | fn bar(x: &Enum2, y: &Enum2) { 113 | // 错误: x和y将被移动 114 | // match (*x, *y) { 115 | // (Var2, _) => {} 116 | // _ => {} 117 | // } 118 | 119 | // OK. 120 | match (x, y) { 121 | (&Var2, _) => {} 122 | _ => {} 123 | } 124 | } 125 | ``` 126 | 127 | 第一个例子是非法的,因为`x`和`y`在被匹配之前首先被解引用,进而被移动走来构造新的tuple对象。在这种条件下只有第二种方法是可用的。当然仍然需要遵守上述的规则来避免移动`x`和`y`的某些部分。 128 | 129 | 如果你只能拿到对数据的一个引用,而你需要这个值本身。那你除了拷贝这个数据别无选择。大多数情况下需要使用`clone()`函数。如果数据的类型没有实现`clone`,你就必须对这个数据进行解构进而自行实现手动拷贝。 130 | 131 | 如果我们有一个值本身,而不是一个指向具有移动语义的值的引用,那移动它是没有问题的,因为我们知道没有任何人拥有对它的引用(编译器可以保证这一点)。比如: 132 | 133 | ```rust 134 | fn baz(x: Enum2) { 135 | match x { 136 | Var1(y) => {} 137 | _ => {} 138 | } 139 | } 140 | ``` 141 | 142 | 还有一些事情需要注意。首先,我们只能将值移动到一个地方,在上面的例子中,我们将`x`的部分移动到了`y`中,而将其他部分忽略掉。如果我们写成`a @ Var1(y) => {}`,也就是试图将`x`的一部分移动到`y`,而剩下的`x`移动到`a`,这样做是非法的。将`a`和`y`的仅一个作为引用将会导致移走有其它引用指向的对象的问题,因而也是非法的。我们可以同时将`a`和`y`作为引用,因为这样做不会影响`x`的完整性。 143 | 144 | 同样的道理,如果有一个包含多个嵌套数据的变体,那我们不能同时移动一个数据并得到对另一个数据的引用的。比如如果有一个`Var4`变体声明为`Var4(Box, Box)`,可以用`Var4(ref y, ref z) => {}`(包含两个引用)或`Var4(y, z) => {}`(包含两个移动)来匹配,但是不能让其中一个为引用而另外一个是移动(`Var4(ref y, z) => {}`)。这是因为部分移动会摧毁整个对象,因此其他引用因此会失效。 -------------------------------------------------------------------------------- /arrays.md: -------------------------------------------------------------------------------- 1 | # Array和Vector 2 | 3 | Rust的数组与C中的数组非常不同。Rust中的数组提供两种在长度确定性上不同的变体,它们分别被称为定长数组和分片。我们将会看到,后一个名字起的并不合适,因为它们都是定长而非可变长的。对于可变长数组,Rust提供了`Vec`容器。 4 | 5 | ## 定长数组 6 | 7 | 定长数组是的长度和类型是静态的。比如说 `[i32; 4]`是一个长度为4的`i32`数组 8 | 9 | 数组字面量和数组访问语法与C相同 10 | 11 | ```rust 12 | let a: [i32; 4] = [1, 2, 3, 4]; // 像往常一样,可以不写类型标记 13 | println!("The second element is {}", a[1]); 14 | ``` 15 | 16 | 跟C中相同,数组下标是从0开始的。但是与C/C++不同,Rust中的数组有边界检查。事实上对数组的所有访问都要进行边界检查,从而使得Rust称为一个更安全的语言。 17 | 18 | 如果你尝试执行`a[4]`,那么就会得到一个运行时错误。不幸的是即使这个错误看起来很明显,Rust编译器仍不能在编译时发现这个错误。 19 | 20 | 如果你宁愿冒风险也要从代码中榨取最后一滴性能,你可以对数组做未检查的访问,也就是使用数组的`get_unchecked`方法。这样的数组访问必须在unsafe块中完成,你应该只会在非常少的情况下用到它。 21 | 22 | 就像Rust中的其它数据结构一样,数组默认是不可变的,而且其可变性是继承的。要修改数组元素需要使用下标语法。 23 | 24 | ```rust 25 | let mut a = [1, 2, 3, 4]; 26 | a[3] = 5; 27 | println!("{:?}", a); 28 | ``` 29 | 30 | 像其它数据一样,你可以通过取得一个引用来借出一个数组。 31 | 32 | ```rust 33 | fn foo(a: &[i32; 4]) { 34 | println!("First: {}; last: {}", a[0], a[3]); 35 | } 36 | 37 | fn main() { 38 | foo(&[1, 2, 3, 4]); 39 | } 40 | ``` 41 | 42 | 注意对借出的数组,下标依然可用 43 | 44 | 对C++程序员来说,Rust的数组有一个很有趣的特点——它们的表示方法。Rust数组是值类型:这意味着它们像其它值一样在栈上分配,而一个数组对象是一系列值,而不是如C中那样,是指向这些值的指针。因此我们上面的代码,`let a = [1_i32, 2, 3, 4];`将会在栈上分配16byte的空间,而执行`let b = a;`将会拷贝16字节。如果你需要一个类似于C中的数组,就必须显式的生成一个指向数组的指针,它会成为一个指向第一个元素的指针。 45 | 46 | Rust数组雨C数组的最后一点区别在于,Rust数组可以实现trait,因而带有方法。你可以通过调用`a.len()`来得到数组的长度。 47 | 48 | 49 | ## 分片 50 | 51 | Rust中的分片(slice)是在编译时无法获知长度的数组。除了没有指定长度,这种类型的声明语法与定长数组相仿:比如说`[i32]`是一个对32bit整数的分片,只是在编译时无法得知它的确切长度。 52 | 53 | 这导致分片有一个缺陷:因为Rust编译器必须知道所有对象的大小,而却无法确知分片的大小,所以我们不能获取分片类型的值。比方说如果你写出`fn foo(x: [i32])`的句子,编译器就会给你一个错误。 54 | 55 | 因此,你只能获取一个指向分片的指针(这一规则确实有一些非常技术性的例外,从而允许你实现自己的智能指针,然而你完全可以忽略这一例外)。你必须写成`fn foo(x: &[i32])`(一个分片的借出指针)或者 `fn foo(x: *mut [i32])`(一个指向分片的可变原始指针)。 56 | 57 | 要创建一个分片,最简单的方法是强制类型转换(coercion)。相比C++而言,在Rust中只有非常少的几个隐式类型转换。其中之一便是从定长数组到分片的类型转换。因为分片只能是指针类型,因此这将是最终是一个指针间的类型转换,比如你可以把`&[i32; 4]`转换为`&[i32]`。 58 | 59 | ```rust 60 | let a: &[i32] = &[1, 2, 3, 4]; 61 | ``` 62 | 63 | 这里右边是一个长度为4的定长数组,分配在栈上。我们之后获得了一个指向它的引用,类型为 `&[i32; 4]`。这个引用之后被转换成了`&[i32]` 并且由let语句赋予了名字`a`。 64 | 65 | 与之前相同,访问分片也与C相同,使用`[...]`,而且访问也会进行边界检查。你也可以通过调用`len()`函数来自己完成类型检查。很明显数组的长度会在其它的地方得到。事实上所有Rust中的数组都有确定的长度,因为它们要进行对内存安全十分重要的边界检查。与定长数组不同,对于分片而言,这个大小是在运行时得到的。因此我们可以说分片类型是动态尺寸类型。 66 | 67 | 因为分片只是一串值的序列,它的大小不能成为分片本身的一部分。所以它的大小被存储在指针当中(还记得分片必须以一个指针类型存在么)。一个指向分片的指针(像所有其它指向动态尺寸类型的指针一样),是一个所谓的胖指针——它的大小是两个字长而不是一个,它会包含一个指向数据的指针,外加一个载荷。对于分片而言,这个载荷就是分片的长度。 68 | 69 | So in the example above, the pointer `a` will be 128 bits wide (on a 64 bit 70 | system). The first 64 bits will store the address of the `1` in the sequence 71 | `[1, 2, 3, 4]`, and the second 64 bits will contain `4`. Usually, as a Rust 72 | programmer, these fat pointers can just be treated as regular pointers. But it 73 | is good to know about (in can affect the things you can do with casts, for 74 | example). 75 | 所以在上面的例子中,指针`a`在64位计算机上的大小为128 bit。前64 bit存储序列`[1, 2, 3, 4]`中`1`(第一个元素)的地址。而后面的64 bit会存储分片大小`4`。作为Rust程序员,这些胖指针完全可以被视为是普通指针,不过知道它们的构成仍然很有帮助)。 76 | 77 | 78 | ### 分片标记和区间 79 | 80 | 分片可以被认为是一个(借出的)数组视图。至此我们仅看到了对整个数组的分片,然而我们也可以得到数组一部分的分片。对此有一个特殊的记号来表示,它有些像下标语法,只是用范围代替了单个整数下标,比如`a[0..4]`表示数组`a`的前四个元素,区间标记不包含上界但包含下界(左闭右开)。 81 | 82 | ```rust 83 | let a: [i32; 4] = [1, 2, 3, 4]; 84 | let b: &[i32] = &a; // 得到整个数组的分片 85 | let c = &a[0..4]; // 整个数组分片的另一种表示 86 | let c = &a[1..3]; // 包含中间两个元素的分片 87 | let c = &a[1..]; // 包含最后三个元素的分片 88 | let c = &a[..3]; // 包含前三个元素的分片 89 | let c = &a[..]; // 整个数组的分片 90 | let c = &b[1..3]; // 可以对分片再次分片 91 | ``` 92 | 93 | 在最后一个例子中,我们仍然需要借出分片的结果。区间语法会产生一个非借出分片(类型为`[i32]`),因此即使我们在对一个借出分片切分我们仍然需要重新借出。 94 | 95 | 区间语法同样用在可以在分片语法外。`a..b`会生成一个从`a`到`b-1`的迭代器。它既可以跟其它迭代器一样使用,也可以用在`for`循环当中。 96 | 97 | ```rust 98 | // 打印从1到10的所有数 99 | for i in 1..11 { 100 | println!("{}", i); 101 | } 102 | ``` 103 | 104 | ## Vec 105 | 106 | 一个向量(vector)是在堆上分配的,而且具有所有权的引用。因此它像`Box<_>`一样,具有移动语义。我们可以把定长数组类比为值,分片类比为对值的借出引用。这样的话,我们可以将向量类比为一个`Box<_>`指针。 107 | 108 | 像`Box<_>`一样,你可以认为`Vec<_>`是一种智能指针,而非值本身。与分片相似,它的长度存储在"指针"中,只是这个"指针"现在是指Vec的值。 109 | 110 | 一个包含`i32`的向量的类型为`Vec`。虽然没有向量字面量这么一说,我们仍然可以通过`vec!`宏来进行相似的初始化。另外我们可以使用`Vec::new()`来创建一个空的向量。 111 | 112 | ```rust 113 | let v = vec![1, 2, 3, 4]; // 一个长度为4的Vec 114 | let v: Vec = Vec::new(); // 一个空的i32向量 115 | ``` 116 | 117 | 在上面的第两个例子中,类型标示是必须的,这样编译器才能知道Vec里存的是什么。如果我们使用这个向量,那它的类型标示可以被忽略。 118 | 119 | 正像数组和分片,我们可以用下标记号来获取vector中的值,也会进行边界检查。我们也可以用分段记号来获取向量中的分片(`&v[1..3]`) 120 | 121 | 向量的另外一个特性是它们的尺寸可变——它们可以随需要增长或缩短。比如使用`v.push(5)`可以将`5`加入到向量尾部(这要求`v`是`mut`的)。注意向量的增长可能造成内存重新分配,对很大的向量这会造成很大的拷贝开销。为了解决这一问题,可以使用`with_capacity`进行预分配,详情可见[Vec docs](https://doc.rust-lang.org/std/vec/struct.Vec.html)。 122 | 123 | 124 | ## `Index` traits 125 | 126 | *至读者:这一部分由大量的资料还没有完成。如果你在学习这个教程,可以先跳过本章,无论如何这都应该算是高级话题了。* 127 | 128 | 在数组和向量中使用的下标语法同样适用于其它容器,比如`HashMap`。如果要实现一个自定义容器,而也想使用类似的下标(和分片)语法,就需要实现`Index` trait。这是展现Rust如何将漂亮的语法应用到用户定义类型的一个很好的例子(`Deref`用来做智能指针的解引用,`Add`以及其它的trait都是一样例子)。 129 | 130 | `Index` trait看起来像这样 131 | 132 | ```rust 133 | pub trait Index { 134 | type Output: ?Sized; 135 | 136 | fn index(&self, index: Idx) -> &Self::Output; 137 | } 138 | ``` 139 | 140 | `Idx`是一个用于进行索引的类型(意即中括号内的类型),大多数情况下它会是`usize`。对于分片来说这个类型会是`std::ops:Range`。`Output`是用该下标访问后返回的类型,这东西每一个容器都不一样。对于分片来说它会是一个分片而不是某类型的单个元素。`index`是一个方法,它用来执行从容器里返回一个(或一系列)元素的工作。注意容器是以引用传递的,同时方法也返回一个生存期相同的元素引用。 141 | 142 | 让我们看一下`Vec`是如何实现上面的trait的 143 | 144 | ```rust 145 | impl Index for Vec { 146 | type Output = T; 147 | 148 | fn index(&self, index: usize) -> &T { 149 | &(**self)[index] 150 | } 151 | } 152 | ``` 153 | 154 | 如我们之前所说,下标是用`usize`来表示。对于`Vec`来说,索引将会返回一个类型为`T`的单个元素,因此`Output`的值为T。对`index`的实现略显奇怪——`(**self)`获取了一个包含整个`Vec`的分片,然后使用分片来获取元素,之后获取一个它的引用。 155 | 156 | 如果要实现一个自己的容器,你需要实现跟上面相似的实现`Index`来完成下标语法和分片语法。 157 | 158 | 159 | ## 初始化语法 160 | 对于Rust中的所有数据,数组和向量都必须能正确的初始化。大多数情况下你可能只需要包含很多零的数组,而使用数组字面量相当麻烦。Rust为你提供了一个小小的语法糖来初始化一个填满某指定值的数组`[value;len]`。比如要创建一个包含100个0的数组,可以写成`[0;100]`。 161 | 162 | 向量也提供类似的语法,`vec![42; 100]`会初始化一个包含100个42的向量。 163 | 164 | 初始化值不仅限于整数,它可以是任何表达式。对于数组初始化,长度必须是一个常量整数表达式。对于`vec!`而言,它可以是具有`usize`类型的任何表达式。 165 | -------------------------------------------------------------------------------- /data types.md: -------------------------------------------------------------------------------- 1 | # 数据类型 2 | 3 | 4 | 5 | 下面我们要讨论Rust中的数据类型。它们大致对应于C++中的class, struct和enum类型。与C++(Java以及大多数面向对象的语言)相比,Rust的数据和行为分离的更为严格。在Rust中,行为可以在traits或impl中由函数定义,其中traits不能包含数据。他们的意义接近于Java中的interface。我将在后面的章节讨论impl和traits,本章则只讨论数据。 6 | 7 | ## 结构体 8 | 9 | 10 | Rust中的结构体(struct)与C中的struct或C++中不带方法的struct相似,只需要列出域的名字。他的语法是: 11 | 12 | ```rust 13 | struct S { 14 | field1: int, 15 | field2: SomeOtherStruct 16 | } 17 | ``` 18 | 19 | 这里我们定义了一个名叫S的struct,并定义了两个域。域之间用逗号分割。如果你喜欢,你也可以在最后一个域后面加逗号。 20 | 21 | 结构体定义一个类型。在这个例子中,我们使用S作为类型。并假设SomeOtherStruct是另一个类型并且以值的形式包含在struct中。所谓值的形式,意思是不使用指向其他结构的指针。 22 | 23 | 要访问结构体中的域,需要使用`.`运算符和域的名字。下面的例子说明了对struct的使用方法 24 | 25 | ```rust 26 | fn foo(s1: S, s2: &S) { 27 | let f = s1.field1; 28 | if f == s2.field1 { 29 | println!("field1 matches!"); 30 | } 31 | } 32 | ``` 33 | 34 | 35 | 这里 `s1`是一个按值传递的struct对象,而`s2`是一个引用传递的struct对象。在函数中,我们都使用`.`来访问域,而不需要使用`->` 36 | 37 | 结构体由结构自面量(struct literals)初始化。需要指定结构体的名字以及每个域的值。下面是一个结构体初始化的例子 38 | 39 | ```rust 40 | fn foo(sos: SomeOtherStruct) { 41 | let x = S { field1: 45, field2: sos }; // initialise x with a struct literal 42 | println!("x.field1 = {}", x.field1); 43 | } 44 | ``` 45 | 46 | 由于结构体定义使用值语义,结构体不能递归定义,这意味着struct内域的类型不能包含自己。像`struct R { r: Option }`这样的定义是非法的,并且会触发编译错误(后面将会详细说到Option)。如果你需要递归引用的结构体,你应该使用某种形式的指针,因为指针中允许存在环。下面的例子就是一个合法的自我引用结构体 47 | 48 | ```rust 49 | struct R { 50 | r: Option> 51 | } 52 | ``` 53 | 54 | 如果上例中没有Option,我们就无法初始化这个结构体,Rust会因此报错。 55 | 56 | 没有域的结构体无论在定义还是字面量中都不使用大括号。大概是为了加速语法分析,在定义中还是需要有一个分号结尾。下例定义了一个空结构体 57 | 58 | ```rust 59 | struct Empty; 60 | 61 | fn foo() { 62 | let e = Empty; 63 | } 64 | ``` 65 | 66 | ## Tuple 67 | 68 | 元组(Tuple)是匿名、异构的数据序列。元组由括号中的类型名来定义。因为元组没有名字,他们是由它们的结构定义的。比如类型`(int,int)`是整数而`(i32, f32, S)`是一个三元组。元组值的初始化语法跟声明语法很像,只不过括号里原来放着类型的地方初始化时写的是具体的值。下面是一个例子 69 | 70 | ```rust 71 | // foo takes a struct and returns a tuple 72 | fn foo(x: SomeOtherStruct) -> (i32, f32, S) { 73 | (23, 45.82, S { field1: 54, field2: x }) 74 | } 75 | ``` 76 | 77 | 元组可以用在let表达式之后用于解构。 78 | 79 | ```rust 80 | fn bar(x: (int, int)) { 81 | let (a, b) = x; 82 | println!("x was ({}, {})", a, b); 83 | } 84 | ``` 85 | 86 | 我们讲在后面详细说解构的问题。 87 | 88 | ## 元组结构体 89 | 90 | 元组结构体是有名字的元组,或者说是没有域名字的结构体。 91 | 元组结构体使用`struct`关键字声明,接着一些逗号分割的类型并用括号包裹,最后跟着一个分号。 92 | 这样就声明了一个具有指定名字的类型。 93 | 其中的域只能像元组一样通过解构操作访问,而不能通过域的名字访问。 94 | 元组结构体不是很常用。下面是一个元组结构体的例子。 95 | 96 | ```rust 97 | struct IntPoint (int, int); 98 | 99 | fn foo(x: IntPoint) { 100 | let IntPoint(a, b) = x; // Note that we need the name of the tuple 101 | // struct to destructure. 102 | println!("x was ({}, {})", a, b); 103 | } 104 | ``` 105 | 106 | ## Enum 107 | 108 | 枚举(Enum)是类似于C++中enum和union的类型,其中可以放置多种类型的值。The simplest kind of enum is just like a C++ enum: 109 | 最简单的一种枚举类型就像C++中的enum: 110 | 111 | ```rust 112 | enum E1 { 113 | Var1, 114 | Var2, 115 | Var3 116 | } 117 | 118 | fn foo() { 119 | let x: E1 = Var2; 120 | match x { 121 | Var2 => println!("var2"), 122 | _ => {} 123 | } 124 | } 125 | ``` 126 | 127 | 然而,Rust中的枚举比C++中的要强大的多:枚举的每个变体都可以包含数据。他们可以像元组一样通过类型列表来定义。在这种情况下enum更像是C++中的union。Rush的enum可使用带标记的union而不像C++中一样不带标记。着意味着你永远不会在运行时把一个变体当作另一个使用。举例来说: 128 | 129 | ```rust 130 | enum Expr { 131 | Add(int, int), 132 | Or(bool, bool), 133 | Lit(int) 134 | } 135 | 136 | fn foo() { 137 | let x = Or(true, false); // x has type Expr 138 | } 139 | ``` 140 | 141 | 在Rust中使用enum可以实现大多数简单的面向对象多态性。 142 | 143 | 我们通常将match表达式于enum一起使用。之前说过match表达式与C++中的switch语句相似。之后我们将深入的讨论match表达式及其与数据解构结合的高级用法。下面是一个例子 144 | 145 | ```rust 146 | fn bar(e: Expr) { 147 | match e { 148 | Add(x, y) => println!("An `Add` variant: {} + {}", x, y), 149 | Or(..) => println!("An `Or` variant"), 150 | _ => println!("Something else (in this case, a `Lit`)"), 151 | } 152 | } 153 | ``` 154 | match的每一个分支匹配了`Expr`的一个变体,而且必须涵盖所有变体。最后一个`_`分支可以涵盖所有没有列出的变体(这个例子中只有`Lit`)。每个变体中的数据都被绑定在一个变量中,如在`Add`分支中我们绑定了`Add`的两个int到`x`和`y`中。如果我们不关心数据,可以像例子中的`Or`分支一样,使用`..`来匹配任意数据。 155 | 156 | ## Option 157 | 158 | Rust中一个很常用的enum类型是`Option`。它有两个变体:`Some`和`None`。`None`不包含数据,而`Some`有一个类型为`T`的数据。`Option`是一个范型enum,我们将在后面详细说明,他的基本思想来源于C++。Option常常表示一个值可以存在也可以不存在。在C++中使用nullptr的地方(可能用于表示不可用、未定义、未初始化或者false),在Rust中都应使用Option。使用Option将会保证在使用值之前对它进行检查,从而避免出现对null指针解引用之类的操作,因而更加安全。同时它更具一般性,既可以与指针一起使用,也可以与值一起使用。下面是一个例子: 159 | 160 | ```rust 161 | use std::rc::Rc; 162 | 163 | struct Node { 164 | parent: Option>, 165 | value: int 166 | } 167 | 168 | fn is_root(node: Node) -> bool { 169 | match node.parent { 170 | Some(_) => false, 171 | None => true 172 | } 173 | } 174 | ``` 175 | 176 | 这里,`parent`域既可以使`None`也可以是包含一个`Rc`类型的`Some`。这个例子中通过使用`_`忽略了`Some`的载荷,因为我们并不需要它,在实际工作中有可能需要对其进行绑定。 177 | 178 | There are also convenience methods on Option, so you could write the body of 179 | `is_root` as `node.is_none()` or `!node.is_some()`. 180 | Option包含了很多方法方便使用,因此你可以将刚才`is_root`函数的函数体写为`node.is_none()`或者`!node.is_some()`。 181 | 182 | ## 可变性的继承与Cell/RefCell 183 | 184 | 在Rust中,本地变量默认是不可变的,可以通过关键字`mut`标记为可变的。Rust中不能标记struct或enum中的域为可变,他们的可变性继承自他们所属的struct和enum。着意味着一个struct中的域是否可变,决定于这个struct本身是否可变。下面是一个例子 185 | 186 | ```rust 187 | struct S1 { 188 | field1: int, 189 | field2: S2 190 | } 191 | struct S2 { 192 | field: int 193 | } 194 | 195 | fn main() { 196 | let s = S1 { field1: 45, field2: S2 { field: 23 } }; 197 | // s 是深度不可变的,下面所有的变化都是不允许的 198 | // s.field1 = 46; 199 | // s.field2.field = 24; 200 | 201 | let mut s = S1 { field1: 45, field2: S2 { field: 23 } }; 202 | // s是可变的,下面的修改是正确的 203 | s.field1 = 46; 204 | s.field2.field = 24; 205 | } 206 | ``` 207 | 208 | 可变性的继承止步于引用。这有点像C++中你可以通过一个const对象中的指针修改非const对象。如果希望一个引用域可变, 209 | 你需要在域类型前面加`&mut`: 210 | 211 | ```rust 212 | struct S1 { 213 | f: int 214 | } 215 | struct S2<'a> { 216 | f: &'a mut S1 // mutable reference field 217 | } 218 | struct S3<'a> { 219 | f: &'a S1 // immutable reference field 220 | } 221 | 222 | fn main() { 223 | let mut s1 = S1{f:56}; 224 | let s2 = S2 { f: &mut s1}; 225 | s2.f.f = 45; // legal even though s2 is immutable 226 | // s2.f = &mut s1; // illegal - s2 is not mutable 227 | let s1 = S1{f:56}; 228 | let mut s3 = S3 { f: &s1}; 229 | s3.f = &s1; // legal - s3 is mutable 230 | // s3.f.f = 45; // illegal - s3.f is immutable 231 | } 232 | ``` 233 | 234 | (在`S2`和`S3`中的`'a`参数是生命周期参数,我们将在后面详细讨论。) 235 | 236 | 有时,虽然一个对象逻辑上不可变,但其中的某些部分内部是可变的,如缓存或引用计数数据(即使是一个const对象的引用计数也需要在其析构时进行修改)。在C++中,可以使用`mutable`关键字来表明一个域即使在const对象中也可以被修改。在Rust中,我们有Cell和RefCell结构体。他们允许不可变对象的某一部分可变。虽然这很有用,但你应该注意当你在Rust中看到一个不可变的对象,它的某些部分可能是可变的。 237 | 238 | RefCell和Cell允许你绕过Rust的可变性与别名限制。虽然编译器不能保证它们的静态不变形,但能保证Rust的不变量在运行时时不变的,因此使用它们时安全的。Cell和RefCell都是单线程对象。 239 | 240 | Cell是用于拥有拷贝语义的类型(也就是原始类型)。Cell有`get`和`set`两个方法用于修改存储的值,同时还有`new`方法用来使用心得值初始化cell。Cell时非常简单的对象,因为在Rust中具有拷贝语义的类型不需要保存引用信息,而Cell又不能在线程间共享,所以使用它多半不会出错。 241 | 242 | RefCell是用于拥有移动语义的类型(基本上包括了Rust中所有的非简单类型)的,最常见的就是struct对象。RefCell也使用`new`来创建并使用`set`函数修改。但是如果要获取RefCell中的值,你必须使用借出函数(`borrow`, `borrow_mut`, `try_borrow`, `try_borrow_mut`)来借出具体的值。这些函数的使用规则与静态借出相同——只能拥有一个可变借出,不能同时存在可变借出和不可变借出。不同的是,当出现违反上述规则的情况时,静态借出会导致编译错误,而RefCell的借出函数将返回运行时错误。`try_`系列函数会返回Option——当能正常借出时将返回`Some`,而不能正常借出时则返回`None`。如果值已经被借出,调用`set`方法也同样会失败。 243 | 244 | 下面是一个对RefCell的使用计数指针的代码(这是非常常见的使用场景) 245 | 246 | ```rust 247 | use std::rc::Rc; 248 | use std::cell::RefCell; 249 | 250 | Struct S { 251 | field: int 252 | } 253 | 254 | fn foo(x: Rc>) { 255 | { 256 | let s = x.borrow(); 257 | println!("the field, twice {} {}", s.f, x.borrow().field); 258 | // let s = x.borrow_mut(); // 错误 —— 前面已经有对X的借出 259 | } 260 | 261 | let s = x.borrow_mut(); // 正常 —— 前一个借出已经退出作用域 262 | s.f = 45; 263 | // println!("The field {}", x.borrow().field); // 错误 —— 不能同时存在可变和不可变借出 264 | println!("The field {}", s.f); 265 | } 266 | ``` 267 | 268 | 如果需要使用Cell或RefCell,你要尽量保证它们的起作用的范围尽量小,也就是说,尽量将它们应用在struct中的几个尽量少的域上,而不要应用在整个struct上。你可以把它们想象成一个单线程锁,更精细的控制锁会有更好的效果,因为它可以避免过多的锁冲突。 -------------------------------------------------------------------------------- /graphs/README.md: -------------------------------------------------------------------------------- 1 | # Graphs and arena allocation 2 | 3 | (Note you can run the examples in this chapter by downloading this directory and 4 | running `cargo run`). 5 | 6 | Graphs are a bit awkward to construct in Rust because of Rust's stringent 7 | lifetime and mutability requirements. Graphs of objects are very common in OO 8 | programming. In this tutorial I'm going to go over a few different approaches to 9 | implementation. My preferred approach uses arena allocation and makes slightly 10 | advanced use of explicit lifetimes. I'll finish up by discussing a few potential 11 | Rust features which would make using such an approach easier. 12 | 13 | A [graph](http://en.wikipedia.org/wiki/Graph_%28abstract_data_type%29) is a 14 | collection of nodes with edges between some of those nodes. Graphs are a 15 | generalisation of lists and trees. Each node can have multiple children and 16 | multiple parents (we usually talk about edges into and out of a node, rather 17 | than parents/children). Graphs can be represented by adjacency lists or 18 | adjacency matrices. The former is basically a node object for each node in the 19 | graph, where each node object keeps a list of its adjacent nodes. An adjacency 20 | matrix is a matrix of booleans indicating whether there is an edge from the row 21 | node to the column node. We'll only cover the adjacency list representation, 22 | adjacency matrices have very different issues which are less Rust-specific. 23 | 24 | There are essentially two orthogonal problems: how to handle the lifetime of the 25 | graph and how to handle it's mutability. 26 | 27 | The first problem essentially boils down to what kind of pointer to use to point 28 | to other nodes in the graph. Since graph-like data structures are recursive (the 29 | types are recursive, even if the data is not) we are forced to use pointers of 30 | some kind rather than have a totally value-based structure. Since graphs can be 31 | cyclic, and ownership in Rust cannot be cyclic, we cannot use `Box` as our 32 | pointer type (as we might do for tree-like data structures or linked lists). 33 | 34 | No graph is truly immutable. Because there may be cycles, the graph cannot be 35 | created in a single statement. Thus, at the very least, the graph must be mutable 36 | during its initialisation phase. The usual invariant in Rust is that all 37 | pointers must either be unique or immutable. Graph edges must be mutable (at 38 | least during initialisation) and there can be more than one edge into any node, 39 | thus no edges are guaranteed to be unique. So we're going to have to do 40 | something a little bit advanced to handle mutability. 41 | 42 | One solution is to use mutable raw pointers (`*mut Node`). This is the most 43 | flexible approach, but also the most dangerous. You must handle all the lifetime 44 | management yourself without any help from the type system. You can make very 45 | flexible and efficient data structures this way, but you must be very careful. 46 | This approach handles both the lifetime and mutability issues in one fell swoop. 47 | But it handles them by essentially ignoring all the benefits of Rust - you will 48 | get no help from the compiler here (it's also not particularly ergonomic since 49 | raw pointers don't automatically (de-)reference). Since a graph using raw 50 | pointers is not much different from a graph in C++, I'm not going to cover that 51 | option here. 52 | 53 | The options you have for lifetime management are reference counting (shared 54 | ownership, using `Rc<...>`) or arena allocation (all nodes have the same lifetime, 55 | managed by an arena; using borrowed references `&...`). The former is 56 | more flexible (you can have references from outside the graph to individual 57 | nodes with any lifetime), the latter is better in every other way. 58 | 59 | For managing mutability, you can either use `RefCell`, i.e., make use of Rust's 60 | facility for dynamic, interior mutability, or you can manage the mutability 61 | yourself (in this case you have to use `UnsafeCell` to communicate the interior 62 | mutability to the compiler). The former is safer, the latter is more efficient. 63 | Neither is particularly ergonomic. 64 | 65 | Note that if your graph might have cycles, then if you use `Rc`, further action 66 | is required to break the cycles and not leak memory. Since Rust has no cycle 67 | collection of `Rc` pointers, if there is a cycle in your graph, the ref counts 68 | will never fall to zero, and the graph will never be deallocated. You can solve 69 | this by using `Weak` pointers in your graph or by manually breaking cycles when 70 | you know the graph should be destroyed. The former is more reliable. We don't 71 | cover either here, in our examples we just leak memory. The approach using 72 | borrowed references and arena allocation does not have this issue and is thus 73 | superior in that respect. 74 | 75 | To compare the different approaches I'll use a pretty simple example. We'll just 76 | have a `Node` object to represent a node in the graph, this will hold some 77 | string data (representative of some more complex data payload) and a `Vec` of 78 | adjacent nodes (`edges`). We'll have an `init` function to create a simple graph 79 | of nodes, and a `traverse` function which does a pre-order, depth-first 80 | traversal of the graph. We'll use this to print the payload of each node in the 81 | graph. Finally, we'll have a `Node::first` method which returns a reference to 82 | the first adjacent node to the `self` node and a function `foo` which prints the 83 | payload of an individual node. These functions stand in for more complex 84 | operations involving manipulation of a node interior to the graph. 85 | 86 | To try and be as informative as possible without boring you, I'll cover two 87 | combinations of possibilities: ref counting and `RefCell`, and arena allocation 88 | and `UnsafeCell`. I'll leave the other two combinations as an exercise. 89 | 90 | 91 | ## `Rc>` 92 | 93 | See [full example](src/rc_graph.rs). 94 | 95 | This is the safer option because there is no unsafe code. It is also the least 96 | efficient and least ergonomic option. It is pretty flexible though, nodes of the 97 | graph can be easily reused outside the graph since they are ref-counted. I would 98 | recommend this approach if you need a fully mutable graph, or need your nodes to 99 | exist independently of the graph. 100 | 101 | The node structure looks like 102 | 103 | ```rust 104 | struct Node { 105 | datum: &'static str, 106 | edges: Vec>>, 107 | } 108 | ``` 109 | 110 | Creating a new node is not too bad: `Rc::new(RefCell::new(Node { ... }))`. To 111 | add an edge during initialisation, you have to borrow the start node as mutable, 112 | and clone the end node into the Vec of edges (this clones the pointer, 113 | incrementing the reference count, not the actual node). E.g., 114 | 115 | ```rust 116 | let mut mut_root = root.borrow_mut(); 117 | mut_root.edges.push(b.clone()); 118 | ``` 119 | 120 | The `RefCell` dynamically ensures that we are not already reading or writing the 121 | node when we write it. 122 | 123 | Whenever you access a node, you have to use `.borrow()` to borrow the `RefCell`. 124 | Our `first` method has to return a ref-counted pointer, rather than a borrowed 125 | reference, so callers of `first` also have to borrow: 126 | 127 | ```rust 128 | fn first(&self) -> Rc> { 129 | self.edges[0].clone() 130 | } 131 | 132 | pub fn main() { 133 | let g = ...; 134 | let f = g.first(); 135 | foo(&*f.borrow()); 136 | } 137 | ``` 138 | 139 | 140 | ## `&Node` and `UnsafeCell` 141 | 142 | See [full example](src/ref_graph.rs). 143 | 144 | In this approach we use borrowed references as edges. This is nice and ergonomic 145 | and lets us use our nodes with 'regular' Rust libraries which primarily operate 146 | with borrowed references (note that one nice thing about ref counted objects in 147 | Rust is that they play nicely with the lifetime system. We can create a borrowed 148 | reference into the `Rc` to directly (and safely) reference the data. In the 149 | previous example, the `RefCell` prevents us doing this, but an `Rc`/`UnsafeCell` 150 | approach should allow it). 151 | 152 | Destruction is correctly handled too - the only constraint is that all the nodes 153 | must be destroyed at the same time. Destruction and allocation of nodes is 154 | handled using an arena. 155 | 156 | On the other hand, we do need to use quite a few explicit lifetimes. 157 | Unfortunately we don't benefit from lifetime elision here. At the end of the 158 | section I'll discuss some future directions for the language which could make 159 | things better. 160 | 161 | During construction we will mutate our nodes which might be multiply referenced. 162 | This is not possible in safe Rust code, so we must initialise inside an `unsafe` 163 | block. Since our nodes are mutable and multiply referenced, we must use an 164 | `UnsafeCell` to communicate to the Rust compiler that it cannot rely on its 165 | usual invariants. 166 | 167 | When is this approach feasible? The graph must only be mutated during 168 | initialisation. In addition, we require that all nodes in the graph have the 169 | same lifetime (we could relax these constraints somewhat to allow adding nodes 170 | later as long as they can all be destroyed at the same time). Similarly, we 171 | could rely on more complicated invariants for when the nodes can be mutated, but 172 | it pays to keep things simple, since the programmer is responsible for safety 173 | in those respects. 174 | 175 | Arena allocation is a memory management technique where a set of objects have 176 | the same lifetime and can be deallocated at the same time. An arena is an object 177 | responsible for allocating and deallocating the memory. Since large chunks of 178 | memory are allocated and deallocated at once (rather than allocating individual 179 | objects), arena allocation is very efficient. Usually, all the objects are 180 | allocated from a contiguous chunk of memory, that improves cache coherency when 181 | you are traversing the graph. 182 | 183 | In Rust, arena allocation is supported by the [libarena](https://doc.rust-lang.org/arena/index.html) 184 | crate and is used throughout the compiler. There are two kinds of arenas - typed 185 | and untyped. The former is more efficient and easier to use, but can only 186 | allocate objects of a single type. The latter is more flexible and can allocate 187 | any object. Arena allocated objects all have the same lifetime, which is a 188 | parameter of the arena object. The type system ensures references to arena 189 | allocated objects cannot live longer than the arena itself. 190 | 191 | Our node struct must now include the lifetime of the graph, `'a`. We wrap our 192 | `Vec` of adjacent nodes in an `UnsafeCell` to indicate that we will mutate it 193 | even when it should be immutable: 194 | 195 | ```rust 196 | struct Node<'a> { 197 | datum: &'static str, 198 | edges: UnsafeCell>>, 199 | } 200 | ``` 201 | 202 | Our new function must also use this lifetime and must take as an argument the 203 | arena which will do the allocation: 204 | 205 | ```rust 206 | fn new<'a>(datum: &'static str, arena: &'a TypedArena>) -> &'a Node<'a> { 207 | arena.alloc(Node { 208 | datum: datum, 209 | edges: UnsafeCell::new(Vec::new()), 210 | }) 211 | } 212 | ``` 213 | 214 | We use the arena to allocate the node. The lifetime of the graph is derived from 215 | the lifetime of the reference to the arena, so the arena must be passed in from 216 | the scope which covers the graph's lifetime. For our examples, that means we 217 | pass it into the `init` method. (One could imagine an extension to the type 218 | system which allows creating values at scopes outside their lexical scope, but 219 | there are no plans to add such a thing any time soon). When the arena goes out 220 | of scope, the whole graph is destroyed (Rust's type system ensures that we can't 221 | keep references to the graph beyond that point). 222 | 223 | Adding an edge is a bit different looking: 224 | 225 | ```rust 226 | (*root.edges.get()).push(b); 227 | ``` 228 | 229 | We're essentially doing the obvious `root.edges.push(b)` to push a node (`b`) on 230 | to the list of edges. However, since `edges` is wrapped in an `UnsafeCell`, we 231 | have to call `get()` on it. That gives us a mutable raw pointer to edges (`*mut 232 | Vec<&Node>`), which allows us to mutate `edges`. However, it also requires us to 233 | manually dereference the pointer (raw pointers do not auto-deref), thus the 234 | `(*...)` construction. Finally, dereferencing a raw pointer is unsafe, so the 235 | whole lot has to be wrapped up in an unsafe block. 236 | 237 | The interesting part of `traverse` is: 238 | 239 | ```rust 240 | for n in &(*self.edges.get()) { 241 | n.traverse(f, seen); 242 | } 243 | ``` 244 | 245 | We follow the previous pattern for getting at the edges list, which requires an 246 | unsafe block. In this case we know it is in fact safe because we must be post- 247 | initialisation and thus there will be no mutation. 248 | 249 | Again, the `first` method follows the same pattern for getting at the `edges` 250 | list. And again must be in an unsafe block. However, in contrast to the graph 251 | using `Rc>`, we can return a straightforward borrowed reference to 252 | the node. That is very convenient. We can reason that the unsafe block is safe 253 | because we do no mutation and we are post-initialisation. 254 | 255 | ```rust 256 | fn first(&'a self) -> &'a Node<'a> { 257 | unsafe { 258 | (*self.edges.get())[0] 259 | } 260 | } 261 | ``` 262 | 263 | ### Future language improvements for this approach 264 | 265 | I believe that arena allocation and using borrowed references are an important 266 | pattern in Rust. We should do more in the language to make these patterns safer 267 | and easier to use. I hope use of arenas becomes more ergonomic with the ongoing 268 | work on [allocators](https://github.com/rust-lang/rfcs/pull/244). There are 269 | three other improvements I see: 270 | 271 | #### Safe initialisation 272 | 273 | There has been lots of research in the OO world on mechanisms for ensuring 274 | mutability only during initialisation. How exactly this would work in Rust is an 275 | open research question, but it seems that we need to represent a pointer which 276 | is mutable and not unique, but restricted in scope. Outside that scope any 277 | existing pointers would become normal borrowed references, i.e., immutable *or* 278 | unique. 279 | 280 | The advantage of such a scheme is that we have a way to represent the common 281 | pattern of mutable during initialisation, then immutable. It also relies on the 282 | invariant that, while individual objects are multiply owned, the aggregate (in 283 | this case a graph) is uniquely owned. We should then be able to adopt the 284 | reference and `UnsafeCell` approach, without the `UnsafeCell`s and the unsafe 285 | blocks, making that approach more ergonomic and more safer. 286 | 287 | Alex Summers and Julian Viereck at ETH Zurich are investigating this 288 | further. 289 | 290 | 291 | #### Generic modules 292 | 293 | The 'lifetime of the graph' is constant for any particular graph. Repeating the 294 | lifetime is just boilerplate. One way to make this more ergonomic would be to 295 | allow the graph module to be parameterised by the lifetime, so it would not need 296 | to be added to every struct, impl, and function. The lifetime of the graph would 297 | still need to be specified from outside the module, but hopefully inference 298 | would take care of most uses (as it does today for function calls). 299 | 300 | See [ref_graph_generic_mod.rs](src/ref_graph_generic_mod.rs) for how that might look. 301 | (We should also be able to use safe initialisation (proposed above) to remove 302 | the unsafe code). 303 | 304 | See also this [RFC issue](https://github.com/rust-lang/rfcs/issues/424). 305 | 306 | This feature would vastly reduce the syntactic overhead of the reference and 307 | `UnsafeCell` approach. 308 | 309 | 310 | #### Lifetime elision 311 | 312 | We currently allow the programmer to elide some lifetimes in function signatures 313 | to improve ergonomics. One reason the `&Node` approach to graphs is a bit ugly 314 | is because it doesn't benefit from any of the lifetime elision rules. 315 | 316 | A common pattern in Rust is data structures with a common lifetime. References 317 | into such data structures give rise to types like `&'a Foo<'a>`, for example 318 | `&'a Node<'a>` in the graph example. It would be nice to have an elision 319 | rule that helps in this case. I'm not really sure how it should work though. 320 | 321 | Looking at the example with generic modules, it doesn't look like we need to 322 | extend the lifetime elision rules very much (I'm not actually sure if 323 | `Node::new` would work without the given lifetimes, but it seems like a fairly 324 | trivial extension to make it work if it doesn't). We might want to add some new 325 | rule to allow elision of module-generic lifetimes if they are the only ones in 326 | scope (other than `'static`), but I'm not sure how that would work with multiple 327 | in- scope lifetimes (see the `foo` and `init` functions, for example). 328 | 329 | If we don't add generic modules, we might still be able to add an elision rule 330 | specifically to target `&'a Node<'a>`, not sure how though. 331 | --------------------------------------------------------------------------------