关于Rust的生命的另一篇文章

新手暴徒的最初几个月通常会涉及到有关终身和财产概念的标题。 有人打破了这一点,但是对于那些能够生存的人来说,这不再是不寻常或错误的。 我将描述关键点,在我看来,这些关键点有助于更快更好地适应生命和财产的概念。


当然,官方时事通讯更完整,更详细,但也需要更多时间和耐心才能完全理解和吸收所有信息。 我试图避免使用大量细节,而是以越来越复杂的顺序介绍所有内容,以使那些刚开始观看栅格或不真正了解官方公告板的人们可以更轻松地使用本文。


这也让我写道,例如,从单子目录中可以找到一些官方培训材料,但是它们并不总是被很好地理解,只有在阅读了有关该主题的“另一篇介绍”之类的内容之后,人们才能理解。


终生


首先,我们需要对两件事感到满意-块的末尾并将值移动到另一个块。 稍后,我们将通过添加“借出”,“可变性”和“隐藏可变性”开始使其复杂化。


首先,价值的寿命由以下部分决定:


  • 生活的起点:创造价值。 这对于大多数编程语言来说是常见的,因此不会带来任何异常的负载。
  • 生命的尽头。 这是Rust会自动调用析构函数并忽略该值的地方。 在作用域块中,这将在该块的末尾发生而不会移动。 在我看来,这是对生命终结的心理追踪,这是与借贷者成功互动的关键。

我将添加一个可能会派上用场的细节:如果范围中有多个值,那么它们将以与创建相反的顺序被销毁。


另一点:我将创建一个字符串,因为它没有复制标记,并且具有该标记的值不会移动而是被复制,这被认为是相当便宜的操作,但会改变移动的行为(并使其更易于使用基本类型),但以后会更多。


示例可以在这里运行: https : //play.rust-lang.org/


