掌握Rust的所有权:记忆安全和效率的关键
#javascript #编程 #开源 #rust

Rust的所有权机制是一个独特的功能,它使其与其他主流编程语言不同。这就是使生锈的记忆安全和高效的原因,同时避免需要垃圾收集器,这是Java,Go和Python等语言中的共同功能。了解所有权对于精通生锈至关重要。在本文中,我们将探讨以下主题:

  1. 堆栈和堆
  2. 可变范围
  3. 所有权
  4. 借用(引用)
  5. 可变参考

注意:本文对初学者友好。熟悉Rust的基本语法,函数中的可变声明以及基本数据类型。如果您需要快速刷新,可以检查此guide

堆栈和堆

生锈中的内存可以分为两个主要部分:堆栈和堆。这两者都可以在编译时间和运行时可用,并且可以实现不同的目的。了解他们的力学对于编写有效的生锈代码至关重要。

堆栈用于在编译时存储具有已知尺寸的数据。存储在堆栈上的数据大小是固定的,并且在运行时不能更改。

堆栈使用第一个,最后一个(FILO)机制进行操作,类似于堆叠板。添加的第一个板是删除的最后一个。

Rust自动将原始类型添​​加到堆栈中:

fn main() {

    let num: i32 = 14;
    let array = [1, 2, 4]; 
    let character = 'a'; 
    let tp = (1, true); 

    println!("number = {}, array = {:?}, character = {}, tp = {:?}", num, array, character, tp); 
}

main方法的堆栈布局如下:

地址 名称 value
3 TP (1,true)
2 字符 'a'
1 数组 [1,2,4]
0 num 14

在此表中,第一个添加的num变量将是函数范围结束时最后删除的。

注意:地址列使用数字作为表示,理想情况下,计算机中的地址不仅仅是数字。

堆用于编译时大小未知的数据。 VecStringBox之类的类型自动存储在堆上。

fn main() {

    let num: i32 = 14;
    let new_str = String::new(); 
}

堆上数据的内存映射如下:

stack mem

地址 名称 value
1 new_str ptr1
0 num 14

heap mem

地址 名称 value
ptr1 “”

将数据存储在堆上时,指针(例如ptr1)首先存储在堆栈中。然后,该指针引用堆上的实际数据。与堆栈访问相比,此额外的步骤使堆访问更慢。

有了堆栈和堆的机制,让我们深入研究可变范围。

可变范围

变量范围定义了变量有效的代码中的范围。在生锈中,卷曲括号会产生块,并且这些块中的变量被瞄准了。

fn main() {
// everything here is scoped to this block
 let hello = "Hello World";
// ...
}

在该块之外无法访问一个块中的变量:

fn main() {
 let hello = "Hello World"; 

  // inner block
  {
    let num = 20;
  }

 // This will fail to compile
 println!("{} {}", hello, num);
}

从上方您不能在内部块外使用num,因为它仅范围内范围为内部块。

Rust运行功能从上到下,根据需要分配堆栈和堆内存。当范围结束时,Rust在内部调用drop方法来处理内存,这是理解Rust的内存管理方法的关键概念。

所有权

在这里,我们来到了文章的核心,我们在这里探索所有权及其对Rust编程的影响。在我们继续时记住这些所有权规则:

所有权规则

  1. 一次只能有一个价值的所有者。
  2. 每个值都有一个所有者。
  3. 如果所有者不在范围内,则值将删除。

让我们看看这是什么意思:

fn main() {
    let array = vec![1,2,3]; 
    let title = String::new(); 

    let new_array = array;
    let new_title = title; 

    println!("array {:?} title {} new_array {:?} new_title {}", array, title, new_array, new_title);
 }

move bug
由于arraytitle均同时拥有多个变量,因此出现了“移动价值借用”的编译误差。这违反了所有权规则。

上面代码的Rust的内存布局如下:

stack mem

地址 名称 value
3 new_title ptr2copy
2 new_array ptr1copy
1 标题 ptr2
0 数组 ptr1

heap mem

地址 名称 value
ptr2和ptr2copy “”
ptr1和ptr1copy vec![1,2,3]

从上面看到的,因为向量和字符串是非固定尺寸类型的,将它们分配到堆中,并且可以通过堆栈的指针访问。

ptr1Copy指针是ptr1 Pointer的副本,ptr2Copy指针是ptr2指针的副本。

生锈了,然后称为drop方法,drop方法在堆栈中行走记住 filo 我们上面解释了吗?首先,将添加到new_title的堆栈中的最后一项开始,然后发现它具有指针(ptr2Copy)通过该指针进入堆内存并删除字符串值。它继续到堆栈new_array上的下一个元素,该元素还具有指针ptr1Copy,并从堆内存中删除了向量。它再次继续到下一个是title,并具有指针ptr2,但是当它遵循指针时,它意识到数据不再退出,因为它已经通过ptr2Copy删除了,这为什么titletitle丢弃了同一件事,array variabe8 variabe a variable of the title

所有权规则仅适用于堆上的生锈类型。堆栈上的原始类型不遵循以下规则:

fn main() {
    let array = [1,2,3]; 
    let title = "hello"; 

    let new_array = array;
    let new_title = title; 

    println!("array {:?} title {} new_array {:?} new_title {}", array, title, new_array, new_title);
 }

primitive copy types

对于熟悉其他语言的读者,将值重新分配到新变量时似乎是直观的。我们如何在Rust中实现这一目标?

