Rust 学习笔记:所有权

rust学习五、认识所有权 按照rust官方的说法,rust要保证内存安全(一定会释放掉),同时还需要保证高效(不能用java 那样的gc管理器),同时还不能太繁琐(像C++那样手动释放)注:第二条规则“任意时刻,一个值只能有一个所有者”这个似乎有问题,因为在书本的第十五章节的Rc(引用计数)指针又声称"然而,有些情况单个值可能会有多个所有者"通过这个提示我们可以看到:在一个作用域之内,如果一个变量转赋给另外一个变量的时候,会发生一件事情:值会从一个变量转到另外一个变量上。一个变量的值被移走后,那么再次使用原来的变量就变得麻烦了。 阅读详情

Rust 学习笔记:所有权

所有权是 Rust 最独特的特性,对语言的其他部分有着深刻的影响。它使 Rust 能够在不需要垃圾收集器的情况下保证内存安全,因此理解所有权是如何工作的很重要。在本文中,我们将讨论所有权以及几个相关的特性:借用、切片,以及 Rust 如何在内存中布局数据。

什么是所有权?

所有权是一组控制 Rust 程序如何管理内存的规则。

所有程序在运行时都必须管理它们使用计算机内存的方式。一些语言有垃圾收集,在程序运行时定期查找不再使用的内存;在其他语言中,程序员必须显式地分配和释放内存。Rust 使用了第三种方法:内存通过一个所有权系统进行管理,该系统拥有一组编译器检查的规则。如果违反了任何规则,程序将无法编译。

所有权的主要目的是管理堆数据。
跟踪代码的哪些部分正在使用堆上的哪些数据,最小化堆上的重复数据量,以及清理堆上未使用的数据,这样就不会耗尽空间,这些都是所有权可以解决的问题。

所有权规则

首先,让我们看一下所有权规则:

  • Rust 中的每个值都有一个所有者。
  • 一次只能有一个所有者。
  • 当所有者超出范围时,该值将被删除。

变量作用域

作用域是程序中某项有效的范围。变量从声明它的地方开始有效,直到当前作用域结束。

示例:

    {                      // s is not valid here, it’s not yet declared
        let s = "hello";   // s is valid from this point forward

        // do stuff with s
    }                      // this scope is now over, and s is no longer valid

这里有两个重要的时间点:

  • 当 s 进入作用域时,它是有效的。
  • 它在超出作用域之前一直有效。

在这一点上,作用域和变量何时有效之间的关系与其他编程语言中的关系类似。

引子:字符串变量和字面量

我们已经看到了字符串字面量,其中字符串值被硬编码到程序中。字符串字面值很方便,但它们并不适合我们可能想要使用文本的所有情况。一个原因是它们是不可变的。另一个问题是,当我们编写代码时,不是每个字符串值都是已知的。

对于这些情况,可以创建字符串变量:

    let mut s = String::from("hello");
    s.push_str(", world!"); // push_str() appends a literal to a String
    println!("{s}"); // This will print `hello, world!`

这种类型的字符串可以被改变。

为什么字符串可以改变,而字面量不能?不同之处在于这两种类型处理内存的方式。

内存和分配

对于字符串字面值,我们在编译时就知道其内容,因此文本直接硬编码到最终的可执行文件中。这就是字符串字面值快速高效的原因。但这些属性仅来自字符串文字的不变性。不幸的是,我们不能为每个在编译时大小未知且在运行程序时大小可能改变的文本块放入二进制文件中的内存块。

对于 String 类型,为了支持可变的、可增长的文本片段,我们需要在堆上分配一定数量的内存(在编译时未知)来保存内容。这意味着:

  • 内存必须在运行时从内存分配器请求。
  • 我们需要一种方法,在使用完 String 后将这些内存返回给分配器。

第一部分由我们完成,当调用String::from时,它的实现请求它所需的内存。这在编程语言中是非常普遍的。

然而,第二部分是不同的。在带有垃圾收集器(GC)的语言中,GC 跟踪并清理不再使用的内存,我们不需要考虑它。

在大多数没有 GC 的语言中,我们有责任识别内存何时不再被使用,并调用代码显式释放它,就像我们请求它一样。正确地做到这一点历来是一个困难的编程问题。如果我们忘记了,我们就会浪费记忆。如果我们做得太早,就会得到一个无效的变量。如果我们做两次,这也是一个 bug。我们需要恰好配对一个已分配的和一个空闲的。

