【Rust】004-Rust 所有权

2024-08-22 22:12
文章标签 rust 004 所有权

本文主要是介绍【Rust】004-Rust 所有权,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

【Rust】004-Rust 所有权

文章目录

  • 【Rust】004-Rust 所有权
  • 一、预备知识
    • 1、堆和栈
    • 2、String 类型
      • &str
      • String
  • 二、所有权规则
    • 1、所有权系统的三条规则
    • 2、代码示例
    • 3、所有权转移
      • 简单示例
      • 复杂类型的拷贝
    • 4、函数的传值与返回
  • 三、引用与借用
    • 1、借用
    • 2、不可变引用(只读)
    • 3、可变引用(可读可写)
    • 4、重要规则
    • 5、NLL
    • 6、悬垂引用
      • 错误代码
  • 四、切片
    • 1、概念
    • 2、字符串切片
      • 基本写法
      • 简化写法
    • 3、其他切片
  • 五、总结

rust 语言很严谨!符合我严谨认真的性格!

一、预备知识

1、堆和栈

堆和栈都可以在程序运行时给程序提供内存空间,但是他们是有区别的。


想象一下,栈就像一叠盘子。先来的盘子在底部,新盘子则放在顶部。取用时,总是先取最上面的盘子,就像在餐馆里洗完的盘子先用先拿。这就是先进后出的原则。但是,盘子大小必须是确定的,否则无法存放在这个“栈”里。


而堆则像一个大杂货仓库。你可以随时放入各种大小形状物品。为了方便找到它们,你需要贴上标签,这些标签就像指针,告诉你物品位置。虽然这样存放物品灵活多变,但要找到合适的空间并读取它们就不如“栈”中的盘子方便了。


性能赛跑中,栈就像速度迅猛的跑车,堆则更像一辆载重卡车。栈的读写速度快,因为一切都井井有条,就像把盘子放在最上面那样简单。而堆要先找到合适的存放空间,读取时还要通过标签指针)找到物品,就像在杂货仓库里寻找一个小小的零件一样,可能需要多花点时间


2、String 类型

Rust 编程世界中,字符串有点像我们现实生活中的名片(印好之后,不再改变)和便签(可以继续往上面写东西)。有两种类型让我们来认识它们:String和&str

&str

存储逻辑

在 Rust 中,字符串字面量(如 "hello")的实际值通常存储在程序的只读数据段中。这个数据段是编译时确定的,并且在程序运行时是不可变的。这意味着字符串字面量在程序的生命周期内是常驻内存的,不会被修改或释放。

当你在代码中使用字符串字面量时,例如:

let s: &str = "hello";

这个 &str 类型的变量 s 是一个胖指针,包含了指向 "hello"内存地址和字符串的长度。这些信息本身存储在栈上,而 "hello" 的实际字符串数据则在程序的只读数据段中。


想象&str是一种名片,它被称为字符串字面值,就像是工厂里生产出来的名片,印好了,就再也不能改了。你可以这样给自己制造一张名片

