<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: zerix</title>
    <description>The latest articles on Forem by zerix (@zerix).</description>
    <link>https://forem.com/zerix</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F307287%2Fe747c61b-6700-47d2-ace8-9ec72af94b3e.png</url>
      <title>Forem: zerix</title>
      <link>https://forem.com/zerix</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/zerix"/>
    <language>en</language>
    <item>
      <title>如何理解 Rust 语言生命周期管理</title>
      <dc:creator>zerix</dc:creator>
      <pubDate>Wed, 12 Apr 2023 10:04:44 +0000</pubDate>
      <link>https://forem.com/zerix/ru-he-li-jie-rustyu-yan-sheng-ming-zhou-qi-guan-li-546p</link>
      <guid>https://forem.com/zerix/ru-he-li-jie-rustyu-yan-sheng-ming-zhou-qi-guan-li-546p</guid>
      <description>&lt;h2&gt;
  
  
  前言
&lt;/h2&gt;

&lt;p&gt;前面我们了解了 Rust 语言的内存模型，知道了 Rust 语言精妙的无 GC 自动内存回收设计。那么，Rust 语言变量在何时开始回收，就属于这节我们需要了解的内容。所谓的生命周期，即一个变量从出现到销毁的过程，对应就是计算机内存的申请和释放。我们将在此节讲解 Rust 语言中局部变量，全局变量，函数变量等等变量生命周期的管理方式，深入理解 Rust 语言对该问题解决思路。&lt;/p&gt;

&lt;h2&gt;
  
  
  基础概念
&lt;/h2&gt;

&lt;p&gt;在前面一节中，我们知道了 Rust 所有权机制，那么，Rust 语言中是否是所有变量的赋值都涉及到所有权的变更呢？答案是否定的。在 Rust 语言中，涉及到赋值所有权相关的，主要有三种操作类型： MOVE、COPY 和 BORROW 。MOVE 操作即转移所有权，COPY 操作相当于直接复制对象，所有权并不变更，BORROW 操作相当于借用所有权，使用完毕是要归还所有权的。&lt;/p&gt;

&lt;p&gt;所有权的变更，都发生在赋值操作时。那么，什么时候的赋值操作是 MOVE，什么时候是 COPY，什么时候是 BORROW 呢？&lt;/p&gt;

&lt;h3&gt;
  
  
  MOVE &amp;amp; COPY 操作
&lt;/h3&gt;

&lt;p&gt;MOVE 操作，实际上就是转移对象的所有权。注意，这里对象的所有权转移，即是把对象的特征值拷贝过来，并释放原有对象特征值内存。这里的特征值可以理解成指针，不同类型的对象特征值不同，可能会是一些胖指针。MOVE 操作，是 RUST 语言变量赋值的默认操作，但是当对象实现了 COPY 特征，或者对象类型非常简单，比如整数，浮点数，字符，布尔，以及 COPY 类型的元组和固定大小数组等。这些对象的赋值，就直接是 COPY 操作了，相当于直接内存拷贝过去，目标对象和原有对象都有相同的值，类似 C++ 语言中的深度拷贝。&lt;br&gt;
在这里，如何确定哪些类型是 COPY 类型操作，有一条规则，就是&lt;strong&gt;&lt;em&gt;任何在值被清除后需要特殊处理的类型都不能是 COPY 类型&lt;/em&gt;&lt;/strong&gt;。比如，Vec 需要释放其元素，File 需要关闭其文件句柄，而 MutexGuard 需要解锁其互斥量。&lt;br&gt;
对于用户自定义的类型，比如 struct 和 enum，都不是 COPY 类型，但是，用户可以通过在自定义类型上添加 #[derive(COPY, Clone)] 宏来把类型标注成 COPY 类型，注意，该标注只有该自定义类型内所有字段都是 COPY 类型时才有效，不然编译会报错。&lt;br&gt;
&lt;strong&gt;&lt;em&gt;注意，将某种类型实现为 COPY 类型，对于实现者而言意味着庄严的承诺：若日后有必要将其改为非 COPY 类型，那用到他的代码，很多可能需要重写。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;COPY 和 CLONE 都属于特性(trait)的例子，后续文章会专门分析 trait 的实现。&lt;/p&gt;

&lt;p&gt;对于对象的生命周期而言，MOVE 意味着原始对象生命周期的终结，赋值对象初始化，获得了所有权，意味着什么周期开始。而 COPY 操作则直接复制了值内容，不涉及到原始对象生命周期的变更。&lt;/p&gt;
&lt;h3&gt;
  
  
  BORROW 操作
&lt;/h3&gt;

&lt;p&gt;BORROW 操作只针对的是引用类型，引用类型是一种指针类型。在引用类型赋值时，相当于把对象所有权借用给了目的对象，在目的对象解引用时，会归还对象的所有权给原始对象。&lt;/p&gt;
&lt;h2&gt;
  
  
  生命周期
&lt;/h2&gt;