Rust 采用不同的路径:一旦拥有内存的变量超出作用域,内存就会自动返回。

    {
        let s = String::from("hello"); // s is valid from this point forward
        // do stuff with s
    }
    // this scope is now over, and s is no longer valid

当变量超出作用域时,Rust 会为我们调用一个特殊的函数。这个函数被称为 drop,该函数将内存返回给分配器。

注意:在 C++ 中,这种在项目生命周期结束时释放资源的模式有时被称为资源获取即初始化(RAII)。

与 Move 交互的变量和数据

在 Rust 中,多个变量可以以不同的方式与相同的数据交互。

示例 1:

    let x = 5;
    let y = x;

我们现在有两个变量,x 和 y,它们都等于 5。因为整数是具有已知的固定大小的简单值,并且这两个 5 值被压入堆栈。

示例 2:

    let s1 = String::from("hello");
    let s2 = s1;

字符串由三部分组成:指向存储字符串内容的内存的指针、长度和容量。长度是指字符串的内容当前使用了多少内存(以字节为单位)。容量是字符串从分配器接收到的总内存量(以字节为单位)。

这组数据存储在堆栈上,右边是堆中保存内容的内存。

在这里插入图片描述

当将 s1 赋值给 s2 时,复制了 String 数据,这意味着复制了堆栈上的指针、长度和容量。但是,不复制指针所指向的堆上的数据。

在这里插入图片描述

前面我们说过,当变量超出作用域时,Rust 会自动调用 drop 函数并为该变量清理堆内存。但是上图显示两个数据指针都指向同一个位置。这是一个问题:当 s2 和 s1 超出作用域时,它们都将尝试释放相同的内存。这被称为 double free error,是内存安全错误之一。释放内存两次可能会导致内存损坏,这可能会导致安全漏洞。

为了确保内存安全,在 let s2 = s1; 行之后,Rust 认为 s1 不再有效。因此,当 s1 超出作用域时,Rust 不需要释放任何东西。

在创建 s2 之后尝试使用 s1 会报错:使用无效的引用。

在 C++ 中,你可能听说过浅拷贝和深拷贝这两个术语,那么在不复制数据的情况下复制指针、长度和容量的概念,可以视为浅拷贝。但是因为 Rust 会使第一个变量无效,所以它被称为移动(Move),而不是浅拷贝。在这个例子中,我们会说 s1 被移动到 s2。

在这里插入图片描述

这就解决了我们的问题!只有 s2 有效,当它超出作用域时,它会单独释放内存,这样就完成了。

此外,这还隐含了一个设计选择:Rust 永远不会自动创建数据的“深度”副本。因此,就运行时性能而言,任何自动复制都可以被认为是廉价的。

范围和分配

作用域、所有权和通过 drop 函数释放的内存之间的关系也是如此。

当你给一个已经存在的变量赋一个全新的值时,Rust 会调用 drop 并立即释放原始值的内存。

示例:

    let mut s = String::from("hello");
    s = String::from("ahoy");

    println!("{s}, world!");

我们首先声明一个变量 s,并将其绑定到一个值为 “hello” 的字符串。然后,我们立即创建一个值为 “ahoy” 的新 String,并将其赋值给 s。此时,原始字符串立即超出了作用域,Rust 运行 drop函数立即释放 “hello” 的内存。

在这里插入图片描述

克隆(Clone)

Rust 提供一个叫 clone 的方法进行深拷贝。

示例:

    let s1 = String::from("hello");
    let s2 = s1.clone();

    println!("s1 = {s1}, s2 = {s2}");

堆数据确实被复制了。

在这里插入图片描述

只在栈上的数据:复制(Copy)

示例:

    let x = 5;
    let y = x;

    println!("x = {x}, y = {y}");

这段代码似乎与我们刚刚学到的内容相矛盾:我们没有调用 clone,但是 x 仍然有效,并且没有移动到 y 中。

原因是,在编译时具有已知大小的整数等类型完全存储在堆栈中,因此可以快速复制实际值。这意味着我们没有理由在创建变量 y 后阻止 x 的有效性。换句话说,这里没有深度复制和浅复制的区别,所以调用 clone 与通常的浅复制没有任何不同,我们可以省略它。

Rust 有一个特殊的注释,叫做 Copy trait,我们可以把它放在存储在堆栈上的类型上,就像整数一样。如果一个类型实现了 Copy 特性,那么使用它的变量不会移动,而是被简单地复制,使它们在赋值给另一个变量后仍然有效。

