├── Arrays and Vectors - Chinese.md ├── Criticizing the Rust Language, and Why C_C++ Will Never Die.md ├── README.md ├── Rust and GO.md ├── creating-a-rust-function-that-accepts-string-or-str.md ├── creating-a-rust-function-that-returns-string-or-str.md ├── r4cpp - Borrowed pointers.md ├── r4cpp - Data types.md ├── r4cpp - Destructuring.md ├── r4cpp - Graphs and arena allocation.md ├── r4cpp - Rc and raw pointers.md ├── r4cpp - Unique Pointers.md ├── string-vs-str-in-rust-functions.md └── why-is-a-rust-executable-large.md /Arrays and Vectors - Chinese.md: -------------------------------------------------------------------------------- 1 | 原文: https://github.com/nrc/r4cppp/blob/master/arrays.md 2 | 翻译者: Scott Huang 3 | 日期: August 22,2015 于 厦门 4 | 5 | # 数组和向量 Arrays and Vectors 6 | 7 | Rust数组和C数组很不同。开胃菜是数组有静态的和动态尺寸的口味。 这些都是比较常见的固定长度的数组Array和切片Slice。正如我们所看到的,前者是一种坏名声,因为两种类型的数组Array拥有固定的(和可增长相反)长度。对于可变长的数组`array`,Rust提供向量`Vec`集合。 8 | 9 | ## 固定长度的数组 Fixed length arrays 10 | 11 | 固定长度的数组Array有已知的静态长度和类型。例如: `[i32; 4]`是指类型为`i32`的长度为4的数组。 12 | 13 | 数组字面量和访问语法和C语言一致: 14 | 15 | ```rust 16 | let a: [i32; 4] = [1, 2, 3, 4]; // 和往常一样,类型注释是可选的 17 | println!("The second element is {}", a[1]); 18 | ``` 19 | 20 | 你会注意到数组的索引以0开始,就像C一样。 21 | 22 | 然而,和C/C++不一样,Rust数组的索引动作会检查边界。实际上数组的所有访问都会进行边界检查,这也从另一方面说明Rust是一个安全的语言。 23 | 24 | 如果你试图`a[4]`,你会得到一个运行时恐慌panic。不幸的是,Rust编译器还没有足够聪明到给你编译时错误,即使有时候错误看起来显而易见(就如本例)。 25 | 26 | 如果你喜欢危险的生活,或者必须榨出你程序的最后一点性能,你仍然可以访问数组而不检查边界。为此,使用数组的`get_unchecked`方法。不检查数组边界的语句必须包在unsafe的语句边界内。 27 | 你应该只在极少数情况下使用这种方法。 28 | 29 | 就像Rust其他数据结构一样,数组缺省是不可变的,易变性是遗传的。突变也是通过索引语法来完成: 30 | 31 | ```rust 32 | let mut a = [1, 2, 3, 4]; 33 | a[3] = 5; 34 | println!("{:?}", a); 35 | ``` 36 | 37 | 就像其它数据一样,你可以通过获取引用来借用数组的使用权: 38 | 39 | ```rust 40 | fn foo(a: &[i32; 4]) { 41 | println!("First: {}; last: {}", a[0], a[3]); 42 | } 43 | 44 | fn main() { 45 | foo(&[1, 2, 3, 4]); 46 | } 47 | ``` 48 | 49 | 注意,在一个借来的数组中索引仍然有效。 50 | 51 | 这是一个很好的时间来谈谈Rust数组中一些最让C++程序员(他们的代表)感兴趣的方面。Rust数组是值类型的:它们在栈Stack分配内存,和其他值一样,数组对象是一系列的值,并不是一个指针指向这些值(C是这么干的)。所以从我们前面的例子, 52 | `let 53 | a = [1_i32, 2, 3, 4];`将会从栈分配16字节并执行` 54 | let b= a; `将会复制16字节。 如果你喜欢一个C风格的数组,你必须明确将指针指向数组,这将给你一个指向第一个元素的指针。` 55 | 56 | Rust和C++数组差异的最后一点是,Rust数组可以实现特性Traits,然后拥有方法。比如你可以用`a.len()`来查出数组的长度。 57 | 58 | ## 切片 Slices 59 | 60 | 一个切片在Rust看来仅仅是一个在编译期时长度未知的数组Array。类型的语法就像是长度固定的数组,但没有长度。 61 | 62 | 例如,`[i32]`是一个32位整数的切片(没有静态已知长度) 63 | 64 | slices要注意的一点:由于Rust编译器必须知道所有对象的长度,而slice的长度未知,因此我们从来不能有值的切片类型。 如果你尝试写`fn foo(x: [i32])`,编译器就会报错。 65 | 66 | 因此,你必须总有指针指向切片slices(这条规定有一些非常技术性的例外,以便您可以实现你自己的智能指针,不过,现在你可以安全的忽略它们)。你必须这样写`fn foo(x: &[i32])` (一个借用来的引用指向切片slice) 或者 `fn foo(x: *mut [i32])` (一个可写的原始指针来指向切片slice), 等等。 67 | 68 | 创建切片slice的最简单的方法是通过强制。Rust比C++更少采用隐式转换。其中一种强制转换可以把固定长度的数组转为切片slices。由于切片必须指向值,这是一种有效率的值和指针之间的强制转换。举例,我们可以转换 `&[i32; 4]` 69 | 到 `&[i32]`, 例如, 70 | 71 | ```rust 72 | let a: &[i32] = &[1, 2, 3, 4]; 73 | ``` 74 | 75 | 这里,右边是一个从栈分配的固定长度为4的数组,然后取一个引用指向它(type `&[i32; 4]`)。那个引用强制转换类型为`&[i32]`,并且用let声明取名为`a`。 76 | 77 | 再次强调,访问和C一样(用`[...]`),并且访问会进行边界检查。你还可以用`len()`来检查自己的长度。所以,很明显,数组的长度是在某处已知的。实际上Rust的所有类型的数组都有已知长度,因为这是边界检查的必要条件,而这是保证内存安全的必须部分。大小已知是动态变化的(和静态的长度固定的数组相对立),并且我们说切片slice类型是动态大小类型(DSTs,还有其他种动态大小的类型,它们会在其它地方提到)。 78 | 79 | 由于切片slice仅仅只是一个系列的值,大小不能存为切片的一部分。取而代之,它被存为指针的一部分(记得切片必须总是以指针类型存在)。一个指向切片slice的指针(像所有DSTs指针)是一个胖指针 - 有两个字words宽,而不仅一个字宽,并且指向数据加一个有效负荷。这里,负荷指的是切片的长度。 80 | 所以,上述例子,指针`a`将有128 bits宽(在64位系统中)。第一个64 bits将存序列`[1,2,3,4]`中`1`的地址。通常,作为Rust程序员,这些胖指针可以仅看待为一个普通指针。但应该知道它的原理(比如它可以影响casts转换等等) 81 | 82 | ### 切片符号和范围 slicing notation and ranges 83 | 84 | 一个切片可以看作是数组的视图(借来的)。到现在为止,我们仅仅看到整个数组的切片,但我们也可以取数组的部分作为切片。这里有一个特殊的符号,就像索引语法,但获取一个范围而不是一个简单的整数。比如,`a[0..4]`,就是取`a`的前面4个元素。注意,范围是在起始位置是包含的,而在结尾部分是排除的(译者:原文有错,这边直接改正了)。比如: 85 | 86 | ```rust 87 | let a: [i32; 4] = [1, 2, 3, 4]; 88 | let b: &[i32] = &a; // Slice of the whole array. 整个数组的切片 89 | let c = &a[0..4]; // Another slice of the whole array, also has type &[i32]. 另外一种整个数组的切片,并且带有类型 &[i32] 90 | let c = &a[1..3]; // The middle two elements, &[i32]. 中间两个元素 91 | let c = &a[1..]; // The last three elements. 最后三个 92 | let c = &a[..3]; // The first three element. 前面三个 93 | let c = &a[..]; // The whole array, again. 所有 94 | let c = &b[1..3]; // We can also slice a slice. 可以取切片的切片 95 | ``` 96 | 97 | 请注意,在最后一个例子里,我们需要借用切片动作的结果。这个切片动作的语法产生一个没有借用的切片(类型:`[i32]`),我们必须接着借用(得到 a `&[i32]`),以至于我们在一个切片基础上继续切片。 98 | 99 | 在切片语法外,还可以使用范围语法。 `a..b`产生一个迭代器从`a`到`b-1`。这可以和其他迭代器结合在一起,通常,可以用在`for`循环中: 100 | 101 | ```rust 102 | // Print all numbers from 1 to 10. 打印所有的数据,从1到10 103 | for i in 1..11 { 104 | println!("{}", i); 105 | } 106 | ``` 107 | 108 | ## 向量 Vecs 109 | 110 | 一个向量vector从堆heap中分配内存并且有自己的引用。因此(就像`Box<_>`),它带有移动语义。我们可以想象一个固定长度的数组类似一个值,一个切片来借用引用。 同样的,想象Rust中一个向量vector类似一个`Box<_>`指针。 把`Vec<_>`想象为某种智能指针而不是一个值本身,就像`Box<_>`。和切片slice类似,长度是存在指针`pointer`里,这种情况下`pointer`就是向量Vec的值。 111 | 112 | 向量`i32`s拥有类型`Vec`。没有向量字变量,但我们可以用`vec!`宏取得同样效果。我们也可以用`Vec::new()`来创建空的向量。 113 | 114 | ```rust 115 | let v = vec![1, 2, 3, 4]; // A Vec with length 4. 长度为4的Vec 116 | let v: Vec = Vec::new(); // An empty vector of i32s. i32s的空向量 117 | ``` 118 | 119 | 在上述情况,类型注释是必不可少的,这样编译器就知道向量是一个装啥的向量了。如果向量有内容,那么类型注解则不是必须的。 120 | 121 | 就像数组arrays和切片slices,我们可以用索引符号来从向量vector中取得一个值(比如`v[2]`)。 再次,这些都会做边界检查。我们也可以用切片符号来从向量获取切片(比如,`&v[1..3]`)。 122 | 123 | 向量vectors的额外特色是它们的容量大小可以改变 - 它们可以按需变长或者变短。 例如,`v.push(5)`将把元素`5`加入向量vector的末尾(这将要求`v`是可变的)。注意,改变向量会导致重新分配内存,对于大的向量来说这意味着一堆拷贝动作。为了避免这个动作,你可以用`with_capacity`预先为向量分配空间,请参考[Vec docs](https://doc.rust-lang.org/std/vec/struct.Vec.html) 124 | 125 | ## `索引`特质 The `Index` traits 126 | 127 | 读者请注意: 本节有很多资料,我没有适当的覆盖到。如果你是按照培训资料顺序阅读的话,你可以跳过这一节,底下是一些高级话题。 128 | 129 | 相同的索引indexing语法被同时用给数组和向量,以及其他一些集合collections,比如`HashMap`s。并且你可以用在你自己的集合类。你可以选择性的用索引(和切片slicing)语法来实现`Index`特质。这是一个好例证来说明Rust可以如何用好的语法来同时服务内置的和用户的类型(`Deref`用来为智能指针解引用,就像`Add`以及其他各种各样的特质都用相似的方法起作用) 130 | 131 | `Index`特质看起来像: 132 | 133 | ```rust 134 | pub trait Index { 135 | type Output: ?Sized; 136 | 137 | fn index(&self, index: Idx) -> &Self::Output; 138 | } 139 | ``` 140 | 141 | `Idx`是用来索引的类型。对于大多数的索引indexing,这是`usize`类型。对于切片来说,这是一个`std::ops::Range`类型。`Output`是一种用来返回索引的类型,因集合不同而不同。 对于切片动作slicing来说,它将返回切片,而不是一个单一元素类型。注意,参考和方法返回集合的是引用的元素,且具有相同的使用期。 142 | 143 | 让我们研究`Vec`的实现,来看一看一个实现长啥样: 144 | 145 | ```rust 146 | impl Index for Vec { 147 | type Output = T; 148 | 149 | fn index(&self, index: usize) -> &T { 150 | &(**self)[index] 151 | } 152 | } 153 | ``` 154 | 155 | 正如我们上面说的,索引indexing采用`usize`。对于`Vec`,索引动作将返回一个类型为`T`的单一元素,就是`Output`的值。`index`的实现看起来有点怪异 - `(**self)`获取整个向量的切片视图,然后我们用切片索引动作来获取元素,最后取得指向它的一个引用。 156 | 157 | 如果你有你自己的集合,你可以用类似的方法实现`Index`。 158 | 159 | ## 初始化语法 Initialiser syntax 160 | 161 | 和Rust里面的所有数据一样,数组和向量必须恰当的初始化。你经常想要一个初始值都是0的数组,如果用数组字面量语法的将写的很痛苦。所以Rust给你一点语法糖来初始化给定值的数组:`[value; len]`。所以,比如要创建一个含100个0,你可以用`[0; 100]` 162 | 163 | 类似的,`vec![42; 100]`将创建一个含100个元素的向量,并且初始值都是42。 164 | 165 | 166 | 初始值不限于整数,它可以是任何表达式。对于数组的初始化,长度必须是整数常量表达式。对于`vec!`,它可以使任何返回`usize`的表达式。 167 | -------------------------------------------------------------------------------- /Criticizing the Rust Language, and Why C_C++ Will Never Die.md: -------------------------------------------------------------------------------- 1 | 原文: (Englisth) http://www.viva64.com/en/b/0324/ 2 | (Russia) http://eax.me/cpp-will-never-die/ 3 | 翻译者: Scott Huang 4 | 日期: August 23,2015 于 厦门 5 | 6 | #批判Rust语言,以及C/C++为什么永远不会死 7 | 8 | 12.05.2015Eax Melanhovich 9 | 10 | (译者缩写了该段)原文是俄语,有人感兴趣,得到作者同意后,把它翻成英文。(译者:然后我再把它翻成中文。) 11 | 12 | (译者缩写了该段)我不能不注意到这篇博客的读者有多大兴趣。显而易见,这篇博文将会导致一场语言大圣战,所以,请三思而后行,确定你将会通过发表“有建设性”的评论参与讨论后再开始阅读这篇文章。 13 | 14 | 再次说明原文是俄语:) (Russia) http://eax.me/cpp-will-never-die/ 15 | 16 | ##注: 进一步讲,我冒昧的假设Rust有意尝试创建一个快速并且安全的语言。 17 | 毕竟,Mozilla的人最初构思用它作为工具来开发一个浏览器引擎。如果它被证明是另外一个仅仅安全的语言,那么我就不可能得到全部东西。 有很多非常不同的安全语言供人们选择和品味,如果Rust没有打算代替C++,那么 18 | (1)为什么它需要包含一个不安全子集; (2)并且,为什么作者要抛弃Rust的轻量级进程?毕竟它们很方便,对吧?换句话说,如果我假设错了,那么整件事情就没有讨论的意义了。 19 | 20 | 如有你碰巧偶尔逛逛linux.org.ru论坛,那么请被警告到这篇文章没有触及为什么不喜欢Rust的那10条纯技术理由。和亲爱的伙伴 @sum3rman的Skype对话已经显示有一个以上的意见说那太“技术性”了。所以,我不得不承认,我下面罗列的东西不讨人喜欢,但是我还是冒险从中引用一些最有趣的条款在这里。实际上,一些普通常识性的理由足以不用触及技术性的讨论。 21 | 22 | 每一个理智的程序员都非常清楚的知道C/C++在最近的将来不会死掉。没有人会尝试用新语言重新编写几乎所有已经存在的桌面应用程序,操作系统内核,编译器,游戏以及浏览器引擎,虚拟机,数据库,压缩工具,音视频编码解码器,以及一堆其他的C库等等。 这是一批数量巨大的快速的、调试过的被时间证明了的代码。重写的代价太过昂贵和太冒险了,并且,诚实的讲,除了一些疯狂的Rust粉丝,没人会认为这有意义。 对C/C++程序员的需求从来都是高的,并且将保持很长一段时间。 23 | 24 | 好吧,那就用Rust写一些新的代码吧? 25 | 26 | 你可能还记得,这并不是第一次尝试创造一个“更好的”C/C++。 拿D语言来举例。它在2001年发布,且确实是一个好的语言。但没有空间发展,没有像样的开发工具,没有著名的成功案例可以联想到它。OpenMW项目最初用D开发,但作者突然决定用C++从头重写。据他们承认,他们收到一大堆的邮件说,“你们创建了一个很酷的项目,我们想做贡献,但是我们不懂,也不喜欢学习这个愚蠢的D语言”。维基百科告诉我们,除了D,还有有其它尝试准备杀死C++ - 举例说 Vala, Cyclone, Limbo, Bitc。 你们中有多少人曾经听说过这些语言? 27 | 28 | 我觉得人们是时候必须从历史中得到教训了。没有一个理智的人会在他们的项目中开始使用一个新语言,直到你给他们一些非常酷的开发支持工具,告诉他们一些成功故事,并且证明一堆程序员靠这个语言做日常工作维持生活。作为程序员,他们从来不会 - 除了一些最年轻的人 - 花他们的时间和健康来学习另外一种“非常棒”的语言,直到你展示一些非常酷的开发工具(不是未完工的像Racer工具那样)和许多确实准备好的库(不是“实验性的”或者“不稳定的”东西),告诉他们一些成功案例,告诉他们有许多空缺在他们的城市或乡镇。你知道,这就像“鸡和蛋”的两难处境。只有非常少的机会,这个问题确实得到解决(最近相关的案例是Go和Scala) - 这得感谢一些大公司(Google,Typesafe)的时间和金钱投入,他们基于一些原因推广一个新语言。 29 | 30 | 正如我提到的,有许多非技术性的理由单独就可以质疑Rust。但是,让我们假想一会儿这些理由都不存在。然后没有理由不用Rust写程序,对不对?嗯,这也是很值得商榷的,至少可以说。 31 | 32 | C/C++因各种原因而受到批判。顺便说一下,大多数批评者还从来没有看过产品级的C++代码。简短的说,C++的问题是非常快(并且只要求一点点内存,电量,等等),但是从允许数组越界,自由的存取内存等方面看不够安全。过去,这个问题促使程序员们开发出一系列安全的语言,比如Java,C#,Python还有其他等等。但是,他们被证明和C++相比,对资源需求太多,同时还有其他一些不足 - 比如, 比如当进行垃圾回收时“停止世界”问题。这也是为什么程序员正努力创建一个和C++一样快,但安全的语言。Rust是其中的候选人之一。 33 | 34 | Rust确实是安全的,但是,不幸的是,远没有快速。在写这篇文章的时候,它和Java,go和Haskell的性能如下图所示:http://www.viva64.com/media/images/content/b/0324_Criticizing-Rust-Language-Why-Cpp-Will-Never-Die/image1.png 35 | 36 | Picture 1 37 | 38 | 我真诚的希望程序员找到一个方法来加速它的时间,但在那之前,它没有比Scala或者Go花更多时间从安全/速度角度进行妥协。是否可以使一种语言即快又安全,或者是否因对数组越界检查,安全包裹C语言库,或则其他一些类似东西而天生注定比C/C++慢两倍的问题仍然是开放的。 39 | 40 | 顺便问一下,究竟是什么使得Rust安全?简单的说,这门语言有内建的代码分析器,它是一个非常棘手的问题:它可以捕获所有典型的C++错误,不只是内存管理,而且同时考虑多线程等。通过一条管道来传递一个指定对象的引用到另外一个线程,然后试着自己使用这个引用 - 程序会拒绝通过编译。这确实非常酷。 41 | 42 | 但C++在过去30年一直屹立不倒,有足够的静态的和动态的分析器在这些时间段被发布出来。只是作为一个例子,看一个关于Google sanitizers(谷歌消毒剂)的一个短片 - 他们确实非常难。无论如何,在任何一个严肃的项目中,你使用一个连续集成的系统,运行一大堆的测试来编译生成。如果你不这么做的话,那么你的麻烦比语言缺乏安全性而言会更糟糕,因为静态的类型输入不会保证程序正确的按你的逻辑运行!所以,既然你总是运行测试,为什么不同时使用sanitizer?真的,他们没有找到所有的错误。 43 | 44 | 另一方面,如果你在你的代码的某个深处地方没有检查数组越界,并且Sanitizer也没有报告这个错误,也许,这仅仅由于所有必须的检查已经在上一层提供过了,同时,多一次检查不是会使程序变慢?即使没有sanitizers,你还可以发现许多东西供你在不同平台编译项目时提供伴随适当的失真的"assert(obj -> isValid)"风格论断来检查你代码的不变性。 粗糙的说,这个问题实际上涉及到过去那些好的老圣战,关于理论家(t)he(o)retic(异教徒)和集体农庄的软件开发方式(这是一个创新,但过于理想化的方法和一个传统实证方式,认为被前者的支持者粗心的简化了 - 原译者注)。 45 | 46 | 你经常可以听到一个论点,即90%的执行时间被10%的代码所执行(据我理解,这仅仅是一条经验主义 - 对一个主题快速的扫描互联网无法替代任何严格的科学的研究)。因此,你可以用安全的Rust语言写你大部分的代码,然后,剩下的10%(那些“热”代码)写在不安全的子集中,所以目前的Rust执行实际上不会有不好的性能。好的,但这不更是暗示我根本不需要Rust,因为我可以用Go写90%代码,然后用C写剩下的10%?只有那些寻找银弹的人和幻想神话的理论家(异教徒)会因为所有代码都100%用同一种语言编写而感到知足,而用Rust。但实际上一种语言有两种方言,这和"Java + C"或者"Go+C"组合没什么不同。 47 | 48 | 但实际上90/10规则是垃圾话。按照它的逻辑,我们可以用Java90%重写Webkit或者VirtualBox或者GCC而得到同样的结果。但这显然是错的。并不是这个比率因不同程序而变动太大,让我们做一些计算来瞧瞧。假设整个程序用不安全的C/C++编写,并且它的执行时间,假设是0.9*1(一小部分热代码)+0.1*1(一个冷代码的大部分)=1。现在和一个用一个带有C代码插入的安全语言编写的程序做比较: 0.9*1 + 0.1*2 = 1.1,这样,理论上说,区别是10%。是多了还是少了?这取决于项目的规模。以Google为例,即使是很少的一点比例就可以节省数百万美元(请看第五小结,“利用”,在这篇文章中)。或者想象下一个更新,JVM突然开始要求多10%的资源!我都害怕猜想需要多少个0在数字后面才能把比率转化为金钱。10%是C和C++完成任务所需要的所有份额。 49 | 50 | 我们不断的唱着“不成熟的优化是所有邪恶的根源”,就像一句咒语。但如果我们想要跟随它,为什么不在所有的代码里使用快速排序来替代冒泡排序?毕竟,我们没有办法确切的知道哪里是瓶颈,对吗?为什么要把普通的活动包含在actors或者事务内存中而不用马上使用更加高效的原子?并且,通常说,在小案例中,强制性的初始化每一个单独的变量,执行一堆辅助性的检查等等根本没有意义。让你仅花了额外的几分钟去思考而获取即使只有2~5%而不是10%的性能改进,其实也不坏。此外,就像我们已经指出的,这会使C/C++程序有巨大的差异!毕竟,谁敢争辩说找到一个热点,重写那些代码(也许有一堆)并且证明这真的变快了是一件比预先考虑性能而言更容易的工作? 51 | 52 | 即使除了速度/安全妥协的问题之外,我还怀疑这种语言设计。特别是关于使用的5种指针类型。一方面,让程序员仔细考虑他们的变量存放在栈或堆里,允不允许被多线程操作的想法并不坏。另一方面,想象你正在写一个程序,并在某一刻发现一些变量应该存在堆里而不是栈上。所以你用Box重写代码。接着你发现你实际上需要Rc或者Arc。再次,你重写了所有的代码。然后,再次,你重写它在栈上拥有普通变量。所有的东西你都手工操作而没有使用一个像样的IDE。正则表达式不会帮助。或者你刚刚结束了一个噩梦般的“Vec>>>” - 对Java说你好吧! 但可悲的事是编译器已经知道每一个变量的生存期,并会自动插入所有这些Box's,Arc's等等。但由于一些原因,这个责任被转移到程序员。它会让程序员更方便的写val(我们活在第三个千年,毕竟!)。在必要时,显式的指定特殊的Box或者Rc。从这一点看,Rust的开发已经搞砸了整件事情。 53 | 54 | 这个,特别的,让Rust's的使用范围变得更窄了。没有一个理智的人会用这样的一个语言写web或者服务器端软件 - 特别当考虑到它并没有提供比JVM相关语言更显著的优势。即使是Go - 带有普通轻量级的进程(没有前途) - 看起来都是一种更好的选择来解决这些任务。至于前途,你得学会如何正确的操作它们而不会砸到自己的脚 - 你说的是“安全语言”,啊?确实,所有的这些语言都有他们自己的特点 - 比如说“停止世界”的例子。但这个问题可以通过把代码分解成微服务或者通过其它技术而解决。并且是的,没有人愿意把Rust转为Javascript,通过它来为AWS写脚本或则为MongoDB来做查询脚本语言。至于安卓,这也是不可能的,但出于不同的原因:有一个以上的架构风格,那么JVM可以做的更好。因此,如果你恰巧认为Rust是“适合所有任务”的话,我让你失望了。 55 | 56 | 还有更多的理由来终结它: 57 | 58 | 宏使用拐杖来弥补正常的例外情况下所造成的过度的冗长。我已经写了关于元编程的问题 - 正是因为他们, 特别的, 导致我们没有办法得到一个像样的Rust IDE。 并且,即使我并不确定, 看起来Rust宏甚至并没有命名空间。 59 | 60 | Cargo积极的鼓励绕过Crates.io从git中直接下载各种仓库就当人们是白痴。 作为一种结果,我们有被被巨量的混乱的包终结的风险,就像Erlang世界中的Rabar。顺便,我怀疑Go世界有同样的麻烦。 61 | 62 | 就像许多新语言,Rust正在走着简化的道路。我可以理解为什么它没有一个像样的继承和例外,但事实本身是有些人替我做让我觉得有些不舒服的决定。C++不会限制程序员说哪些他们可以或者不可以做。 63 | 64 | 现在,由于我们已经采取了简化的路径,为什么不扔掉所有的那些语言扩展?目前的事情的状况类似于Haskell的世界里,每个程序员都在用他们自己的方言在编码。 65 | 66 | 智能指针,让你知道,远远不会是免费的,不保证一个固定的垃圾收集时间。假如一些线程荣幸的释放一个非常深的数据结构会发生什么?当死引用在一个迷宫里徘徊时,所有依赖它的其他线程都安静的耐心的等待着。Erlang以及它的小块有同样的困难 - 我曾经自己面对过它许多次。智能指针自己本身也有一些问题 - 例如内存碎片化和泄漏。 就像在一个循环结构里只留下弱指针 - 整件事情搞砸了。所有这些都是一个语言试图假装变得安全点...如果你需要一个固定的GC时间,研究您的程序的行为,并采取预防措施(例如,提供对象池),如果你不满意这数字,或者可以手动管理内存。 67 | 68 | 有没有人看到Rust里面严格的语义描述吗?它至少有一个内存模型吗?当你考虑到它可以用10种方法解释源代码时,你还会称它为一个“安全”的语言,可以写出“确保正确”的程序? 69 | 70 | 我不得不再一次提醒你,麻烦的根源通常是人类,而不是科技。如果你的C++代码没有足够好,或者Java代码痛苦的运行缓慢,这不是由于这个技术是坏的 - 这是因为你还没有学会如何正确的使用它。 因此,你也不会因为一些其他原因而对Rust满意。学习那些更流行的工具并且喜欢上它不是更容易吗? 71 | 72 | 所以,我要总结一下,在未来五年里,我会投资我的时间去学习C/C++而不是Rust。C++是一个工业标准。过去30年来,程序员们一直用它去解决各种各样的任务。至于Rust和其他类似的 - 他们仅仅是奇怪的玩具带有模糊的未来。从2000年开始人们已经预测C++很快就会死亡,但自从那时开始起,C/C++并没有变得较少使用。事实恰恰相反,它是不断演进的(C++11, C++14),新工具的发布(举例说Clion和Clang),并且空间巨大。 73 | 74 | 一个C++程序员从来不会很难找到一个工资不错的工作,如果需要,可以很快的学会Rust。但相反的情况是非常,非常不可能发生。顺便说一下,找工作时,语言的选择从来都不是唯一的和最重要的事情。此外,一个熟练的C/C++程序员可以很容易地找到自己的方式在学习PostgreSQL's或者Linux核心代码,接触现代强大的开发工具,并且有一大堆的书和文章在手(比如说OpenGL) 75 | 76 | 所以,照顾一下您的健康吧,不要浪费你的时间 - 你只有比你想象的更少的时间! -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | # Rust Articles Translation 7 | ==================== 8 | 9 | Note: 10 | 11 | Start from August 29, I remove the original English text, and only left the Chinese translation, it make the full article read more smoothly and avoid fragment. 12 | 13 | Start from July 1, 2016, I move the contents to gitbook, it can help you to easily download as PDF and other format to read offline. 14 | 15 | 从2016年7月1号起,我把文章转移到gitbook,因为编写更加简单,各位也可以下载PDF格式或者其他格式以便线下阅读。 16 | ####[Gitbook - Rust文章翻译 - Scott Huang](https://scotthuang_hzl.gitbooks.io/rust_articles/content/) - Start from @ August 22, 2015 17 | -------------------------------------------------------------------------------- /Rust and GO.md: -------------------------------------------------------------------------------- 1 | 2 | 原文: https://medium.com/@adamhjk/rust-and-go-e18d511fbd95 3 | 4 | 翻译者: Scott Huang 5 | 6 | 翻译日期: August 22,2015 于厦门 7 | 8 | #Rust and Go 9 | 10 | 我已经花了不少时间在把玩一些新语言 – 特别要指出,Rust抓住了我的想象力。我们在Chef中使用大量的Ruby,Erlang和Javascript(后来是Angular)代码。以下是我喜欢这些语言的东西: 11 | 12 | ·Ruby的感觉就像永远在敲打我头脑中“whipuptitude”部分。它很容易,可以简单的坐下来开始编码,你基本不会碰到困难。它也有类似Perl中的我很喜欢的表达力。你越熟练这个语言,你就会越觉得你好像可以用英语(母语)表达自己的想法。 13 | 14 | ·Erlang和OTP极好操作。那些模式匹配,Actor并发,单一赋值,还有令人愉快的运行时导致运行、管理、调试产品服务是一种乐趣,我认为它语法是丑陋的,但是当你掌握它后,你会发现它的某种简洁美。 15 | 16 | ·现代JavaScript以它自己的方式变得越来越讨人喜欢。比如你很容易就可以获取社区贡献的各种包和框架,像Angular那样的纯粹的表现力,以及语言的经常使用部分的渐进式减肥都使体验变得愉快。过去我不喜欢它。 17 | 18 | 所以 – 我决定写一些Rust,还有Go,因为在我的世界里,每个人似乎看起来对它都很着迷。 19 | 20 | #Rust 21 | 22 | 我几个月前开始注意到Rust。这个语言被设计用来取代C/C++,适合进行底层开发,带有高的安全性保障,和革新性的内存管理方式(borrowing借用)。它还有我喜欢的和Erlang蛮类似的语言特性:模式匹配,Actor样式并发,缺省不可变变量。 23 | 24 | 我使用C/C++的历史并不多。诚实的讲,我的强类型语言的编程经验并不多。作为一个系统管理员,我们通常就是给某人的程序打补丁(我做了很多这种事,但打补丁不像从头开发东西)。在我拥有的有限经验中,我发现编译->运行->排错循环太令人恼火。我承认这是我个人的无知:如果我多花时间来理解语言,我一定可以发现发现那些定期打击我的陷阱, 25 | 26 | 因此,我开始用Rust重写一个Ruby开发的命令行工具。我无意推销或者分享,它仅仅是我学习语言的一个借口(动力)。以下是我观察到的: 27 | 28 | Cargo包管理器非常好 29 | 30 | Cargo是负责Rust项目管理的一个非常好的前端工具。创建一个新的Rust项目,添加测试、依赖等等都非常的容易和简洁。 31 | 32 | 像脚本语言一样富有表现力 33 | 34 | Rust就像一种非常富有表达力的底层脚本语言。更多请看下面这个链接: 35 | 36 | https://gist.github.com/adamhjk/e296257b32818ee8691d.js 37 | 38 | 有些东西像as_slice()和Vec也许需要一点时间来解释,但它非常的直白。下面链接可以回顾这些函数: 39 | 40 | https://gist.github.com/adamhjk/ead9de51d6f6d3aefeb2#file-review-rs 41 | 42 | 再次,我不认为写Rus会比写一种脚本语言更难。这需要一点时间来适应事实,比如,我从have_dot_git(cwd)返回一个选项Option,并且用模式匹配来抽取值(或者抛出错误)-但当你意识到引擎下面发生什么事情后,你就会觉得这很棒。 43 | 44 | Strongly typed 强类型 45 | 46 | 在所有的强类型语言中,我总是觉得我和编译器在进行摔跤比赛。我发现很难信任编译器,与其说它可以阻止我做一些傻事,还不如说它没有理解我的方式。(我承认这是我自己的一种情感缺陷)Rust因以下这三个原因让人觉得不同: 47 | 48 | ·它的编译错误信息非常的清楚,直接指出哪里错了,还经常给你提供精准的解决方案(“你是不是想…”) 49 | 50 | ·编译循环非常快,当我的代码成功编译后,它总是按我的想法准确的运行。 51 | 52 | ·借用、使用期等概念需要点时间来习惯。但是,编译器确实每次都代表你的利益,再次,错误提示信息和规则是明确的。Rust没有传统的垃圾收集器-但它有非常清晰的规则来指示在栈和堆上的东西该活多久。这些规则和区域结合的很紧,在某种程度上,感觉很自然。 53 | 54 | 对于我最重要的是,我的代码从来没有segfaulted或者恐慌。这些都是我以前用底层系统编程语言中从来没有拥有过的体验。如果编译器接受我的输入,它开始运行-快速并且准确。 55 | 56 | Rust有些库还不存在 57 | 58 | Rust还在进化,快到1.0了(译者:现在1.2都出来了)。作为一个副作用,许多高级库都还不存在,或者还没被写出来。一个好的(小)例子是命令行语法解析库还不存在。有一个包包含在标准库里,但是界面效果不佳。Github中有一些有希望的库,但是最好的那个(从接口、功能性等来看)没有办法用最新的Rust来编译通过。(这是由于fail!(..)宏被改为panic!(..)-不是一件大事,但也是一件说明Rust还在演进的事实。) 59 | 60 | 同样,Cargo可以很好的获取依赖-但没有说明那些包可以获取,就像NPM,Rubygems或者CPAN那样。 61 | 62 | 关于Rust的最终想法 63 | 64 | Rust是强大的,高度深思熟虑的系统编程语言,带有一些很好的、很有用的特性。(泛型,特质,模式匹配,Actor并发,借用-不胜枚举)。 那里需要一下税负来学习并用“Rust方法”来解决问题-语言本身是简单的,但需要和一些不同的概念较劲。(译者:如果你了解函数式编程Scala, Haskell,就会觉得学习Rust很快) 65 | 66 | 也就是说,我第一次觉得自己可以相信自己可以快速写出一个快速、高效的底层系统代码。编译器就是你的好友,它提供的错误提示非常有用和清晰。当它演进到1.0,并且库的生态系统开始升温时-Rust将是一股不可忽视的力量。 67 | 68 | #Go 69 | 70 | 很多人说了很多关于Go的如何神奇的事情。当我花了几个小时用Go重构一些东西后,我明白了这个诉求。 71 | 72 | Go is small Go比较小巧 73 | 74 | 如果你有一些C衍生语言的编程经验,那么你可以很快的学会这门语言的一些表面领域。有很多东西你已经知道如何在语言中使用了:if/then/else,switch,for。它们的使用语法你已经知道了。它提供一些速记语法来简化一些类型冗长的打字工作。(比如 :=)。 75 | 76 | Go是自以为是的 – 很多事情 77 | 78 | 当你读“怎么编写Go代码”时,它就开始告诉你怎样从顶层源代码目录开始的每件东西你该怎么用Go写。Go的工具链知道怎样取得结构和做正确的事情-编译二进制到你的’bin’目录,运行你的测试,捕获依赖。 79 | 80 | 静态输出是好的-和现实,任何你本地编译的都会是你用户所消费的,感觉不错。 81 | 82 | Go避免你的方法 83 | 84 | Rust编译器让你很确定你的想法会如何在底层代码中工作,Go坐在中间。语言是强类型的,但在实践中,它大部分时间都似乎可以推断出你的意图。举例: 85 | 86 | https://gist.github.com/adamhjk/5a475b8dd45971a4e814#file-dot_git_dir-go 87 | 88 | 在函数中唯一的类型注释是输入和输出-其他的每件事就是工作。感觉快速、容易、平易近人和非常少的麻烦。 89 | 90 | Go的运行时失败 91 | 92 | Rust运行时非常非常难出错。编译器让我确信我检查了每一个代码路径,总是确保我覆盖了每个角落。Go很乐意做我告诉它的东西 – 虽然编译器会阻止我的愚蠢,但它不会阻止我做这样的事: 93 | 94 | https://gist.github.com/adamhjk/9c7eae1d053a1788f581#file-panic-go 95 | 96 | 在第九行盲目的假设正表达式找到匹配,然后导致运行时错误信息。这在Rust中绝不会发生。 97 | 98 | https://gist.github.com/adamhjk/2eb74d326242a9093f9e#file-current_branch-rs 99 | 100 | 因为regex捕获的返回类型是Option选项,编译器知道我需要处理所有可能的值。我真的不能选择忽略它。这在我写代码的时候就发生了-在Go中,我只写了我认为我想要的,然后在运行时才了解哪里错了(这是个很小的fix)在Rust中,编译器在我第一次尝试这个函数时告诉我,我还没有对Option返回值处理None结果。 101 | 102 | 速度,可访问性,还有库 103 | 104 | 权衡是有趣的。Go代码用更少的时间来写,语言容易访问,生态系统强大。它让我感觉在用脚本语言在工作,但同时避免了大多数恶名昭著的错误。因为唯一的语言结构是最常见结构,在语言中感觉很浅显,根本没有时间。 105 | 106 | Go had a huge library ecosystem, a clear way to find them, and an easy way to get them. 107 | 108 | Go有巨大的库生态系统,一个清晰的方式来寻找它们,和一个简单的方式来得到它们。 109 | 110 | 所以...Rust还是Go? 111 | 112 | 我想,务实的回答是‘物尽其善,那个实用用哪个’。Go可以替代人们用Ruby,Pythong或者Perl所写的东西。它是一个简单,平易近人的语言,加上强类型和难以置信的生态系统。我明白人们为什么喜欢它。静态二进制,很好的生态系统,配合的很好。我写Go代码大概比Rust快一倍。 113 | 114 | 但是,我更喜欢Rust。我喜欢它有模式匹配,泛型,缺省的不可变数据。我喜欢如果我的代码编译通过,程序按我设计的意愿运行的可能性是极高的。当我想象一个生态系统将在这样的一个基础上建立出来时,我愿意活在那个世界里。这比我依赖大量语言用例,并且让我没有真正感觉到舒适的空间的世界要好。我很确定如果你用C/C++的话,Rust将非常吸引你。而你用Ruby或Python的话Go对你更有吸引力。然而,我想一旦Rusts生态系统起飞,它将同时吸引那些使用Ruby/Python的人。 115 | 116 | 我想起了一次高层招聘规则获取的教训,规则之一是他/她必须在某些方面让人惊讶.一个在很多方面“好”的人,通常来说不是伟大的。Go就是给我这种感觉-它在每一方面都好,但没有抓住我的心,没有让我觉得因为在我已有的生态系统里面没有这些东西而觉得非常兴奋,Rust绝对惊人的保证你写的代码是在运行时正确和安全的-这是一种启示。 117 | 118 | 你一定要两者同时试一下。 119 | -------------------------------------------------------------------------------- /creating-a-rust-function-that-accepts-string-or-str.md: -------------------------------------------------------------------------------- 1 | * 原文: 2 | * http://hermanradtke.com/2015/05/06/creating-a-rust-function-that-accepts-string-or-str.html 3 | * Written by Herman J. Radtke III on 06 May 2015 4 | * 翻译者: Scott Huang 5 | * 翻译日期: Sep 13, 2015 于厦门 6 | 7 | #Creating a Rust function that accepts String or &str 创建一个接受String或&str的Rust函数 8 | 9 | 在我的[上一篇博文](https://github.com/ScottHuangZL/Rust-Articles-Translation/blob/master/string-vs-str-in-rust-functions.md) 10 | 我们谈了很多关于使用&str作为函数接受一个string参数的优选。在那篇文章快结尾时,我们讨论了一些何时在结构中使用`String`还是`&str`。我认为这个建议是好的,但在有些情况下,使用&str代替string不是最优的,我们需要另一种策略来对付这些使用用例。 11 | 12 | #A struct Containing Strings 包含字符串的结构 13 | 14 | 考虑如下Person结构。为了讨论,让我们假设Person有一个真正需要拥有所有权的name变量。因此我们选择使用String类型而不是&str。 15 | ```rust 16 | struct Person { 17 | name: String, 18 | } 19 | ``` 20 | 现在我们需要实现一个new()函数。根据我的上一篇博客的讨论,我们倾向使用&str: 21 | ```rust 22 | impl Person { 23 | fn new (name: &str) -> Person { 24 | Person { name: name.to_string() } 25 | } 26 | } 27 | ``` 28 | 29 | 只要我们记得在`new()`函数里调用`.to_string()`,上述代码可以工作。然而,这个函数的人体工程学设计的不太理想。如果我们使用string字面量,那么我们可以像`Person.new("Herman")`那样创建一个新的Person。如果我们已经有一个String,我们需要一个引用来指向这个String: 30 | ```rust 31 | let name = "Herman".to_string(); //String 32 | let person = Person::new(name.as_ref()); 33 | ``` 34 | 这感觉就像我们在兜圈子。我们有一个`String`,然后我们调用`as_ref()`把它变成一个`&str`,然后把它放回了new()函数,使用`.to_stirng()`再把它变回`String`。我们可以回去,使用一个String像使用`fn new(name: String) -> Person {`,但这意味着我们需要强迫调用者当使用string字面量时要`.to_string()`。 35 | 36 | #Into conversions Into转换 37 | 38 | 我们可以用[Into trait](http://doc.rust-lang.org/nightly/core/convert/trait.Into.html)来使我们的函数更容易被使用者调用。这一特性将自动转换为一个&str到一个string。如果我们已经有了一个String,则没有转换发生。 39 | ```rust 40 | struct Person { 41 | name: String, 42 | } 43 | 44 | impl Person { 45 | fn new>(name: S) -> Person { 46 | Person { name: name.into() } 47 | } 48 | } 49 | 50 | fn main() { 51 | let person = Person::new("Herman"); //&str 52 | let person = Person::new("Herman".to_string()); //String 53 | } 54 | ``` 55 | 56 | 这个new()语法看起来有点不同。我们使用[泛型](http://doc.rust-lang.org/nightly/book/generics.html)和[特性](http://doc.rust-lang.org/nightly/book/traits.html)来告诉Rust那个S类型必须为Sring类型实现Into特质。String类型实现`Into`为空操作,因为我们已经有一个String。 `&str`类型实现`Into`采用相同的`.to_string()`方法像在我们原本在new()函数做的那样。所以我们不是回避需要`.to_string()`调用,但我们正在为调用者取走调用需求。你可能会问,如果使用`Into`会影响性能吗,答案是否定的。Rust使用[静态调度](http://doc.rust-lang.org/nightly/book/trait-objects.html#static-dispatch)和[单型](http://stackoverflow.com/questions/14189604/what-is-monomorphisation-with-context-to-c/14198060#14198060)的概念在编译期来处理所有的这些。 57 | 58 | 别担心那些像静态调度和单型的事情会混淆不清。你只需要知道使用上面的语法你可以创建一个可以同时接受String和&str的函数。如果你认为`fn new>(name:S) -> Person {`拥有大量的语法,它是。重要的是要指出,`Into`没有什么特别的,它只是一个特性,是Rust标准库的一部分。如果你想的话,你可以自己实现这一特性。你可以实现类似的你发现有用的特质并把他们发布到[crates.io](https://crates.io/)。所有这些用户创建能力使得Rust成为一个令人敬畏的语言。 59 | 60 | #Another Way To Write Person::new() 另外一种写Person::new()的方法 61 | 62 | 下面的另外一种写法在语法上可能更容易阅读,尤其是当函数签名变得更加复杂: 63 | 64 | ```rust 65 | struct Person { 66 | name: String, 67 | } 68 | 69 | impl Person { 70 | fn new(name: S) -> Person where S: Into { 71 | Person { name: name.into() } 72 | } 73 | } 74 | ``` 75 | #相关链接 76 | 77 | * [String vs &str in Rust functions](https://github.com/ScottHuangZL/Rust-Articles-Translation/blob/master/string-vs-str-in-rust-functions.md) 78 | * [Creating a Rust function that returns a &str or String](http://hermanradtke.com/2015/05/29/creating-a-rust-function-that-returns-string-or-str.html) 79 | -------------------------------------------------------------------------------- /creating-a-rust-function-that-returns-string-or-str.md: -------------------------------------------------------------------------------- 1 | * 原文: 2 | * http://hermanradtke.com/2015/05/29/creating-a-rust-function-that-returns-string-or-str.html 3 | * Written by Herman J. Radtke III on 29 May 2015 4 | * 翻译者: Scott Huang 5 | * 翻译日期: Sep 15, 2015 于厦门 6 | 7 | #Creating a Rust function that returns a &str or String 创建一个返回&str或String的Rust函数 8 | 9 | 我们已经学了如何[创建一个接受String或&str的Rust函数](https://github.com/ScottHuangZL/Rust-Articles-Translation/blob/master/creating-a-rust-function-that-accepts-string-or-str.md). 现在我想向您展示如何创建一个函数,返回`String`或`&str`。我也要讨论为什么我们要这样做。让我们从写一个删除一个给定字符串的所有空格的函数开始,我们的函数可能看起来像这样: 10 | ```rust 11 | fn remove_spaces(input: &str) -> String { 12 | let mut buf = String::with_capacity(input.len()); 13 | 14 | for c in input.chars() { 15 | if c != ' ' { 16 | buf.push(c); 17 | } 18 | } 19 | 20 | buf 21 | } 22 | ``` 23 | 这个函数为字符串缓冲区分配内存,循环检查每个输入的字符,并把所有非空白字符追加到字符串缓冲区。现在我问:假如我的输入不包含空格的话,会怎样?输入的值将和缓冲区的值相同。在这种情况下,不先创建缓冲区将是更有效的。相反,我们想把给定的输入返回给调用方。输入的类型是一个&str但我们的函数返回一个String,虽然,我们可以将输入的类型更改为String: 24 | 25 | ```rust 26 | fn remove_spaces(input: String) -> String { ... } 27 | ``` 28 | 但这会导致2个问题。首先,输入是String类型的话,我们强迫调用方将输入的所有权转移到我们的函数中。这会阻止调用者在未来继续使用该值。我需要获取所有权仅当我们真的需要它。第二,输入可能已经是&str类型,我们现在迫调用方把它转换成String,这将导致我们创建缓冲区而不分配新内存的尝试失败。 29 | 30 | #Clone-on-write 31 | 32 | 我们真正想要的是当没有空格时可以直接返回我们的输入字符串的能力(`&str`)和当需要删除空白时返回一个新字符串(`String`)。这是一种可以使用`clone-on-write`或者`Cow`类型的地方。Cow类型可以让我们抽象无论是自有或者是借贷来的东西。在我们的例子中,&str是一个已有字符串的引用,将借贷数据。如果有空格,那么我们需要为一个新的字符串分配内存。这个新的字符串的缓冲区由buf变量拥有。通常,我们会将缓冲区的所有权返回给调用者。用Cow的时候,我们要把buf所有权转移到Cow并且返回。 33 | 34 | ```rust 35 | use std::borrow::Cow; 36 | 37 | fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> { 38 | if input.contains(' ') { 39 | let mut buf = String::with_capacity(input.len()); 40 | 41 | for c in input.chars() { 42 | if c != ' ' { 43 | buf.push(c); 44 | } 45 | } 46 | 47 | return Cow::Owned(buf); 48 | } 49 | 50 | return Cow::Borrowed(input); 51 | } 52 | ``` 53 | 我们函数现在检查给定的输入是否包含一个空格,然后才将内存分配给一个新的缓冲区。如果输入不包含空格,则简单地返回输入。我们增加了许多[运行时复杂度]来优化我们如何分配内存。注意,我们的Cow类型拥有和&str类型相同的使用期。正如我们前面所讨论的,编译器需要跟踪`&str`参考以便知道什么时候可以安全的释放(或终止)内存。 54 | 55 | Cow的美在于它实现了`Deref`特质,所以你调用immutable函数而不用知道是否该结果是一个新的string缓冲区或者不是。例子 56 | 57 | ```rust 58 | let s = remove_spaces("Herman Radtke"); 59 | println!("Length of string is {}", s.len()); 60 | ``` 61 | 如果我真的需要改变s,那么我可以使用`into_owned()`函数来将它转换成一个拥有所有权的变量。如果Cow的变体已经拥有所有权,那么我们就只需移动所有权。如果Cow的变体是借来的,那么我们就要分配内存。这让我们只有当想写(或改变)变量时才懒克隆(分配内存)。 62 | 63 | 当Cow::Borrowed是可改变的例子: 64 | ```rust 65 | let s = remove_spaces("Herman"); // s 是一个 Cow::Borrowed 变量 66 | let len = s.len(); // immutable function call using Deref 67 | let owned: String = s.into_owned(); // memory is allocated for a new string 68 | ``` 69 | 70 | 当Cow::Owned是可改变的例子: 71 | ```rust 72 | let s = remove_spaces("Herman Radtke"); // s 是一个 Cow::Owned 变量 73 | let len = s.len(); // immutable function call using Deref 74 | let owned: String = s.into_owned(); // no new memory allocated as we already had a String 75 | ``` 76 | Cow的想法是双重的: 77 | 78 | * 尽可能的延迟分配内存。在最好的情况下,我们永远不需要分配任何新的内存。 79 | * 允许remove_spaces函数调用者不用关心是否需要分配内存。在任何情况下,使用的Cow类型是相同的。 80 | 81 | #Leveraging the Into Trait 利用Into特质 82 | 83 | 我们之前讨论的使用`Into`特征的转换&str为String。我们还可以使用`Into`特征的转换&str或String到一个合适的Cow变体。通过调用`.into()`,编译器将自动完成转换。使用.into()不会加快或减慢代码。这是一个简单的选择,以避免显式地指定`Cow::Owned`或者`Cow::Borrowed` 84 | 85 | ```rust 86 | fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> { 87 | if input.contains(' ') { 88 | let mut buf = String::with_capacity(input.len()); 89 | let v: Vec = input.chars().collect(); 90 | 91 | for c in v { 92 | if c != ' ' { 93 | buf.push(c); 94 | } 95 | } 96 | 97 | return buf.into(); 98 | } 99 | return word.into(); 100 | } 101 | ``` 102 | #Real World Uses of Cow 103 | 104 | 我去空格的例子看起来有点做作,但有一些杰出的实际应用也采取这一策略。在Rust的核心有一个函数[有损地转换字节到UTF-8](https://github.com/rust-lang/rust/blob/720735b9430f7ff61761f54587b82dab45317938/src/libcollections/string.rs#L153)和一个函数将[转换CRLF到LF](https://github.com/rust-lang/rust/blob/c23a9d42ea082830593a73d25821842baf9ccf33/src/libsyntax/parse/lexer/mod.rs#L271)。这些函数都有这样一个用例:在最佳的情况下,一个&str可以返回,另一种情况下,一个String必须分配。其他的例子我想有正确编码的XML / HTML字符串或一个SQL查询正确转义。在许多情况下,输入已正确编码或是转义。在这种情况下,最好是将输入字符串返回给调用方。当输入需要修改时,我们不得不用字符串缓冲区的形式来分配新的内存,并返回给调用方。 105 | 106 | #Why use String::with_capacity() ? 107 | 108 | 当我们谈到有效管理内存的话题,请注意我用`Stirng::with_capaticy()`代替`String::new`创建字符串缓冲区的时候。你可以使用String::with_capacity()而不是String::new(),因为它是更有效地一次性分配内存缓冲而不是随着我们压入更多的字符到缓冲区时不断重新分配内存。让我们简单过一遍当我们使用String::new(),然后把字符压到到字符串的案例。 109 | 110 | 一个String实际上是一个UTF-8代码点向量。当String::new()被调用,Rust创建一个零字节容量的向量。如果我们将字符压到字符串缓冲区。就像`input.push('a')`,Rust不得不增加vector的容量。在这种情况下,它将分配2个字节的内存。当我们压入更多的字符并且超过容量,Rust将通过每一次扩容一倍的方法重新分配内存。内存分配顺序为0,2,4,8,16,32…2^n,n是Rust发现容量被超过的次数。重新分配内存很慢(注:kmc_v3 [解释](https://www.reddit.com/r/rust/comments/37q8sr/creating_a_rust_function_that_returns_a_str_or/croylbu),它可能不是我想的那么慢)。不仅要向内核要新的内存,它还必须从旧的内存空间复制内容到新的内存空间。查看[Vec::push](https://github.com/rust-lang/rust/blob/720735b9430f7ff61761f54587b82dab45317938/src/libcollections/vec.rs#L628)源码来看第一手的resizing逻辑。 111 | 112 | 一般来说,只有当我们需要它时我们才要分配新内存,且只要分配我们所需要的大小。对于小的字符串,像`remove_spaces("Herman Radtke")`,重新分配内存不是一件大事。但如果我想移除我们网站上每个JavaScript文件的空白呢?重新分配内存的压力将很大。当压入数据到一个向量时,我们最好先指定一个容量。最好的情况是,你已经知道长度,然后可以精确设置容量。Vec的[代码评论](https://github.com/rust-lang/rust/blob/720735b9430f7ff61761f54587b82dab45317938/src/libcollections/vec.rs#L147-152)给出类似的警告。 113 | 114 | #相关链接 115 | 116 | * [String vs &str in Rust functions](https://github.com/ScottHuangZL/Rust-Articles-Translation/blob/master/string-vs-str-in-rust-functions.md) 117 | * [Creating a Rust function that accepts String or &str](https://github.com/ScottHuangZL/Rust-Articles-Translation/blob/master/creating-a-rust-function-that-accepts-string-or-str.md) 118 | -------------------------------------------------------------------------------- /r4cpp - Borrowed pointers.md: -------------------------------------------------------------------------------- 1 | 原文: https://github.com/nrc/r4cppp/blob/master/borrowed.md 2 | 翻译者: Scott Huang 3 | 翻译日期: Agust 26, 2015 于厦门 4 | 5 | # Borrowed pointers | 借贷指针 6 | 7 | 在上一篇文章中,我介绍了独特的指针(所有权指针)。这一次我会谈谈另一种更常见于大多数Rust程序的指针:借贷指针(或借用的引用,或只是引用)。 8 | 9 | 如果我们想有一个引用指向现有的值(而不是创建一个在堆上的新值并指向它,作为所有权指针),我们必须使用`&`,一个借来的引用。这可能是Rust最常见的一种指针,如果你想用某些东西来替代一个C++指针或引用(例如,通过引用传递函数的参数),这可能就是它。 10 | 11 | 我们使用`&`操作符创建一个借来的引用,并表示参考类型,并用`*`来解引用。自动解引用规则也应用于借贷指针,和所有权指针一样。例如, 12 | 13 | ```rust 14 | fn foo() { 15 | let x = &3; // type: &i32 类型: &i32 16 | let y = *x; // 3, type: i32 3, 类型: i32 17 | bar(x, *x); 18 | bar(&y, y); 19 | } 20 | 21 | fn bar(z: &i32, i: i32) { 22 | // ... 23 | } 24 | ``` 25 | 26 | `&`操作符不分配内存(我们只创建一个借贷指针来引用现有值),如果借贷指针超出了使用范围,没有内存被删除。 27 | 28 | 借用的引用不是独一无二的-你可以有多个借来的引用指向同一值。例如, 29 | 30 | ```rust 31 | fn foo() { 32 | let x = 5; // type: i32 33 | let y = &x; // type: &i32 34 | let z = y; // type: &i32 35 | let w = y; // type: &i32 36 | println!("These should all 5: {} {} {}", *w, *y, *z); 37 | } 38 | ``` 39 | 40 | 就像值,借用的引用是默认不可变的。你也可以使用`&mut`来获取可变的引用,或表示可变的引用类型。 41 | 可变借贷引用是独一无二的(你只可以有一个唯一的可变引用指向值,你也只能有一个可变的引用如果没有 42 | 不可改变的引用的话)。你可以使用可变的引用假设只需要一个不可变引用,但不是反之亦然。把所有的这些概念放在下面这个例子: 43 | 44 | ```rust 45 | fn bar(x: &i32) { ... } 46 | fn bar_mut(x: &mut i32) { ... } // &mut i32 是一个指向i32的引用,并且可以改变 47 | 48 | fn foo() { 49 | let x = 5; 50 | //let xr = &mut x; // 错误 - 无法从一个不可变的变量上创建一个可变的引用 51 | 52 | let xr = &x; // 可以创建一个不可变的引用 53 | 54 | bar(xr); 55 | //bar_mut(xr); // 错误,期望一个可变的引用 56 | 57 | let mut x = 5; 58 | let xr = &x; // 可以,创建出一个不可变的引用 59 | //*xr = 4; // 错误,试图改变不可变引用 60 | //let xr = &mut x; // 错误 - 已经有一个不可变的引用,所以没有办法同时创建一个可变的引用 61 | 62 | 63 | let mut x = 5; 64 | let xr = &mut x; // 可以,创建一个可变的引用 65 | *xr = 4; // Ok 66 | //let xr = &x; // 错误 - 已经有一个可变的引用,所以不能同时创建一个不可变的引用 67 | //let xr = &mut x; // 错误 - 同时只能有一个可变的引用。 68 | bar(xr); // Ok 69 | bar_mut(xr); // Ok 70 | } 71 | ``` 72 | 73 | 注意,引用也许可以改变(或不)独立于变量所持有的引用的可变性。这类似于C++的指针可以 74 | 是常量(或不)独立的他们所指向的数据。这和所有权指针不同,所有权指针的可变性和数据的可变性是关联在一起的。例如, 75 | 76 | ```rust 77 | fn foo() { 78 | let mut x = 5; 79 | let mut y = 6; 80 | let xr = &mut x; 81 | //xr = &mut y; // 错误, xr是不可变的 82 | 83 | let mut x = 5; 84 | let mut y = 6; 85 | let mut xr = &mut x; 86 | xr = &mut y; // Ok 87 | 88 | let mut x = 5; 89 | let mut y = 6; 90 | let mut xr = &x; 91 | xr = &y; // 可以 - xr 是可变的,即使所引用的数据不可改变 92 | } 93 | ``` 94 | 95 | 如果一个可变的值是借来的,那么它在借来的期间变成不可变的了。一旦借用的指针超出范围,该值可以被继续改变。这与所有权指针相反,一旦移动就永远不能被再次使用。例如, 96 | 97 | ```rust 98 | fn foo() { 99 | let mut x = 5; // type: i32 100 | { 101 | let y = &x; // type: &i32 102 | //x = 4; // 错误 - x 已经被借用 103 | println!("{}", x); // 可以, x可以读 104 | } 105 | x = 4; // 可以, y已经不存在了 106 | } 107 | ``` 108 | 109 | 如果我们取一个可变引用的值也会发生同样的事情 - 那个值仍不能修改。Rust一般来说,数据只可以通过 110 | 一个变量或指针被修改。此外,当我们有一个可变的引用时,我们不能同时取一个不可改变的引用。这限制了我们如何使用底层的值: 111 | 112 | ```rust 113 | fn foo() { 114 | let mut x = 5; // type: i32 115 | { 116 | let y = &mut x; // type: &mut i32 117 | //x = 4; // 错误, x已经被借用了 118 | //println!("{}", x); // 错误, 需要借用x 119 | } 120 | x = 4; // 可以,y已经不存在了 121 | } 122 | ``` 123 | 124 | 与C++不同,Rust不会自动为你的对一个值创建引用。因此,如果一个函数通过引用调用一个参数,调用者必须引用实际参数。但是,指针类型将自动转换为引用: 125 | 126 | ```rust 127 | fn foo(x: &i32) { ... } 128 | 129 | fn bar(x: i32, y: Box) { 130 | foo(&x); 131 | // foo(x); // 错误,期望&i32,但找到i32 132 | foo(y); // 可以 133 | foo(&*y); // 也可以, 并且更显式,但不是一个好的风格 134 | } 135 | ``` 136 | 137 | ## `mut` vs `const` 138 | 139 | 在这个阶段,应该值得比较Rust里的`mut`和C++里的`const`。 140 | 表面上他们是对立的。默认情况下,Rust的值是不可变的,可以通过使用`mut`来改变值。C++里默认值是可变的,但可以通过使用`const`来常数化。更微妙、更重要的区别 141 | 是C++常量性仅适用于当前使用的值,而Rust的不可变性适用于所有值的使用。所以,在C++里如果我有一个`const`变量,其他人可以有一个非const引用指向它,他可以改变这个值而不通知我。在Rust里,如果你有一个不可变的变量,Rust会保证这个值不会改变。 142 | 143 | 正如我们上面提到的,所有可变的变量是独一无二的。所以如果你有一个可变的值,你知道这是不会改变的除非你改变它。此外,你可以自由地改变它,因为你知道,没有任何人正在依靠在它的不变性。 144 | 145 | ## Borrowing and lifetimes 借贷和使用期 146 | 147 | Rust的主要安全目标之一是避免悬空指针(一个指针指向一个已释放的内存)。在Rust里,不可能有一个悬空的借贷指针。这是唯一合法的创建借贷指针,只有当所指向内存的存在时间比引用长时(嗯,至少也和引用活的一样长)。换句话说,引用的寿命必须比参考值的寿命要短。 148 | 149 | 在这篇文章的所有例子中已经展示过。作用域使用`{}`被导入或者函数绑定在使用期界限-当一个变量超出范围时,它的寿命结束。如果我们试着把一个引用指向一个短寿命的值,例如 150 | 在较窄的范围内,编译器会给我们一个错误。例如, 151 | 152 | ```rust 153 | fn foo() { 154 | let x = 5; 155 | let mut xr = &x; // 可以,x和xr都有相同的使用期 156 | { 157 | let y = 6; 158 | //xr = &y // 错误, xr将比y活的久 159 | } // y在这里被释放 160 | } // x和xr在这里被释放 161 | ``` 162 | 163 | 在上面的例子中,x和XR不具有相同的使用期。因为XR开始的比x晚,但是,使用期的终结点更令人感兴趣,因为你在任何情况下都不能引用一个比引用更早死去的变量-Rust强制执行这些东西,这使得它比C++更 安全。 164 | 165 | ## Explicit lifetimes 显式使用期 166 | 167 | 玩了一段时间借贷指针后,你可能会遇到一个借来的指针有一个明确的使用期。这些语法是`&'a T`(cf 168 | `&T`)。它们是一个大主题,因为我需要覆盖使用期-多态性在同一时间,所以我将用另外一篇文章来讨论(有几个更不 169 | 常见的指针类型需要先提到)。现在,我只想说`&T`是`&'a T`的速记,当`a`是当前作用域,这是 170 | 声明类型的范围。 -------------------------------------------------------------------------------- /r4cpp - Data types.md: -------------------------------------------------------------------------------- 1 | 原文: https://github.com/nrc/r4cppp/blob/master/data%20types.md 2 | 翻译者: Scott Huang 3 | 翻译日期: Sep 6, 2015 于厦门 4 | 5 | # Data types 数据类型 6 | 7 | 在这篇文章中,我将讨论Rust的数据类型。这些大致相当于C++中的类,结构,和枚举。一个区别是Rust的数据和行为比C++(或Java,或其他面向对象语言)更严格地分开。行为是由函数定义的,可以定义在 8 | 特质traits和`impl`s(实现),但traits不能包含数据,它们类似于Java的接口。我将在下一篇文章谈到traits和impls,这一篇都是关于数据。 9 | 10 | ## Structs 结构 11 | 12 | 一个Rust的结构类似于C或C++中没有方法的结构,它简单地列出字段。最好是举一个例子来看看语法: 13 | 14 | ```rust 15 | struct S { 16 | field1: int, 17 | field2: SomeOtherStruct 18 | } 19 | ``` 20 | 21 | 在这里,我们定义了一个带有两个字段的结构称为`S`。字段由逗号分隔,如果你喜欢,你也可以用逗号结束最后一个字段。 22 | 23 | 结构引入一个类型。例如,我们可以使用`S`作为一种类型。`SomeOtherStruct`是另一个结构(在本例中作为一个类型),像C++,它包含值,也就是没有指针指到另一个在内存中的结构对象。 24 | 25 | 结构的字段使用操作符`.`和它们的名字存取。一个使用结构的例子: 26 | ```rust 27 | fn foo(s1: S, s2: &S) { 28 | let f = s1.field1; 29 | if f == s2.field1 { 30 | println!("field1 matches!"); 31 | } 32 | } 33 | ``` 34 | 35 | 这里`s1`是传值获得的结构对象,`s2`是传引用获得的一个结构对象。至于调用方法,我们使用相同的`.`来访问字段,不需要的`->`。结构初始化使用struct字面量。它们是结构每个字段的名字和值。例如, 36 | 37 | ```rust 38 | fn foo(sos: SomeOtherStruct) { 39 | let x = S { field1: 45, field2: sos }; // 用一个结构字面量初始化x 40 | println!("x.field1 = {}", x.field1); 41 | } 42 | ``` 43 | 44 | 结构不能是递归的,即结构的字段名字和类型不能与结构相同而导致循环。这是结构的值语义所导致的。例如,`struct R { r: Option}`是非法的,将导致编译错误(见下面更多关于选项)。如果你需要这样的结构,你应该使用一些指针,指针循环是允许的: 45 | 46 | ```rust 47 | struct R { 48 | r: Option> 49 | } 50 | ``` 51 | 52 | 如果我们上面的结构没有`Option`,就没有办法实例化struct,并且Rust会发出错误信号。 53 | 没有字段的结构不需要使用花括号,但定义确实需要;来结尾,这大概只是为了方便编译器解析。 54 | 55 | ```rust 56 | struct Empty; 57 | 58 | fn foo() { 59 | let e = Empty; 60 | } 61 | ``` 62 | 63 | ## Tuples 元组 64 | 65 | 元组是匿名的,异构数据序列。作为一种类型,他们是在圆括号中声明为一个序列类型。因为没有名字,他们是通过结构来识别。例如,类型`(int,int)`是一对整数和`(i32,f32,S)`是三元组。元组的值初始化 66 | 和元组类型声明采用同样的方式,但以值代替类型组件,例如,`(4,5)`。一个例子: 67 | 68 | ```rust 69 | // foo利用一个结构并返回一个元组 70 | fn foo(x: SomeOtherStruct) -> (i32, f32, S) { 71 | (23, 45.82, S { field1: 54, field2: x }) 72 | } 73 | ``` 74 | 75 | 元组可以通过一个`let`表达式来解构,例如, 76 | 77 | ```rust 78 | fn bar(x: (int, int)) { 79 | let (a, b) = x; 80 | println!("x was ({}, {})", a, b); 81 | } 82 | ``` 83 | 84 | 我们会在下次再具体谈谈解构。 85 | 86 | ## Tuple structs 元组结构 87 | 88 | 元组结构称为元组,或者,未命名的字段结构。他们使用`struct`关键字声明,一组类型被列在括号中, 89 | 加上一个分号。这样的声明将它们的名称作为一种类型。他们的字段必须被解构(如元组)后才能访问,而不是按名字访问。元组结构不是很常见。 90 | 91 | ```rust 92 | struct IntPoint (int, int); 93 | 94 | fn foo(x: IntPoint) { 95 | let IntPoint(a, b) = x; // 注意,我们需要元组结构的名字来解构 96 | println!("x was ({}, {})", a, b); 97 | } 98 | ``` 99 | 100 | ## Enums 枚举 101 | 102 | 枚举类型像C++的枚举或联合,是可以获取多值的类型。最简单的一种枚举类型就像C++枚举: 103 | 104 | ```rust 105 | enum E1 { 106 | Var1, 107 | Var2, 108 | Var3 109 | } 110 | 111 | fn foo() { 112 | let x: E1 = Var2; 113 | match x { 114 | Var2 => println!("var2"), 115 | _ => {} 116 | } 117 | } 118 | ``` 119 | 120 | 然而,Rust的枚举比这强大的多。每一个变体可以包含数据。像元组,这些是由一列的类型所定义。在这种情况下,他们更像C++的联合而不是C++的枚举。Rust枚举是有标记的联合而不是非标记的联合(在C++),这意味着你不会在运行时误用一个枚举的变量到另外一个枚举的变量。一个例子: 121 | 122 | ```rust 123 | enum Expr { 124 | Add(int, int), 125 | Or(bool, bool), 126 | Lit(int) 127 | } 128 | 129 | fn foo() { 130 | let x = Or(true, false); // x 有类型Expr 131 | } 132 | ``` 133 | 134 | 许多面向对象多态的简单的案例都可以在Rust中用枚举来处理的更好。使用枚举我们通常使用模式匹配。记住,这些和C++的开关语句类似。我将在下次再深入研究用这以及其他方式来解构数据。下面是一个例子: 135 | 136 | ```rust 137 | fn bar(e: Expr) { 138 | match e { 139 | Add(x, y) => println!("An `Add` variant: {} + {}", x, y), 140 | Or(..) => println!("An `Or` variant"), 141 | _ => println!("Something else (in this case, a `Lit`)"), 142 | } 143 | } 144 | ``` 145 | 146 | 模式匹配的每一个分支匹配一个`Expr`变体。所有变体必须被覆盖。最后一种情况下(`_`)涵盖了所有剩余的变体,虽然在本例中只有`Lit`。变体中的任何数据都可以被绑定到一个变量中。 147 | 在`Add`分支我们绑定两个整数在到`x`和`y`。如果我们不关心的数据,我们可以使用`..`来匹配任何数据,如我们在`Or`分支所做的那样。 148 | 149 | ## Option 选项 150 | 151 | 在一个特别常见的Rust枚举是`Option`。它有2个变体-`Some`和`None`。`None`没有数据和`Some`有一个单一的类型为`T`的字段(`Option`是一个通用的枚举,我们将在后面谈,但希望和C++分的很清楚)。Options用来表示一个值可能有或可能没。在C++中,任何使用一个空指针来表示一个值在某种程度上是不确定的,未初始化,或假的地方,你都应该使用Rust的Option。使用Option是安全的,因为您必须总是在使用前检查它;没有办法解引用一个空指针。他们也更通用,你可以就像使用指针那样使用它们的值。一个例子 152 | ```rust 153 | use std::rc::Rc; 154 | 155 | struct Node { 156 | parent: Option>, 157 | value: int 158 | } 159 | 160 | fn is_root(node: Node) -> bool { 161 | match node.parent { 162 | Some(_) => false, 163 | None => true 164 | } 165 | } 166 | ``` 167 | 168 | 169 | 在这里,父字段可以是一个`None`或者一个包含一个`Rc`的`Some`。在例子中,我们从来没有真正使用该有效载荷,但在现实生活中你通常会。 170 | 171 | 还有一些方便的方法,所以你可以在主体中像`node.is_none()`或`!node.is_some()`那样写`is_root`。 172 | 173 | ## Inherited mutabilty and Cell/RefCell 继承的可变性和Cell/RefCell 174 | 175 | Rust的局部变量是默认不可变的,可以使用标记`mut`来使其可变。我们在结构或枚举里并没有标记字段为可变的,他们的可变性是继承得来的。这意味着,在一个结构对象的字段是可变或不可变的取决于对象本身是可变的和不变的。例子: 176 | 177 | ```rust 178 | struct S1 { 179 | field1: int, 180 | field2: S2 181 | } 182 | struct S2 { 183 | field: int 184 | } 185 | 186 | fn main() { 187 | let s = S1 { field1: 45, field2: S2 { field: 23 } }; 188 | // s 是深度不可变的,下面的改变动作是被禁止的 189 | // s.field1 = 46; 190 | // s.field2.field = 24; 191 | 192 | let mut s = S1 { field1: 45, field2: S2 { field: 23 } }; 193 | // s 是可变的,下面是允许的 194 | s.field1 = 46; 195 | s.field2.field = 24; 196 | } 197 | ``` 198 | 199 | Rust的继承可变性在使用引用时停止。这是类似于C++,你可以通过const对象的指针来修改非const对象。如果你想要要改变一个引用字段,你必须在字段类型上使用`&mut`: 200 | 201 | ```rust 202 | struct S1 { 203 | f: int 204 | } 205 | struct S2<'a> { 206 | f: &'a mut S1 // 可变引用字段 207 | } 208 | struct S3<'a> { 209 | f: &'a S1 // 不可变引用字段 210 | } 211 | 212 | fn main() { 213 | let mut s1 = S1{f:56}; 214 | let s2 = S2 { f: &mut s1}; 215 | s2.f.f = 45; // 合法,即使s2自己是不可变的 216 | // s2.f = &mut s1; // 不合法,s2是不可变的 217 | let s1 = S1{f:56}; 218 | let mut s3 = S3 { f: &s1}; 219 | s3.f = &s1; // 合法,s3是可变的 220 | // s3.f.f = 45; // 不合法,s3.f 是不可变的 221 | } 222 | ``` 223 | 224 | (在`S2`和`S3`上的`'a`是使用期参数,我们会很快涉及到他们) 225 | 226 | 有时,当对象在逻辑上是不可变的,它有部分需要内部是可变的。考虑各种缓存或引用计数(这不会带来真正的逻辑上的不可变性,因为改变引用计数的影响可以通过析构观察到)。在C++中,即使对象是const的,你也可以使用`mutable`关键词允许这样的改变。在Rust里,我们有Cell和RefCell。这些允许不可变对象的一些部分是可变的。虽然这是有用的,但这意味着你需要知道,当你看到Rust里一个不可改变的对象,它的有些部分可能实际上是可变的。 227 | 228 | RefCell和Cell让你绕过Rust的可变性和别名性的严格规定。他们可以被安全的使用,因为他们确保Rust的不变量都是受注意的动态,即使编译器不能保证这些不变保持静态。Cell和RefCell都是单线程的对象。 229 | 230 | 使用带有复制语义的Cell类型(相当多的只是原始类型)。Cell有`get`和`set`的方法,用于改变存储的值,和一个`new`方法来初始化单元值。Cell是一个非常简单的对象,不需要任何智能,因为带有复制语义的对象不能保持引用在Rust的其他地方,他们不能共享线程,所以没有太大的犯错可能。 231 | 232 | 使用具有移动语义类型RefCell,这意味着Rust里几乎所有的东西,结构对象是一个常见的例子。RefCell也用`new`来创建,并且有`set`方法。要取得RefCell里的值,你必须用借的方法借它(`borrow`,`borrow_mut`,`try_borrow`,`try_borrow_mut`)这会给你一个借来的RefCell参考对象。这些方法遵循相同的规则,作为静态的借用-你只能有一个可变的借贷,不能同时有一个可变的和不可变的借贷。不然,你将得到一个运行时失败,而不是编译错误。`try_`变体返回一个选项-如果值可以借,你得到`Some(val)`,反之,得到一个`None`。如果一个值是借来的,调用`set`将失败。这里有一个例子使用引用计数的指针指向一个RefCell(常见的情况): 233 | 234 | ```rust 235 | use std::rc::Rc; 236 | use std::cell::RefCell; 237 | 238 | Struct S { 239 | field: int 240 | } 241 | 242 | fn foo(x: Rc>) { 243 | { 244 | let s = x.borrow(); 245 | println!("the field, twice {} {}", s.f, x.borrow().field); 246 | // let s = x.borrow_mut(); // 错误,我们已经借用x的内容 247 | } 248 | 249 | let s = x.borrow_mut(); // 哦,早期的借贷已经超出范围了。 250 | s.f = 45; 251 | // println!("The field {}", x.borrow().field); 252 | // 错误,不能同时拥有mut和immut借贷 253 | println!("The field {}", s.f); 254 | } 255 | ``` 256 | 257 | 如果你使用Cell/RefCell,,你应该尽量把它们放在最小的对象上。就是,倾向于把它们放在结构的几个字段上,而不是 258 | 整个结构。它们像单线程锁,细粒度锁定是更好的,因为你更可能避免锁冲突。 -------------------------------------------------------------------------------- /r4cpp - Destructuring.md: -------------------------------------------------------------------------------- 1 | * 原文: 2 | * https://github.com/nrc/r4cppp/blob/master/destructuring.md 3 | * https://github.com/nrc/r4cppp/blob/master/destructuring%202.md 4 | * 翻译者: Scott Huang 5 | * 翻译日期: Sep 10, 2015 于厦门 6 | 7 | # Destructuring 解构 8 | 9 | 上次我们看了Rust的数据类型。一旦你有了一些数据结构,你会想要从结构中取出数据。对于结构体,Rust就像C++那样,可以通过字段访问。对元组,元组结构和枚举,您必须使用解构(库里面有各种方便的函数,他们内部都使用解构)。解构数据结构不会在C++中发生,但如果使用过Python或各种函数式语言的话,你就会很熟悉它。这个想法是,正如你可以创建一个数据结构,用一堆本地变量填写它的字段,你也已可以从数据结构中赋值数据到一堆局部变量中去。从这个简单的开始,解构成为Rust的其中一个强大的功能。还有另外一种使用方法,解构结合模式匹配可以把匹配到内容分配到本地变量。 10 | 11 | 解构主要是通过let和match声明完成任务。当被解构的结构有不同的变体,比如枚举时,我们一般使用match声明。一个let表达式将变量拉入当前的范围,而匹配引入了一个新的范围。比较: 12 | 13 | ```rust 14 | fn foo(pair: (int, int)) { 15 | let (x, y) = pair; 16 | // 我们现在可以在foo的任何地方使用 x 和 y 17 | 18 | match pair { 19 | (x, y) => { 20 | // x 和 y 只能用在这个范围。 21 | } 22 | } 23 | } 24 | ``` 25 | 26 | 模式的语法(在上面的例子中,用在`let`之后,以及`=>`之前)在这两种情况下是(基本)相同的。您还可以在函数声明中的参数位置使用这些模式: 27 | 28 | ```rust 29 | fn foo((x, y): (int, int)) { 30 | } 31 | ``` 32 | 33 | 大多数的初始化表达式可以出现在解构模式,并且他们可以任意复杂。可以包括引用和简单字面量以及数据结构。例如, 34 | 35 | ```rust 36 | struct St { 37 | f1: int, 38 | f2: f32 39 | } 40 | 41 | enum En { 42 | Var1, 43 | Var2, 44 | Var3(int), 45 | Var4(int, St, int) 46 | } 47 | 48 | fn foo(x: &En) { 49 | match x { 50 | &Var1 => println!("first variant"), 51 | &Var3(5) => println!("third variant with number 5"), 52 | &Var3(x) => println!("third variant with number {} (not 5)", x), 53 | &Var4(3, St { f1: 3, f2: x }, 45) => { 54 | println!("destructuring an embedded struct, found {} in f2", x) 55 | } 56 | &Var4(_, x, _) => { 57 | println!("Some other Var4 with {} in f1 and {} in f2", x.f1, x.f2) 58 | } 59 | _ => println!("other (Var2)") 60 | } 61 | } 62 | ``` 63 | 64 | 注意我们在模式如何通过引用`&`以及如何使用混合字面量(`5`,`3`,`St { ... }`),通配符(`_`),和变量(`×`)来解构。你可以使用`_`当你想要忽略模式中的一个项目,所以如果我们不在乎整数的话,我们可以用`&Var3(_)`。在第一个`Var4 `分支我们解构嵌入结构(嵌套模式),在第二个`Var4`分支将整个结构体赋予一个变量。 65 | 你也可以使用`..`在代替一个元组或结构的中的所有字段。所以如果你想为每个枚举变量做点啥,却不关心的变体的内容,你可以这么写: 66 | 67 | ```rust 68 | fn foo(x: En) { 69 | match x { 70 | Var1 => println!("first variant"), 71 | Var2 => println!("second variant"), 72 | Var3(..) => println!("third variant"), 73 | Var4(..) => println!("fourth variant") 74 | } 75 | } 76 | ``` 77 | 78 | 当解构结构时,字段不需要排序,并且你你可以使用`..`来忽略其余字段。例如, 79 | 80 | ```rust 81 | struct Big { 82 | field1: int, 83 | field2: int, 84 | field3: int, 85 | field4: int, 86 | field5: int, 87 | field6: int, 88 | field7: int, 89 | field8: int, 90 | field9: int, 91 | } 92 | 93 | fn foo(b: Big) { 94 | let Big { field6: x, field3: y, ..} = b; 95 | println!("pulled out {} and {}", x, y); 96 | } 97 | ``` 98 | 99 | 作为一个速记,你可以只使用字段名称创建一个本地变量名。在上面的例子中,创建了两个新的本地变量`x`和`y`。或者,你可以这么写: 100 | 101 | 102 | ```rust 103 | fn foo(b: Big) { 104 | let Big { field6, field3, .. } = b; 105 | println!("pulled out {} and {}", field3, field6); 106 | } 107 | ``` 108 | 109 | 现在我们以字段相同的名称来创建本地变量,在这种情况下`field3`和`field6`被创建出来。 110 | 111 | Rust的解构有几个技巧。让我们假设你想要一个指向一个模式中的变量的引用。你不能使用`&`,因为这匹配一个引用,而不是创建一个新的(从而导致解引用一个对象的效果)。例如, 112 | 113 | ```rust 114 | struct Foo { 115 | field: &'static int 116 | } 117 | 118 | fn foo(x: Foo) { 119 | let Foo { field: &y } = x; 120 | } 121 | ``` 122 | 123 | 在这里,`y`有`int`类型,是x中的一个字段的副本。创建模式中的指向某些东西的指针,你要使用`ref`关键字。举例: 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,St { f1:3,f2:x },45)`。在这种模式中,我们为一个结构的字段命名,但你也可能想命名整个结构对象。你可以写`&Var4(3,s,45)`,将绑定结构对象到`s`,但然后你不得不使用字段名来访问每个字段,或者如果你想匹配字段中特定的值,你必须使用一个嵌套匹配。那不好玩。Rust允许你用`@`来命名部分匹配。例如`& Var4(3,s @ St{f1:3,f2:x },45)`可以让我们为`f2`命名`x`,同时为这个结构命名`s`。 135 | 136 | 这只不过涉及到了Rust模式匹配的部分内容。还有一些特性我还没有谈到,如匹配向量,但希望你知道如何使用`match`和`let`,并看到了你可以做一些强大的东西。 137 | 138 | 第二部分我会涉及匹配和借贷之间的一些微妙的相互作用,这曾经在我学习Rust时困扰过我好几次。 139 | 140 | # Destructuring pt2 - match and borrowing 解构第二部分 - 匹配和借贷 141 | 142 | 当解构时你会发现一些借贷指针所导致的惊讶。希望,一旦你真正明白了借贷指针,就没有什么令人惊讶的,但值得讨论(我花了一段时间才弄明白,这是因为,当然事实上,自从我第一次把这篇博客的第一个版本搞砸后)。想象一下,你有一些`&Enum`变量`x`(其中`Enum`是一些枚举类型)。你有两种选择:你可以匹配`*×`然后列出所有变体(`variant1 => ...`等)或你可以匹配`x`,并列出引用到变量模型(`&Variant1 =>...`,等)。(作为一种风格的问题,可能的话更喜欢第一种形式,因为有更少的语法噪音)。`x`是一个借来的引用,如何解引用一个借贷指针是有严格的规定,这些与匹配表达式以令人惊讶的方式进行交互至少让我惊讶,尤其是当你以一个看似无害的方式修改现有的枚举而导致编译器在匹配的某个地方发生爆炸时。 143 | 144 | 在我们进入match表达式细节前,让我们回顾一下Rust的值传递规则。在C++中,当为一个变量赋值时,或传递它到一个函数时,有2个选择--通过值传递,或者通过引用传递。前者是默认的,也就是指一个值是使用拷贝构造函数或者按位拷贝进行复制。如果您用'&'注释函数传递目的地或者赋值,那么就是传引用了-只有一个指向值的指针被复制,当你操作新变量时,你是也在同时操作旧的值。 145 | 146 | Rust有传引用选项,虽然在Rust里,源以及目的地必须注明`&`。至于Rust的值传递,有进一步的两种选择-复制或移动。一个复制和C++中的语义相同(除了Rust没有拷贝构造函数)。一个移动会复制值但同时释放旧的值 - Rust的类型系统确保您不能再访问旧值。作为例子,`int`具有复制语义而`Box`具有移动语义: 147 | 148 | ```rust 149 | fn foo() { 150 | let x = 7i; 151 | let y = x; // x 是复制的 152 | println!("x is {}", x); // OK 153 | 154 | let x = box 7i; 155 | let y = x; // x 是移动的 156 | //println!("x is {}", x); // 错误: 使用已经移动的值:`x` 157 | } 158 | ``` 159 | 160 | Rust通过寻找析构函数来确定一个对象是否具有移动或复制的语义。析构函数可能需要单独的一篇的博客来解释,但是现在,一个对象在Rust里具有析构函数是看是否实现了`Drop`特质。正如C++,析构函数在对象被摧毁之前执行。如果一个对象有一个析构函数,那么它具有移动语义。如果它没有,那么所有的字段被检查,如果某个字段有析构函数,那么整个对象具有移动语义。整个对象结构都以此类推。如果在对象的任何地方都没有发现析构函数,那么它具有复制的语义。 161 | 162 | 现在,重要的是,一个借用的对象是不能移动的,否则你会有一个引用指向旧的不再有效的对象。这相当于 163 | 在超出范围后还持有一个引用指向一个已被摧毁的对象 - 这是一种悬空指针。如果你有一个指向对象的指针,可能还有其他的引用指向它。因此,如果一个对象具有移动语义且你有一个指向它的指针,那么该指针解引用是不安全的。(如有该对象是复制语义学,解引用将复制一份拷贝,旧的对象还存在,所以其他引用将没问题)。 164 | 165 | 好的,回到匹配表达式。正如我前面所说的,如果你想匹配一些具有`&T`类型的`x`,那么你可以在匹配条款时解引用一次或者在匹配分支的每个表达式中解引用。例如: 166 | 167 | ```rust 168 | enum Enum1 { 169 | Var1, 170 | Var2, 171 | Var3 172 | } 173 | 174 | fn foo(x: &Enum1) { 175 | match *x { // 选项1 : 在这里解引用. 176 | Var1 => {} 177 | Var2 => {} 178 | Var3 => {} 179 | } 180 | 181 | match x { 182 | // 选项2: 在每个分支解引用. 183 | &Var1 => {} 184 | &Var2 => {} 185 | &Var3 => {} 186 | } 187 | } 188 | ``` 189 | 190 | 在这种情况下,你可以尝试任何一种方法,因为`Enum1`有复制语义。让我们仔细看看每一个方法:在第一种方法中我们解引用`x`到一个类型为`Enum1`的临时变量(即复制`x`中的值)然后对三种`Enum1`做匹配。这是一个'一层'的匹配,因为我们没有深入到值的类型。第二种方法没有解引用。我们对一个类型为`&Enum1`的值来匹配每个变体的引用。这种match往下走了2层 - 它与类型匹配(通常是一个引用),并同时查看所引用的值(这是`Enum1`)。 191 | 192 | 无论哪种方式,我们必须确保编译器可以确保我们尊重Rust的不变量的移动和引用-如果有指针指向对象,那我们就不能移动对象的任何一部分。如果匹配的值有复制语义,那么就没有关系了。如果它有移动语义,那么我们必须确保移动不发生在任何match的分支。这是通过忽略会移动的数据,或者引用它(所以我们得用传引用的方式传参,而不是移动)。 193 | 194 | ```rust 195 | enum Enum2 { 196 | // Box有析构器,所以Enum2有移动语义 197 | Var1(Box), 198 | Var2, 199 | Var3 200 | } 201 | 202 | fn foo(x: &Enum2) { 203 | match *x { 204 | // 我们忽略了所包含的内容,所以这样没问题 205 | Var1(..) => {} 206 | // 其他分支没有变化 207 | Var2 => {} 208 | Var3 => {} 209 | } 210 | 211 | match x { 212 | // 我们忽略了所包含的内容,所以这样没问题 213 | &Var1(..) => {} 214 | // 其他分支没有变化 215 | &Var2 => {} 216 | &Var3 => {} 217 | } 218 | } 219 | ``` 220 | 221 | 在任何一种方法中,我们不涉及任何嵌套的数据,所以没有一项是移动的。在第一种方法中,即使`x`是被引用的,但我们没有触碰到解引用的范围的内核(即,匹配表达式)所以没有东西会被遗忘。我们也不把这个值(即,绑定的`*x`到一个变量),所以我们也不会移动对象整体。 222 | 223 | 我们可以在第二个match中取的任何一个变体的引用,但不能在解引用的版本。所以,在第二种方法中,用一个`a @ &Var2 = > {}`替换第二个分支是可以的,因为(`a`是一个引用),但是第一种方法下的我们不能写`a @ Var2 => {}`因为这将意味着移动`*×`到`a`。我们可以写`ref a @ Var2 => {}`(在这里`a`也是一个引用),虽然这不是一个你经常看到的结构。 224 | 225 | 但是,如果我们要使用嵌套在`Var1`中的数据该咋办?我们不能写: 226 | 227 | ```rust 228 | match *x { 229 | Var1(y) => {} 230 | _ => {} 231 | } 232 | ``` 233 | 234 | 或者 235 | 236 | ```rust 237 | match x { 238 | &Var1(y) => {} 239 | _ => {} 240 | } 241 | ``` 242 | 243 | 因为在这两种情况下,它都意味着移动`x`的一部分到`y`。我们可以用`ref`关键词来获得一个引用指向`Var1`中的数据:`&Var1(ref y) => {}`。这样可以,因为现在我们不在任何地方解引用,因此不移动`x`的任何部分。相反,我们正在创建一个指向`x`内部的指针。 244 | 245 | 或者,我们可以解构盒子(这种匹配将到达三级深度):`&Var1(box y) => {} `。同样,这样可以,因为`int`具有复制语义,并且`y`是`Box`内的一个`int`的复制(这是`inside`借用参考)。因为`int`已经具有复制语义,我们不需要移动`x`的任何的一部分。我们还可以创建一个引用到int而不是复制:`&Var1(box ref y) => {}`。再次,这样可以,因为我们不做任何解引用,因此不需要移动`x`的任何部分。如果盒子内容里有移动语义,那么我么不能写`&Var1(box y) => {}`,我们将被迫使用引用版本。我们也可以使用类似技术与第一种方法来匹配,这看起来一样,但没有第一个`&`。例如,`Var1(box ref y) => {}`。 246 | 247 | 现在让我们变得更加复杂。假设你想匹配一对引用枚举值。现在我们不能用第一种方法: 248 | 249 | ```rust 250 | fn bar(x: &Enum2, y: &Enum2) { 251 | // 错误,x和y正在被移动 252 | // match (*x, *y) { 253 | // (Var2, _) => {} 254 | // _ => {} 255 | // } 256 | 257 | // 可以 258 | match (x, y) { 259 | (&Var2, _) => {} 260 | _ => {} 261 | } 262 | } 263 | ``` 264 | 265 | 第一种方法是非法的,因为被匹配的值是通过对`x`和`y`解引用来创建的,然后移动他们到一个新的元组对象。所以在这种情况下,只有第二种方法适用。当然,你仍然必须遵循上述规则,以避免移动`x`和`y`的部分内容。 266 | 267 | 如果你最终只能得到一个数据的引用,但你需要值本身的话,除了复制这些数据外,你别无选择。通常利用`clone()`。如果数据没有实现克隆,你将需要进一步解构来手动复制或实现克隆自己。 268 | 269 | 如果我们没有一个指向移动语义的值的引用,只有值本身。那么移动是可以的,因为我们知道没有人有一个引用指向这个值(编译器确保如果他们这样做,我们就不能使用该值)。对于例子 270 | 271 | ```rust 272 | fn baz(x: Enum2) { 273 | match x { 274 | Var1(y) => {} 275 | _ => {} 276 | } 277 | } 278 | ``` 279 | 280 | 还有一些事情要知道。首先,你只能移动一个地方。在上面的例子中,我们正在移动`x`的一部分到`y`中,我们会忘记其它部分。如果我们写`a @ Var1(y) => {}`我们会试图移动`x`所有的一切到`a`,并且部分`x`到`y`。这是不允许的,一个这样的分支是非法的。做一个`a`或`y`参考(使用`ref a`,等)也不是可选项,那么我们会有上面描述的问题,我们移动同时保持一个引用。我们可以对`a`和`y`做两个引用,那么这样我们可行 - 也没有移动,所以`x`仍然完整,并且我们有指针指向它的整体和它的一部分。 281 | 282 | 类似地(或更常见),如果我们有多个嵌套的变体数据,我们不能以一个数据为基准,然后移动另一个数据。例如,如果我们有一个`Var4`声明为`Var4(Box, Box)`我们可以有一个匹配的分支 283 | 引用两(`Var4(ref y, ref z) => {}`)或配合分支同时移动(`Var4(y,z) => {}`)但你不能有一个匹配的分支,部分移动而部分引用(`Var4(ref y,z) => {} `)。这是因为部分移动 284 | 仍然破坏了整个对象,所以引用将无效。 -------------------------------------------------------------------------------- /r4cpp - Graphs and arena allocation.md: -------------------------------------------------------------------------------- 1 | * 原文: 2 | * https://github.com/nrc/r4cppp/blob/master/graphs/README.md 3 | * 翻译者: Scott Huang 4 | * 翻译日期: Sep 19, 2015 于厦门 5 | 6 | 7 | # Graphs and arena allocation 图与竞技场分配 8 | 9 | (请注意,你可以通过[下载该目录](https://github.com/nrc/r4cppp/tree/master/graphs)和`cargo run`运行本章例子)。 10 | 11 | Rust严格的使用期lifetime和可变性mutability的要求导致构建图的方法有点笨拙。对象图在面向对象程序设计中非常常见。在本教程中,我将要详谈几个不同的实现方法。我的首选方法是使用竞技场arena分配,并稍微使用一些显式使用期(lieftime)的高级用法。我会在结束的时候讨论几个Rust潜在的特性,这些特性也许会使得尝试变容易一点。 12 | 13 | 一个[图](http://en.wikipedia.org/wiki/Graph_%28abstract_data_type%29)是一个 14 | 节点的集合,其中一些节点有边连接。图是列表和树的泛化。每个节点可以有多个孩子和多个父母(我们通常谈论的是边接进入或伸出去一个节点,而不是父母/孩子)。图可以表示为邻接表或邻接矩阵。前者基本上是一个节点对象代表图的每个节点,其中每个节点对象保留其相邻节点的列表。邻接矩阵是一个布尔值矩阵,指示是否有边从行节点到列节点。我们将只讨论邻接表表示,邻接矩阵所具有的问题和邻接表有所不同,并且它们并非是Rust独有的问题。 15 | 16 | 有两个本质正交问题:如何处理图的使用期和如何处理图的可变性。 17 | 18 | 第一个问题本质上归结为用什么样的指针来指向图中的其他节点。由于图形类的数据结构是递归的( 19 | 类型是递归的,即使数据不是递归的)我们也被迫使用某种指针,而不是一个完全基于值的结构。因为图可以循环的,但Rust的所有权不可循环,我们不能使用的`Box`作为我们的指针类型(正如我们可能做的树一样的数据结构或链表)。 20 | 21 | 没有图是真正不可变的。因为可能有循环,图不能在单语句中创建。因此,至少,图在初始化阶段必须是可变的。Rust存在一个普遍的本质规律,即所有的指针必须要么是独享的,要么是不可变的。图的边必须是可变的( 22 | 至少在初始化期间),可以有一个以上的边连接到任何节点,因此,没有边保证是独一无二的。所以我们要做一点高级的处理绕过Rust的可变性限制。 23 | 24 | 一种解决方案是使用可变的裸指针(`*mut Note`)。这是最灵活的做法,也是最危险的。你必须在没有类型系统的任何帮助下手动管理所有的使用期。你可以用这种方式做出非常灵活和高效的数据结构,但你必须非常小心。这种尝试使用一种生猛的方法来处理使用期和可变性问题,本质上忽略了所有Rust的好处-你没有办法在这里得到编译器的帮助(这也不是特别便利,因为裸指针不自动解引用)。因为使用Rust裸指针处理一张图和使用C++处理一张图区别不大,所以, 我不会在这里讨论。 25 | 26 | 您对使用期管理的可选项是引用计数(共享所有权,使用的是`Rc<...>`)或竞技场分配(所有节点都有被一个舞台所管理的相同的使用期;使用租借的引用`&...`)。前者更灵活(你可以拥有从图形外部的到个别点的任何生命周期的引用),后者是更好的其他方式。 27 | 28 | 至于管理可变性,您可以使用`RefCell`,即,利用Rust的动态、内部可变性设施,或者你可以自己管理可变性(在这种情况下,你必须使用`UnsafeCell`来和编译器沟通内部可变性)。前者更安全,后者更有效,都不是特别符合人体工程学。 29 | 30 | 请注意,如果你的图形可能有循环,那么如果你使用的是`Rc`,需要进一步的动作来打破循环而不泄漏内存。因为Rust没有循环`Rc`指针的集合,如果你的图有循环,引用计数将永远不会降到零,并且图形将不会释放。当你知道这个图应该被销毁,你可以在图中使用`Weka`弱指针解决这个问题或者手工打断循环。前者更可靠。我们也不再这里讨论,在我们的例子中,我们就是泄漏内存。使用租借引用和竞技场分配并没有这个问题,因此在这方面具有优势。 31 | 32 | 我会用一个非常简单的例子来比较不同的方法。我们会有一个`Node`对象代表图中的一个节点,它将有一些 33 | String数据(代表一些更复杂的数据有效载荷)和`Vec`相邻节点(`edges`)。我们会有一个`init`函数来创建一个简单图节点,和一个`traverse`函数来遍历预先排序、深度优先的图。我们将用这个方法来打印每个节点的负载图。最后,我们将有一个`Node::first`的方法,它返回一个引用第一个相邻节点的`self`节点和功能`foo`,打印单个节点的负载。这些函数代替更为复杂的操作,涉及到图形的节点内部操作。 34 | 35 | 我将尽可能丰富信息而避免让你觉得无趣,我将覆盖2个组合的可能性:引用计数和`RefCell`,还有竞技台分配和`UnsafeCell`。我会把另外两个组合留作练习。 36 | 37 | ## `Rc>` 38 | 39 | 参考 [完整的例子](https://github.com/nrc/r4cppp/blob/master/graphs/src/rc_graph.rs). 40 | 这是更安全的选择,因为没有不安全的代码。它也是最没有效率和最不人体工程学的选择。然而它是相当灵活的,图的节点可以很容易地在图外重复使用,因为他们是引用计数的。我会建议这种方法,如果你需要一个完全可变的图,或者你需要节点独立存在于图。 41 | 42 | 节点结构看起来像: 43 | 44 | ```rust 45 | struct Node { 46 | datum: &'static str, 47 | edges: Vec>>, 48 | } 49 | ``` 50 | 51 | 创建一个新的节点不是太糟糕:`Rc::new(RefCell::new(Node { ... }))`。在初始化期间添加一条边,你必须租借起始节点为可变的,并且 52 | 克隆终端点到边Vec(这克隆指针,增加引用计数,而不是克隆实际的节点)。例如, 53 | 54 | ```rust 55 | let mut mut_root = root.borrow_mut(); 56 | mut_root.edges.push(b.clone()); 57 | ``` 58 | 59 | `RefCell`的动态确保我们不会在写入它的时候正在读取或者写入节点。 60 | 61 | 当你访问一个节点,你必须使用`.borrow()`来租借`RefCell`。我们的`first`第一个方法不得不返回一个引用计数的指针,而不是租借的引用,所以调用者的`first`也必须租借: 62 | 63 | ```rust 64 | fn first(&self) -> Rc> { 65 | self.edges[0].clone() 66 | } 67 | 68 | pub fn main() { 69 | let g = ...; 70 | let f = g.first(); 71 | foo(&*f.borrow()); 72 | } 73 | ``` 74 | 75 | 76 | ## `&Node` and `UnsafeCell` 77 | 78 | 参考 [完整的例子](https://github.com/nrc/r4cppp/blob/master/graphs/src/ref_graph.rs). 79 | 80 | 在这种方法中,我们使用租借的引用作为边。这很好,符合人体工程学,并让我们用`regular`的Rust库,主要操作租借的引用(注意,关于Rust引用计数对象的一个很好的事是,他们和使用期系统配合得很好。我们可以创建一个租借的引用直接到`Rc`(并安全)参考的数据。在前面的例子,这`RefCell`阻止我们这样做,但`Rc` / `UnsafeCell `方法应该允许它)。 81 | 82 | 解构也被正确地处理过--唯一的约束就是所有的节点必须在同一时间被销毁。节点的销毁和分配 83 | 都使用同一个竞技台。 84 | 85 | 另一方面,我们需要使用相当多的明确的使用期。不幸的是,我们无法从这里得到使用期省略的好处。在最后一节我将讨论一些可以使事情变好的语言的未来方向。 86 | 87 | 在构建过程中我们将改变我们的节点,也会改变多个引用。这不可能发生在Rust的安全代码,所以我们必须在一个不安全的`unsafe`块里初始化。既然我们的节点是可变的和多参考,我们必须使用一个` UnsafeCell`和Rust编译器打交道,它不能依赖它通常的不变量。 88 | 89 | 什么时候这种方法可行?图必须仅能在初始化时可改变。此外,我们要求图中的所有节点有相同的使用期(我们可以放松这些限制,允许后来添加节点,只要他们能在同一时间被销毁)。类似地,当节点可变时我们可以依赖于更复杂的不变量,但把事情简单化需要代价,因为程序员需要在这些方面自己负责安全。 90 | 91 | 竞技场分配是一个内存管理技术,其中一组对象有同样的使用期并可以在同一时间段释放。舞台是一个对象 92 | 负责分配和释放内存。由于一次性地分配和释放大块内存(而不是一个个地分配),竞技场的分配是非常有效的。通常,所有的对象都是从一个连续的内存块的分配,当你遍历图时,提高了缓存一致性。 93 | 94 | Rust中,舞台上的分配是由crate [libarena](https://doc.rust-lang.org/arena/index.html)支持,在整个编译器中使用。有2种类型竞技场 - 类型化和非类型化。前者是更有效和更容易使用,但只能 95 | 分配单一类型对象。后者更灵活,可以分配任何对象。竞技场分配的对象都有相同的使用期,这是一个 96 | 竞技场对象参数。类型系统确保竞技场分配的对象的引用不能比舞台本身活得更长。 97 | 98 | 我们的节点结构现在必须包括图的使用期,`'a`。我们包装我们`Vec`相邻节点在一个`UnsafeCell`,表明我们将改变它即使它应该是不可改变的: 99 | 100 | ```rust 101 | struct Node<'a> { 102 | datum: &'static str, 103 | edges: UnsafeCell>>, 104 | } 105 | ``` 106 | 107 | 我们的新函数还必须使用这个使用期,必须作为一个竞技场分配的参数: 108 | 109 | ```rust 110 | fn new<'a>(datum: &'static str, arena: &'a TypedArena>) -> &'a Node<'a> { 111 | arena.alloc(Node { 112 | datum: datum, 113 | edges: UnsafeCell::new(Vec::new()), 114 | }) 115 | } 116 | ``` 117 | 118 | 我们用竞技场来分配节点。图的使用期来自指向arena的引用的使用期,所以arena一定要从覆盖图的使用期的范围中传递进来。对于我们的例子,这意味着我们把它传递到`init`方法。(可以想象一个类型系统的扩展允许在其词汇范围以外创建值,但 119 | 近期没有计划添加这样的东西)。当竞技场超出范围,整个图被销毁(Rust的类型系统确保我们不能在那一点上还保有对图形的引用)。 120 | 121 | 添加一个边看起来有点不同: 122 | 123 | ```rust 124 | (*root.edges.get()).push(b); 125 | ``` 126 | 127 | 我们本质上是在做明显的 `root.edges.push(b)`推一个节点(`b`)到边的列表。然而,由于`edges`包装在一个`UnsafeCell`,我们要在其上调用`get()`。这给了我们一个可变的裸指针到边(`*mut 128 | Vec<&Node>`),这让我们改变`edges`。然而,它也需要我们手动解引用指针(裸指针不会自动解引用),因此 129 | 需要`(*...)`。最后,对裸指针解引用是不安全的,整段都要被包裹在一个不安全的区域里。 130 | 131 | `traverse`遍历的有趣部分是: 132 | 133 | ```rust 134 | for n in &(*self.edges.get()) { 135 | n.traverse(f, seen); 136 | } 137 | ``` 138 | 139 | 我们遵循先前的模式来获取边的列表,这需要一个不安全块。在这种情况下,我们知道,这是安全的,因为我们必须是延后初始化,因此不会有改变。 140 | 141 | 再次,`first`方法遵循相同的模式得到的`edges`列表。又必须是在一个不安全的块。然而,在对比图 142 | 使用`Rc>`,我们可以返回一个明确的租借引用到节点。那很方便。我们可以认为不安全的块是安全的因为我们没有改变,并且我们延后初始化。 143 | 144 | ```rust 145 | fn first(&'a self) -> &'a Node<'a> { 146 | unsafe { 147 | (*self.edges.get())[0] 148 | } 149 | } 150 | ``` 151 | 152 | ### Future language improvements for this approach 153 | 154 | 我相信竞技场分配和使用租借引用是一个重要的Rust模式。我们应该在语言层面做的更多,使这些模式更安全、更容易使用。我希望正在开发的[allocators](https://github.com/rust-lang/rfcs/pull/244)使得arena变得更加符合人体工程学。我看到了其它三个改进: 155 | 156 | #### Safe initialisation 安全初始化 157 | 158 | 在面向对象的世界中有大量关于确保可变性仅发生在初始化期间的机制的研究。Rust在这方面如何工作的更精确是一个公开的研究课题,似乎我们需要表现一个指针是可变的,但不是唯一的,且必须限制在某个范围。超出该范围之外的任何现有的指针将成为正常的租借引用,即,不可变的*或*唯一的。 159 | 160 | 这样一个方案的优点是,在初始化期间我们有办法在初始化期间表达可变共同模式,然后不变。它也依赖于不变量的是,当单个个体对象是乘法拥有的,聚合(在这种情况下是一个图)是唯一拥有的。我们应该可以采取引用和`UnsafeCell`和方法,没有`UnsafeCell`s和不安全块,使这种方法更符合人体工程学和更安全。 161 | 162 | 苏黎世联邦理工大学的Alex Summers和Julian Viereck正在进一步调查这个。 163 | 164 | #### Generic modules 泛型模块 165 | 166 | 任何特定的图的`图的使用期`是恒定的。重复的使用期仅是样板代码。可以让其更便利的一个方法是允许图模块可以被使用期参数化,所以这就不需要被添加到每一个结构,impl,和函数。一个图的使用期仍然需要在模块外指定,但希望接口可以照顾大多数的使用(因为函数调用是这样的)。 167 | 168 | 请参考[ref_graph_generic_mod.rs](https://github.com/nrc/r4cppp/tree/master/graphs/src/ref_graph_generic_mod.rs)来看它大概是啥。 169 | (我们也应该能够使用安全初始化(上述建议的)来删除不安全的代码)。 170 | 171 | 又见[RFC issue](https://github.com/rust-lang/rfcs/issues/424)。 172 | 173 | 此功能将大大减少的引用和`UnsafeCell`方法的语法开销。 174 | 175 | #### Lifetime elision 使用期缺省 176 | 177 | 我们现在允许程序员忽略一些使用期在函数签名来提高工效学。一个原因是图的`&Note`方法有点丑陋,因为它没有受益于任何的使用期省略规则。 178 | 179 | Rust的一种常见的模式是数据结构用一个共同的使用期。这种数据结构的引用引起的类型如`&'a Foo<'a>`,例如`&'a Node<'a>` 在图的例子中。在这种情况下,有一个好的省略规则将起帮助。但我还不知道该如何工作。 180 | 181 | 用泛型模块的例子看,它看起来不像需要我们非常多地扩展使用期的省略规则(实际上我不知道 182 | `Node::new`是否可以在没有给定使用期下工作,但即使不是的话,它似乎也是一个相当小的扩展便可以使它工作)。我们可能要增加一些新的规则允许省略泛型模块使用期,如果他们是唯一在范围(除`'static`外),但我不知道它如何与多个in-scope的使用期合作(见`foo`和`init`函数,例如)。 183 | 184 | 如果我们不加泛型模块,还可以添加一个省略规则,特别针对`&'a Node<'a>`目标,只是还不知道该怎样处理。 185 | 186 | * 译者注:这一篇比较难,我自己还需要时间搞透源代码,谢谢。 187 | -------------------------------------------------------------------------------- /r4cpp - Rc and raw pointers.md: -------------------------------------------------------------------------------- 1 | 原文: https://github.com/nrc/r4cppp/blob/master/rc%20raw.md 2 | 翻译者: Scott Huang 3 | 翻译日期: Agust 29, 2015 于厦门 4 | 5 | # Reference counted and raw pointers | 引用计数和原始指针 6 | 7 | TODO: 拟加讨论自定义指针和Defer特质(也许以后,不在这里) 8 | 9 | 到目前为止我们已经涵盖了所有权指针(或者 unique 独特指针)和借贷的指针。所有权指针是非常 10 | 类似C++中的new std::unique_ptr,而借贷指针类似你在C++中通常使用的“默认”的指针或引用。Rust还有一些罕见的指针在库里或者内建在语言里。这些大多是类似于C++中你已经习惯的的各种智能指针。 11 | 12 | 这篇文章花了一段时间来写,但我还是不喜欢它。有许多零碎资料,有些是我导致的,有些是Rust本身导致的。我希望有些会在以后的文章中得到改善,而有些则随着语言的发展而会变得更好。如果你在学习Rust,你可能甚至想跳过这个内容,现在,希望你不需要它。它在这里只是为了续上前几篇关于指针类型的文章。 13 | 14 | 你可能会感觉Rust有很多指针类型,但它是和C++非常相似的,一旦你想到了C++库里各种各样的智能指针。然而,在Rust里,你更有可能在你刚开始学习语言时就见到这些指针。因为Rust指针有编译器支持,你在使用它们时会有更少的机会出现错误。 15 | 16 | 我不打算像讨论独特指针或借贷指针那样涉及到许多细节,因为,坦率地说,他们并不重要。我可能稍后会再回来讨论更多细节。 17 | 18 | ## Rc 引用计数 19 | 20 | 引用计数指针是Rust标准库的一部分。他们在`std::rc`模块(我们将很快涉及到模块。模块是在实例使用`use`的原因)。一个指向类型对象`T`的引用计数的指针具有类型`Rc`。创建引用计数指针使用静态方法(现在你可以认为类似于C++,但我们后面会看到有点不同)-`Rc::new(...)`需要一个值来创建指向的指针。这种构造方法遵循了Rust的一般移动/复制的语义 21 | (如我们讨论的所有权)-在任何情况下,在调用Rc::new后,您将只能通过指针访问该值。 22 | 23 | 正如其他类型的指针,`.`算子做了所有你需要的解引用动作。你可以使用`*`手动解引用。 24 | 25 | 传递引用计数的指针需要使用`clone`(`克隆`)方法。这有点糟糕,希望我们会解决,但(可悲的)是不确定。你可以 26 | 用(借用的)指针指向值,所以希望你不需要太经常克隆。Rust的类型系统保证了引用计数的变量在任何引用过期之前不会被删除。采用一个引用的额外好处是它不需要递增或递减ref计数,这样的性能会更好(尽管,区别也许很微小,因为Rc的对象局限于一个单独的线程,所以引用计数操作不必是原子的)。正如在C++,你也可以用对Rc指针取引用。 27 | 28 | 一个Rc的例子: 29 | ```rust 30 | use std::rc::Rc; 31 | 32 | fn bar(x: Rc) { } 33 | fn baz(x: &i32) { } 34 | 35 | fn foo() { 36 | let x = Rc::new(45); 37 | bar(x.clone()); // 增加计数 38 | baz(&*x); // 不增加计数 39 | println!("{}", 100 - *x); 40 | } // 当到达范围时,所有的计数指针(Rc pointers)会消失,所以ref-count == 0, 并且以前分配的内存会被删除。 41 | ``` 42 | 43 | 引用计数的指针总是不变的。如果你想要一个可变的引用计数对象,你需要使用RefCell(或Cell)来包裹一个`Rc`。 44 | 45 | ## Cell and RefCell | 细胞和引用细胞 46 | 47 | Cell和RefCell一种允许你“欺骗”的可变性规律的结构(structs)。如果你第一次涉及Rust数据结构及 48 | 他们怎样处理可变性的话,将很难向你解释清楚它,所以我要稍后回到这些略棘手的对象。现在,你应该知道,如果你想要一个可变的,引用计数对象,你需要一个Cell(细胞)或RefCell包裹一个Rc。作为第一 49 | 尝试,你可能需要为了原始数据而需要Cell,为了对象而需要RefCell,且移动语义。因此,对一个可变的,引用计数的int你需要使用`Rc>`. 50 | 51 | ## \*T - raw pointers | 原始指针 52 | 53 | 最后,Rust有两种原始指针(又名不安全的指针):`*const T`是一个不可变的原始指针,`*mut T`是一个可变的原始指针。他们是使用`&`或`&mut`来创建(你也许需要指定类型来获取`*T`而不是一个`&T`因为`&`操作符可以创建一个借贷指针或者一个原始指针)。原始指针是Rust里面唯一的一种可以拥有null值的指针。原始指针没法自动解引用(所以,为了调用一个方法,你不得不写成`(*x).foot()`,并且没有自动引用。最重要的限制是他们不能在一个unsafe块的外面被解引用(且因此不能被使用)。在普通的Rust代码中,你只可以传递他们。 54 | 55 | 那么,什么是不安全的代码?Rus有很强的安全保障,而且他们很少阻止你做你需要做的事。因为Rust是一种系统语言,它必须在必要的时候能够做任何事情,有时这意味着所做事情的编译器无法验证是安全的。要做到这一点,Rust有不安全的块的概念,用“unsafe”的关键字做标志。在不安全的代码中,你可以做不安全的事-用原始指针,数组索引没有边界检查,通过FFI调用另外一种语言写的代码,或转换变量。很显然,相对于普通的代码,你需要很小心的写不安全的代码。事实上,你应该很少写不安全码。它主要是用在非常小的库中,而不是在客户代码中。在不安全的代码中,你必须做向你通常在C++中所做的那样来确保安全。此外,您必须手动确保您保持编译器通常会强制执行的不变量。不安全的块允许你手动确保Rust的不变量,它不允许你打破那些不变量。如果你做了,你会在安全和不安全的代码中引入错误。 56 | 57 | 使用原始指针的示例: 58 | 59 | ```rust 60 | fn foo() { 61 | let mut x = 5; 62 | let x_p: *mut i32 = &mut x; 63 | println!("x+5={}", add_5(x_p)); 64 | } 65 | 66 | fn add_5(p: *mut i32) -> i32 { 67 | unsafe { 68 | if !p.is_null() { // 注意这个 * -指针没有自动解引用,所以这是一个实现在*i32的方法,而不是i32 69 | 70 | *p + 5 71 | } else { 72 | -1 // 不是一个推荐的错误处理策略 73 | } 74 | } 75 | } 76 | ``` 77 | 78 | 我们关于Rust指针的讨论在这里结束。下次我们要休息一下,不谈指针而谈Rust的数据结构。但是,我们在后面的文章继续谈借贷指针。 -------------------------------------------------------------------------------- /r4cpp - Unique Pointers.md: -------------------------------------------------------------------------------- 1 | 原文: https://github.com/nrc/r4cppp/blob/master/unique.md 2 | 翻译者: Scott Huang 3 | 翻译日期: Agust 25, 2015 于厦门 4 | 5 | # Unique pointers | 独一无二的指针(所有权指针) 6 | 7 | Rust是一种系统级的语言,因此必须给您访问原始内存的能力。它通过指针(如在C++里)来实现。指针是Rust和C++有很大区别的地方之一,语法和语义上都有不同。Rust通过指针类型检查来确保内存安全。这是它的相对其他语言的一个主要优点。虽然该类型系统有点复杂,但是您可以获得内存安全和裸金属性能。 8 | 9 | 我曾打算在一篇文章中涵盖所有的Rust的指针,但我认为这个主题太大。所以这篇文章将只包括一个独特的指针(unique pointers)—其他类型将在后续的博文中谈论。 10 | 11 | 第一,不用指针的例子: 12 | 13 | ```rust 14 | fn foo() { 15 | let x = 75; 16 | 17 | // ... do something with `x` ... 操作`x` 18 | } 19 | ``` 20 | 21 | 当我们到达`foo`函数末尾,`x`超出范围(在Rust里和C++一样)。那意味着变量不可以再被访问,这个变量所占的内存可以被回收重复使用。 22 | 23 | 在Rust里,对每一个类型`T`,我们可以写`Box`所有权(也叫独一的)指针来指向它。我们用`Box::new(...)`来分配堆空间,并用给定的值来初始化这块空间。 这类似于c++语言的`new`。 24 | 例如, 25 | 26 | ```rust 27 | fn foo() { 28 | let x = Box::new(75); 29 | } 30 | ``` 31 | 32 | 这里`x`是一个指向一个堆位置的指针,其中包含的值是75。`x`拥有类型`Box`;我们可以写成`let x: Box = Box::new(75);`。这类似于C++语言的`int* x = new int(75);`。不像在C++,Rust会为我们整理内存,这样就不需要调用“释放”或“删除”了。独一的指针的行为类似于值-当变量超出范围时被删除。在我们的示例中,在函数`foo`结束后,`x`不能够被继续访问,并且`x`指向的内存可以被重复使用。 33 | 34 | 所有权指针使用`*`来解引用,和C++一样。例如, 35 | 36 | ```rust 37 | fn foo() { 38 | let x = Box::new(75); 39 | println!("`x` points to {}", *x); 40 | } 41 | ``` 42 | 43 | 与Rust里的原始类型类似,所有权指针和和他所指向的数据默认是不变的。与C不同,你不能有一个可变的(独一的)指针来指向不变的数据,反之亦然。数据的可变性,跟从指针。 44 | 例如, 45 | 46 | 47 | ```rust 48 | fn foo() { 49 | let x = Box::new(75); 50 | let y = Box::new(42); 51 | // x = y; // Not allowed, x is immutable. 不允许,x是不变的 52 | // *x = 43; // Not allowed, *x is immutable. 不允许, *x是不变的 53 | let mut x = Box::new(75); 54 | x = y; // OK, x is mutable. 可以,x是可变的 55 | *x = 43; // OK, *x is mutable. 可以,*x是可变的 56 | } 57 | ``` 58 | 59 | 所有权指针可以从一个函数中返回,并继续存在。如果他们返回,然后他们的内存不会被释放,即,没有悬空的Rust指针。内存不会泄露。然而,它最终会超出范围,然后它将被释放。例如, 60 | 61 | ```rust 62 | fn foo() -> Box { 63 | let x = Box::new(75); 64 | x 65 | } 66 | 67 | fn bar() { 68 | let y = foo(); 69 | // ... use y ... 使用y 70 | } 71 | ``` 72 | 73 | 在这里,内存在`foo`里面初始化,回到`bar`。`x`从`foo`返回并存储在`y`,所以不能删除。在“bar”结束后,“y”超出范围,然后内存被回收。 74 | 75 | 所有权指针是独一无二的(也称为线性),因为在任何时候只有一个(所有权)指针指向同一内存。这是通过移动语义实现的。当一个指针指向某个值时,任何先前的指针都不能继续访问。例如, 76 | 77 | ```rust 78 | fn foo() { 79 | let x = Box::new(75); 80 | let y = x; 81 | // x can no longer be accessed x 不再能被访问 82 | // let z = *x; // Error. 错误 83 | } 84 | ``` 85 | 86 | 同样,如果一个所有权指针被传递到另一个函数或存储在字段,它不再可以被访问: 87 | 88 | ```rust 89 | fn bar(y: Box) { 90 | } 91 | 92 | fn foo() { 93 | let x = Box::new(75); 94 | bar(x); 95 | // x can no longer be accessed x不再能被访问 96 | // let z = *x; // Error. 错误 97 | } 98 | ``` 99 | 100 | Rust的所有权指针类似C++的 `std::unique_ptr`s。 在Rust里,如同在C++里,只能有一个所有权指针指向一个值,当指针超出范围时该值被删除。Rust的大部分检查都发生在静态时而不是在运行时。所以,在C++访问一个独一的指针,其值移动将导致一个运行时错误(因为它将是空的)。在Rust里,这产生一个编译时错误,保证在运行时不出错。 101 | 102 | 稍后我们会看到Rust有可能创建其他类型的指针指向一个独特指针的值。这是类似于C++。然而,在C++里,当你在运行时拥有一个指向已释放内存的指针将导致错误。但那不可能在Rust(我们会看到,当我们谈到Rust的其他指针类型时)发生。 103 | 104 | 如上面所示,所有权指针必须解引用才能使用他们的值。然而,方法调用会自动解引用,这样就不需要一个`->`操作符或使用'*'的方法调用。用这种方法,Rust指针有点同时类似于C++指针和引用。例如, 105 | 106 | ```rust 107 | fn bar(x: Box, y: Box>>>) { 108 | x.foo(); 109 | y.foo(); 110 | } 111 | ``` 112 | 113 | 假设类型`Foo`有方法`foo()`,这些表达式都OK的。 114 | 用一个存在的值调用`Box::new()`,它并不会获取引用,它拷贝值。所以, 115 | 116 | ```rust 117 | fn foo() { 118 | let x = 3; 119 | let mut y = Box::new(x); 120 | *y = 45; 121 | println!("x is still {}", x); 122 | } 123 | ``` 124 | 125 | 在一般情况下,Rust具有移动而不是复制的语义(如上所见unique指针)。原始类型具有复制的语义,所以在上面的例子中,值3是复制的,但是对于更为复杂的值,它将被移动。我们将在后面进行更详细的讨论。 126 | 127 | 有时在编程时,我们需要一个以上的引用指向一个值。对于这个,Rust有借贷指针。我将在下一篇文章中包括这些。 -------------------------------------------------------------------------------- /string-vs-str-in-rust-functions.md: -------------------------------------------------------------------------------- 1 | * 原文: 2 | * http://hermanradtke.com/2015/05/03/string-vs-str-in-rust-functions.html 3 | * Written by Herman J. Radtke III on 03 May 2015 4 | * 翻译者: Scott Huang 5 | * 翻译日期: Sep 12, 2015 于厦门 6 | 7 | #String vs &str in Rust functions 8 | 9 | 这篇文章写给那些不得不使用`to_string()`而让程序通过编译的沮丧的人。对于那些不太理解为什么Rust有两个字符串类型`String`和`&str`的人,我希望给你一些帮助。 10 | 11 | ##Functions That Accept A String | 接受一个String的函数 12 | 13 | 我想讨论如何构建接受字符串的接口。我是一个狂热的超媒体迷,我痴迷于设计使用方便的界面。让我们从接受一个字符串的方法开始吧。我们的调研显示`std::string::String`是一个很好的选择。 14 | 15 | ```rust 16 | fn print_me(msg: String) { 17 | println!("the message is {}", msg); 18 | } 19 | 20 | fn main() { 21 | let message = "hello world"; 22 | print_me(message); 23 | } 24 | ``` 25 | 这导致一个编译器错误: 26 | ```rust 27 | 期望 `collections::string::String`, 28 | 发现 `&str` 29 | ``` 30 | 所以一个字符串字面量是`&str`类型,它不与`String`类型兼容。我们可以改变message类型为`String`, `let message = "hello world".to_string();` 可以编译成功。这可行,但它是类似于使用`clone()`来绕过所有权/借贷错误。相反的,这里有三个理由来改变print_me来允许接受`&str`: 31 | 32 | * `&`符号是一个引用类型,意味着我们借用这个变量。当print_me用完那个变量,所有权将返回给原来的拥有者。除非我们有很好的理由将message变量的所有权转移到我们的函数,不然我们应该选择借用。 33 | * 使用引用更有效。使用String的message意味着程序必须复制该值。使用一个引用时,如`&str`,不需要复制。 34 | * 一个String类型可以神奇地变成了一个&str类型当使用Defer特质和类型强制。这将使一个例子更有意义。 35 | 36 | ##Deref Coercion例子: 37 | 38 | 这个例子通过四种不同的方式创建strings,并且都可以成功调用print_me函数。使这一切可工作的关键是通过引用传值。我们通过`&String`传递,而不是通过把owned_string作为一个String传递给print_me。当编译器看到一个&String传递给一个采取&Str的函数,它强制`&String`转为`&Str`。这一强制同时也发生在引用计数和自动引用计数的字符串那里。String变量已经是一个引用,所以当调用print_me(string)函数时就没有必要使用&。知道了这一点,我们不再需要调用我们`.to_string()`来制造一些垃圾到我们的代码。 39 | 40 | ```rust 41 | fn print_me(msg: &str) { println!("msg = {}", msg); } 42 | 43 | fn main() { 44 | let string = "hello world"; 45 | print_me(string); 46 | 47 | let owned_string = "hello world".to_string(); 48 | // 或者String::from_str("hello world") 49 | print_me(&owned_string); 50 | 51 | let counted_string = std::rc::Rc::new("hello world".to_string()); 52 | print_me(&counted_string); 53 | 54 | let atomically_counted_string = std::sync::Arc::new("hello world".to_string()); 55 | print_me(&atomically_counted_string); 56 | } 57 | ``` 58 | 59 | 你也可以使用Deref来强制其他类型,如Vector。毕竟,String只是一个拥有8字节字符的vector。你可以参考[Deref coercions](http://doc.rust-lang.org/nightly/book/deref-coercions.html)来阅读更多内容。 60 | 61 | ##Introducing struct 62 | 63 | 在这一点上我们应该没有外来to_string()调用我们的功能。然而,当我们试图引入一个结构时我们会遇到了一些问题。用我们刚学过的,我们可以做一个象这样的结构: 64 | 65 | ```rust 66 | struct Person { 67 | name: &str, 68 | } 69 | 70 | fn main() { 71 | let _person = Person { name: "Herman" }; 72 | } 73 | ``` 74 | 我们得到错误: 75 | ```rust 76 | :2:11: 2:15 error: missing lifetime specifier [E0106] 漏掉声明使用期 77 | :2 name: &str, 78 | ``` 79 | 80 | Rust试图保证Person不能有活的更久的引用指向name。如果Person确实有这种指针的话,那么我们就面临程序崩溃的风险。Rust做的所有一切就是防止这种意外出现。所以让我们开始试着编译这个代码。我们需要指定一个使用期,或范围,然后Rust可以保证我们安全。传统的使用期说明符是`'a`。我不知道为什么人们选它,但让我们先这么着吧。 81 | ```rust 82 | struct Person { 83 | name: &'a str, 84 | } 85 | 86 | fn main() { 87 | let _person = Person { name: "Herman" }; 88 | } 89 | ``` 90 | 91 | 再次编译器,然后我们得到另外一个错误: 92 | ```rust 93 | :2:12: 2:14 error: use of undeclared lifetime name `'a` [E0261] 94 | :2 name: &'a str, 95 | ``` 96 | 让我们想想这个。我们知道我们想暗示Rust编译器,我们的Person结构不应该outlive name。所以,我们需要为Person结构声明使用期。一些搜索会告诉我们`<'a>`是用来声明使用期的语法。 97 | 98 | ```rust 99 | struct Person<'a> { 100 | name: &'a str, 101 | } 102 | 103 | fn main() { 104 | let _person = Person { name: "Herman" }; 105 | } 106 | ``` 107 | 现在编译通过!我们通常为我们的结构实现方法。让我们给我们的Person类加上一个greet函数。 108 | 109 | ```rust 110 | struct Person<'a> { 111 | name: &'a str, 112 | } 113 | 114 | impl Person { 115 | fn greet(&self) { 116 | println!("Hello, my name is {}", self.name); 117 | } 118 | } 119 | 120 | fn main() { 121 | let person = Person { name: "Herman" }; 122 | person.greet(); 123 | } 124 | ``` 125 | 126 | 我们现在得到错误: 127 | ```rust 128 | :5:6: 5:12 error: wrong number of lifetime parameters: expected 1, found 0 [E0107] 129 | :5 impl Person { 130 | ``` 131 | 132 | 我们Person结构有一使用期参数,所以我们的实现也应该有。我们声明`'a`使用期到Person的实现代码,就像`impl Person<'a>`。不幸的是,编译器给了一个让我们疑惑的错误提示: 133 | 134 | ```rust 135 | :5:13: 5:15 error: use of undeclared lifetime name `'a` [E0261] 136 | :5 impl Person<'a> { 137 | ``` 138 | 为了让我们声明使用期,我们需要在实施`impl <'a> Person {`后马上指定使用期。再次编译,我们得到错误: 139 | ```rust 140 | :5:10: 5:16 error: wrong number of lifetime parameters: expected 1, found 0 [E0107] 141 | :5 impl<'a> Person { 142 | ``` 143 | 现在我们回到正轨。让我们加回我们的使用期参数到Person的实现,就像 `impl <'a> Person<'a> {`。现在我们的程序通过编译。这里是完整的工作代码: 144 | 145 | ```rust 146 | struct Person<'a> { 147 | name: &'a str, 148 | } 149 | 150 | impl<'a> Person<'a> { 151 | fn greet(&self) { 152 | println!("Hello, my name is {}", self.name); 153 | } 154 | } 155 | 156 | fn main() { 157 | let person = Person { name: "Herman" }; 158 | person.greet(); 159 | } 160 | ``` 161 | ##String or &str In struct 162 | 163 | 现在的问题是是否使用一个String或一个&str在你的结构中。换句话说,什么时候我们要使用一个引用到结构中的另一个类型?我们应该使用一个引用如果我们的结构不需要拥有变量的所有权。这个概念可能有点模糊,但让我用一些规则来回答。 164 | 165 | * 我是否需要使用变量在我的结构以外? 166 | 这里是一个做作的例子: 167 | 168 | ```rust 169 | struct Person { 170 | name: String, 171 | } 172 | 173 | impl Person { 174 | fn greet(&self) { 175 | println!("Hello, my name is {}", self.name); 176 | } 177 | } 178 | 179 | fn main() { 180 | let name = String::from_str("Herman"); 181 | let person = Person { name: name }; 182 | person.greet(); 183 | println!("My name is {}", name); // 所有权移动错误 184 | } 185 | ``` 186 | 我应该在这里使用一个引用,因为我以后还需要使用这个变量。这是一个真实的例子[rustc_serialize](https://github.com/rust-lang/rustc-serialize/blob/master/src/json.rs#L552)。`Encoder`结构不需要拥有`std::fmt::Write`实现中的writer变量,只是借用它一会儿。事实上,String实现Write。在这个例子中,使用[encode](https://github.com/rust-lang/rustc-serialize/blob/master/src/json.rs#L372)函数,`String`类型的变量传递给编码器,然后返回`encode`到编码的调用方。 187 | 188 | * 我的类型是大的吗?如果类型是大的,那么通过引用传递将节省不必要的内存开支。请记住,通过引用并不会引起变量拷贝。考虑一个包含大量数据的String缓冲区。复制那些将会导致程序变慢得多。 189 | 190 | 我们现在应该能够创建函数接受字符串是否为&str, String 或者 event reference counted。我们也可以创建有某些变量拥有引用的结构。这个结构的使用期被联系到这些被引用的变量以确保结构不比引用活的短,而避免造成对程序不好的影响。我们也有一个初步的了解,我们的结构的变量是否应该是类型或引用类型。 191 | 192 | ##What about 'static 193 | 194 | 值得一提的是,我们可以用一个`'static` 使用期来让我们最初的例子通过编译,但我得警告你: 195 | 196 | ```rust 197 | struct Person { 198 | name: &'static str, 199 | } 200 | 201 | impl Person { 202 | fn greet(&self) { 203 | println!("Hello, my name is {}", self.name); 204 | } 205 | } 206 | 207 | fn main() { 208 | let person = Person { name: "Herman" }; 209 | person.greet(); 210 | } 211 | ``` 212 | `'static`使用期对整个程序的有效。你可能不需要Person或name存在这么久。 213 | 214 | ##Related 215 | 216 | * [Creating a Rust function that accepts String or &str](http://hermanradtke.com/2015/05/06/creating-a-rust-function-that-accepts-string-or-str.html) 217 | * [中文](https://github.com/ScottHuangZL/Rust-Articles-Translation/blob/master/creating-a-rust-function-that-accepts-string-or-str.md) 218 | -------------------------------------------------------------------------------- /why-is-a-rust-executable-large.md: -------------------------------------------------------------------------------- 1 | **为什么Rust可执行程序巨大** 2 | 3 | [原文链接](https://lifthrasiir.github.io/rustlog/why-is-a-rust-executable-large.html) 4 | 5 | 译者:[Scott Huang](https://github.com/ScottHuangZL) 20160628 于厦门 6 | 7 | 假设你是一个主要用可编译语言工作的程序员。莫名其妙的或者基于一些正当的原因,你已经厌倦这些语言了,并且听说一个时髦的语言叫Rust。浏览了一些网页和官方论坛,觉得还不错,你决定试试看。Rust过去似乎不容易安装,但感谢Rustup,这个问题看起来已经解决了。 Cargo看起来也很好,所以你根据书的第一部分用新语言写了一个小的欢迎程序。 8 | ``` 9 | fn main() { 10 | println!("Hello, world!"); 11 | } 12 | ``` 13 | 14 | 令人惊讶的是Cargo很顺畅的就编译完成。这和你过去不得不配置一个编译脚本或者make文件然后才可以编译来比简直就是某种奇迹。你会注意到在target/debug/hello目录里有编译完成的可执行文件。你本能的用 ls -al 或者 dir列一下文件,但你几乎不相信自己的眼睛: 15 | ``` 16 | $ ls -al target/debug/hello 17 | -rwxrwxr-x 1 lifthrasiir 650711 May 31 20:00 target/debug/hello* 18 | ``` 19 | (译者Scott: windows下甚至高达1689K) 20 | 650K的文件仅仅是打印一些文字?!你记得Rust几乎是唯一的有机会取代C++的语言,并且C++被大家熟知并诟病它的代码膨胀;难道Rust无法解决这个C++的大问题?出于好奇心,你用C语言写了一个同样的程序并且编译了它。结果令人瞠目: 21 | ``` 22 | $ cat hello-c.c 23 | #include 24 | int main() { 25 | printf("Hello, World!\n"); 26 | } 27 | $ make hello-c 28 | $ ls -al hello-c 29 | -rwxrwxr-x 1 lifthrasiir 8551 May 31 20:03 hello-c* 30 | ``` 31 | 你想也许C语言得益于拥有贴近系统内核的裸金属库。这次你用C++也用iostream写了一个同样的程序,iostream比C的原始printf要安全一些。但令人吃惊的是编译出来的程序仍然比Rust的小很多: 32 | ``` 33 | $ cat hello-cpp.cpp 34 | #include 35 | using namespace std; 36 | int main() { 37 | cout << "Hello, World!" << endl; 38 | } 39 | $ make hello-cpp 40 | $ ls -al hello-cpp 41 | -rwxrwxr-x 1 lifthrasiir 9168 May 31 20:06 hello-cpp* 42 | ``` 43 | Rust哪里出问题了? 44 | 看起来许多人会不爽Rust令人吃惊的编译后的大二进制文件。这个问题不是新的;在StakOverflow上搜索这个知名的虽然有点老的问题“为什么Rust编译后文件巨大”,你会找到更多类似问题。 考虑到如此多的类似疑问,我们居然还没有一篇明确的文章来阐明它确实令人意外。所以,我尝试写这篇文章。 45 | 46 | 谨慎一点:这是一个值得考虑的问题吗?我们有几百G的存储空间,即使不是几T,而且人们今天应该在用相当好的ISP所提供的网络,所以二进制文件大小不应该是个问题,对吧?答案是,这仍然值得考虑,虽然和以前比不那么重要了:) 47 | 48 | 阿卡麦互联网调查显示虽然超过80%的发达国家用户享受4Mbps或者更高的速率,但是在发展中国家,则远远低于这个数字。平均连接速度提高了很多(几乎所有国家都能达到平均1Mbps),但是整体的分布仍然停止不前。我有幸在一个每月只需30$就能享受G速率的互联网的国家,但许多其他人也许并不是。 49 | 50 | 普通的消费者只拥有有关计算的狭窄知识面,并且他们喜欢用他们所知道的来解释任何相关问题。一个共同的观点是可执行文件膨胀导致执行缓慢。不幸的是,这是真的,而你想要避免这种情况出现。 51 | 52 | 为一些好奇心重的读者:所有例子都用Rust 1.9.0 和 1.11.0 nightly (a967611d8 2016-05-30)测试。除非特别指明,主要的操作系统是Linux 3.13.0 on x86-64. 你的机器也许不一样。 53 | 54 | **优化水平** 55 | 56 | 如果问道上面问题,几乎所有有经验的Rust用户都会回问你: 你打开了release开关吗? 57 | 58 | 这个开关会关掉调试参数。Cargo的文档精确的解释两者的不同,但通常来讲,release编译会去除一些只有开发过程需要的路由和数据,并且进行了许多优化。这个开关不是缺省打开的,因为,好吧,调试需求比发布需求更多。 59 | 60 | 请注意Jorge Aparicio已经正确的指出release编译并不会产生最小可能的可执行文件。 这是因为release编译缺省优化水平设置为3 (-C opt-level=3),这可能为性能而牺牲一些文件大小。文件大小优化水平(-C opt-level=s or -C opt-level=z) 最近发布了,所以你可以在后面用这些参数。现在,让我们仍然坚持缺省参数。 61 | 62 | 让我们尝试一下release编译! 63 | ``` 64 | $ cargo build --release 65 | $ ls -al target/release/hello 66 | -rwxrwxr-x 1 lifthrasiir 646467 May 31 20:10 target/release/hello* 67 | ``` 68 | 这实际上并没有什么区别!这是因为优化仅仅处理了用户代码,而我们几乎没有写什么用户代码。几乎所有的二进制代码来自标准库,而且我们不清楚可以做些啥... 69 | 70 | **链接时间优化(LTO)** 71 | 72 | …可惜我们可以进入链接时间优化。 73 | 74 | 因此故事就如下面:我们可以分别优化每一个crate,实际上所有标准库已经用优化形式发布。一旦编译器产生一个优化后的二进制文件,它被"the linker"组装成一个单独的可执行文件。但我不需要整个标准库:举例,一个简单的"Hello, workd"程序当然不需要std::net库。然而,链接器这么笨以至于它不会尝试移除crates中没用到的部分;它仅仅把这些库贴到最后的可执行文件中。 75 | 76 | 其实传统linker这么做有一个好的理由。链接器通常用在C和C++,而且每个文件单独编译。这和Rust有很大的不同,因为整个crate是一起编译的。除非需要的函数分布在各个文件中,链接器可以相当容易的一次性去除一些没有用到的文件。这不完美,但基本接近我们的需要:移除没有用到的函数。一个不利的地方是编译器没有办法优化函数调用,当它指向其他文件时;简单来说是缺少需要的信息。 77 | 78 | C和C++伙计们已经适应这个大约几十年了,但在最近的一二十年,他们觉得受够了,并且开始提供一个选项来启用链接时间优化(link-time optimization LTO)在这个计划里,编译器为每个文件产生优化的二进制代码而没有关注其它方面,然后链接器积极的看所有的东西并且尝试优化最终的二进制文件。这比直接处理(内部简化)源文件要难多了,而且这使编译的时间大大变长了,但又值得一试,如果必须获得一个更小的且/或更快的可执行文件。 79 | 80 | 到目前为止,我们谈论了C和C++,但LTO对Rust而言更加有益。Cargo.toml有一个参数可以启用LTO: 81 | ``` 82 | [profile.release] 83 | lto = true 84 | ``` 85 | 这有用吗?嗯,有些。 86 | 87 | ``` 88 | $ ls -al target/release/hello 89 | -rwxrwxr-x 1 lifthrasiir 615725 May 31 20:17 target/release/hello* 90 | ``` 91 | (注,在译者的机器上从1689K减少到295K) 92 | 这比调整优化水平所获得效果要大的多,但还不够。也许现在是时候瞧瞧可执行程序自身。 93 | 94 | **那么,什么东西在可执行程序文件中?** 95 | 96 | 有些工具可以直接和可执行程序一起工作,但最有用的一个也许是GNU binutils。所有的unix类型系统和windows(举例MinGW有一个单独安装) 97 | 98 | Binutils有许多工具,但strings也许是最简单的一个。它简单的爬行二进制文件来发现用0字节结尾的可打印字符序列,一个典型的C表达方式的string。这样它尝试从二进制文件中抽取可读的strings,这对我们很有帮助。所以,让我们试一下,注意准备滚屏: 99 | ``` 100 | $ strings target/release/hello | head -n 10 101 | /lib64/ld-linux-x86-64.so.2 102 | bki^ 103 | B , 104 | libpthread.so.0 105 | _ITM_deregisterTMCloneTable 106 | _Jv_RegisterClasses 107 | _ITM_registerTMCloneTable 108 | write 109 | pthread_mutex_destroy 110 | pthread_self 111 | 哇哦,确实有一些东西是我们没有预料到的: pthreads. (然而,让我们稍后再谈) 确实有一大堆字符串在我们的可执行文件中: 112 | 113 | $ strings target/release/hello | wc -c 114 | 94339 115 | ``` 116 | 哈,我们程序中的六分之一是我并没有用到的strings! 在进一步的检查中,这个观察并不准确因为strings提供了许多假的事实,但也有一些有意义的strings: 117 | 118 | 那些以jemalloc_和je_开头的字符串, 这些名字来自jemalloc,一个高效的内存分配器。这是Rust替代经典malloc/free,用来做内存管理用的。 这不是一个小库,而且毕竟我们没有自动做任何动态分配动作。 119 | 120 | 那些以backtrace_ 和 DW_开头的字符串,它们是从库 libbacktrace获得的。一个用于产生栈跟踪的库。 Rust用它来打印一些有用的panic跟踪信息(当 RUST_BACKTRACE=1 环境下有效)。然而,我们没有pacnic。 121 | 122 | 那些以_ZN开头的字符串,这些是“mangled”的名字,从Rust标准库中来。 123 | 124 | 为什么我们刚开始会有这些strings?因为它们是调试符号,相对于机器处理的二进制文件,这些合适的名字大概人类还可以阅读一下。你还记得上面提到的libbacktrace?它需要这些调试符号来打印出任何有用的信息。然而,因为我们确实想要发布一个release版,我们可以选择不包含调试信息。Rust本身没有这个选项,因为他们一般是利用一个外部工具叫做strip。因此,让我们来瞧一下用它如何工作。 125 | 126 | **调试符号,从我的草坪中去除!** 127 | 128 | 所以,我们有三个目标:没有jemalloc, 没有libbacktrace, 并且没有调试符号。我已经提到用strip来去除调试符号,所有,让我们先做这个。注意到,strip包含在binutils,所以我们可以执行这个: 129 | ``` 130 | $ strip target/release/hello 131 | $ target/release/hello 132 | Hello, world! 133 | $ ls -al target/release/hello 134 | -rwxrwxr-x 1 lifthrasiir 347648 May 31 20:23 target/release/hello* 135 | ``` 136 | 现在,文件更小了!大概一半的可执行文件是调试符号。请注意,当去除了我们的符号后,当panic时我们没有办法获取一个良好的调试信息: 137 | ``` 138 | $ sed -i.bak s/println/panic/ src/main.rs 139 | $ cat src/main.rs 140 | fn main() { 141 | panic!("Hello, world!"); 142 | } 143 | 144 | $ cargo build --release && strip target/release/hello 145 | $ RUST_BACKTRACE=1 target/release/hello 146 | thread '
' panicked at 'Hello, world!', src/main.rs:2 147 | stack backtrace: 148 | 1: 0x7fde451c1e41 - 149 | Illegal instruction 150 | $ mv src/main.rs.bak src/main.rs # tidy up 151 | …and it somehow aborted. Probably a libbacktrace issue, I don’t know, but that doesn’t harm much anyway. 152 | ``` 153 | 154 | **搞定jemalloc** 155 | 156 | 我们已经搞定调试符号了,现在让我们去除剩下的库。一些坏消息:从这个点开始,你进入了nightly rust领域。这个领域并没有你想的那么可怕,因为它并没有打你脸,但它会在一些小地方打击你(这也是为什么我们叫他nightlies!)。这也是为啥我们没有nightly特性在stable版本里的主要理由--它们也许会改变。幸运的是,我们下面在用的那些特性已经相当稳定了,而且你也许可以跟踪这篇文章来得知更多的nightlies。但之后,我会坚持一个特定的nightly版本。 157 | 158 | 一些好消息是:用rustup安装 nightlies (不管是最新版本的还是某个特定版本)都非常简单。 159 | ``` 160 | $ rustup override set nightly-2016-05-31 161 | $ cargo build --release 162 | $ ls -al target/release/hello 163 | -rwxrwxr-x 1 lifthrasiir 620490 May 31 20:35 target/release/hello* 164 | $ strip target/release/hello 165 | $ ls -al target/release/hello 166 | -rwxrwxr-x 1 lifthrasiir 351520 May 31 20:35 target/release/hello* 167 | 好的,文件大小没有多少变化,因为我们刚用过strip。让我们打倒jemalloc先— 书里面有很好的描述,但要旨是这下面这两行来改变allocator: 168 | 169 | #![feature(alloc_system)] 170 | extern crate alloc_system; 171 | 172 | fn main() { 173 | println!("Hello, world!"); 174 | } 175 | 并且,这确实有很大的变化: 176 | 177 | $ cargo build --release 178 | $ ls -al target/release/hello 179 | -rwxrwxr-x 1 lifthrasiir 210364 May 31 20:39 target/release/hello* 180 | $ strip target/release/hello 181 | $ ls -al target/release/hello 182 | -rwxrwxr-x 1 lifthrasiir 121792 May 31 20:39 target/release/hello* 183 | ``` 184 | 好的!我们从600K减少到120KB。(译者机器显示为1689K->290K->170K)。 Jemalloc 确实是一个大的库; 你可以很好的调试它的性能,但就跑题了。 185 | 186 | 没有付出没有收获! 187 | 188 | 我们只剩下libbacktrace没有处理。我们的代码不会panic,所以我们不需要打印backtrace,对吧?好的,我们遇到限制了:libbacktrace深度集成到标准库,唯一避免它的方法是不用 libstd. 简直就是一条死路。 189 | 190 | 但故事还没有结束。 Panicking 给我们一个backtrace, 但也给我们一个能力来unwind任何东西。且 unwinding 也被另一些代码,叫做 libunwind所支持。这显示我们可以禁用unwinding来去除这些。在Cargo.toml中加入下面这些内容: 191 | ``` 192 | [profile.release] 193 | lto = true 194 | panic = 'abort' 195 | 我们看到一些效果: 196 | 197 | $ cargo build --release 198 | $ ls -al target/release/hello 199 | -rwxrwxr-x 1 lifthrasiir 200131 May 31 20:44 target/release/hello* 200 | $ strip target/release/hello 201 | $ ls -al target/release/hello 202 | -rwxrwxr-x 1 lifthrasiir 113472 May 31 20:44 target/release/hello* 203 | ``` 204 | 就是这样了,如果你不打算改变代码。 205 | 206 | **中场休息,后文待译...** 207 | --------------20160629继续----------- 208 | 209 | 在探索更模糊的地带前,这是一个合适的时间点来承认我欺骗了大家关于C和C++二进制文件的大小。更公平的比较是下面: 210 | ``` 211 | $ touch *.c *.cpp 212 | $ make hello-c CFLAGS='-Os -flto -Wl,--gc-sections -static -s' 213 | cc -Os -flto -Wl,--gc-sections -static hello-c.c -o hello-c 214 | $ make hello-cpp CXXFLAGS='-Os -flto -Wl,--gc-sections -static -static-libstdc++ -s' 215 | g++ -Os -flto -Wl,--gc-sections -static -static-libstdc++ hello-cpp.cpp -o hello-cpp 216 | $ ls -al hello-c hello-cpp 217 | -rwxrwxr-x 1 lifthrasiir 758704 May 31 20:50 hello-c* 218 | -rwxrwxr-x 1 lifthrasiir 1127784 May 31 20:50 hello-cpp* 219 | ``` 220 | (注意:所有这些编译选项是必须,因为需要符合Rust的等价编译选项。 221 | -Wl,--gc-sections 也许是唯一漏掉的选项;它是一个简化版的类似LTO的选项,它没有进行优化,而仅仅是移掉没有用到的代码—非常感谢Alexis Beingessner and Corey Richardson 指出这个疏忽。Rust缺省加上这个选项,而且可以独立于LTO而应用,所以一个公平的对比也需要它 222 | ) 223 | 而且,Rust的二进制必须重新编译: 224 | ``` 225 | $ cargo rustc --release -- -C link-args=-static 226 | $ strip target/release/hello 227 | $ ls -al target/release/hello 228 | -rwxrwxr-x 1 lifthrasiir 105216 May 31 20:51 target/release/hello* 229 | ``` 230 | (译者现在windows 7, x64下编译出来的程序是147K,比先前的170K有减少了一些) 231 | 好的,看起来Rust实际上远比C和C++的编译效果好。但是...为啥它是“公平”的?不管任何语言,难道一个这么简单的程序需要这么多空间吗? 232 | 233 | 一个二进制可执行文件不是一个简单的数据格式。它一般是由一个OS routine,也叫"dynamic linker"来处理(不要和之前说的"linker"混淆)。动态链接允许程序动态的调用其他系统自带的(通常是通用的)库,而直到现在,我们这里隐式链接到C和C++的标准库—glibc and libstdc++!然而,Rust不需要(好的,几乎不需要)它们,所以整个比较对Rust不公平。 234 | 235 | 这种类型的动态链接是一把双刃剑。它使得更新多个程序共享的库变得不那么难,而且理论上,总的二进制文件大小应该减少。然而,有一个很强的少数派观点反对动态链接库,因为这也使得破坏库变得不那么难(可惜的是dynamic linker不像Cardo),且它减少二进制文件大小的效果有点被夸大了。为了在后面详述,这篇文章先前说LTO最终解决了问题,而动态链接库忍受同样的问题,却没有解决方案。在动态链接库之上进行LTO会破坏整个好处。 236 | 237 | 但,不管这些问题,动态链接库仍然是许多系统上一个通行的选择,而特别的,C和C++标准库通常只提供动态链接库。是的,我们指导linker来静态链接所有东西,但glibc撒谎了,一些函数仍然要求动态库: 238 | 239 | ``` 240 | $ # restore debug symbols 241 | $ touch *.c 242 | $ make hello-c CFLAGS='-Os -flto -static' 243 | cc -Os -flto -static hello-c.c -o hello-c 244 | $ strings hello-c | grep dlopen 245 | shared object cannot be dlopen()ed 246 | dlopen 247 | invalid mode for dlopen() 248 | do_dlopen 249 | sdlopen.o 250 | dlopen_doit 251 | __dlopen 252 | __libc_dlopen_mode 253 | ``` 254 | 这实际上是一个聪明的办法来获取一个几乎静态的库,只带有一些动态特性(比如iconv)。当然,如果你依赖这些动态特性,你就完了。这么说吧,你仍然需要启用linker动态链接来避免偏差;这个 **-static** linker 选项因此是必须的。非常有趣的是,我们看到Rust二进制文件在静态链接后实际上比它们小。它确实依赖C标准库,但只依赖一点(举例,标准I/O完全绕过stdio),所以它确实在某种程度上成功减少glibc的重量。 255 | 256 | 情况已经改变了许多,但这些比较看起来仍然不公。毕竟这都是glibc的错!我们有一些libc的替代品,musl看起来是不错的一个替代品。即使静态链接,它也产生非常紧凑的二进制文件: 257 | ``` 258 | $ touch *.c 259 | $ make hello-c CFLAGS='-Os -flto -static -s' CC=musl-gcc 260 | musl-gcc -Os -flto -static -s hello-c.c -o hello-c 261 | $ ls -al hello-c 262 | -rwxrwxr-x 1 lifthrasiir 5328 May 31 20:59 hello-c* 263 | ``` 264 | 我们可以在Rust里面用musl吗?当然。你可以通过rustup为不同目标平台安装标准库。这一次选项 -C link-args=-static 不是必须的了,因为musl缺省是静态链接的。 265 | ``` 266 | $ rustup target install x86_64-unknown-linux-musl 267 | $ cargo build --release --target=x86_64-unknown-linux-musl 268 | $ ls -al target/x86_64-unknown-linux-musl/release/hello 269 | -rwxrwxr-x 1 lifthrasiir 263743 May 31 21:07 target/x86_64-unknown-linux-musl/release/hello* 270 | $ strip target/x86_64-unknown-linux-musl/release/hello 271 | $ ls -al target/x86_64-unknown-linux-musl/release/hello 272 | -rwxrwxr-x 1 lifthrasiir 165448 May 31 21:07 target/x86_64-unknown-linux-musl/release/hello* 273 | ``` 274 | 好的,现在终于看起来公平了。这160K的可执行程序可以恰当的归功于Rust的“膨胀”;它包含 libbacktrace and libunwind (总共大概50K), 而且libstd仍然很难完全优化掉(大概40K的纯Rust代码,引用了libc的不同的bit)。这是Rust标准库的目前状态,必须解决掉。好吧,至少这是每一个可执行程序的一次性的代价,所以实际上他们也没啥重要。 275 | 276 | 我们还有一个尝试可以是比较更加公平。假如Rust可以用动态链接会怎么样?这基本不可能,我们知道所有的操作系统还没自带Rust标准库,但是,好吧,值得尝试。注意,LTO, 定制化内存分配,不同的panic策略无法和动态链接兼容,所以,你不得不让这些选项缺省不变。 277 | 278 | ``` 279 | $ cargo rustc --release -- -C prefer-dynamic 280 | $ ls -al target/release/hello 281 | -rwxrwxr-x 1 lifthrasiir 8831 May 31 21:10 target/release/hello* 282 | ``` 283 | 所以动态链接可以和通常的C/C++程序对比。然而,这里大部分是因为好奇。(译者:因为编译出来的程序缺少标准库而不能运行) 284 | 285 | **让我们和libstd说再见吧** 286 | 287 | **我们到现在为止尝试在几乎不改变代码的情况下试图大量减少可执行文件的大小。但如果你愿意付出对价,你可以获得一个小的多的执行文件。注意:通常不推荐这样,这一部分会进入到一个非常严格的成本-效益分析。** 288 | 289 | 我们都知道libstd是非常友好和便利的,但有时它提供太多东西了(而且它是我们没有用到的libbacktrace和libunwind的源泉)。开始避免libstd。 RUST书上有整整一节来讲述如何不用stdlib代码,所以,让我们依葫芦画瓢。新的源码如下: 290 | 291 | ``` 292 | #![feature(lang_items, start)] 293 | #![no_std] 294 | 295 | extern crate libc; 296 | 297 | #[start] 298 | fn start(_argc: isize, _argv: *const *const u8) -> isize { 299 | // since we are passing a C string, 300 | // the final null character is mandatory 301 | const HELLO: &'static str = "Hello, world!\n\0"; 302 | unsafe { libc::printf(HELLO.as_ptr() as *const _); } 303 | 0 304 | } 305 | 306 | #[lang = "eh_personality"] extern fn eh_personality() {} 307 | #[lang = "panic_fmt"] extern fn panic_fmt() -> ! { loop {} } 308 | ``` 309 | 而且你需要添加一个独立的libc crate在Cargo.toml里: 310 | ``` 311 | [dependencies] 312 | libc = { version = "0.2", default-features = false } 313 | ``` 314 | 就是这样了!这非常接近你从C程序所获得的:strip前8503字节,strip后6288字节。(I’m tried of faking the console output (note the modified time), so I’ll omit them from now on.) musl更进一步,但你需要额外的选项来恰当的链接libc crate 到 musl: 315 | ``` 316 | $ export RUSTFLAGS='-L native=/usr/lib/x86_64-linux-musl' 317 | $ cargo build --release --target=x86_64-unknown-linux-musl 318 | $ strip target/x86_64-unknown-linux-musl/release/hello 319 | ``` 320 | 现在,我们把字节减少到5360.仅比最好的C程序结果多32字节!我么可以做的更好吗?不用stdio,你可以直接调用系统功能(当然,仅在unix下): 321 | ``` 322 | #[start] 323 | fn start(_argc: isize, _argv: *const *const u8) -> isize { 324 | const HELLO: &'static str = "Hello, world!\n"; 325 | unsafe { 326 | libc::write(libc::STDOUT_FILENO, 327 | HELLO.as_ptr() as *const _, HELLO.len()); 328 | } 329 | 0 330 | } 331 | ``` 332 | 这去除了更多的字节,最终strip的大小是5072字节。 333 | 334 | 有些人会争议说以上不是真实的Rust代码,而是一个Rust形式的依赖于unix系统调用的代码。好的。没有真实的Rust代码看起来像这样。相反的,我们可以创造我们自己的Stdout类型,直接连线到系统调用,而且用普通的宏格式: 335 | 336 | ``` 337 | use core::fmt::{self, Write}; 338 | 339 | struct Stdout; 340 | impl Write for Stdout { 341 | fn write_str(&mut self, s: &str) -> fmt::Result { 342 | let ret = unsafe { 343 | libc::write(libc::STDOUT_FILENO, 344 | s.as_ptr() as *const _, s.len()) 345 | }; 346 | if ret == s.len() as isize { 347 | Ok(()) 348 | } else { 349 | Err(fmt::Error) 350 | } 351 | } 352 | } 353 | 354 | #[start] 355 | fn start(_argc: isize, _argv: *const *const u8) -> isize { 356 | let _ = writeln!(&mut Stdout, "Hello, world!"); 357 | 0 358 | } 359 | ``` 360 | 这实际上是更自然的Rust代码。Strip后是9248字节,暗示一下,最小的代码成本大约是4KB。然而其他类型强加更多在头上;举例,打印3.14f64大概会产生25K额外的二进制文件大小,因为浮点数到小数转换不容易。而且注意到这没有用buffers(除了为pioes的内核buffer). 361 | 362 | 还有一件事情... 363 | 364 | 我们还可以在这份上更进一步,因为我们的可执行文件还包含一些没有用到的大块字节。欢迎来到真正底层的编程(和我们通常用C/C++编程相比)。其实,这个领域有一些先锋已经彻底的探险过,所有我就直接提供链接以便找到他们: 365 | 366 | Keegan McAllister made a [151-byte x86-64 Linux program](http://mainisusuallyafunction.blogspot.kr/2015/01/151-byte-static-linux-binary-in-rust.html) printing “Hello!”. Well, that’s not what we wanted, and the proper “Hello, world!” program will probably cost 165 bytes; the original program (ab)used the ELF header to put the string constant there but there isn't much space to put a longer “Hello, world!”. 367 | 368 | Peter Atashian made a [1536-byte Windows program](https://github.com/retep998/hello-rs/blob/master/windows/src/main.rs) printing “Hello world!” (note no comma). I’m less sure about the size of the proper program, but yeah, you have an idea. This is notable because Windows essentially forces you to use dynamic linkage (Windows system calls are not stable across versions), and the dynamic symbol table costs bytes. 369 | 370 | For the comparison and your geeky pleasure, the shortest known version of a x86-64 Linux program printing “Hello, world” (note no exclamation mark) costs only [62 bytes](http://www.muppetlabs.com/~breadbox/software/tiny/hello.asm.txt). The general technique is better explained in [this classical article.](http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html) 371 | 372 | Hey, I’ve learned the hard way that we have tons of variations over the “Hello, world!” programs, and none of those programs print the proper and longest version of greeting! 373 | 374 | 这篇文章有意探索多种技术(并不是所有这些都可以派上实际用场)来减少程序大小,但如果你需要一些结论,可操作的实用结论是: 375 | 376 | **1.用 --release编译** 377 | 378 | **2.启用LTO,并strip二进制文件** 379 | 380 | **3.如果你的程序没有密集用到内存管理,请用 system allocator (假定你用 nightly版本).** 381 | 382 | **4.你也许可以在未来使用编译参数开关 s/z** 383 | 384 | 我没有提到这点是因为它对于这么一个小程序没有起到改进作用。假如你的可执行文件巨大,你也可以使用UPX或其他可执行程序压缩软件来压缩。 385 | 386 | 伙计们,这些就是全部了! 387 | --------------------------------------------------------------------------------