&lt;p&gt;了解了 MOVE、COPY 和 BORROW 操作，我们来看看一个变量的什么周期管理是如何影响 Rust 代码编写的。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                &lt;span class="c1"&gt;// ---------+-- 'a&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;                          &lt;span class="c1"&gt;//          |&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;                     &lt;span class="c1"&gt;//          |&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// -+-- 'b  |&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;//  |       |&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;                     &lt;span class="c1"&gt;// -+       |&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;                          &lt;span class="c1"&gt;//          |&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"r: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//          |&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这段代码中，如果编译，会发现第9行报错。主要原因是，第6行将x的引用赋值给r，相当于讲所有权借用给了r，到第7行，退出作用域，x销毁，成为悬停指针，那么第9行打印r，相当于引用了一个空值，所以报错。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;注意，实际上面的解释存在瑕疵，x赋值为5的数值常量，是存放在静态区的，他的生命周期是跟随全程序，第7行只是调用了drop trait把x值变成不可用。&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;从上面例子可以看出来，Rust 语言可以在编译阶段推断出对象的生命周期，并且括号在 Rust 语言中表示作用域范围。在 Rust 语言中，包括且不限于以下几种结构中的大括号都有自己的作用域：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;if、while等流程控制语句中的大括号&lt;/li&gt;
&lt;li&gt;match模式匹配的大括号&lt;/li&gt;
&lt;li&gt;单独的大括号&lt;/li&gt;
&lt;li&gt;函数定义的大括号&lt;/li&gt;
&lt;li&gt;mod定义模块的大括号&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;除了括号，引用的生命周期还受到&lt;strong&gt;&lt;em&gt;解引用&lt;/em&gt;&lt;/strong&gt;的影响。&lt;/p&gt;

&lt;p&gt;那么，Rust 语言是否能做到所有对象各种场景下的生命周期推断呢？答案是否定的，在一些复杂情况下，我们需要手动标注生命周期来解决该问题。&lt;/p&gt;

&lt;h2&gt;
  
  
  生命周期标注
&lt;/h2&gt;

&lt;p&gt;Rust 语言中，自动推断对象的生命周期需要遵循下面规则：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;每一个引用参数都会获得独自的生命周期。&lt;/li&gt;
&lt;li&gt;若只有一个输入生命周期(函数参数中只有一个引用类型)，那么该生命周期会被赋给所有的输出生命周期，也就是所有返回值的生命周期都等于该输入生命周期。
3.若存在多个输入生命周期，且其中一个是 &amp;amp;self 或 &amp;amp;mut self，则 &amp;amp;self 的生命周期被赋给所有的输出生命周期。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当超出上面3种推断规则，我们就需要进行生命周期标注。需要注意的是，生命周期标注的作用是向编译器告知引用之间的 drop 关系，而非扩展或延长引用的存在时间。&lt;/p&gt;