如果类型或其任何部分实现了 Drop 特性,Rust 将不允许我们用 Copy 注释类型。如果该类型需要在值超出作用域时发生一些特殊的事情,并且向该类型添加 Copy 注释,则会得到编译时错误。

一般地,任何一组简单标量值都可以实现 Copy,并且不需要分配或某种形式的资源来实现 Copy。下面是一些实现 Copy 的类型:

  • 所有整数类型,如 u32。
  • 所所有浮点类型,如 f64。
  • 所布尔类型,bool。
  • 所字符类型,char。
  • 只包含实现 Copy 的类型的元组。例如,(i32, i32)实现了Copy,但(i32, String)没有。

所有权与函数

将值传递给函数的机制类似于将值赋给变量的机制。将变量传递给函数会移动或复制,就像赋值一样。

示例:

fn main() {
    let s = String::from("hello");  // s comes into scope

    takes_ownership(s);             // s's value moves into the function...
                                    // ... and so is no longer valid here

    let x = 5;                      // x comes into scope

    makes_copy(x);                  // because i32 implements the Copy trait,
                                    // x does NOT move into the function,
    println!("{}", x);              // so it's okay to use x afterward

} // Here, x goes out of scope, then s. But because s's value was moved, nothing
  // special happens.

fn takes_ownership(some_string: String) { // some_string comes into scope
    println!("{some_string}");
} // Here, some_string goes out of scope and `drop` is called. The backing
  // memory is freed.

fn makes_copy(some_integer: i32) { // some_integer comes into scope
    println!("{some_integer}");
} // Here, some_integer goes out of scope. Nothing special happens.

如果我们试图在调用 takes_ownership 之后使用 s, Rust 会抛出一个编译时错误。

返回值和作用域

返回值也可以转移所有权。

示例:

fn main() {
    let s1 = gives_ownership();         // gives_ownership moves its return
                                        // value into s1

    let s2 = String::from("hello");     // s2 comes into scope

    let s3 = takes_and_gives_back(s2);  // s2 is moved into
                                        // takes_and_gives_back, which also
                                        // moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
  // happens. s1 goes out of scope and is dropped.

fn gives_ownership() -> String {             // gives_ownership will move its
                                             // return value into the function
                                             // that calls it

    let some_string = String::from("yours"); // some_string comes into scope

    some_string                              // some_string is returned and
                                             // moves out to the calling
                                             // function
}

// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
                                                      // scope

    a_string  // a_string is returned and moves out to the calling function
}

变量的所有权每次都遵循相同的模式:将值赋给另一个变量会移动该变量。当包含堆上数据的变量超出作用域时,除非数据的所有权已移动到另一个变量,否则该值将通过 drop 清除。

Rust 允许我们使用元组返回多个值,虽然这是可行的,但是获取所有权并返回每个函数的所有权有点繁琐。

示例:

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

    let (s2, len) = calculate_length(s1);

    println!("The length of '{s2}' is {len}.");
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len() returns the length of a String

    (s, length)
}

如果我们想让一个函数使用一个值,但不获得所有权,该怎么办?

幸运的是,Rust 有一个不用转移所有权就能使用值的特性,叫做引用。

引用(References)和借用(Borrowing)

引用类似于指针,因为它是一个地址,我们可以按照它访问存储在该地址的数据。

引用用 & 符号表示。与使用 & 进行引用相反的是解引用,它是通过解引用操作符 * 完成的。

与指针不同,引用保证在其生命周期内指向特定类型的有效值。

示例:

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize { // s is a reference to a String
    s.len()
} // Here, s goes out of scope. But because s does not have ownership of what
  // it refers to, the value is not dropped.

我们称创建引用的操作为借用。&s1 语法允许我们创建一个引用 s,该引用引用 s1 的值,但不拥有该值。因为引用不拥有它,所以当引用停止使用时,它所指向的值不会被删除。同样,函数的定义使用 & 表示形参 s 的类型是引用。

在这里插入图片描述

引用也是不可变的。我们不允许修改我们引用过的东西。

示例:

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

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

这段代码会报错:error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference。

Rust 通过“借用检查器”确保引用的安全性。

变量对其数据有三种权限:

  • 读:数据可以被复制到另一个位置。
  • 写:数据可以被修改。
  • 拥有:数据可以被移动或释放。

这些权限在运行时并不存在,仅在编译器内部存在。

