研读Rust圣经解析——Rust learn-5(所有权,强大的String)

所有权

Rust 的核心功能(之一)是 所有权(ownership),Rust 通过所有权系统管理内存,编译器在编译时会根据一系列的规则进行检查。如果违反了任何这些规则,程序都不能编译。在运行时,所有权系统的任何功能都不会减慢程序。

栈和堆

这两个概念可以说是非常经典,在Rust中我们应该比其他语言考虑和理解这两个概念更多

相同点

栈和堆都是代码在运行时可供使用的内存

  1. 栈中的所有数据都必须占用已知且固定的大小
  2. 后进先出
  3. 存储速度块

  1. 存储在编译时大小未知或大小可能变化的数据
  2. 存储时会需求一块尽可能大的内存区域
  3. 指针指向,访问慢

所有权规则

  1. Rust 中的每一个值都有一个 所有者(owner)。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。

请大家牢牢记住这三大规则,因为如果你刚接触Rust不久,你很容易犯所有权的错误!特别是最后一条

作用域

作用域是一块区域,当变量在自己所属的作用域中变量是有效的,出了则变量被释放
我们来看看这个例子:

fn scope_area() -> &str {
    
    
    let a ="5";
    return a;
}

fn main() {
    
    
    let x = scope_area();
    println!("{}", x);
}

这段代码在scope_area方法中声明了一个a变量,然后将其返回,注意了,这里我们返回的是&str类型,简单来说它是字符串切片,是对字符串的借用,这段代码看似编辑器通过了,没有语法错误,实则:

在这里插入图片描述
出现如上的错误,说这个需要一个生命周期标识,我们来说一下为什么?
首先&str不是String!而是对String的借用,是字符串切片,他只是借了String,不是它本身,就好比,别人借用了你的一支笔,他就不能说这支笔是他的,他没有处理这只笔的权限,所以他不能再把你的笔借给其他人。
那么&str也是这样,他也无法把所有权移交给其他变量,所以一出作用域,他就失效了,被释放了,新的变量自然无法获取到返回值。当然这里编译器告诉了你解决的方法,就是使用生命周期,但是现在我们没学到,所以不予采纳。
正确方式应该是:

fn scope_area() -> String {
    
    
    let a = "5";
    return a.to_string();
}

fn main() {
    
    
    let x = scope_area();
    println!("{}", x);
}

这里我们最后通过to_string将字符串切片转化为了String,自然没有这个问题了
另一种:

fn scope_area() -> String {
    
    
    let a = "5";
    return a.to_owned();
}

fn main() {
    
    
    let x = scope_area();
    println!("{}", x);
}

我们通过to_owned方法获取到了a真实字符串的所有权,此时相当于别人说我把笔送你了,你就有了笔的所有权,你这时候就可以处理这只笔了,你再把笔怎么样都是你的事情。

String

String but not only String,这是我认为对Rust中的String最准确的一句话,因为Rust中String不是简单的一个char数组,它有很多花样,以至于我看来,如果你弄懂了Rust的String你就掌握了类型精髓的三分之一(可能大家觉得三分之一很少,但是真正了解后你会知道这是很多的内容),接下来我会详细的解析’String’类型

String

UTF-8编码的可增长字符串。 字符串类型是对字符串内容拥有所有权的最常见的字符串类型。它与借用的对应物原始str有着密切的关系。String是存在堆上的

创建String

创建空字符串

let x = String::new();

从字符串字面量创建(将&str转化为String)

let a = String::from("test");

str

称为“字符串切片”,是最原始的字符串 类型。它通常以其借来的形式出现,&str

特点

  1. 不可变
  2. 在编译时知道内容
  3. 直接以硬编码写入可执行文件
  4. 快速高效

创建str

按照如下写法,直接创建就是一个&str,

let a = "test";

出了上方的String|str实际上Rust中还有很多类型的String,如OsString

所有权转移

这个名字只是我这样叫而已
我们来说明一下为什么我这样说,首先我们要清楚一个概念,在Rust中String也是复合类型不是简单类型,简单类型指的是那些没有容量一经声明值就能确定的,即:i8,u8,i32,f64,bool等这些在前面介绍到的,而String实则是一个Vector!

String源码

以下是String的源码:

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

我们看到实则String是Vec<u8>至于为什么是u8,大家应该知道u8是无符号整数,范围0~255,这个就是我们常说的ASCLL码的有效范围

所以我们要明确一个概念,String是复合类型,有长度有容量,可以扩容,存在堆中,他的引用也就是指针在栈中!

所以当我们使用如下的代码时会出现问题:

fn main() {
    
    
    let a = String::from("test");
    let b = a;
    println!("{}", b);
    println!("{}", a);
}

在这里插入图片描述
从错误中我们看到 value borrowed here after move借用的值在移动后,也就是说所属于a的值被移动了,移动到了b,此时b抢占了a的String的所有权,致使a的值失效了!
我们一样举例子理解一下:首先a有一支笔,a把笔送给了b,此时b拥有了笔的所有权,a已经没有笔了,所以再去问a借笔的时候,就出现了错误
这就是所有权转移产生的问题
那么如何解决这个问题呢?

深克隆

为了解决所有权转移的问题,我们用到了深克隆,我们复制出一份一摸一样的数据,这样两个变量都会有值,就不存在所有权转移的问题

clone方法

通过使用clone方法直接就能克隆一份一样的数据,注意前提是实现了Clone的trait!当然这里你不知道什么是trait,如果你有其他语言的基础,那就理解为接口,或者你理解为一个规则,实现了规则就相当于有了一个入场的门票,就可以进出了

fn main() {
    
    
    let a = String::from("test");
    let b = a.clone();
    println!("{}", b);
    println!("{}", a);
}

如果你自己写了一个结构体,也想要clone一下有没有办法呢?其实这很简单,就是实现一下Clone,但实际上我们不会这样干,因为这是耗费时间耗费精力的,Rust提供了一个#[derive(Clone)]标注以帮助开发人员直接实现clone

#[derive(Clone)]
struct test{
    
    
	//..
}

作用域消费(所有权转移-续)

我们先来看一份代码

fn scope_area(mut b: String) -> String {
    
    
    b.push_str("hello");
    return b;
}


fn main() {
    
    
    let mut a = String::from("test");
    scope_area(a);
    println!("{}", a);
}

看上去好像没啥问题,但是编译后,会告诉你:
在这里插入图片描述
出现了和前面我们在如下代码里一样的问题

fn main() {
    
    
    let a = String::from("test");
    let b = a;
    println!("{}", b);
    println!("{}", a);
}

这说明了两者的本质问题一样,都是所有权转移了
接下来我们来理解一下,有个a是String,有个函数将a作为参数传入,在进行一系列操作后函数执行结束,此时我们再去访问a的时候所属于a的值已经被函数抢占走了,a就没有值了,函数对a的值进行一些操作后函数结束,这时所属于a的值没有被还回来,而是被释放了,这就是原因

打个比方:你有500块钱,跑到一个娱乐场所消费了一圈,最终你把500块钱全部用完,店铺挣得了你的钱,钱就不是你的,这不就是前面的所有权转移吗!

猜你喜欢

转载自blog.csdn.net/qq_51553982/article/details/130169455