fn main() { { //    let a = "a".to_string(); // <-   "a" let b = 100; // <-   "b" // <-   b // <-   a } //    //     "a"  "b" } 

一个简单的块,一切都相对简单,当我们使用看似简单的东西(例如函数和闭包)时,下一阶段就会发生:


搬家


添加诸如移动值之类的概念。 用简单的话说,“移动”意味着当前块不再对值的命运感兴趣,而忘记了它,并且其命运被转移到另一个块,例如,转移到另一个函数,一个闭包或简单地转移到另一个值。


 fn f<T: std::fmt::Display>(x: T) { //   ,         . println!("{}", x); // <-  ,   "a",    . } fn main() { let a = "a".to_string(); // "a"    let b = 2; f(a); //   "a"  f //        f(a) -   ,    "a"        .    a  b,    ,      Copy   . // "b" . } 

带封口。


为了使闭包将捕获的值移动到其块中,使用了关键字move,如果您不编写move,则借用该值,我很快就会写到。


 fn main() { let a = "a".to_string(); // "a"    let b = 2; let f_1 = move || {println!("{}", a)}; //   "a" //    "a"    . // let f_2 = move || {println!("{}", a)}; f_1(); } 

您既可以移至函数,也可以移至函数或另一个值。


该示例演示了如何跟踪价值移动的方式,以便与借贷者和平相处。


 fn f(x: String) -> String { x + " and x" //    x   +,     . //  +   String,    . } fn main() { let a = "a".to_string(); //  "a" let b = f(a); //  "a"  "f",  f     b. println!("{}", b); // "a"   . } 

借贷


我们引入了这个新概念:与移动不同,这意味着当前块保留对值的控制,它仅允许另一个块使用其值。


我注意到借贷也发生在借记处,这在这些示例中不是很重要,但会在下一段中弹出。


注意:我不会写关于如何直接在函数中指定生存期的信息,因为现代rust会比过去自动完成更好的工作,而所有这些披露的内容还有几页。


 fn f(x: &String) { //   &,    . println!("{}", x); // <-  ,  "x"     } fn main() { let a = "a".to_string(); // "a"    f(&a); //   "a"  f //   f(&a); //    -  . println!("{}", a); //   // "a"  . } 

与闭包类似:


 fn main() { let mut a = "a".to_string(); // "a"    let f_1 = || a.push_str("and x"); //   "a" let f_2 = || a.push_str("and x"); //   f_1(); f_2(); println!("{}", a); // "a"  . } 

实际上,在大多数这些简单的构造中,用户只需要决定他要终止值寿命的位置:在当前块的末尾并将其借给某些函数,或者,如果我们知道不再需要该值,则将其移至最后的函数中。通过它本身将被销毁,我们释放内存的速度就越快,但是该值将在当前块中不再可用。


变异性


在rasta中,例如在kotlin中,分为可变值和不稳定值。 但是出现的问题是可变性会影响贷款:
您可以多次借用非稳定值,并且可变值只能相互借用一次。 您不能更改先前已经借用的值。


一个与之前的示例无关的示例,当此概念通过禁止同时可变和不稳定的贷款使我们免于问题时:


 fn main() { let mut a = "abc".to_string(); for x in a.chars() { //   a.push_str(" and "); //  .  . a.push(x); } } 

在这里,有必要总结各种花样,以便在很大程度上满足其他需求。 在上面的示例中,最简单的方法是克隆“ a”->该克隆将具有不稳定的借贷,并且与原始“ a”无关。


 for x in a.clone().chars() { //  ,   . a.push_str(" and "); //  .      -   . 

但是为了保持一致性,我最好回到我们的示例。 我们需要更改“ a”,但不能更改。


 fn main() { let mut a = "a".to_string(); // "a"    let mut f_1 = || a.push_str(" and x"); //   "a".   - ,  mut  mut. //      ,   f_1  . let mut f_2 = || a.push_str(" and y"); //     : second mutable borrow occurs here f_1(); f_2(); println!("{}", a); } 

隐藏突变


从理论上讲,可以将闭包传递给某个函数进行处理,例如,在另一个线程中异步处理,然后我们确实会遇到问题,但是在这种情况下,借位检查程序已重新保险,尽管这并不能消除我们需要以某种方式达成共识的事实。 。


底线:我们需要两次突变借用,但是rast只允许一件事情,但是rasta的狡猾发明者提出了“隐藏突变”:RefCell。


RefCell-我们包装在RefCell中的内容-栅格认为它是可取的,但是,使用borrow_mut()函数,我们可以临时提取一个可变链接,通过它可以更改值, 但是有一个重要的细微差别 :只有当RefCell在运行时确保没有其他链接时,才能获取该链接有效贷款,否则如果使用try_borrow_mut(),他将引发恐慌或返回错误。 即 在这种情况下,增长使用户担心所有贷款,他本人必须确保自己不会一次从多个地方借钱。


 use std::cell::RefCell; fn main() { let a = RefCell::new("a".to_string()); // "a"    let f_1 = || a.borrow_mut().push_str(" and x"); //    "a" let f_2 = || a.borrow_mut().push_str(" and y"); //    f_1(); //      a.borrow_mut() ,           mut    . f_2(); //   . println!("{}", a.borrow()); //         . } 

RC链接计数器


例如,当我们由于某种原因而无法借用一个值,并且对于一个值需要有多个参考值时,这种构造在许多语言中都很熟悉,并且用在了rast中。 顾名思义,Rc只是拥有一个值的引用计数器,它可以借用非稳定链接,计算它们的数量,并且一旦重置它们的数量,它就会破坏值及其自身。 事实证明,Rc确实可以秘密地扩展其中包含的值的生存期。


我将补充说,rast可以自动为其定义的结构进行解引用,这意味着要使用Rc,通常不需要内部值的任何其他提取,我们只需像使用Rc一样使用内部值即可。


在这里,一个简单的示例经过深思熟虑,让我们尝试模仿上面示例中的闭包不希望接受&T或&String,而只是想要String:


 fn f(x: String) { //  String,    &String println!("{}", x); } fn main() { let a = "a".to_string(); let f_1 = move || f(a); //   move,    ... let f_2 = move || f(a); // ...     ,           f_1(); f_2(); println!("{}", a); } 

如果我们可以将函数更改为fn f(x: &String) (or&str),则可以轻松解决此问题,但是让我们想象一下,由于某种原因,我们不能使用&


我们用RC


 use std::rc::Rc; fn f(x: Rc<String>) { //       Rc println!("{}", x); //     ,  println          ,           ,       ,    . } fn main() { let a_rc = Rc::new("a".to_string()); //  Rc   let a_ref_1 = a.clone(); //   -,  . let a_ref_2 = a.clone(); //   let f_1 = move || f(a_ref_1); //      - let f_2 = move || f(a_ref_2); //  f_1(); f_2(); println!("{}", a_rc); //     Rc  . //    a_rc       . } 

我将添加最后一个示例,因为可以找到的最频繁的容器对之一是Rc <RefCell>

 use std::rc::Rc; use std::cell::RefCell; fn f(x: Rc<RefCell<String>>) { x.borrow_mut().push_str(" and x"); //      ,       ,   . } fn main() { let a = Rc::new(RefCell::new("a".to_string())); //      let a_ref_1 = a.clone(); let a_ref_2 = a.clone(); let f_1 = move || f(a_ref_1); let f_2 = move || f(a_ref_2); f_1(); f_2(); println!("{}", a.borrow()); // Rc   ,   RefCell   } 

此外,将本教程移至Rc-Arc的线程安全类似物然后继续讲述Mutex,这是合乎逻辑的,但是您不会在一个段落中谈论线程安全和借阅检查器,而且由于有官方线程,因此也不清楚是否需要这种类型的文章。 所以我得出结论。

Source: https://habr.com/ru/post/zh-CN442962/


All Articles