默认情况下,变量对其数据具有读/拥有的权限,如果变量是可变的,那么它还拥有写权限。

重点:引用可以临时解除这些权限。

可变的引用

修改之前的代码,使其允许我们通过使用可变引用的一些小调整来修改借用值。

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

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

用 &mut s 就可以创建一个可变引用。

可变引用有一个很大的限制:如果对一个值有一个可变引用,那么就不能有对该值的其他引用。下面的代码试图创建对 s 的两个可变引用将会失败:

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

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

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

规定不能同时对同一数据进行多个可变引用的限制,这样做的好处是 Rust 可以在编译时防止数据竞争。数据竞争类似于竞争条件,发生在以下三种行为时:

  • 两个或多个指针同时访问相同的数据。
  • 至少有一个指针被用来写数据。
  • 没有使用任何机制来同步对数据的访问。

数据竞争会导致未定义的行为,当你试图在运行时追踪它们时,可能很难诊断和修复它们。Rust 通过拒绝编译带有数据竞争的代码来防止这个问题!

我们可以使用花括号来创建一个新的作用域,允许多个可变引用,只是不能同时使用:

    let mut s = String::from("hello");
    {
        let r1 = &mut s;
    } // r1 goes out of scope here, so we can make a new reference with no problems.
    let r2 = &mut s;

Rust 对可变引用和不可变引用的组合强制了类似的规则:

  • 对同一值有不可变引用的同时,也不能有可变引用。
  • 允许使用多个不可变引用。

示例:

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

    let r1 = &s; // no problem
    let r2 = &s; // no problem
    let r3 = &mut s; // BIG PROBLEM

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

报错:error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable。

请注意,引用的作用域从它被引入的地方开始,一直持续到最后一次使用该引用的时候。例如,下面这段代码可以编译,因为不可变引用的最后一次使用是在 println!,在引入可变引用之前:

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

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

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

编译器可以在作用域结束之前的某个点上判断出引用不再被使用。

加深对可变引用的理解

可变引用提供对数据唯一的且非拥有的访问。

不可变引用是只读的。可变引用在不移动数据的情况下,临时提供可变访问。

在这里插入图片描述

可变引用临时降级为只读引用

对一个可变引用再进行引用,可以暂时剥夺可变引用的写权限,新引用只具有读权限。

在这里插入图片描述

权限在生命周期结束时被返回

在创建了 x 的引用 y 后,x 的写和拥有权限暂时被剥夺,直到 y 的最后一次被使用之后,x 重新获得写和拥有权限。

在这里插入图片描述

悬空的引用

在使用指针的语言中,很容易通过释放一些内存而保留指向该内存的指针来错误地创建悬空指针。

相比之下,在 Rust 中,编译器保证引用永远不会是悬空引用:如果你有对某些数据的引用,编译器将确保数据不会在对数据的引用超出作用域之前超出作用域。

即,数据必须在其所有的引用存在的期间存活。

示例:创建了 s 的引用 s_ref 后, s 的拥有权限暂时被剥夺,于是不能移动或删除,程序在 drop(s) 出报错。

在这里插入图片描述

让我们尝试创建一个悬空引用,看看 Rust 是如何用编译时错误来防止它们的:

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

报错:error[E0106]: missing lifetime specifier。

这个错误消息涉及到一个我们尚未涉及的特性:生命周期。在这里我们先不讨论这个特性,而是分析 dangle() 函数的错误原因:因为 s 是在 dangle 内部创建的,所以当 dangle 的代码完成时,s 将被释放。但是我们试着返回对它的引用。这意味着这个引用将指向一个无效的字符串,于是发生了错误。

这里的解决方案是直接返回 String,而不是其引用:

fn no_dangle() -> String {
    let s = String::from("hello");

    s
}

总结:

  1. 在任何给定的时间,可以有一个可变引用或任意数量的不可变引用。
  2. 引用必须总是有效的。

接下来,我们来看看另一种类型的引用:切片。

切片(Slice)

切片允许引用集合中连续的元素序列,而不是整个集合。

切片是一种引用,所以它没有所有权。

这里有一个小编程问题:编写一个函数,它接受一个由空格分隔的单词字符串,并返回它在该字符串中找到的第一个单词的长度。如果函数在字符串中没有找到空格,则整个字符串必须是一个单词,因此应该返回整个字符串的长度。

让我们来看看如何在不使用切片的情况下编写这个函数,以理解切片将解决的问题:

fn first_word(s: &String) -> usize {
	// convert String to an array of bytes
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

我们现在有一种方法来找出字符串中第一个单词末尾的索引,但是有一个问题。我们自己返回了一个 usize,但它只是一个独立于 String 的值,所以不能保证它在将来仍然有效。

示例:

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

    let word = first_word(&s); // word will get the value 5

    s.clear(); // this empties the String, making it equal to ""

    // `word` still has the value `5` here, but `s` no longer has any content
    // that we could meaningfully use with the value `5`, so `word` is now
    // totally invalid!
}

这个程序编译时没有任何错误,如果在调用 s.clear() 之后使用 word,也不会出现任何错误。因为 word 根本没有连接到 s 的状态,所以 word 仍然包含值 5。我们可以使用这个值 5 和变量 s 来尝试提取出第一个单词,但这将是一个 bug,因为自从我们将 5 保存在 word 中以来,s 的内容已经发生了变化。

word 中的索引与 s 中的数据不同步是危险的。幸运的是,Rust 有一个解决方案:字符串切片。

字符串切片是对字符串的一部分的引用,它看起来像这样:

    let s = String::from("hello world");

    let hello = &s[0..5];
    let world = &s[6..11];

指定 [starting_index…ending_index],其中 starting_index 是片中的第一个位置,ending_index 比片中的最后一个位置大 1。

在这里插入图片描述

如果想从下标 0 开始,starting_index 可以省略,下面两种切片是一样的:

let s = String::from("hello");

let slice1 = &s[0..2];
let slice2 = &s[..2];

同样,如果切片包含 String 的最后一个字符,ending_index 也可以省略,下面两种切片是一样的:

let s = String::from("hello");

let len = s.len();
let slice1 = &s[3..len];
let slice2 = &s[3..];

掌握了切片后,重写之前的 first_word 函数,这次返回的是字符串而非下标:

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

还记得之前程序中的错误吗?当我们获得了第一个单词末尾的索引,但随后清除了字符串,因此索引无效。这段代码在逻辑上是不正确的,但没有立即显示出任何错误。如果我们一直尝试将第一个单词索引与空字符串一起使用,那么问题就会出现。切片使这个 bug 不可能出现,并让我们更快地知道我们的代码有问题。

示例:

fn main() {
    let mut s = String::from("hello world");
    let word = first_word(&s);
    s.clear(); // error!
    println!("the first word is: {word}");
}

报错:error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable。

回顾借用规则,如果有对某物的不可变引用,就不能同时使用可变引用。因为 clear 需要截断 String,所以它需要获得一个可变引用。println! 在调用 clear 之后使用 word 中的引用,因此不可变引用在那时必须仍然是活动的。Rust 不允许 clear 中的可变引用和 word 中的不可变引用同时存在,编译失败。

作为切片的字符串字面量

回想一下,我们讨论过将字符串字面值存储在二进制文件中。现在我们知道了切片,我们可以正确地理解字符串字面值:

let s = "Hello, world!";

这里 s 的类型是 &str,一个指向二进制数据中特定点的切片。这也是为什么字符串字面值是不可变的,&str 是一个不可变引用。

作为参数的字符串切片

知道可以取字面量和字符串值的切片后,我们对 first_word 又做了一个改进,那就是它的声明:

fn first_word(s: &str) -> &str {

定义一个函数来接受一个字符串切片而不是一个字符串的引用,将提高函数的灵活性。如果我们有一个字符串切片,我们可以直接传递。如果有一个 String 对象,则可以传递 String 对象的切片或对 String 对象的引用。这种灵活性利用了取消强制转换。

其他切片

数组也可以有切片:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);

这个切片的类型是 &[i32]。

总结

所有权、借用和切片的概念确保了 Rust 程序在编译时的内存安全。Rust 语言提供了对内存使用的控制,但是当数据所有者超出范围时,数据所有者会自动清理数据,这意味着不必编写和调试额外的代码来获得这种控制。

rust编程学习:所有权模型 阅读详情

相关推荐

深入理解 Rust 所有权

Rust的所有权机制是其内存管理的核心创新,通过编译期严格检查确保内存安全。本文从底层堆栈知识入手,解释了所有权产生的背景:当多个变量指向同一堆内存时,传统语言面临释放时机的难题。Rust的解决方案是确立"唯一所有者"原则,当所有权转移时原变量失效,并在所有者离开作用域时自动调用drop释放资源。该机制依赖三大语言特性:自动析构机制保证资源释放,编译期检查确保所有权转移合规,以及Copy trait区分复制语义。所有权转移不仅发生在赋值时,也存在于函数参数传递和返回值场景。这种设计既避免