我们可以使用.clone方法,该方法复制了堆值并将其存储在堆上的新位置:

请参阅下文:

fn main() {
    let array = vec![1,2,3]; 
    let title = String::new(); 

    let new_array = array.clone();
    let new_title = title.clone(); 

    println!("array {:?} num {} new_array {:?} new_num {}", array, title, new_array, new_title);
}

从上面的代码起作用,让我们看看如何在堆栈上完成翻译和堆

stack mem

地址 名称 value
3 new_title ptr2copy
2 new_array ptr1copy
1 标题 ptr2
0 数组 ptr1

heap mem

地址 名称 value
ptr2copy “”
ptr1copy vec![1,2,3]
ptr2 “”
ptr1 vec![1,2,3]

尽管这种方法有效,但可能会导致不必要的内存消耗和效率低下。

借用

在生锈中,借用(或引用)使我们可以使用价值而无需所有权。要引用一个值,我们使用&符号。

fn main() {
    let hello = String::from("hello");
    let another_hello = &hello;

    let arr = vec![1];
    let another_arr = &arr;

    println!("hello = {} another_hello = {} {:?} {:?}", hello, another_hello, another_arr, arr)
  }

通过引用值,我们可以读取数据而无需复制数据,避免不必要的内存开销。

这也适用于函数参数。功能参数具有自己的范围,将其传递给它将自动将所有权移至参数。

fn main() {
    let name = String::from("John");
    say_hello(name);
    println!("{}", name)
}

fn say_hello(name: String) {
    println!("hello {}", name)
}

ownership in function params
以上失败是因为“约翰”的价值所有权已移至函数参数。

我们可以通过从名称所有者那里借入来解决此错误,请参见下文

fn main() {
    let name = String::from("John");
    say_hello(&name);
    println!("{}", name)
}

fn say_hello(name: &String) {
    println!("hello {}", name)
}

我们没有错误,到目前为止看起来不错。

所有这些,而我们仅阅读所有者的价值,如果我们想更新该价值而仍然没有所有权,该怎么办?

可变的参考

rust中的突变值需要mut关键字,并且要通过参考突变值,我们使用&mut关键字。

fn main() {
  let mut name = String::from("John");

  full_name(&mut name);

  println!("Your full name is: {}", name);
}

fn full_name(name: &mut String) {
  name.push_str(" Doe");
}

full_name函数上方可以更新名称价值,而无需拥有该值。

总是要记住的一件事是,在任何给定时间,您都可以有一个可变的参考或任何数量不变的参考文献。
因此以下代码将无法编译:

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}

multiple mutable reference fail

即使我们只读了一个值得untable的参考锈蚀,即以下仍然无法编译

fn main() {
    let mut s = String::from("hello");

    let r1 = &s;
    let r2 = &s;
    let r3 = &mut s;

    println!("{}, {}, {}", r1, r2, r3);
}

原因是要避免数据不一致,因为生锈无法保证在什么时候发生读数或数据更改会发生。

我们可以通过范围使上述工作。

fn main() {
    let mut s = String::from("hello");

    // inner scope 
    {
     let r1 = &mut s;
     println!("{}", r1);
    }

    let r2 = &mut s;
    println!("{}", r2);

}

我们还可以阅读所有阅读参考文献并在突变之前使用它们。

fn main() {
 let mut s = String::from("hello");

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{} and {}", r1, r2);
    // variables r1 and r2 will not be used after this point

    let r3 = &mut s; // no problem
    println!("{}", r3);
}

Rust强制我们正确考虑我们的代码,以避免编写难以修复的错误代码。

结论

在编程语言的世界中,由于其所有权机制,Rust是一个非凡的选择。这种机制虽然最初具有挑战性,但它是Rust记忆安全和效率的基石。通过严格遵守执行单个所有权的规则,Rust减轻了内存泄漏的风险,数据不一致和其他常见的编程陷阱。

通过本文,我们探讨了Rust所有权概念的复杂工作。我们从对堆栈和堆的基本理解开始,这是支持记忆分配的支柱。堆栈,用于固定尺寸数据的高效以及堆,可容纳动态数据,使生锈能够平衡性能和灵活性。

随着我们深入研究块级范围的重要性,可变范围的重要性变得显而易见。 Rust的严格范围管理不仅简化了代码,而且可以通过降落机制来自动化内存DEADLOCATION。

所有权是Rust的内存管理理念的核心,它教会了我们一个宝贵的教训,只有一个实体才能在任何给定时间拥有价值。该原则最初似乎是限制的,但这是针对数据竞赛和记忆问题的必不可少的保护。

我们通过利用借款来学习如何浏览这些所有权规则,该机制允许对价值进行控制而不会损害所有权。我们看到了参考文献(&)和可变参考(&mut)如何促进此过程,从而在不牺牲数据完整性的情况下可以阅读和操纵。

在经常为方便的情况下牺牲记忆安全的景观中,生锈是一种迫使开发人员采用最佳实践的语言。虽然所有权模型可能具有挑战性,但掌握它将使您成为更有效和有效的Rust程序员。

当您继续使用Rust旅行时,请记住,所有权不仅是技术性;这是一种心态。拥抱它,让它指导您的编码决策,您将解锁此语言的真正潜力。 Rust的所有权不仅是要克服的障碍。这是负责任地挥舞的超级大国。

因此,当您编写生锈代码时,请记住这些所有权原则,您将在创建经受时间考验的强大,高效和可靠的软件方面做得很好。