Rust -- String类型引出Rust的一些特性

Rust还有第二种字符串类型:String

它是在 Heap 上进行分配的,能够存储在编译时未知数量的文本,相对于那些基础标量数据类型更加的复杂,基础标量数据类型是存放在 Stack上的,它们在离开自己的 scope 时会自动弹出栈,我们如果使用字符串字面值声明字符串的话是不可变的,String就是为了此需求而诞生的

创建 String 类型

我们可以使用 from 函数从字符串字面值创建出 Strng 类型:

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

    println!("{}",s);

    s.push_str("_World");

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

// Hello
// Hello_World

而 String类型可以修改的主要原因就是处理内存的方式和字符串字面值处理内存的方式不一样

字符串字面值在编译的时候就知道它的内容了,其文本内容是直接被硬编码到最终的可执行文件中的,所以字符串字面值的优势就是速度快,这得益于它的不可变性

String类型就是为了支持可变性而诞生的,它需要在 Heap 上分配内存来保存编译时未知的文本内容

  • 操作系统必须在运行时进行内存的请求(通过 String::from 来实现)
  • 当使用完毕 String 之后,需要使用某种方式将内存返回给操作系统(这步就有意思了)

如果在拥有 GC 的语言中,GC 会跟踪并清理不再使用的内存,如果该语言没有 GC ,那么就需要我们自己去识别内存何时不再使用,并调用代码将它返回,如果我们没有返回,内存就会浪费,如果返回提前了,变量则会非法,如果做了俩次返回操作,则会出现不可预估的BUG

当然,既然提到了这点,那么 Rust 必然是有解决办法的,对于某个值来说,当拥有它的变量走出 scope 时,内存则会立即释放,也就是立即自动的将内存返回给操作系统,这一步的实现归功于 Rust 的 drop函数,只要变量走出 scope, Rust立即执行 drop 函数,进行内存释放

Move

Rust 中有 Move这样一个概念,应用于变量和数据的交互类型:多个变量可以于同一个数据使用一种独特的方式来进行交互

fn main() {
    let x = 66;

    let y = x;
    // 这里的 x 是之前 x 的副本

    println!("{}--{}",x,y);
}
// 66--66

整数是已知且固定大小的简单的值,这俩个整数字面量被压到了 Stack 中

当然这是标量数据类型,如果是存储在 Heap 内的数据呢?我们以 String 类型为例子:

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

   let s2 = s1;

   println!("{}",s2);
}
// Hello

这没有问题呀,有什么区别吗? 🤔

区别其实就在于 s1 不见了!不信我们可以输出 s1:

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

   let s2 = s1;

   println!("{}",s2);

   println!("{}",s1);
}
// borrow of moved value: `s1`

Rust 说 s1 被移动了?这是怎么回事?
这个问题其实还和数据存储离不开,我们来看一下 String 的结构:

  • String 中有一个指向存放字符串内容的内存指针
  • 长度(存放字符串内容所需要的字节数)
  • 容量(String从操作系统总共获取的内存的总字节数)

没错,指针、长度、容量,String就是由这三个部分组成的、这些东西将存放在 Stack 上、存放真正字符串内容的部分不在 Stack上,而是在 Heap 上

在这里插入图片描述

当我们把 s1 赋给 s2 的时候, String 的数据被复制了一份,具体是怎么进行的呢?

  • 在 Stack 上复制了一份指针、长度以及容量
  • Stack中的信息给到 s2,s1失效

为什么 s1会失效?

其实不难理解,就是因为我们上述提到的解决内存释放问题 Rust 给我们的答案:

当变量离开 scope 的时候,Rust 会自动调用 drop 函数,这个我们之前提到过,并将变量使用的 Heap 内存释放,所以按照这个逻辑,s1、s2都离开 scope 时会怎么做?必定会引起二次释放,然后导致程序错误

为了保证内存的安全,Rust 没有去复制被分配的内存、而是让 s1 失效,当 s1 离开 scope 时无需释放任何内存

所以让我们的思路再次回到标题: Move

我之前是前端开发工程师、所以对于 JavaScript 有些见解、对于深浅拷贝也是烂熟于心,在Rust 中也是有这样一个模糊概念的,为什么是模糊概念?我们可以将复制 Stack 理解为浅拷贝,但是深拷贝呢?Rust 已经让 s1 失效了,所以 Rust 叫它为 移动,Rust是不会自动去创建数据深拷贝的

在运行时,为了性能的极致追求,任何的自动赋值的操作都是廉价的

如果我们真的有需求对 Heap 上的数据进行深拷贝的话,可以使用 Rust 为我们提供的 clone 方法

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

   let s2 = s1.clone();

   println!("{}--{}",s1,s2);
}
// Hello--Hello

Copy trait

上面我们提到了 Heap 上的数据克隆、接下来我们谈谈 Stack 上的数据复制怎么理解

在 Rust 上有这样一个接口:Copy trait ,它用于类似整数这样的完全存放在 Stack 上的数据类型的复制

如果某一个类型实现了 Copy 这个 trait,那么旧的变量在赋值之后任然可以使用

如果一个类型的一部分实现了 Copy trait,那么 Rust 是不允许它实现 Cpoy trait的,这主要是针对元组这样的数据类型的,如果 Tuple 中所有字段拥有 Copy trait的话,那么该 Tuple 也将拥有 Copy trait

拥有 Copy trait 的类型:

  • 任何简单的标量数据类型,例如整数类型、bool类型、char类型、浮点类型以及上面提到的复合条件的 Tuple

当然函数在返回值的过程中同样也会发生所有权的转移

也就是说一个变量总会遵循以下模式:

  • 将一个值赋给其他变量的时候就会发生Move
  • 当一个包含 Heap 数据的变量离开 scope 时,它的值就会被 drop 函数清理,除非数据的所有权移动到了另一个变量上

那么使函数使用某一个值但是不获取其所有权呢?

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

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

   println!("{}->length:{}",s2,len);
}

fn func(s:String) -> (String,usize) {
    let length = s.len();
    (s,length)
}
// Hello->length:5

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Try Tomato

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

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

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

打赏作者

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

抵扣说明:

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

余额充值