Laurence的技术博客 2342

理解Rust的所有权

什么是所有权 Rust的所有权,是一个跨时代的理念,是内存管理的第二次革命。 较低级的语言依赖程序员分配和释放内存,一不小心就会出现空指针、野指针破坏内存;较高级的语言使用垃圾回收的机制管理内存,在程序运行时不断地寻找不再使用的内存,虽然安全,却加重了程序的负担;Rust的所有权理念横空出世,通过所有权系统管理内存, 编译器在编译时会根据一系列的规则进行检查,在运行时,所有权系统的任何功能都不会减...

鸿鹄之志 2046

Rust学习笔记与经典项目案例:吃透所有权、借用和生命周期

内存管理是系统编程语言的永恒话题,而Rust通过所有权、借用与生命周期机制,在编译期就保证了内存安全,无需垃圾回收。对于初学者而言,理解值在赋值与传参时如何移动、不可变借用与可变借用如何共存,以及生命周期标注如何约束引用范围,是真正跨过Rust门槛的关键。掌握这些概念后,才能顺畅地处理命令行工具、异步服务乃至嵌入式no_std项目中的资源管理问题。本资料以学习笔记与经典项目案例相结合的方式,覆盖从基础语法到实际工程排错的完整路径,帮助学习者在实践中内化Rust独特的内存思维,从而摆脱“看完教程写不出代码”的

weixin_32075843的博客 191

Rust核心功能之一(所有权)

所有权(系统)是 Rust 最为与众不同的特性,对语言的其他部分有着深刻含义。它让 Rust 无需垃圾回收(garbage collector)即可保障内存安全,因此理解 Rust 中所有权如何工作是十分重要的。所有程序都必须管理其运行时使用计算机内存的方式。一些语言中具有垃圾回收机制,在程序运行时有规律地寻找不再使用的内存;在另一些语言中,程序员必须亲自分配和释放内存。Rust 则选择了第三种方式:通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。

专注于大数据方向,区块链,前后端,数据可视化,人工智能等领域 2493

Rust学习笔记:从所有权到嵌入式实战,项目驱动带你避坑

系统编程语言中,内存安全始终是开发者关注的焦点,而 Rust 通过所有权机制在编译期就消除了悬空指针与数据竞争,成为兼顾性能与安全的热门选择。理解所有权、借用与生命周期三大核心规则,是掌握 Rust 的必经之路,它们不仅影响代码的编写方式,更决定了并发场景下的安全性。随着异步编程与嵌入式开发的兴起,Rust 在服务端和物联网领域展现出强劲潜力,开发者需要将抽象概念落到具体项目中才能真正内化。本文从调试环境配置、经典命令行工具、网络抓包到 ESP32 嵌入式实践,提供一套经过真实踩坑沉淀的学习路径,帮助读者构

weixin_33483304的博客 269

第1.1节 构网型 vs. 跟网型:根本区别与技术原理

构网型与跟网型变流器的本质区别在于控制哲学与等效模型。跟网型变流器采用"电压跟随,电流注入"策略,等效为受控电流源,依赖电网电压作为参考,在弱电网下存在稳定性问题。构网型变流器采用"自主同步,电压构建"策略,等效为受控电压源,能主动构建电压和频率,具备自主运行、黑启动和电网支撑能力。两者的核心差异体现在同步机制、电网依赖性、故障行为和系统支撑等方面。构网型变流器更适合高比例新能源、弱电网和微电网场景,是未来电力系统的重要发展方向。

技术工作者 1394

Rust 所有权(Ownership)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、Ownership是什么?二、规则集三、理论+示例四、引用和借用五、切片类型(slice)总结 前言 Rust的所有权是其最独特的功能,其对Rust的其它部分也有很大影响。所有权概念让Rust即没有GC,又能够保证内存安全。 GC就是garbage collection,比较直观。 内存安全指没有内存泄露、野指针、内存重复释放之类?有没有包括多线程的并发访问呢? 一、Ownership是什么? 总体讲,所有权是ru.

wenyuanhust的专栏 2135

Visual studio 2013 配置 Halcon 13 (非抄袭)

