text: rust怎么建不了研究工作台
  所有权类型系统通过对对象的别名(引用)进行限制来帮助程序员实施低级编程的安全模式，这些别名(引用)可以在程序执行的任何给定点上使用。
  然而，Rust至少以两种新颖而令人兴奋的方式超越了之前工作的所有权类型系统:
  Rust使用了借用和生存期机制，这使得表达通用c++风格的习惯用法更加容易，并确保它们被安全地使用。
  Rust还提供了一组丰富的api-例如，用于并发ab- stractions、高效的数据结构和内存管理-这些api通过支持混叠和变异的更灵活组合从根本上扩展了语言的能力，这比Rust的核心类型系统所允许的更灵活。
  相应地，这些api不能在Rust的安全片段中实现:相反，它们在内部使用了可能不安全的c风格的语言特性，但是以一种安全的封装方式，声称不会影响Rust语言级别的安全保证。
  Rust设计的这些方面不仅对它的成功至关重要--它们还提出了关于它的语义和安全保障的基本研究问题--这些都是编程语言社区刚刚开始探索的问题。
  如果没有更多的空间来容纳额外的元素，则分配一个新的缓冲区，并移动所有现有的元素。
  因为vptr仍然指向旧的缓冲区。
  换句话说，向v添加一个新元素，vptr就变成了一个悬浮指针。
  这是可能的，因为两个指针都有别名:通过指针(v)的操作通常也会影响它的所有别名(vptr)。
  事实上，这个问题非常常见，它的一个实例有自己的名称:迭代器失效，它指的是迭代器(通常在内部使用指针实现)失效的情况，因为迭代器所遍历的数据结构在迭代期间发生了突变。
  它最常见的情况是在循环中迭代某些容器数据结构时，间接地但偶然地调用了改变数据结构的操作。
  请注意，在实践中，对改变数据结构的操作的调用(在我们的示例的第3行中向后推_)可能深深嵌套在几个抽象层之后。
  特别是当代码被重构或新特性被添加时，通常几乎不可能确定推送到某个向量是否会使程序中其他地方将要再次使用的指针失效。
  Java、Go和OCaml等语言使用垃圾收集避免了释放后再使用的错误:内存只在程序不再使用时才被释放。
  因此，不会有悬空指针，也不会有after-free。
  垃圾收集的一个问题是，为了提高它的效率，这些语言通常不允许内部指针(即指向数据结构的指针)。
  例如，Java中的数组int[]的表示类似于c++中的std::vector(除了Java中的ar射线不能增长)。
  然而，与c++不同的是，我们只能获取和设置Java数组的元素，而不能对它们进行引用。
  要使元素本身可寻址，它们需要是单独的对象，然后可以将对这些对象的引用存储在数组中--也就是说，元素需要“装箱”。
  这牺牲了性能和对内存布局的控制，以换取安全。
  最重要的是，垃圾收集甚至不能正确解决迭代器失效的问题。
  类似地，虽然Java确实防止了由空指针误用引起的安全漏洞，但它通过运行时检查来做到这一点，这会引发NullPoin- terException。
  在这两种情况下,结果明显优于相应的c++程序的未定义的行为,它仍有很多不足之处:而不是船运incor--矩形的代码,然后在运行时检测问题,我们要防止错误发生在第一个地方。
  Rust解决指针失效的方法。
  在Rust中，像迭代器失效和空指针误用这样的问题是由编译器静态检测的，它们会导致编译时错误，而不是运行时异常。
  和c++版本一样，在内存中有一个缓冲区，vptr指向缓冲区的中间(导致混叠);Push可能会重新分配缓冲区，这将导致VPTR成为一个悬空指针，并导致在第4行中出现use-after-free。
  但这些都没有发生;相反，编译器会显示一个错误:“一次不能多次借用可变的v。”我们很快就会回到“借用”的问题，但是关键的思想--即Rust在指向数据结构的指针存在时实现内存安全的机制--已经在这里可见了:类型系统强制这样的原则(有一个显著的例外，我们将在后面讨论):引用永远不能同时别名化和可变。
  这个原则在并发环境中应该听起来很熟悉，实际上Rust也使用它来确保没有数据竞争。
  然而，正如我们被Rust编译器拒绝的例子所示，不受限制的混叠和变异的组合会导致灾难，即使对顺序程序也是如此:在第3行，VPTR和v别名(v被认为指向它的所有内容，与VPTR重叠)，我们正在进行一个突变，这将导致在第4行内存访问bug。
  Rust防止不受控制的混叠的核心机制是所有权。
  Rust中的内存总是有一个唯一的所有者，如图2中的示例所示。
  在这里，我们构造与第一个例子相似的v，然后将它传递给consume。
  这是因为Rust认为v的所有权已经转移到了consume上，这意味着consume可以对w做任何它想做的事情，并且调用者可能不再访问支持这个向量的内存。
  在Rust中，所有权不仅可以防止内存错误--它还形成了Rust内存管理方法的核心，更普遍地说，是资源管理方法。
  当一个拥有自己内存的变量(例如，一个类型为Vec的变量，它拥有支持vector的内存中的缓冲区)超出作用域时，我们可以确定这段内存将不再需要--因此编译器可以在此时自动释放内存。
  为此，编译器透明地插入析构函数调用，就像在c++中一样。
  例如，在consume函数中，实际上并不需要显式调用析构函数方法(drop)。
  我们可以让函数体为空，它会自动释放w本身。
  因此，Rust程序很少需要担心备忘管理:它在很大程度上是自动的，尽管缺少垃圾收集器。
  此外，内存管理也是静态的(在编译时确定)，这一事实带来了巨大的好处:它不仅有助于降低最大内存消耗，还可以在响应性系统(如Web服务器)中提供良好的最坏情况下的延迟。
  在此之上，Rust的方法概括了内存管理之外的内容:其他资源，如文件描述符、套接字、锁句柄等等，都是用相同的机制处理的，因此Rust程序员不必担心，例如，关闭文件或释放锁。
  使用析构函数进行自动资源管理是在c++的RAII(资源获取即初始化)形式中率先提出的;31在Rust中关键的区别是，类型系统可以静态地确保资源在被析构后不再被使用。
  可变的引用 。
  一个严格的所有者-所有者纪律是好的和简单的，但不幸的是不太方便工作。
  通常情况下，我们想要快速地向某个函数提供数据，但是在该函数完成后再返回数据。
  例如，我们希望v.push(12)授予push修改v的权限，但我们不希望它消耗向量v。
  在《Rust》中，这是通过借鉴而实现的，它从之前关于区域类型的工作中获得了很多灵感。
  函数add_ something接受一个类型为&mut Vec的参数，表示对Vec的可变引用。
  操作上，这就像c++中的引用，也就是说，Vec是通过引用传递的。
  在类型系统中，这被解释为添加_某物，从调用者那里借用Vec的所有权。
  函数add_something演示了在格式良好的程序中借位是什么样子的。
  要了解为什么编译器接受这段代码，而拒绝前面的指针失效例子，我们必须引入另一个概念:生存期。
  就像在现实生活中一样，借东西的时候，可以通过事先约定借东西的时间来避免误解。
  所以,当创建的引用,就像--签署了一辈子,该记录的完整形式引用类型:&狗T终身的。
  com -堆垛机确保参考(v,在我们前充足的)只有在生活--时间,再参照不习惯,直到生命结束。
  在我们的例子中，生存期(都是由编译器推断出来的)分别只持续add _ something和Vec::push的持续时间。
  当上一个借位的生命周期仍在进行时，使用Never v。
  相反，图4显示了从图1推断出的前一个示例的生命周期。
  vptr的borrow的生命周期a从第2行开始，一直持续到第4行。
  它不能再短了，因为vptr在第4行使用。
  然而，这意味着在第3行中，当存在未偿还借款时使用了v，这是一个错误。
  总而言之:无论什么东西是通过价值传递的(如在consume中)，Rust将其解释为所有权转移;当某个东西通过引用传递时(如在add_ something中)，Rust将其解释为一个特定的生命周期。
  共享引用。
  遵循可以使用别名或变异(但不能同时使用)的原则，可变引用是唯一指针:它们不允许别名。
  为了完成这幅图，Rust有第二种引用，共享引用写为&Vec或&'a Vec，它允许混列但不允许变异。
  共享引用的一个主要用例是在多个线程之间共享只读数据，如图5所示。
  这里，我们创建了一个共享引用vptr，指向(并借用)v。
  这里的竖线表示不带任何参数的闭包函数(有时也称为匿名函数或“lambda”)。
  这些闭包被传递给join，这是“并行组合”的Rust版本:它接收两个闭包，并行运行它们，等待它们都完成，并返回它们的结果。
  当join返回时，借位结束，所以我们可以再次改变v。
  就像可变引用一样，共享引用也有生命周期。
  这里的关键区别是，在同一生命周期内允许多个共享引用共存，只要它们只用于读，而不是写。
  我们可以通过将示例中的两个线程中的一个更改为|| v.push(12)来见证这一限制的实施:然后编译器会抱怨不能同时拥有对Vec的可变引用和共享引用。
  实际上，该程序在读取线程和推入向量的线程之间有一个致命的数据竞争，所以编译器静态地检测这种情况是很重要的。
  共享引用在序列代码中也很有用;例如，在对vector对象进行共享迭代时，仍然可以将对整个vector对象的共享引用传递给另一个函数。
  但在本文中，我们将重点讨论并发共享的使用。
  为了获得安全性，Rust类型系统要求引用不能同时有别名和可变。
  拥有类型为T的值意味着您完全“拥有”它。
  Rust的核心所有权规程足够灵活，可以考虑许多低级编程习惯用法。
  但是对于实现特定的数据结构，它可能会有过多的限制。
  例如，如果没有别名状态的任何变化，则不可能实现双重链表，因为每个节点都由它的下一个和前一个邻居别名。
  Rust采用了一种不同寻常的方法来解决这个问题。
  与其使其类型系统复杂化以考虑不符合它的数据结构实现，或引入动态检查以在运行时加强安全性，Rust允许通过开发安全的api来放松自己的规则，这些api通过允许安全控制别名可变状态的使用来扩展语言的表达能力。
  尽管这些api的实现不遵守Rust严格的所有权规则(这一点我们稍后再讨论)，但api本身关键地利用了Rust的所有权和借用机制，以确保它们保持了Rust作为一个整体的安全保证。
  现在让我们看几个例子。
  共享可变状态。
  Rust的共享引用允许多个线程并发读取共享数据。
  但是只读数据的线程只是故事的一半，所以接下来我们将看看互斥锁API是如何使一个人安全地跨线程边界共享可变状态的。
  起初，这似乎与我们迄今为止所说的关于Rust的安全性的所有内容相矛盾:Rust所有权原则的全部意义不就是防止共享状态的突变吗
  确实如此，但我们将看到如何使用互斥锁来充分限制这种变异，从而不破坏内存或线程安全。
  考虑图6中的示例。
  我们再次使用结构化并发和共享引用，但现在我们将vector封装在一个互斥锁中:变量mutex_v的类型为Mutex<Vec>。
  互斥锁上的键操作是锁，它会阻塞，直到它能获得排他锁为止。
  当变量超出作用域时，v的析构函数隐式地释放锁。
  最后，如果第一个线程首先获得锁，这个程序要么打印，要么打印，如果第二个线程获得锁。
  为了理解我们的示例程序是如何进行类型检查的，让我们仔细看看锁。
  它(几乎)有类型fn(&'a Mutex) ->MutexGuard <'a, T>。
  这种说锁可以被称为共享引用μ- tex,这就是为什么生锈让我们叫锁在两个线程:两个闭包捕获一个互斥<Vec <等>>,并与vptr捕获的类型等,在我们的第一个并发的例子中,两个线程同时可以使用引用。
  事实上，至关重要的是，锁采用共享引用而不是muta- ble引用，否则，两个线程无法同时尝试获取锁，而且从一开始就不需要锁。
  此外，当它超出作用域时，它会自动释放锁(在c++世界中称为rai31的习惯用法)。
  在我们的示例中,这意味着两个线程暂时的独占访问权向量,他们有一个可变参考,反映了事实,由于正确实现互斥锁,他们永远都有一个可变参考- ence同时,如此独特的--洛克可变的属性引用。
  换句话说，互斥可以安全地提供别名状态的变化，因为它实现了运行时检查，确保在每次变化期间，状态没有别名。
  引用计数。
  我们已经看到，共享引用提供了一种在程序中的不同方之间共享数据的方法。
  但是，共享引用具有静态确定的生存期，当该生存期结束时，数据又被唯一地拥有。
  这在结构化并行中工作得很好(如前面示例中的join)，但不适用于非结构化并行，在非结构化并行中，线程被派生出来并独立于父线程运行。
  我们首先创建一个指向通常向量的Arc。
  然后生成一个使用arc_v2的线程;即使当我们在这里编写的函数返回时，这个线程仍然在后台运行。
  因为这是一种非结构化并行，所以我们必须显式地将arc_v2的所有者转移到另一个线程中运行的闭包中。
  Arc是一个“智能指针”(类似于c++中的shared_ptr)，所以我们几乎可以把它当作&Vec来使用。
  隐式地，当arc_v1和arc_v2超出作用域时，会调用它们的析构函数，最后一个要销毁的Arc会释放vector对象。
  因此，Rc可能更快，但不是线程安全的。
  类型Rc在复杂的顺序代码中很有用，在这些代码中，共享引用强制的静态作用域不够灵活，或者不能静态地预测对象的最后一个引用何时会被销毁，从而可以释放对象本身。
  因为Rc不是线程安全的，我们需要确保程序员不会在应该使用Arc时意外使用Rc。
  这是很重要的:如果我们以之前的Arc为例，用Rc替换所有的Arc，程序会有一个数据竞争，可能会过早地释放内存或根本不释放内存。
  然而，非常值得注意的是，Rust编译器能够捕捉到这个错误。
  它的工作方式是Rust em-使用了一种称为Send trait的东西:一种类型的属性，只有当类型T的元素可以安全地发送到另一个线程时，类型T才能使用这种属性。
  类型为Arc是Send，类型为Rc不是。
  join和spawn都要求它们运行的闭包捕获的所有内容都是Send，因此，如果我们在闭包中捕获非Send类型Rc的值，编译将失败。
  Rust对Send特性的使用说明，有时强静态类型施加的限制可以导致更强的表达能力，而不是更弱。
  特别地，c++的智能引用计数指针，std::shared_ ptr，总是使用原子指令，因为使用像Rc这样更高效的非线程安全的变量被认为风险太大。
  相比之下,生锈的特性允许发送到“黑客而不用担心:“26日有两个线程提供了一种方法--安全数据结构(如弧)和非线程安全的数据结构(如Rc)在相同的语言,同时确保到模块化,这两个不习惯不正确的方法。
  我们已经看到了像Arc和Mutex这样的类型是如何让Rust程序安全地使用引用计数和共享可变状态等特性的。
  然而，有一个问题:这些类型实际上不能在Rust中实现。
  或者，更确切地说，它们不能在安全的Rust中实现:编译器会因为可能违反混叠规则而拒绝Arc的实现。
  事实上，它甚至会拒绝Vec的实现来访问可能未ini化的内存。
  为了提高效率，Vec手动管理底层缓冲区并跟踪它的哪些部分被初始化。
  当然，Arc的实现实际上并没有违反混淆规则，Vec实际上也没有访问未初始化的内存，但是建立这些事实所需的参数太微妙了，Rust编译器无法推断。
  为了解决这个问题，Rust有一个“出口”:Rust不仅包含我们目前讨论过的安全语言--它还提供了一些不安全的特性，比如c风格的无限制指针。
  这些特性的安全性(内存安全性和/或线程安全性)无法由编译器保证，因此它们只在标记为不安全关键字的语法块中可用。
  这样，就可以确保不会意外地离开安全的Rust领域。
  例如，Arc的实现使用了不安全的代码来实现一个模式，这个模式不能用安全的代码来表达:Rust:共享没有明确的own- er，由线程安全的引用计数管理。
  由于支持“弱引用”，情况变得更加复杂:这些引用不会使referent保持活动状态，但可以原子性地检查活动状态并升级到完整的Arc。
  Arc的正确性依赖于相当微妙的并发推理，而Rust编译器根本没有办法静态地验证，当引用计数达到0时，释放内存实际上是安全的。
  不安全块的替代品。
  可以将Arc或Vec之类的东西转换为语言原语。
  例如，Py- thon和Swift有内置的引用计数，Python有list作为Vec的内置等效物。
  除此之外，将不安全的操作限制在语言原语的实现中也严重限制了灵活性。
  例如，Firefox使用一个Rust库实现了一个不支持弱引用的Arc变体，这就提高了不需要弱引用的代码的空间利用率和性能。
  语言是否应该为任何内置类型的设计空间中的每一个可想到的地方提供原语
  另一个避免不安全代码的选择是使类型系统具有足够的表现力，从而能够实际验证类型(如Arc)的安全性。
  然而,由于如何微妙的正确性的数据结构(事实上弧和简化的变异作为主要案例研究在最近的一些正式的验证papers9、12、18),这个基本上需要一种通用的定理prover-and研究员足够的背景来使用它。
  定理证明社区离让开发人员自己进行这种证明还有很长的路要走
  安全的抽象。
  相反，Rust选择允许程序员在必要时灵活地编写不安全的代码，尽管它应该由安全的api封装。
  安全封装意味着，不考虑像Arc或Vec这样的Rust api是用不安全代码实现的，这些api的用户应该不会受到影响:只要用户在Rust的安全片段中编写了类型良好的代码，他们就不应该能够观察到由于在api实现中使用不安全代码而导致的异常行为。
  这与c++形成了鲜明的对比，后者的弱类型系统甚至缺乏强制api安全使用的能力。
  因此，像shared_ptr或vector这样的c++ api很容易被误用，导致引用计数错误和迭代器失效，而这些在Rust中不会出现。
  编写不安全代码的能力就像一个杠杆，Rust程序员用它使类型系统更有用，而不用把它变成一个定理证明，事实上，我们相信这是Rust成功的关键因素。
  Rust社区正在开发一个安全可用的高性能库的完整生态系统，使程序员能够在这些库之上构建安全高效的应用程序。
  但是当然，没有免费的午餐:这取决于Rust库的作者以某种方式确保，如果他们编写不安全的代码，他们会非常小心，不破坏Rust的安全保证。
  一方面，这比在C/ c++中要好得多，因为绝大多数的Rust代码是在该语言的安全片段中编写的，所以Rust的“攻击面”要小得多。
  另一方面，当需要不安全的代码时，程序员如何知道他们是否足够“小心”是很不明显的。
  为了保持对Rust生态系统安全性的信心，我们真的想要有一种方式，正式说明和验证将不安全的代码安全地封装在一个安全的API后面意味着什么。
  这正是锈带项目的目标。
  验证Rust的安全声明的关键挑战是解释安全代码和不安全代码之间的交互。
  要了解这为什么具有挑战性，让我们简要地看一看验证编程语言安全性的标准技术-所谓的语法方法。
  使用这种技术，安全性用语法类型判断来表示，语法类型判断用一些数学推理规则来形式化地说明类型检查器。
  如果一个程序e在语法类型判断方面是良好类型的，那么e是安全的。
  不幸的是，这个定理对于我们的目的来说太弱了，因为它只讨论语法安全的程序，从而排除了使用不安全代码的程序。
  例如，如果为真{e} else {crash()}不是语法良好的类型，但它仍然是安全的，因为crash()永远不会执行。
  关键的解决方案:语义类型的可靠性。
  为了说明安全代码和不安全代码之间的交互，我们转而使用一种称为语义类型可靠性的技术，它根据程序的“行为”而不是一组固定的推理规则来表达安全性。
  语义健全的关键因素是逻辑关系，它为每个API分配了一个安全契约。
  它表示，如果API中每个方法的输入符合它们指定的类型，那么输出也符合。
  使用来自正式验证的技术，可以证明API的实现满足分配的安全契约，如图8所示。
  语义类型健全性是推理使用安全代码和不安全代码组合的程序的理想选择。
  对于任何使用不安全代码的库(如Arc、Mutex、Rc和Vec)，必须手工证明其实现满足安全契约。
  Arc满足其安全契约。
  对于程序中安全的部分，验证是自动的。
  这可以用下面的定理来表达，它说，如果一个组件写在Rust的安全片段中，它通过构造来满足其安全契约。
  定理3(基本定理)。
  如果组件e在语法上是良好类型的，那么e满足它的安全契约。
  总之，这意味着如果不安全的块只出现在已经手动验证以满足其安全契约的库中，那么Rust程序就是安全的。
  使用Iris逻辑对安全契约进行编码。
  语义类型稳健性是一种古老的技术，至少可以追溯到Milner 1978年关于类型稳健性的开创性论文28，但将其扩展到现实的现代语言(如Rust)已被证明是一个困难的挑战。
  事实上，将其扩展到具有可变状态和高阶函数的语言仍然是一个开放的问题，直到21世纪初，“步进索引Kripke逻辑关系”(SKLR)模型的开发，作为基本携带代码证明项目的一部分。
  即使这样，直接使用SKLR模型编码的安全契约的验证也变得非常乏味、低级，并且难以维护。
  在锈带中，我们建立在Iris的最新工作之上，Iris是一个用于高阶、并发、强制程序的验证框架，在Coq证明助手中实现。
  Iris为编码和使用SKLR模型提供了更高级的语言，从而使我们能够扩展这样的模型来处理像Rust这样复杂的语言。
  特别是，Iris是基于分离逻辑的，它是Hoare逻辑的扩展，专门针对指针操作程序的模块化推理，并以所有权的概念为中心。
  这为我们提供了一种理想的语言，可以在Rust中对所有权类型的语义建模。
  Iris扩展了传统的分离逻辑，增加了几个对Rust建模至关重要的特性:
  ˲Iris支持用户定义的虚状态:定义自定义逻辑资源的能力，这些资源对于证明程序的正确性很有用，但并不直接对应于其物理状态中的任何东西。
  Iris的用户定义的ghost状态使我们能够验证像Arc这样的库的合理性，对于这些库，所有权并不对应于物理所有权(例如，两个独立拥有的Arc可能由相同的底层内存支持)--这是一种被称为“虚拟分离”的现象。
  10,11通过(在Iris内)导出一个新的、领域特定的“生命周期逻辑”，它也使我们能够在更高的抽象层次上对Rust的借用和生命周期进行推理。
  Iris支持命令式不变量:程序状态上的不变量，可以循环地指向其他不变量的存在谓词不变量在建模中心类型系统特性(如递归类型和闭包)中扮演着重要的角色。
  Rust的复杂性要求我们的语义合理性证明是机器检查的，因为手工做证明太乏味且容易出错。
  幸运的是，Iris提供了一套丰富的分离逻辑策略，这些策略是模仿标准Coq策略的，因此可以以Coq用户熟悉的经过时间检验的方式交互式地开发机器检查的语义合理性证明。
  它用更合理的东西代替了用于蓝图的实验系统:基于 工作台 的技术树系统，因此您可以始终朝着要制作的物品工作。
  正如 Rust 的创建者Garry Newman在本周的博客文章中所解释的那样，技术树更新旨在解决以下事实:随机实验...Rust的12月更新是一个很大的更新。
  它用更合理的东西代替了用于蓝图的实验系统:基于工作台的技术树系统，因此您可以始终朝着要制作的物品工作。
  正如Rust的创建者Garry Newman在本周的博客文章中所解释的那样，技术树更新旨在解决以下事实:随机实验系统已被证明令人沮丧且昂贵。
  在该系统下，玩家可以使用废料进行“实验”并获得随机的新蓝图。
  现在，您将能够查看工作台技术级别上可用项目的整个技术树。
  这样，您就可以找到所需的物品并为之努力，而不必担心您的珍贵废料将浪费在不需要或不需要的图纸上。
  从现在开始，新玩家应该有更多的时间来建立有用的蓝图，而不是像纽曼所说的那样，“必须向RNG众神祈祷以在板条箱中找到SAP”。
  如下面的预告片所示，还有更多更改:
  纽曼说，Facepunch Studios的团队希望他们需要在技术树上取得平衡，并且玩家的反馈对于评估新系统的运行状况将非常有价值。
  该更新还进行了约300项余额更改，触及了生存游戏中的“几乎所有物品” 。
  减少了大多数制作时间
  降低了所有电气制作成本
  现在可以堆叠大多数电器
  危险品套装现在使用条件
  减少路标装甲制作成本
  增加了火箭发射器的部署时间
  烟雾弹现在可以制造和研究了
  从桶和板条箱中删除了蓝图滴
  完全重新平衡躯干下装
  大型medkit现在可提供少量即时健康且手工艺便宜
  服用抗辐射药时可大大减少脱水量
  减少医用注射器的制作时间
  加强型窗户栏现在装有玻璃
  您还会发现空投事件已被完全重做，战利品表“从头开始重写”，以确保至少两件像样的装甲和一件非常好的或两件像样的武器。
  您将再也无法在空投中找到木甲，长剑，地雷或石矛。
  它是无法拆卸的，无法修复，但可以在类似于潜水箱的工作台上重新注入空气。
  您可以在工具箱中找到它，它可以在大院出售。
  快乐的矿石狩猎 5.罗盘 我们为游戏中的HUD增加了一个指南针。
  这里有一个新的团队用户界面，旁边有手提钻，可展开的了望塔等等。
  此修补程序擦除服务器。
  2.团队系统
  我们增加了一个基本的团队系统。
  这意味着您可以创建团队并邀请其他玩家加入。
  这将使所有团队成员能够远距离区分彼此，并在地图上看到彼此。
  您还可以查看是否有人在线或死亡。
  要创建团队，请按Tab键并单击“创建团队”按钮。
  要邀请其他玩家，请向他们走，然后按E.
  要踢出一名球员，你必须是队长(选中标记):将鼠标悬停在选项卡菜单中并按“踢”。
  要将其他玩家提升为领导者(如果您正在注销)，请在团队成员上持有“E”3秒钟。
  要离开团队，请按标签菜单中的“离开团队”按钮。
  团队的默认最大大小为8，任何服务器都可以使用启动命令maxteamsize 0完全禁用团队系统。
  将此添加到游戏中的决定一直存在争议，一些玩家表示会破坏游戏，而其他人则张开双臂欢迎它。
  这就是我们的立场:
  有效地团队合作的唯一方法是使用第三方语音软件并要求玩家跳过以确定他们是谁。
  当然，这增加了一个人们非常擅长跳跃检查的元素，这让他们比其他玩家更有优势，但说实话:这是愚蠢的。
  你也可以获得皮肤并且有匹配的制服，但即使这样也非常难以管理，它只适用于那些没有拼凑物品运行的玩家，如果有人拥有皮肤，它就无法运作禁用。
  如果你只是遇到一个盟友并想要一起滚动然后遇到另一组玩家，情况会更糟。
  没有办法区分谁和谁的游戏分崩离析。
  或者更糟糕的是，你必须在射击前犹豫不决，这会导致你死亡，失去所有的屎，以及愤怒的退出游戏。
  在一天结束时，通过团队系统，我觉得我们获得的收益超过了失败，我们可以随着时间的推移进行调整和修改。
  最糟糕的情况是它糟透了我们必须删除它; 最好的情况是它让游戏变得更好。
  无论哪种方式，都没有伤害。
  我希望你们喜欢这个系统，尽管我讨厌通过滥用和垃圾邮件进行阅读，但我会在接下来的几个星期内检查一些建设性的批评。
  3.岗楼
  我们为你们添加了一些低调的果实，我认为它会给游戏添加一个新的有趣的机制，以及云杉玩家制造的化合物。
  您在各种纪念碑(如采石场)看到的了望塔现在是一个可部署的对象。
  您可以在方形1级板条箱中找到它们作为蓝图。
  您可以研究它们的75种废料，并为2000木材制作它们。
  特别感谢Pojomania提出的建议，这个道具很完美。
  4.手持式凿岩机
  本月我们已经将Pneumatic手提钻加入游戏中。
  就资源收集而言，它是链锯的阳气，绝对会破坏矿石节点。
  您甚至不必瞄准特定的热点，因为它会在几秒钟内拆除整个节点以获得全部产量。
  如果你足够接近，它也会磨灭人类。
  快乐的矿石狩猎
  我们为游戏中的HUD增加了一个指南针。
  它位于屏幕顶部，显示您当前的标题以及包含方向标注的编号标记。
  有些人抱怨这是防锈的，应该是某种手持式产品。
  也许他们是对的，但我相信如果我们用完了另一个皮带插槽，我们会有更多的瑕疵。
  让我们试一试，看看我们是否需要在未来进行任何调整。
  您可以将选项菜单中的罗盘可见性更改为关闭，切换，保持，打开，默认罗盘按钮为C.
  快乐导航!
  6.纪念碑拼图
  本月没有新的谜题，但是对于谜题元素来说，这是一个当之无愧的视觉上更新。
  花在这些新元素上的工作将重新用于播放器电路(当它到来时)。
  以下是其中一些孤立的:
  7.世界SDK
  我一直致力于SDK，以支持为Rust创建自定义地图工具。
  它被称为Rust.World，源代码可以在MIT许可下的GitHub上获得。
  事情还处于早期阶段，但我希望本周末能够将第二版SDK完成。
  我们已经在野外发现了一些有趣的东西，我相信这里的东西只会越来越疯狂。
  对于这个补丁，我正在推出一个大的，早该进行的，主要是对水系统的内部改造。
  我们的系统在历史上有点过于静态，无法处理我们的程序场景和动态游戏，因此本月的大部分工作都致力于使系统更加强大。
  我们的设计师现在可以在场景编辑期间预览最终水，而无需启动游戏。
  现在可以在游戏过程中添加，移除或更换任何水体，从而开辟了许多可能性，如产卵水池或在任何时间点改变海水水位。
  有一些轻微的视觉改进，但主要与泡沫有关，不再反映太多的周围天空，呈现白色像大自然最初的意图。
  还修复了一些根据地形坡度和基于波浪的泡沫变形引起不规则图案的错误。
  还有很多工作和美学方面的改进，我希望在处理下一个补丁的一些优先事项后再回过头来。
  9.环境遮挡更新
  我们将Ambient Occlusion解决方案更新为最新版本，具有良好的性能提升(>30%)和轻微的质量改进。
  从我们的测试来看，运行中档显卡的任何人 – 例如NV GTX 960，NV GTX 1060+，AMD R9 380 – 都应该注意到全高清分辨率开启时性能影响非常小(1-2 FPS)。
  现在借此机会将其打开，因为它在将事物捆绑在一起起着非常重要的作用，为视觉效果增加了深度:
  10.强盗镇和沼泽预览
  尽管我们曾希望如此，但Bandit Town纪念碑将不会在本月的补丁中出现。
  虽然大部分艺术作品已经完成，但游戏部分尚未实现，因此我们决定将其推迟到下个月并对其进行打磨，而不是添加半成品内容。
  但是，有一个额外的月份让我们有时间进一步扩展这个想法。
  在下一个补丁中，你会发现我们不仅会有一个Bandit Town，而且还会出现一个新的沼泽迷你生物群落，它将出现在Tundra和Temperate气候中。
  这些沼泽地标将以类似于冰湖的方式运作，不仅可以提供视觉多样性，还可以让您在那里建立基地并完成您作为怪物史莱克的幻想。
  自上次更新以来，Bandit Town本身经历了一些变化。
  我们用更高质量的资产取代木屋，并用汤姆制作的一些新道具装扮起来。
  现在还有新的沼泽植物，如文森特制作的芦苇和水草。
  11.环保服饰资产
  正如达米安所说，强盗镇经历了一些重大变化。
  我们专注于通过创造一些“装扮”环境的资产来使其更加生活。
  这些也将转移到其他纪念碑。
  还有更多的入站:悬挂工具和架子等等。
  所有这些都是为了让我们真正能够让环境变得更加可靠，而不是那么像shell那样。
  12.Jackhammer艺术品
  这是一个非常有趣的工具。
  Paul和Alex的设计过程非常有凝聚力。
  有几次迭代，但我们对结果非常满意。
  C4&Anti-Rad模型艺术
  我注意到我们使用的C4是我很久以前制作的一个严重过时的资产，并没有使用与最新资产(PBR纹理)相同的艺术工作流程。
  所以它看起来有点不合适。
  它甚至与实际模型有完全不同的图标。
  我对旧版本进行了一些更改，将其替换为稍微升级。
  这也让我意识到我们没有抗辐射药的模型。
  希望当你服用所有这些辐射药片时，这些看起来会很好看
  在化合物上投掷近战武器和工具不应该让科学家疯狂。
  需要检查他们对某些刺激的高度感觉。
  由于从旧的感觉系统到新感觉系统的过渡中的一个错误，Junkpile科学家对服务器的最后补丁生效了。
  他们现在应该正确地尊重每台服务器允许的最大垃圾堆科学家数量。
  值得庆幸的是，负载均衡器似乎可以胜任超过200名垃圾邮件科学家在服务器上运行的任务，而不会导致任何重大性能命中。
  所有的科学家现在都应该在走路时更贴合地面，而不再漂浮在地面上。
  Junkpile科学家再次变得敌对，但仅限于那些过于接近的人。
  防止动物进入建筑物的技术看到了这个补丁的一些优化，但仍然可以做出很多改进。
  它应该有助于提高性能，特别是在大型基地被搜查时。
  于是，如何让界面保持清爽，同时又不 降低 工作 效率，成了很多开发者的最大痛点。
  我们程序员 开发过程中， 尤其是在程序调试的过程中， 通常会并行开 许多Terminal 窗口。
  不过时间一久， 可能 就会忘了每个终端都是用于来干嘛的。
  于是，如何让界面保持清爽，同时又不降低工作效率，成了很多 开发者的最大痛点 。
  是一款非常优秀的终端工作区和多路复用器(类似于tmux和screen)，由于使用Rust语言开发，因此与 与WebAssembly原生兼容。
  作为一款功能强大，同时又容易上手的终端复用工具，将会话(session)和窗口解耦，使得用户可以在单个窗口内运行多个虚拟终端，真正做到保持界面清爽还提高了工作效率。
  笔者注意到在过去的几个月中， 的开发团队一直在对 进行优化与排坑，他们发布了一些很多意义的技术博客来记录整个优化过程。
  博客中展示了一些非常值得总结和重视的问题，通过他们的分享我们可以看到， 的开发者们提出了很多创造性的解决方案。
  通过两个主要的技术提升点，他们大幅调优了 在大量显示刷新场景下的性能。
  下面我把相关技术博客为大家进行解读。
  Zellij看似简单，但实则代码量庞大，细抠所有技术细节，可能会把读者完全绕晕。
  因此本文使用的代码示例都是简化后的版本，仅用于讨论问题的示例。
  Zellij 是一个终端多路复用器，就像我们刚刚在截图中展示的那样，它允许用户创建多个“选项卡”和“窗口”，Zellij 会为每个终端窗口进行状态保持，其中状态信息包括文本、样式以及窗口内光标位置等要素。
  这种设计可以方便用户每次连接到现有会话时都保证用户体验的一致性，并可以支持用户在内部选项卡之间自由切换。
  不过状态在之前版本中 Zellij 窗口中显示大量数据时，性能问题会非常明显。
  例如，cat输入一个非常大的文件，这时Zellij会比裸终端仿真器慢得多，甚至比与其他终端多路复用器也慢。
  下面笔者将带着大家共同深入研究这个问题。
  Zellij使用多线程架构，PTY线程和Screen渲染线程执行特定任务并通过 互相通信。
  其中PTY线程查询 ，也就是用户屏幕上的输入、输出，并将原始数据发送到Screen线程。
  该线程解析数据并建立终端窗口的内部状态。
  PTY线程会将终端的状态呈现到用户屏幕上，并向Screen线程发送渲染请求。
  一种是轮循机制:PTY 线程不断轮询 PTY，以查看它在异步数据接收的while循环中是否有新数据。
  如果没有接收到数据，则休眠一段固定的时间。
  另一种是POLL机制:让数据流来驱动更新，这种设计一般认为效率比较高。
  如果 PTY 有大量数据流涌入，那么用户将在屏幕上实时看到这些数据的更新。
  task::spawn({
  // TerminalBytes是异步数据流 let mut terminal_bytes = TerminalBytes::new(pid);
  let mut last_render = Instant::now();
  let mut pending_render = false;
  let max_render_pause = Duration::from_millis(30);
  while let Some(bytes) = terminal_bytes.next().await {
  let receiving_data = !
  bytes.is_emPTY();
  if receiving_data {
  send_data_to_screen(bytes);
  pending_render = true;
  if pending_render &&last_render.elapsed() >max_render_pause {
  send_render_to_screen();
  last_render = Instant::now();
  pending_render = false;
  receiving_data {
  task::sleep(max_render_pause).await;
  为了测试这个大规模显示流程的性能，开发者们cat了一个 2,000,000 行的bigfile文件，并使用 基准测试工具，并使用--show-output参数来测试标准输出场景，并使用 进行对比。
  hyperfine --show-output "cat /tmp/bigfile"在 tmux 内运行的结果:(窗口大小:59 行，104 列)
  Time (mean ± σ): 5.593 s ± 0.055 s [User: 1.3 ms, System: 2260.6 ms]
  max): 5.526 s …5.678 s 10 runs
  hyperfine --show-output "cat /tmp/bigfile"在 Zellij 内部运行的结果:(窗口大小:59 行，104 列)
  可以看到优化前tmux的性能几乎是Zellij的8倍多。
  第一个性能问题是MPSC 通道的溢出，由于 PTY 线程和屏幕线程之间没有同步控制，PTY进程发送数据的速度要远比Screen线程处理数据的速度要快很多。
  PTY和SCREEN之间的不平衡将在以下几个方面影响性能:通道缓冲区空间不断增长，占用越来越多的内存
  屏幕线程渲染的次数远比合理值要高，因为屏幕线程需要越来越多的时间来处理队列中的消息。
  这个紧迫问题的解决方案是限制通道的缓冲区大小，并由此在两个线程之间创建同步关系。
  为此开发者们放弃了MPSC而选择了有界同步通道 提供了一个非常有用的宏select!
  此外，开发者们还删除了自定义的后台轮询的异步流实现，转而使用 async_std 以获得“异步 i/o”效果。
  我们来看看代码中的变化:
  let render_pause = Duration::from_millis(30);
  let mut render_deadline = None;
  let mut buf = [0u8; 65536];
  let mut async_reader = AsyncFileReader::new(pid); // 用async_std实现异步IO
  //以下是异步实现在deadline时进行特殊处理
  match deadline_read(&mut async_reader, render_deadline, &mut buf).await {
  ReadResult::Ok(0) | ReadResult::Err(_) =>break, // EOF or error ReadResult::Timeout =>{
  async_send_render_to_screen(bytes).await;
  render_deadline = None;
  ReadResult::Ok(n_bytes) =>{
  let bytes = &buf;
  async_send_data_to_screen(bytes).await;
  render_deadline.get_or_insert(Instant::now() + render_pause);
  改进之后的性能测试，如下:。
  hyperfine --show-output "cat /tmp/bigfile"(窗格大小:59 行，104 列):
  # Zellij before this fix
  # Zellij after this fix
  Time (mean ± σ): 9.658 s ± 0.095 s [User: 2.2 ms, System: 2426.2 ms]
  max): 9.433 s …9.761 s 10 runs
  5.678 s 10 runs
  虽然有了近一倍的性能提升，但从 Tmux 的数据来看，Zellij仍然可以做得更好。
  接下来开发者们又将管道绑定到屏幕线程，如果提高屏幕线程中两个相关作业的性能，能够使整个过程运行得更快:解析数据并将其渲染到用户终端。
  屏幕线程的数据解析部分的作用是将ANSI/VT等控制指令(如\r
  这样的回车或者换行符)转化为Zellij可以控制的数据结构。
  以下是这些数据结构的相关部分:
  struct Grid {
  viewport: Vec<Row>,
  cursor: Cursor,
  width: usize,
  height: usize,
  }struct Row {
  columns: Vec<TerminalCharacter>,
  }struct Cursor {
  x: usize,
  y: usize
  }#[derive(Clone, Copy)]struct TerminalCharacter {
  character: char,
  styles: CharacterStyles
  解析器执行最频繁的操作就是给一行文字内添加显示的字符。
  特别是在行尾添加字符。
  这个动作主要涉及将那些TerminalCharacters推入到列向量中。
  每个推送都涉及一个从堆上分配一段内存空间，这个内存分配的操作是非常耗时的，这点笔者在之前的博客《一行无用的枚举代码，却让Rust性能提升10%》中有过介绍。
  因此可以通过在每次创建行或调整终端窗口大小时预分配内存，来获得性能上的提升。
  所以开发者们从改变 Row(行)类的构造函数开始:impl Row {
  columns: Vec::new(),
  impl Row {
  pub fn new(width: usize) ->Self {
  columns: Vec::with_capacity(width),//通过指定capacity来预分配一段内存
  缓存字符宽度
  我们知道一些特殊的字符比如中文全角字符会比普通的英文字符占用更多的空间。
  这方面Zellij 又引入了 crate 来计算每个字符的宽度。
  在Zellij给一行内容中添加字符时，终端仿真器需要知道该行的当前宽度，以便决定是否应该将字符换行到下一行。
  所以它需要不断地查看和累加行中前一个字符的宽度。
  因为需要找到一个计算字符宽度的方法。
  #[derive(Clone, Copy)]struct TerminalCharacter {
  }impl Row {
  pub fn width(&self) ->usize {
  let mut width = 0;
  for terminal_character in self.columns.iter() {
  width += terminal_character.character.width();