fn main() {// 使用字符串字面量创建静态字符串let hello = "hello world";}

这张名片是固定的,不可变的,因为它是硬编码到程序中的,就像是直接印在墙上的字。


String

存储逻辑

  1. 堆上存储数据
    • String 的实际字符串数据存储在堆上。这意味着 String 可以在程序运行时动态调整其大小,而不受栈大小的限制
  2. 栈上存储元数据
    • String 类型在栈上存储了一些元数据,包括一个指向堆上数据的指针、字符串的长度以及当前的容量(即分配的堆内存大小)。这些元数据使得 String 可以管理堆上的内存。
  3. 容量与长度
    • 长度:表示当前存储在 String 中的字符数。
    • 容量:表示已经分配的内存空间,允许在不重新分配的情况下扩展字符串。String 可能会预先分配比实际需要更多的内存,以优化性能,减少频繁的内存分配和复制操作。
  4. 增长策略
    • String 的内容增加到超出其当前容量时,String 会自动分配更大的内存块,并将现有数据复制到新分配的内存中。这通常是通过倍增策略来实现的,以平衡内存使用和性能。
  5. 所有权与内存管理
    • String 拥有其堆上数据的所有权,这意味着当 String 被丢弃时,它会自动释放其占用的堆内存。这是 Rust 所有权模型和借用检查器的一个重要特性,确保了内存的安全管理。
  6. 可变性
    • String 是可变的,可以通过方法如 pushpush_str 来追加数据,或者通过 truncate 来减少长度。

但生活中不是所有的信息都是确定的,有时候我们需要一些便签。这就是String的角色了,比如你想要记录下用户的一些临时想法或命令。String的数据存储在上,就像前面提到的杂货仓库,可以随时变动。你还记得堆和栈的故事吗?

创建一个String就像是抓一张空白的便签纸,你可以从字符串字面值开始书写:

fn main() {// 创建一个可变字符串let mut hi = String::new();// 写入文字hi.push_str("hello");hi.push_str(" world");}

二、所有权规则

1、所有权系统的三条规则

  1. Rust 中每个值都有一个所有者;
  2. 一个值同时只能有一个所有者;
  3. 当所有者离开作用域范围,这个值将被丢弃。

2、代码示例

fn main() {// 规则 1:Rust中每个值都有一个所有者let s1 = String::from("hello"); // s1 是值 "hello" 的所有者{// 规则 2:一个值同时只能有一个所有者let s2 = s1; // 所有权从 s1 转移到 s2(s1 不再有效)// println!("{}", s1); // 这会导致编译时错误,因为 s1 不再有效println!("{}", s2); // 这是允许的,因为 s2 现在拥有该值} // s2在此处超出范围// 规则 3:当所有者离开作用域范围,这个值将被丢弃// 由于 s2 超出范围,因此为值 "hello" 分配的内存在此处自动释放。
}`  

3、所有权转移

简单示例

i32这样的简单类型,赋值的时候 Rust 会自动进行拷贝(Copy)。

let x = 5;
let y = x;

这段代码中,首先将 5 绑定到 x,接着再将 x 的值拷贝给 y。这两行执行完, x 和 y 都是 5,且都可以正常使用。稍稍改变一下这个例子。

而对于 String 这样的分配到堆上的复杂类型,发生的却是所有权的转移,而不是拷贝。

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

简单类型:自动拷贝(简单类型的实际数据内容存储在栈上的);

复杂类型:所有权转移(拷贝的是内存地址,实际数据内容在堆上,但会导致一个数据被两个变量同时拥有,离开作用域会出现双重释放的情况,进而导致安全问题,因此Rust设计了所欲全转移机制!)。


复杂类型的拷贝

这种拷贝不存在所有权的转移,他们是相互独立的!

let s1 = String::from("hello");
let s2 = s1.clone();println!("s1 = {}, s2 = {}", s1, s2); // s1 = hello, s2 = hello

4、函数的传值与返回

将值传给函数跟上面讲的赋值类似,都会进行转移或者拷贝的过程。**函数返回一个值的时候,也会经历所有权转移的过程。**我们用下面的例子来说明:

fn takes_ownership(s: String) {println!("Received string: {}", s);
} // s 离开作用域,被丢弃fn gives_ownership() -> String {String::from("hello")
} // 返回了String的所有权fn main() {// s 拿到了"hello"的所有权let s = String::from("hello");// 所有权转移给了 takes_ownership 函数的参数:stakes_ownership(s); // s转移到了函数内,不再可用// s 不再可用// 此处还可以声明一个 s ,是因为上面的 s 已经被回收了!let s = gives_ownership(); // s 获得了返回值的所有权
}

三、引用与借用

1、借用

只使用变量,而不拿走所有权,叫“借用”!


2、不可变引用(只读)

fn main() {// s1 拿到"hello"的所有权let s1 = String::from("hello");// 使用 &s1 而不是 s1,借出去,只是借出去,并不允许值被改变// 使用 len 接收返回值let len = calculate_length(&s1);// s1 仍然具有"hello"的所有权// len 是借用出去后所得到 len() 的返回值println!("The length of '{}' is {}.", s1, len);
}// 使用 &String 表示借用,是 String 类型的引用
fn calculate_length(s: &String) -> usize {// 从借来的 s 取得 len() 的值,并返回s.len()
}

3、可变引用(可读可写)

fn main() {// s 拿到 "hello" 的所有权,s 本身是可修改的let mut s = String::from("hello");// 将 s 借出去,并允许被修改change(&mut s);// s 的值被修改了println!("The updated string is: {}", s);
}// 这里使用 &mut String 来接收,表示要求可被修改
fn change(s: &mut String) {// 修改借来的数据s.push_str(", world!");
}

4、重要规则

对于一个变量,同时只能存在一个可变引用或者多个不可变引用。

fn main() {let mut s = String::from("hello");// 多个不可变引用是允许的let r1 = &s;let r2 = &s;println!("r1: {}, r2: {}", r1, r2);// 在这里,多个不可变引用是允许的,因此打印不会引发错误。// 一个可变引用let r3 = &mut s;println!("r3: {}", r3);// 在这里,只有一个可变引用,因此打印不会引发错误。// 不允许同时存在可变引用和不可变引用// let r4 = &s; // 这会导致编译时错误// 如果这里只打印 r4 是不会报错的,因为 r3 在上面已经释放,但此处打印了 r3 ,r3 就不会在此前被释放了!// println!("r3: {}, r4: {}", r3, r4);// 如果取消注释上面两行,同时存在可变引用和不可变引用将导致编译时错误。
}

5、NLL

在老版本的 Rust 编译器中(1.31之前),确实上述的r1,r2r3是会报错的。但是这样其实会带来很多麻烦,导致代码很难写。于是 Rust 编译器做了一项优化:引用的作用域结束的位置不再是花括号的位置,而是最后一次使用的位置。因此,在现在 Rust 的版本中,上面的例子并不会报错。


6、悬垂引用

悬垂引用指的是指针指向的是内存中一个已经被释放的地址**,这在其他的一些有指针语言中是很常见的错误。而 Rust 则可以在编译阶段就保证不会产生悬垂引用。也就是说,如果有一个引用指向某个数据编译器能保证在引用离开作用域之前,被指向的数据不会被释放。

错误代码

fn main() {let reference_to_nothing = dangle();
}
fn dangle() -> &String {let s = String::from("hello");// 这里会报错,因为 s 已经被释放了!返回的地址是一个无效的地址!// this function's return type contains a borrowed value, but there is no value for it to be borrowed from// 该函数返回了一个借用的值,但是没有可以借用的来源// 引用必须是有效的&s
}

四、切片

1、概念

切片可以让我们引用集合中的一段连续空间。切片也是一种引用,因此没有所有权


2、字符串切片

基本写法

fn main() {let s = String::from("hello world");let hello = &s[0..5];let world = &s[6..11];println!("{}", s);println!("{}", hello);println!("{}", world);}

简化写法

let s = String::from("hello");
let len = s.len();// 以0开始时,0可以省略
let slice = &s[0..2];
let slice = &s[..2];// 以最后一位结束时,len可以省略
let slice = &s[3..len];
let slice = &s[3..];// 同时满足上述两条,那么两头都可以省略
let slice = &s[0..len];
let slice = &s[..];

3、其他切片

String 本身就是数组

#[derive(PartialEq, PartialOrd, Eq, Ord)]
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(not(test), lang = "String")]
pub struct String {vec: Vec<u8>,
}

除了 String,数组类型也有切片。例如:

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

五、总结

本节内容较多,主要包含了三部分的知识:所有权,借用和切片。所有权这套系统是 Rust 内存安全的重要保障。有了这套系统,我们既可以享受不需要手动释放内存的便利,又可以对内存使用有足够的控制,保证内存安全。

这篇关于【Rust】004-Rust 所有权的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1097534

相关文章

【Rust练习】12.枚举

练习题来自:https://practice-zh.course.rs/compound-types/enum.html 1 // 修复错误enum Number {Zero,One,Two,}enum Number1 {Zero = 0,One,Two,}// C语言风格的枚举定义enum Number2 {Zero = 0.0,One = 1.0,Two = 2.0,}fn m

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

第二十四章 rust中的运算符重载

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust中的运算符重载 目录 注意一、前言二、基本使用三、常用运算符四、通用约束 一、前言 C/C++中有运算符重载这一概念,它的目的是让即使含不相干的内容也能通过我们自定义的方法进行运算符操作运算。 比如字符串本身是不能相加的,但由于C++中的String重载了运算符+,所以我们就可以将两个字符串进行相加、但实际

【Rust光年纪】Rust 机器人学库全景:功能、安装与API概览

机器人学+Rust语言=无限可能:六款库带你开启创新之旅! 前言 随着机器人技术的快速发展,对于机器人学领域的高效、可靠的编程语言和库的需求也日益增加。本文将探讨一些用于 Rust 语言的机器人学库,以及它们的核心功能、使用场景、安装配置和 API 概览,旨在为机器人学爱好者和开发人员提供参考和指导。 欢迎订阅专栏:Rust光年纪 文章目录 机器人学+Rust语言=无限可能:

第二十二章 rust数据库使用:sea-orm详解

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust数据库使用:sea-orm详解 目录 注意一、前言二、项目管理三、迁移文件四、实体文件五、业务使用 一、前言 只要开发稍微大型一点的项目,数据库都是离不开的。 rust目前并没有特别成熟的数据库框架,sea-orm这个框架是我目前所看到的成熟度最高的一个,并且仍在积极开发中。 所以本文将以sea-orm框

Rust使用之【宏】

一、简单使用clap clap = { version = "4.5.17", features = ["derive"] } 其中,什么是features = ["derive"]:表示你希望在添加 clap 依赖时启用 derive 特性。这通常意味着你希望使用 clap 的派生(derive)宏功能,这些功能可以简化创建命令行接口的代码。例如,derive 特性可以让你使用 #[der

第二十一章 rust与动静态库的结合使用

注意 本系列文章已升级、转移至我的自建站点中,本章原文为:rust与动静态库的结合使用 目录 注意一、前言二、库生成三、库使用四、总结 一、前言 rust中多了很多类型的库,比如前面章节中我们提到基本的bin与lib这两种crate类型库。 如果你在命令行执行下列语句: rustc --help 那么你将能找到这样的内容: --crate-type [bin|li

Rust的常数、作用域与所有权

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学,之一  -CSDN博客 Rust到底值不值得学,之二-CSDN博客 Rust的数据类型-CSDN博客 3.7  常数的数据类型 在Rust语言中,变量有类型,常量也有类型。我们知道,在定义const常量的时候,就要

004: VTK读入数据---vtkImageData详细说明

VTK医学图像处理---vtkImageData类 目录 VTK医学图像处理---vtkImageData类 简介: 1 Mricro软件的安装和使用 (1) Mricro安装 (2) Mricro转换DICOM为裸数据  2 从硬盘读取数据到vtkImageData 3 vtkImageData转RGB或RGBA格式 4 练习 总结 简介:         对于医

搭建Rust的开发环境

目标 我没有使用过Rust,但我听说它是一个可靠的语言,可以保证内存安全和线程安全。我对此很有兴趣,就想试一试这个语言。 在官网上有介绍他们所推荐的编辑器: 我将选择 Visual Studio Code (关于 【Visual Studio】和【Visual Studio Code】的区别:【VS】是完整的集成开发环境,而【VSCode】只算的上是一个相对轻量级的文本编辑器,并附带一些便捷