Visual studio 2013 配置 Halcon 13.0.2.2 (其他版本适用) 针对网上抄袭的配置方法均不可用,写了一篇亲试可行的配制方法。如下: 1.下载Halcon安装后(C:\Program Files\MVTec\HALCON-13.0) 添加环境变量,(按下win按键,键入“环境变量”,出现 “编辑系统环境变量”后按下enter即可打开,下方找到“环境变量”按钮)的系...

huyanjackson的专栏 1041

【Rust 基础篇】Rust 所有权详解

所有权是指对内存资源的控制权和管理权。在Rust中,每个值都有一个唯一的所有者。当所有者超出作用域时,该值将被释放。这种所有权的机制确保了内存资源的安全和高效使用。Rust的所有权系统是一种强大的内存管理机制,它通过移动、克隆和借用操作来管理值的所有权,确保内存安全和避免常见的错误。本篇博客详细介绍了Rust中的所有权概念、所有权规则和最佳实践,并提供了相关代码示例。通过合理使用所有权,我们可以编写出高效、安全和易于维护的Rust代码。

繁依Fanyi的博客 1817

PCL1.12.1+VTK9.1+Qt5.14.2实战:解决QVTKOpenGLNativeWidget点云渲染窗口初始化报错问题

本文针对PCL1.12.1、VTK9.1与Qt5.14.2集成开发中,QVTKOpenGLNativeWidget点云渲染窗口初始化时出现的报错与黑屏问题,提供了深度分析与两种实战解决方案。核心在于理解VTK版本升级导致的渲染窗口类型不匹配,并指导开发者通过显式创建vtkGenericOpenGLRenderWindow或利用窗口ID嵌入,正确初始化并绑定可视化组件,从而稳定实现点云渲染。

weixin_29284657的博客 221

【安徽理工大学】一文带你高分拿下计算机考研复试(85+的秘密)

一文带你高分拿下安徽理工大学计算机考研复试(85+的秘密)

猫天意的博客 1523

Rust学习笔记004:Rust的所有权机制

移动构造函数的目标是在没有不必要的资源复制的情况下,将资源从一个对象“移动”到另一个对象,以提高效率。在C++中,移动构造函数(Move Constructor)是一种特殊的构造函数,用于在对象的所有权被转移(移动)时创建新对象。移动构造函数通常用于提高性能,特别是在涉及资源管理的情况下,比如使用动态内存分配的对象。这意味着在函数中你可以使用该值,但在函数返回后,调用者将无法再使用它。在 Rust 中,所有权系统与函数的参数传递和返回密切相关,它确保在函数调用过程中对所有权的正确管理。类具有移动构造函数。

ResumeProject的博客 1919

14 ABSOLUTE评估肿瘤纯度

14 ABSOLUTE评估肿瘤纯度 Absolute介绍 ABSOLUTE可以直接从体细胞突变和拷贝数变异的结果中推断肿瘤纯度和倍性的计算方法。 通常来说,我们取肿瘤组织进行测序,往往是一个混合样品,既包括肿瘤细胞也包括正常细胞,因此需要进行肿瘤纯度 purity 的评估。当从混合样品中提取 DNA 进行测序后,得到的也是一个混合样品的结果。肿瘤不一定是单纯的二倍体了,其本身异质性高,存在部分突变部分不突变的情况,直接分析拷贝数变异,得到的结果并不准确,评估肿瘤倍性 ploidy 也更加必要。而 ABSO

qq_23435961的博客 4215

Rust 学习笔记:修复所有权常见错误

Rust 学习笔记:修复所有权常见错误

ProgramNovice的博客 701

爬虫瑞数5案例:联通

瑞数动态安全 Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类 Web、HTML5 提供强大的安全保护。

老衲爱洗头的博客 4470

把数字化转型真正落到组织里:读懂 SAP 的 OCM 六维框架

摘要: SAP的变革管理框架(OCM)强调数字化转型不仅是技术任务,更需关注组织与人的适应性。OCM包含六大维度:变革战略(明确方向与风险)、变革领导力(统一关键管理者行动)、变革沟通(分层传递信息)、变革实现(细化岗位级影响)、变革赋能(按角色设计培训)和变革有效性(持续评估)。该框架贯穿项目全周期,尤其在云项目中,需从早期规划变革策略,避免后期高代价调整。OCM的核心是将系统设计转化为组织语言,确保业务真正采纳新方式运作。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1433

Rust_0基础学习笔记_01:所有权机制1