&lt;p&gt;我们来看一个例子：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;string1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"abcd"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;string2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"xyz"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;longest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string1&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;string2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The longest string is {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;longest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;上面的代码主要功能是找出最长的一个字符串，我们编译就会发现该段代码会报错。根据自动推断的三条规则，我们发现longest函数有两个入参，那么，编译器就会推断不出返回值到底是和哪个参数的生命周期一致了，这个时候，我们对longest的参数进行生命周期标注，就可以解决该问题。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="n"&gt;longest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;标记语法需要注意下：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;和泛型一样，使用生命周期参数，需要先声明 &amp;lt;'a&amp;gt;。&lt;/li&gt;
&lt;li&gt;x、y 和返回值至少活得和 'a 一样久(因为返回值要么是 x，要么是 y)。
代码标记了两个入参的生命周期和返回值的生命周期一致，那么在后续使用过程中，返回值的实际生命周期是和两个入参生命周期中最短的一个一致，所以一定要注意这一点，标记生命周期只是告诉编译器不要报错，实际调用的适合需要了解返回值的生命周期是两个入参中最短的一个。可以看下面代码：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;string1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"long string is long"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;string2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"xyz"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;longest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string1&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;string2&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The longest string is {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;该段代码就会报错，实际上string2在括号外已经失效了，那么再去访问result返回参数，就会报错。&lt;/p&gt;

&lt;p&gt;当然，在实际编码过程中，在很复杂的情况下，如果发现编译器推断的生命周期有问题，我们还可以使用杀手锏来屏蔽掉编译错误（当然，首先你要确认代码没问题）。使用 'static 生命周期标注，拥有该生命周期的引用可以和整个程序活得一样久。可以把他理解为 C 语言里面的全局变量，当然也符合 C 语言开发实践里的“非必要不要使用全局变量”箴言。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"持续到整个程序结束，活得久"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>agentaichallenge</category>
      <category>gratitude</category>
    </item>
    <item>
      <title>如何理解 Rust 语言内存模型</title>
      <dc:creator>zerix</dc:creator>
      <pubDate>Sun, 22 Jan 2023 13:10:25 +0000</pubDate>
      <link>https://forem.com/zerix/rustyu-yan-nei-cun-mo-xing-fen-xi-65h</link>
      <guid>https://forem.com/zerix/rustyu-yan-nei-cun-mo-xing-fen-xi-65h</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;随着Rust越来越多的应用良好表现，吸引了越来越多的开发者关注和各领域对应的Rust解决方案出现。对于从C/GO/Java/Python这些语言开发者来说，学习Rust语言最大的挑战就是需要理解Rust语言的内存管理模型。而Rust开创性的所有权管理机制，是我们理解和精通该门语言必须要首先弄清楚的要点。而这也是为什么大家一致认为Rust的学习曲线陡峭的最核心原因。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;本系列文章主要是阅读图灵系列丛书《Rust程序设计》读书笔记，再以有经验程序员新学Rust语言的路线来编写。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  示例
&lt;/h2&gt;

&lt;p&gt;我们首先来看一段代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;34&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;aside&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这段代码逻辑很简单，仅仅是定义了三个变量，并做一些赋值和打印操作。三个变量定义如下：&lt;br&gt;
1.变量v，定义v为一个向量，其内包含了6个Int类型数字。&lt;br&gt;
2.变量r，再将v的地址赋值给变量r，相当于r是v向量变量的引用。&lt;br&gt;
3.变量aside，最后将变量v赋值给变量aside。&lt;br&gt;
最后打印r变量的第0个元素。&lt;br&gt;
如果根据我们以往的编程经验，该段代码找不出来任何逻辑和写法问题，但是在Rust中，编译运行该段代码，其会报错：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;warning: unused variable: &lt;span class="sb"&gt;`&lt;/span&gt;aside&lt;span class="sb"&gt;`&lt;/span&gt;
 &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; src/main.rs:4:9
  |
4 |     &lt;span class="nb"&gt;let &lt;/span&gt;aside &lt;span class="o"&gt;=&lt;/span&gt; v&lt;span class="p"&gt;;&lt;/span&gt;
  |         ^^^^^ &lt;span class="nb"&gt;help&lt;/span&gt;: &lt;span class="k"&gt;if &lt;/span&gt;this is intentional, prefix it with an underscore: &lt;span class="sb"&gt;`&lt;/span&gt;_aside&lt;span class="sb"&gt;`&lt;/span&gt;
  |
  &lt;span class="o"&gt;=&lt;/span&gt; note: &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="c"&gt;#[warn(unused_variables)]` on by default&lt;/span&gt;

error[E0505]: cannot move out of &lt;span class="sb"&gt;`&lt;/span&gt;v&lt;span class="sb"&gt;`&lt;/span&gt; because it is borrowed
 &lt;span class="nt"&gt;--&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; src/main.rs:4:17
  |
3 |     &lt;span class="nb"&gt;let &lt;/span&gt;r &lt;span class="o"&gt;=&lt;/span&gt; &amp;amp;v&lt;span class="p"&gt;;&lt;/span&gt;
  |             &lt;span class="nt"&gt;--&lt;/span&gt; borrow of &lt;span class="sb"&gt;`&lt;/span&gt;v&lt;span class="sb"&gt;`&lt;/span&gt; occurs here
4 |     &lt;span class="nb"&gt;let &lt;/span&gt;aside &lt;span class="o"&gt;=&lt;/span&gt; v&lt;span class="p"&gt;;&lt;/span&gt;
  |                 ^ move out of &lt;span class="sb"&gt;`&lt;/span&gt;v&lt;span class="sb"&gt;`&lt;/span&gt; occurs here
5 |     println!&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"{}"&lt;/span&gt;, r[0]&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  |                    - borrow later used here

For more information about this error, try &lt;span class="sb"&gt;`&lt;/span&gt;rustc &lt;span class="nt"&gt;--explain&lt;/span&gt; E0505&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;从这段错误，我们可以得出以下结论：&lt;br&gt;
1.Rust能够在编译阶段发现代码runtime阶段内存错误问题。&lt;br&gt;
2.Rust语言编译时能够非常详尽的解释编译错误。&lt;br&gt;
3.编译器提示在将v的引用赋值给r时，相当于将v借用给了r，借用完成后，再次将v赋值给aside操作时，所有权出现了Move操作，v的所有权到了变量aside之上，这个时候，再去访问r变量，就会出现报错，相当于访问了一个悬空指针。&lt;br&gt;
而这个报错，正是由于Rust的所有权机制导致的。那么，我们这篇文章主要是为了解释下面两个问题:&lt;br&gt;
1.Rust所有权制度到底是为了解决什么问题出现的？&lt;br&gt;
2.Rust所有权制度如何解决该问题的？&lt;/p&gt;
&lt;h2&gt;
  
  
  Rust内存模型
&lt;/h2&gt;

&lt;p&gt;在我们过往的编程语言使用经验中，我们都了解类似C/C++语言，内存管理都是靠程序员手动来维护的，new/free的操作都是程序员自己去控制，其性能非常高，但是由此引入了非常多的内存问题，比如访问了已经释放了的内存地址，或者内存没有释放导致内存泄漏从而系统不稳定等等问题。为了最大限度避免这些问题，在使用C/C++语言时，大多数程序员都要手写一个内存池来进行内存管理，而由此带来的内存碎片等问题都不容易处理。而Java类的，内存管理都由Jvm虚拟机来完成，减少了程序员的出错，但是Jvm虚拟机进行自动内存整理(GC)时，又带来了很大的CPU波动。影响了业务系统，导致业务系统在高性能计算时由于GC出现而出现大幅度时延。&lt;br&gt;
而Rust即想得到C/C++手动管理内存的性能，又想自动化去管理内存，还要避免类似Java统一GC的性能损失。不得不说，这种成年人的选择总会让人为之精神一振。在此背景下，Rust所有权系统应运而生，做到了熊掌与鱼兼得，后续，我们主要分析下Rust所有权系统是如何工作的。&lt;/p&gt;
&lt;h2&gt;
  
  
  Rust内存结构示例
&lt;/h2&gt;

&lt;p&gt;Rust所有权系统主要是用来做自动化内存管理，那么，我们首先分析下Rust代码内存结构。其示意图如下所示：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnx7zpi93u1y03ijhrw1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnx7zpi93u1y03ijhrw1g.png" alt="Image description" width="703" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;和C语言类似，Rust程序内存布局包括了堆、栈、静态数据区、只读数据区和只读代码区。&lt;br&gt;
其中，对于每个区存放的内容，大抵可以如下分类：&lt;br&gt;
1.栈：在编译阶段就可以确定哪些数据可以存放到栈上，由编译器管理，函数局部变量等，存放到栈中。&lt;br&gt;
2.堆：由程序员编写的代码来申请使用，一般做大量数据读写时使用，运行时申请。&lt;br&gt;
3.静态数据区：一般的静态函数、静态局部变量和静态全局变量存放区域，在程序启动时初始化。&lt;br&gt;
4.Literals（只读数据区）：存放代码的文字常量区域。&lt;br&gt;
5.Instructions（只读代码区）：存放可执行代码区域。&lt;/p&gt;

&lt;p&gt;我们以一段Rust代码来举例，了解下Rust中各变量是如何存储的，代码如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;noodles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"noodles"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;oodles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;noodles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;poodles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"👁_👁"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;该段代码内存布局如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fxzquzvfwcheb598c49.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fxzquzvfwcheb598c49.png" alt="Image description" width="800" height="320"&gt;&lt;/a&gt;&lt;br&gt;
分析内存结构图，我们可以了解到下面结论：&lt;br&gt;
1.noodles,oodles,poodles三个变量都存储在栈上，并且都是三个胖指针。&lt;br&gt;
2.noodles和oodles是指向同一块内存，只不过指针首地址不一样。&lt;br&gt;
3.poodles变量的数据内容是存储在预分配的只读内存区。&lt;br&gt;
4.noodles的变量存储格式包括三个部分，第一个部分是指向数据存储堆内存首地址，第二个部分是该变量的容量，第三个部分是该变量的长度。(实际上Rust里String是用Vec来实现的，所以这里的容量是Vec管理策略来决定，Rust里分配原则是2-&amp;gt;4-&amp;gt;8，如果容量不够，下次申请的为前一次的2倍。)&lt;br&gt;
5.字符串常量poodles的内存是提前分配好的只读内存区。&lt;br&gt;
6.引用并不做深度拷贝操作，仅仅是指针指向数据堆内存地址。&lt;/p&gt;
&lt;h2&gt;
  
  
  多语言内存赋值解析
&lt;/h2&gt;

&lt;p&gt;内存管理体现在每个语言对赋值操作的实现中，我们可以对比下Python、C++和Rust这几种比较有代表性的语言，了解下他们各自对赋值操作内存是如何管理的。&lt;/p&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;p&gt;我们以下面代码为例：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;udon&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ramen&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;soba&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;整个操作的内存变化如下图所示：&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkg8dr2tv0mybopgq4ri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdkg8dr2tv0mybopgq4ri.png" alt="Image description" width="800" height="380"&gt;&lt;/a&gt;&lt;br&gt;
我们分析可以得出结论：&lt;br&gt;
1.Python的字符串和列表底层都是胖指针的形式存储，列表指针的存储内容为：引用计数，列表长度，列表数据指针，列表容量。字符串指针的存储内容为：引用计数，字符串长度，文本数据内容。&lt;br&gt;
2.局部变量存储在栈中。&lt;br&gt;
3.赋值操作过程为，t = s，新建一个对象t，指向s的内存地址，并将s对象的引用计数+1。u = s，再新建一个对象u，指向s的内存地址，并将s对象的引用计数+1，s对象的引用计数为3，表示被3个对象使用。&lt;br&gt;
4.释放s内存数据得维护s的引用计数，引用计数为0时可以清理该内存数据。&lt;/p&gt;
&lt;h3&gt;
  
  
  C++
&lt;/h3&gt;

&lt;p&gt;对应的，C++赋值示例代码如下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"udon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ramen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"doba"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;该段代码内存结构变化如下图所示：&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj66zqx9zmiy4tj83kav.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faj66zqx9zmiy4tj83kav.png" alt="Image description" width="800" height="457"&gt;&lt;/a&gt;&lt;br&gt;
分析后，我们可以得出结论：&lt;br&gt;
1.向量s局部变量在内存中存储在栈中。其也是一个胖指针，三个字段内容为向量数据堆内存地址，向量占用空间大小，向量长度。堆内存地址数据存储也是三个胖指针，指针地址字段指向的分别是三个字符串的内存地址。&lt;br&gt;
2.t = s操作过程实际上是复制了一份s对象的数据，包括堆内存数据，并将新的堆内存数据指向t胖指针的堆内存数据地址。&lt;br&gt;
3.u = s操作过程和t = s操作过程一致。&lt;br&gt;
4.完成赋值操作后，内存中有三份s对象一样的数据，存储在不同的堆中。&lt;br&gt;
5.释放s，t，u三个对象内存很简单，各自维护自己生命周期即可。&lt;/p&gt;
&lt;h3&gt;
  
  
  Rust
&lt;/h3&gt;

&lt;p&gt;最后，我们再来看Rust赋值代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nd"&gt;vec!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"udon"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"ramen"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"soba"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;()];&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rust内存结构变化如下图所示：&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fga4x0js1mzmd5berk9ky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fga4x0js1mzmd5berk9ky.png" alt="Image description" width="800" height="471"&gt;&lt;/a&gt;&lt;br&gt;
我们可以得出结论：&lt;br&gt;
1.Rust中向量存储和字符串存储方式和C++一样，都是胖指针，指针内容格式一致。&lt;br&gt;
2.t = s操作，将t胖指针的堆内存数据地址指向s的堆内存数据地址，s对象变成悬空指针，无法访问。&lt;br&gt;
3.u = s操作，会报错，此时s为悬空指针，不能访问。&lt;br&gt;
4.向量s在堆内存中数据只有一份。&lt;br&gt;
5.释放数据的操作也简单，因为堆内存中只有一份数据，在脱离了作用域后会自动释放内存数据。&lt;/p&gt;
&lt;h3&gt;
  
  
  三类语言对比
&lt;/h3&gt;

&lt;p&gt;对比C++，Python和Rust语言在相同赋值语句下，其内存布局变化，我们可以很直观的得出下面结论：&lt;br&gt;
1.整个赋值过程占用内存最小的是Rust语言和Python语言。&lt;br&gt;
2.C++语言赋值操作最为笨重，需要做数据深度拷贝。&lt;br&gt;
3.在释放内存操作时，最高效简单的是Rust语言和C++语言。&lt;br&gt;
4.Python语言在释放内存时需要维护引用计数，较为复杂。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;注意，这里只是对比最常用情况，实际上Rust也支持类似Python的引用计数内存管理方法和C++的深度拷贝操作。有兴趣可以去了解相关文档。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  引用介绍
&lt;/h2&gt;

&lt;p&gt;在上述变量赋值操作过程中，实际上是一个变量所有权转移的过程。那么，是否可以直接使用类似C语言指针的方式去操作一个变量呢？答案是肯定的，Rust提供一种引用的数据类型来完成此目的。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Rust中对变量的引用，称之为借用（Borrowing），使用完毕后，需要归还。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;我们来看一段示例代码：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Rc&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;Rc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Rc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"shirataki"&lt;/span&gt;&lt;span class="nf"&gt;.to_string&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;Rc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;Rc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;这段代码中，是用了Rust的Rc包来创建一个可以被多个变量同时借用的引用变量，Rust中还提供一个Arc包来实现相同功能，区别是Rc非线程安全型，Arc是线程安全型的。Rc使用引用计数方式来实现，Arc是Atomic Rc。Arc相比Rc会额外带来性能损耗，需要用户根据场景选用。&lt;/p&gt;

&lt;p&gt;引用适用领域：&lt;br&gt;
1.图操作中一个点被多个边包含。&lt;br&gt;
2.一个变量被多个线程同时操作。&lt;/p&gt;

&lt;p&gt;上段代码在内存中的存储格式为：&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qjfex64ad5zejp6izk6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0qjfex64ad5zejp6izk6.png" alt="Image description" width="775" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;为了保证整个机制在各个场景下的可靠，不出现数据竞争情况，Rust引入了所有权树。&lt;/p&gt;
&lt;h2&gt;
  
  
  所有权树
&lt;/h2&gt;

&lt;p&gt;Rust中，所有权规则总结如下：&lt;br&gt;
1.Rust中的每个值都有一个被称为其所有者的变量(即：值的所有者是某个变量)。&lt;br&gt;
2.值在任一时刻有且只有一个所有者。&lt;br&gt;
3.当所有者(变量)离开作用域，这个值将被销毁。&lt;/p&gt;

&lt;p&gt;如下图所示，变量的所有权可以被借用，主要包括了可变引用和非可变引用。这里的可变引用和不可变引用可以使用读写锁来理解。通常来说，只读的不可变引用即是只读引用，变量可以被多个只读引用来同时借用，由于是只读的，所以不存在变量共享问题。而可修改引用，则要求一个变量只能被一个可修改引用借用，而且，对该变量的访问，只能通过该可变引用来访问。规则如下图所示：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni4iifs2cdqct9k3jg7b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fni4iifs2cdqct9k3jg7b.png" alt="Image description" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;所有权树表示了所有权行为都是可以推导的，也就是说在编译阶段编译器即可发现各类的内存管理问题，所以这也是Rust内存安全性的保证。&lt;/p&gt;
&lt;h2&gt;
  
  
  所有权示例代码分析
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;大家可以通过下面的代码片段及后面注释中的解释来理解Rust的所有权树。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;1.代码片段1：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;//可以，允许多次共享借用&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;//错误，不能给x赋值，因为他已经被借用了&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;//可以&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;//错误，不能借两次给可修改引用&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;//错误，不能直接访问y，因为他已经借出了可修改引用&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;代码片段2：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;//可以，共享引用可以再借用为共享引用&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;//错误，共享引用不能再借用为可修改引用&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;代码片段3：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;136&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;139&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;m0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="na"&gt;.0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;//可以，可修改引用可以再借用为可修改引用&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;m0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;137&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;r1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;//可以，可修改应用可以再借为共享引用，且不与可 &lt;/span&gt;
                             &lt;span class="c1"&gt;//修改引用m0重叠&lt;/span&gt;

&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="na"&gt;.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                  &lt;span class="c1"&gt;//错误，禁止通过其他路径访问可修改引用&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  思考
&lt;/h2&gt;

&lt;p&gt;1.Rust中是否不存在内存泄漏？&lt;/p&gt;

&lt;p&gt;如下图所示，在Rust中可以创建引用循环，在此情况下，引用计数永远不可能为0，就会发生内存泄漏。&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm8hruu89ac2mm2jwbsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm8hruu89ac2mm2jwbsd.png" alt="Image description" width="574" height="451"&gt;&lt;/a&gt;&lt;br&gt;
为了避免该问题，Rust中引入了RefCell机制，该机制不在本文详细描述，大家可以搜索下相关文章，后续其他文章也会专门讲解。&lt;/p&gt;

</description>
      <category>gratitude</category>
    </item>
    <item>
      <title>如何设计持续集成代码版本发布流程</title>
      <dc:creator>zerix</dc:creator>
      <pubDate>Fri, 13 Jan 2023 02:34:50 +0000</pubDate>
      <link>https://forem.com/zerix/gitopsban-ben-fa-bu-liu-cheng-gui-fan-5ejn</link>
      <guid>https://forem.com/zerix/gitopsban-ben-fa-bu-liu-cheng-gui-fan-5ejn</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;为了减少沟通成本，提高开发和运维效率，需要提供一套规范的持续迭代发布Apps的流程。而规范的主要目的不在于整体流程本身，在于需要形成一套能够持续迭代优化的发布机制。本文在于提供一套较为完善的代码持续迭代发布的流程方案。&lt;/p&gt;

&lt;h2&gt;
  
  
  目标
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;保证完善的发布更新流程，减少回滚的次数。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;减少手动操作行为，尽可能自动化完成。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;减少发布到客户环境后出现过多问题的次数。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  关键环境约束
&lt;/h2&gt;

&lt;p&gt;一般来说，完善的发布流程需要测试环境、预发布环境、生产环境，并且每个环境的权限配置和操作使用者是完全不一样的。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;测试环境，大家所有人都是管理员权限，可以操作任何资源，没有限制，所有人员可以进入ssh后台。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;预发布环境，仅仅只有测试人员才有操作权限，其他人员只有读的权限，比如查看配置和查看日志，没有任何写权限。只有测试人员才可以进入ssh后台。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;生产环境，仅仅只有发布人员有操作权限，其他所有人员只有读的权限，只有发布人员可以进入ssh后台。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;预发布环境是开发人员在测试环境测试通过后，测试团队根据开发人员填写的发布文档发布到预发布环境，注意严格按照发布文档来操作，相当于所有操作有记录，避免生产环境发布不成功。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;生产环境发布是发布人员在经过测试人员确认发布文档内容可以在生产环境操作后触发。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;再次要注意几点：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;预发布环境或者生产环境发现BUG，最好能够通过日志系统定位到，如果不行的话需要增加相关日志来定位BUG，实在需要主机ssh权限需要申请并说明理由，并且要思考后续是否可以在代码中解决来避免该问题。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;预发布环境一般是测试一周时间，是否测试通过并且可以发布到生产环境是由测试人员来决定。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;各种更新升级或者数据库刷数最好能做到自动化完成，减少发布人员的手动操作。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  发布流程图
&lt;/h2&gt;

&lt;p&gt;要保证更新和发布自动化完成，首先，需要两部分的资源：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;所有APP配置代码化。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;只有app安装部署行为代码化后，才可以使用git来进行版本的管理，才能做到可记录，可追溯，可更新，可回滚。（在Kubernetes环境下可以使用Pulumi/Terraform来实现IaC）&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;所有APP都需要容器化，以镜像对外发布内容。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;APP配置分支和APP内容分支之间，仅仅通过镜像来完成两边内容的匹配，并且APP的配置分支命名规则必须和APP内容分支命名规则一致，且发布规则一致。&lt;br&gt;
举例：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;项目Hive repo，
分支名称为release#v1.0-20221202，
那么，该分支构建出来的Hive镜像tag为v1.0-20221202。

在pulumi apps repo，
分支名称也要为release#v1.0-20221202,
且hive pulumi app中镜像对应的tag和hive项目构建出来的镜像tag一致，为v1.0-20221202。

备注：项目repo和app repo是独立的两个repo，项目repo主要是项目代码，并完成artifact构建或者docker镜像构建。app repo是该项目对应的应用定义，可能一个项目repo对应多个app。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  发布操作流程如下：
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;开发人员编码，完成镜像构建和测试，并完成app分支镜像版本更新。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;每周五，测试人员发布下下周上生产(下周上预发布环境)的上线单，通知有发布功能人员填写。&lt;br&gt;
发布单内容主要有：app名称，app repo地址，app repo是否创建对应release分支，是否有手动操作，发布失败回归操作方法，预发布环境/生产环境验证是否通过。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;每周一，测试人员根据发布单完成预发布环境app更新，并通知相关人员验证，完成验证操作后将验证结果填写到发布单，验证失败则要bugfix，并更新上线单再次上线。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;测试人员一周内进行测试和验证。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;测试人员验证完毕，可以发布，如果未验证完毕，则需要将推迟发布的app内容填写到下一次发布单上面。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;每周一，生产环境发布人员检查本周生产环境上线内容，完成发布，并通知测试人员和相关人员验证。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;验证通过，则发布完成，验证失败，则需要提bugfix进行紧急修复，并且要思考该问题为何预发布环境没有发现，后续如何避免。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;备注：整体系统功能性验证应该是自动化运行的脚本，预发布环境发布或者生产环境发布后，测试人员会进行统一自动化测试。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  备注
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;重大release分支发布一般会有一个较长的周期，主要区别点在于，重大release分支测试必须要更全面，测试人员，产品人员和相关开发都需要走一遍所有的功能流程。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;重大release分支内容一般要开发/产品/测试提前沟通确定好，确定好内容后，该分支原则上不会做新功能添加，只会做bug修复，相关新功能进入dev分支，走下一次大版本发布。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;在确定了release分支可发布后，需要申请空的测试环境，并由产品或者其他测试人员走几遍整体从0开始安装发布流程和验证流程，相关测试验证做了2-3次后，可以封版。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  版本命名规则
&lt;/h2&gt;

&lt;p&gt;在开源世界，实际上有一个通用的代码版本标准，称作 SemVer 规范。具体参考如：&lt;a href="https://semver.org/"&gt;https://semver.org/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gitops</category>
      <category>workflow</category>
      <category>devops</category>
      <category>ci</category>
    </item>
    <item>
      <title>如何设计适合持续集成的代码分支管理体系</title>
      <dc:creator>zerix</dc:creator>
      <pubDate>Fri, 13 Jan 2023 02:25:29 +0000</pubDate>
      <link>https://forem.com/zerix/gitopsri-chang-kai-fa-ti-xi-5h1n</link>
      <guid>https://forem.com/zerix/gitopsri-chang-kai-fa-ti-xi-5h1n</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;常见的Git开发流程中，建立好分支管理和发布规则，以及良好的日常开发分支管理，能够较大提高开发人员效率，降低沟通维护成本，便于各个项目的codeReview，提供整个产品质量。&lt;/p&gt;

&lt;h2&gt;
  
  
  分支管理配置
&lt;/h2&gt;

&lt;p&gt;首先，有几条规则需要确定。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;dev/master分支不允许直接push，向这两个分支合并代码需要提交MR或者PR。&lt;br&gt;
所有release分支以 release#VERSION-日期 来命名，比如release#4.0-20221212 表示2022年12月12日发布的4.0分支。relase开头的分支同样不允许直接PUSH，必须提MR来合并代码。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;客户定制需求相关分支以  project#客户名称#VERSION-日期 来命名，比如 project#xinjiangkashi#4.0-20221212，表示新疆喀什的客户分支，project开头的分支不允许直接PUSH，必须提MR来合并代码。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;新功能开发分支为 feature#原始分支名#新增功能说明，比如 feature#dev#added-spark-locality-support ，表示新功能开发，从dev分支checkout出来，新功能是增加 spark locality功能。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;bug修复分支以 bugfix#原始分支名#修复BUG说明，比如 bugfix#release#4.0-20221212#fix-null-point-bug。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;hotfix分支，一般仅仅作为 release或者project 分支发布后，出现了问题，需要紧急修复，可以创建该分支，创建规则一致。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;feature/bugfix/hotfix提交到了一个分支后，如果该功能需要提交到其他分支，可以在页面上操作 cherry-pick 到其他即可。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;dev/master/release*/project*开头的分支在git/gitlab服务端配置成保护分支，其他人就不能直接push。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  GIT workFlow示例：
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvbaynj65qfdub514k8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjvbaynj65qfdub514k8d.png" alt="Image description" width="800" height="1059"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  日常开发流程
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;本文以dev为主要开发分支来介绍，dev分支内包含测试过可以上线的最新代码，根据不同情况，也可以使用master分支来做为开发主分支，遵循策略一致。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  新功能开发流程
&lt;/h3&gt;

&lt;p&gt;1.从dev分支checkout出来feature分支，checkout命令：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature#dev#added-locality-support
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;说明：feature表示新功能开发，第一个#和第二个#之间内容为checkout原始分支名称，如果是从master分支切换出来，则为feature#master#，最后一部分内容为feature具体新增功能。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;2.编写代码，完成测试，测试完成后提交代码到本地，命令：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-am&lt;/span&gt; &lt;span class="s2"&gt;"added locality function."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3.提交代码到远程。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git push origin feature#dev#added-locality-support
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4.进行CI构建，测试，测试通过后，提交MR/PR merge到dev分支，相关人员review后最终merge到dev分支。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;此处可以配置仓库权限，只有仓库的maintainer可以merge，其他开发人员提交MR后，maintainer需要进行codeReivew，通过后才可以merge到分支，提MR的分支必须要保证可以编译通过，可以正常运行。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;在此条件下，dev分支任何时候去构建，都是正常可以编译通过，并且可以发布正常运行的。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;5.确认该分支是否需要 cherry-pick 到其他分支，如果需要，则操作 cherry-pick 流程到其他分支。&lt;/p&gt;

&lt;h3&gt;
  
  
  bug修复流程
&lt;/h3&gt;

&lt;p&gt;BUG修复流程和上述开发流程一致，仅仅是分支名称不一样而已，但是bug的修复要注意提交到release分支后，也需要cherry-pick到dev分支。&lt;/p&gt;

&lt;h2&gt;
  
  
  Release分支管理
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;每周从dev分支checkout出来release分支，如果发布到预发布环境，可以在分支名称末尾增加 -rc 字样来表示，预发布环境测试通过后，从rc分支再checkout出正式分支，发布。（此处相当于release包含两个分支，-rc为预发布环境分支，不带-rc为生产环境分支，上生产后，两个分支内容应该一致。）&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;对project分支，如果不想维护太多project分支，对每个客户可以只使用一个project分支，在每次发布后，增加tag来标识此次发布行为，tag仅仅做标识，不和CI结合。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;对于 一个分支是否需要向多个分支进行cherry-pick，需要根据业务场景来确定，比如有些定制化内容，不需要进入dev分支，则不需要cherry-pick到dev，比如一个bugfix需要更新多个project分支和release，则需要逐个cherry-pick。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CI可以根据不同的分支来进行相应的构建，推荐使用gitlab CI直接可以通过不同分支的配置来进行触发构建。&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>git</category>
      <category>gitops</category>
    </item>
    <item>
      <title>如何使用 Pulumi 构建云平台 Apps 项目</title>
      <dc:creator>zerix</dc:creator>
      <pubDate>Wed, 11 Jan 2023 02:12:20 +0000</pubDate>
      <link>https://forem.com/zerix/pulumi-xiang-mu-zu-zhi-shi-jian-4fin</link>
      <guid>https://forem.com/zerix/pulumi-xiang-mu-zu-zhi-shi-jian-4fin</guid>
      <description>&lt;h2&gt;
  
  
  背景
&lt;/h2&gt;

&lt;p&gt;Iac 领域，terraform 应用比较广，之前公司主要也是使用 kubevela 来进行整个平台组件的架构部署。&lt;br&gt;
在使用过程中，kubevela 本身的弊端也显而易见。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;kubevela 本身不支持模板，为了支持不同的测试、预发布、生产环境的配置，或者其他需要动态化配置，则需要在 kubevela 之上再封装一层模板解析过程代码，增加了整体维护复杂性。&lt;/li&gt;
&lt;li&gt;在一些全局功能中，比如日志收集 sidecar 注入或者 Prometheus 数据接入等通用功能，依赖平台对 kubevela 的扩展能力，即 workload type 和 traits 的自定义支持，从而增加了对底层平台开发人员的依赖，影响 app 开发集成效率。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;基于这两点考虑，我们调研了 Pulumi 对此类问题的解决方案。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pulumi 使用 golang 等通用编程语言来管理整个 app 的部署生命周期，并且使用 stack 来管理不同的测试、预发布、生产环境的配置，增加了整个架构部署平台的代码复用能力。&lt;/li&gt;
&lt;li&gt;Pulumi 在构建整个架构代码时，每个 app 都是独立的 pulumi project，用户直接使用 Pulumi api 来操作，并不需要依赖底层架构开发人员的支持，减少了沟通成本，增加了 app 开发部署效率。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  目标
&lt;/h2&gt;

&lt;p&gt;基于目前公司的业务，我们对底层基础架构平台有两点基本要求，后续所有问题解决方案参考该基本要求来确认边界。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;app 的增加集成要方便开发人员测试，开发人员可以使用 pulumi 相关原生命令来测试整个 app 的创建、更新、删除等等流程管理，而不需要其他外部资源支持。&lt;/li&gt;
&lt;li&gt;对于整体基础架构的发布，要方便平台发布人员操作使用，平台发布人员不需要对基础架构代码进行任何修改，完成一键发布、更新等操作，对于每次发布，需要生成相关的发布 preview ，方便和开发人员来确认发布计划是否存在偏差。&lt;/li&gt;
&lt;li&gt;所有 app 的管理统一集中管理，方便运维开发人员进行整体平台创建。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Pulumi 项目结构规划
&lt;/h2&gt;

&lt;p&gt;确认了目标之后，我们确定 pulumi-apps 的项目结构需要围绕这两个目标来构建。&lt;br&gt;
由于Pulumi stack机制的限制，如果两个project都公用相同的stack名称时，操作一个project会删除掉另外一个project，主要原因还是pulumi后台stack管理的BUG，官方有相关讨论，暂时还未修复，参见Issues：&lt;br&gt;
&lt;a href="https://github.com/pulumi/pulumi/issues/8402"&gt;https://github.com/pulumi/pulumi/issues/8402&lt;/a&gt;&lt;br&gt;
在基于此情况下，我们在automation中管理各个apps的安装，删除和与发布操作时，每个app都使用其app名称+dev/prod/test后缀来命名每个app project对应的stack名称，整个automation使用Pulumi automation API实现，参见文档：&lt;br&gt;
&lt;a href="https://www.pulumi.com/docs/guides/automation-api/getting-started-automation-api/"&gt;https://www.pulumi.com/docs/guides/automation-api/getting-started-automation-api/&lt;/a&gt;&lt;br&gt;
整体结构如下所示：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;|____automation
|        |______main.go
|        |______go.mod
|        |______go.sum
|        |______Pulumi.Yaml
|        |______utils
|____apps
      |______minio.app
      |         |______main.go
      |         |______go.mod
      |         |______go.sum
      |         |______Pulumi.Yaml
      |         |______Pulumi.minio.app.dev.Yaml
      |_______volcano.app
      |         |______main.go
      |         |______go.mod
      |         |______go.sum
      |         |______Pulumi.Yaml
      |         |______Pulumi.volcano.app.dev.Yaml
      |________x.app
      |________y.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;该结构有以下几个特点：&lt;br&gt;
1、automation目录是给平台升级维护人员使用，可以管理所有apps。&lt;br&gt;
2、apps目录主要是给app开发人员使用，开发人员添加一个新的app，只需要使用pulumi new命令来操作生成即可，并且，不限制pulumi app project的开发语言，开发人员可以根据自己喜好来选择app开发语言。&lt;/p&gt;

&lt;h2&gt;
  
  
  后续更新
&lt;/h2&gt;

&lt;p&gt;1、后续会在automation目录下提炼出整个系统的全局变量维护和底层服务(promethues/logging)统一接入维护。&lt;br&gt;
2、会利用automation不同的stack来维护整体对应不同集群配置。&lt;br&gt;
3、automation功能会识别pulumi up/destroy/preview等命令，来达到pulumi原生命令的统一支持。&lt;br&gt;
4、可以再封装一层使用户不关注 pulumi api。&lt;/p&gt;

</description>
      <category>pulumi</category>
      <category>iac</category>
      <category>kubernetes</category>
      <category>terraform</category>
    </item>
    <item>
      <title>Hello World!</title>
      <dc:creator>zerix</dc:creator>
      <pubDate>Fri, 03 Jan 2020 12:32:17 +0000</pubDate>
      <link>https://forem.com/zerix/hello-world-2gf9</link>
      <guid>https://forem.com/zerix/hello-world-2gf9</guid>
      <description>&lt;h1&gt;
  
  
  init project.
&lt;/h1&gt;

&lt;p&gt;Today, 20200103.&lt;/p&gt;

&lt;p&gt;Hope it will be continue...&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