Rust_0基础学习笔记_01:所有权机制1

pentaxxx的博客 610

EfficientNet 改进:StripCGLU模块的创新与应用

本文提出了一种改进的EfficientNet-b0模型,核心创新是通过引入StripCGLU模块来提升网络性能。StripCGLU结合了水平/垂直条带卷积和GLU激活机制,具有参数效率高、计算量少的优点。改进策略包括选择性替换部分MBConv为StripCGLU模块(每隔3个块插入),同时保留早期层结构。测试表明该模型适用于移动端视觉任务,在保持效率的同时提升性能。代码实现包含模块定义、模型构建及测试部分,展示了如何通过精心设计的模块改进现有网络架构。

Henry的博客 241

《Rust程序设计》学习笔记三:所有权与移动

在Rust中,对大多数类型来说,为变量赋值、将其传给函数或从函数返回这样的操作,都不会复制值,而是会移动值。每个值都有决定其生命周期的唯一拥有者,当拥有者被释放时,它拥有的值也会同时被释放。拥有者及其拥有的那些值形成了一棵树:值的拥有者是值的父节点,值拥有的值是值的子节点。如果有两个引用计数的值是先相互指向的,那么其中一个值就会让另一个值的引用计数保持在0以上,因此这些值将永远不被释放。// 错误,因为Rc指针拥有的值是不可变的。// 错误,只要任何一条路径移动了x的所有权,x在这里就是未初始化的。

look4liming的专栏 438

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Rust_0基础学习笔记_02:所有权机制2

Rust_0基础学习笔记_02:所有权机制2

pentaxxx的博客 194

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Rust 学习笔记:Drop trait

Rust 学习笔记:Drop trait

ProgramNovice的博客 535

【Rust学习笔记】Rust 的所有权介绍

在 Rust 中,所有权这个概念内置于语言本身,并通过编译期检查强制执行。每个值都有决定其生命周期的唯一拥有者。当拥有者被释放时,它拥有的值也会同时被释放,在 Rust 术语中,释放的行为被称为丢弃(drop)。这些规则便于通过检查代码确定任意值的生命周期,也提供了系统级语言本应支持的对生命周期的控制。

从善若水的博客 1228

【Rust】所有权

所有权是一套规则,用于管理Rust程序如何使用内存。Rust通过所有权系统来管理内存,编译器会检查这些规则的遵守情况。违反任何规则将导致程序无法编译。所有权的特点是不会影响程序的运行速度。

qq_61867911的博客 1587

Rust中的所有权是什么

既然我们已经掌握了基本语法,将不会在之后的例子中包含 fn main() { 代码,所以如果你是一路跟过来的,必须手动将之后例子的代码放入一个 main 函数中。这样,例子将显得更加简明,使我们可以关注实际细节而不是样板代码。 在所有权的第一个例子中,我们看看一些变量的 作用域(scope)。作用域是一个项(item)在程序中有效的范围。假设有这样一个变量: 变量 s 绑定到了一个字符串字面值,这个字符串值是硬编码进程序代码中的。这个变量从声明的点开始直到当前 作用域 结束时都是有效的。示例中的注释标明了变

write less , do more 2432

Rust所有权学习笔记

本文是的学习笔记。大部分代码是菜鸟教程中给出的,文字稍加提炼,加入了一些自己的理解我们都知道,C/C++需要申请和释放内存,即手动管理内存。所有权概念是为了让 Rust 在编译阶段更有效地分析内存资源的有用性以实现内存管理而诞生的概念。所有权是Rust语言的精髓之一!

qq_74143285的博客 795

Rust学习笔记-认识所有权

出于同样原因,处理器在处理的数据彼此较近的时候(比如在栈上)比较远的时候(比如可能在堆上)能更好的工作。不幸的是,我们不能为了每一个在编译时大小未知的文本而将一块内存放入二进制文件中,并且它的大小还可能随着程序运行而改变。在大部分没有 GC 的语言中,识别出不再使用的内存并调用代码显式释放就是我们的责任了,跟请求内存的时候一样。图 4-3 中的那样,如果 Rust 也拷贝了堆上的数据,那么内存看起来就是这样的。类型,为了支持一个可变,可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容。

快乐的小小程序猿 1277
上一篇: Rust 学习笔记:函数和控制流
下一篇: Rust 学习笔记:编程练习(一)
UestcXiye
博客等级 码龄8年 9029粉丝 1306原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

UestcXiye

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值