Rust入门之:所有权

什么是所有权?

Rust的核心特性就是所有权

大家知道java保证内存安全是通过垃圾收集机制,不断的扫描不在使用的内存来进行内存释放从而保证内存安全。而C语言是通过手动分配和释放内存来保证内存安全

但是Rust的所有权能让Rust无需GC就能保证内存安全

栈内存(stack)和堆内存(heap)

1、stack的存储顺序先入后出(FILO);添加数据叫入栈,移除数据叫出栈;

2、所有存在stack上的数据,必须拥有已知的固定大小,编译时未知的数据或者是运行时会发生改变的数据会存放在heap上。

3、当数据存放在heap上时,操作系统会分配一块足够大的空间,并把此内存块标记为已用,并返回一个指针,指针指向此内存地址。

4、因为指针大小是固定的,所以可以把指针放在stack中,但是想要实际的数据,则需要通过指针去定位实际数据

5、将数据存放在stack上要比存放在heap快得多,因为stack不需要操作系统分配内存,永远都是把最新数据放在最顶端,在heap上分配也是需要不少时间的

6、访问heap中的数据要比访问stack中的数据慢得多,因为需要指针去定位heap中的数据(指针在内存中跳转次数越少,查找越快)

7、当你调用函数时,值被传入到函数中(也包括指向heap指针),函数内部的变量会入栈,当函数执行完毕,函数内部变量会出栈

8、所有权能跟踪那些代码正在使用存在heap上的数据,最小化heap上重复数据量,清理heap上未使用的数据

规则、内存、分配
规则

1、每个值都有一个变量,该变量是该值的所有者

2、每个值同事只能有一个所有者

3、当所有者超出作用域时,该值会被删除

变量作用域

1、Scope是程序中一个项目的有效范围

例子:

fn main() {
    //xxx不可用
    let xxx = "hello,world";//xxx可用
    //对xxx一系列操作
}//作用域结束,xxx不可用
String类型

前面提到过4种基础数据类型(整数,小数,浮点,布尔),都是存放在stack上,而String类型是存放在heap上

字符串字面值是不可变的, String的值是可变的(因为他们处理内存的方式不同)

fn main() {
    let xxx = "hello,world";
    let yyy = "123";
    let z = format!("{},{}",xxx,yyy);
    println!("新值:{}",z);

    let mut str = String::from("你好,我是赛利亚");
    str.push_str("今天又是充满希望的一天");
    println!("结果:{}",str);
}
输出:
新值:hello,world,123
结果:你好,我是赛利亚今天又是充满希望的一天
内存与分配

字符串字面值在编译时就知道其内容,其内容被硬编码到可执行文件中,所以它是不可变的

String为了支持其可变性,在编译时是未知的,操作系统必须在运行时来请求内存,

- 通过String::from 来实现

- 使用完String后,通过某种方式把内存返回给操作系统

        - 对于有GC的语言来说,GC会清理不在使用的内存

        - 对于无GC的语言来说,就需要我们自己去识别哪些内存还在使用,哪些内存不在使用

我们Rust就采用当变量走出作用域时,就立刻调用drop函数把内存返回给操作系统,属于主动行为。

变量与数据交互方式
移动(Move)

多个变量可以与同一个数据用一种特殊的方式进行交互

基础数据类型例子:

fn main() {
    let x = 5;
    let y = x;
    println!("y: {}", y);
}
输出:
y: 5

此处y=x的副本,所以最终会有两个5会入栈

String类型例子:

fn main() {
    let x = String::from("你好");
    let y = x;
    println!("y: {}", y);
}
输出:
y: 你好

String类型与基础数据类型不同,x的内容存在heap中,x的(指针、长度、容量)存在stack中(我们称为x1),当我们把x赋值给y时,其实是复制了一份x1为y1,把y1赋值给了y。当最后x1、y1离开作用域时,会释放两次相同的内存,会引起一个二次释放内存的bug。所以Rust在把x1复制成y1后,此时x1已经被释放,是不能再使用的。从而解决了二次释放内存的bug

例子:

fn main() {
    let x = String::from("你好");
    let y = x;
    println!("x: {}", x);
    println!("y: {}", y);
}
编译时错误:
  |
2 |     let x = String::from("你好");
  |         - move occurs because `x` has type `String`, which does not implement the `Copy` trait
3 |     let y = x;
  |             - value moved here
4 |     println!("x: {}", x);
  |                       ^ value borrowed here after move

我们把x1赋值给y1可以称为浅拷贝,由于x1已经失效了,我们称这种操作为移动(Move),Rust不会自动创建数据的深拷贝。

深拷贝和浅拷贝

stack上的数据复制称为浅拷贝

heap伤的数据复制称为深拷贝

克隆(clone)

如果想使用到heap的深拷贝,那么我们可以使用clone。

例子:

fn main() {
    let x = String::from("你好");
    let y = x.clone();
    println!("x: {}", x);
    println!("y: {}", y);
}
输出:
x: 你好
y: 你好

x的(指针、长度、容量)存在stack中(我们称为x1),x的内容存在heap中(我们称为x2),当我们把x.clone()赋值给y时,其实是复制了一份x1为y1,x2为y2,把y1和y2赋值给了y。也就是把stack伤的数据复制了一份和heap上的数据都克隆了一份

复制(copy)

针对在stack上的数据,我们不需要克隆,我们称为复制

fn main() {
    let x = 5;
    let y = x;
    println!("x: {}", x);
    println!("y: {}", y);
}
输出:
x: 5
y: 5

在rust中基础数据类型默认实现了copy trait,所以旧变量在赋值之后仍然是可用的

任何简单标量数据类型都是实现了copy trait,任何需要分配内存的都没有实现copy trait

以下这些类型实现了copy trait

1、所有整数类型- u32

2、bool

3、char

4、浮点类型 - f64

5、tuple元组,如果所有元素都实现了copy trait那么该元组就能实现copy trait,反之则不能

所有权与函数

将值传递给函数和传递给变量是类似的

将值传递给函数讲发生移动复制

fn main() {
    let s = String::from("你好呀,我是赛利亚");//s有效
    sai_li_ya(s);//s离开作用域
    println!("s de result :{}",s);//s无效 因为s是String类型,调用sai_li_ya函数后,已经发生了移动

    let x = 10;//x有效
    base_fn(x);//x有效
    println!("x de result :{}",x);//x有效,x是整数类型,默认实现了copy trait,所有调用base_fn时是传递的x的副本

}

fn sai_li_ya(s:String) {
    println!("dnf: {}", s)//s有效
}//s离开作用域

fn base_fn(x : i32){
    println!("base: {}", x)
}

编译错误:
2  |     let s = String::from("你好呀,我是赛利亚");//s有效
   |         - move occurs because `s` has type `String`, which does not implement the `Copy` trait
3  |     sai_li_ya(s);//s离开作用域
   |               - value moved here
4  |     println!("s de result :{}",s);//s有效
   |                                ^ value borrowed here after move
返回值与作用域

一个函数在返回值的过程中也会发生所有权的转移

一个变量的所有权总是遵循同样的模式

把一个值复制给另一个变量时就会发生移动

当一个在heap上的变量离开作用域时,会被drop函数清理掉,除非数据的所有权转移到另一个变量上

fn main() {
    let s1 =  sai_li_ya();//此时s1拥有sai_li_ya返回的s的所有权

    let s2 = String::from("勇士");//s2被创建

    let s3 = s_send(s2);//s2被函数传递,所有权被传递到s_send函数中,s2作用域结束//s3拥有s_str返回的所有权
}//s1,s3作用域结束

fn sai_li_ya() -> String{
    let s = String::from("你好呀,我是赛利亚"); //s创建
    s //s所有权被返回,s作用域结束
}//s离开作用域

fn s_send(s_str : String)->String{//s_str拥有s2的所有权,
    s_str//s_str所有权又被返回,作用域结束
}
如何让函数获取起值,但不获取其所有权呢?
引用(reference)与借用

以引用作为函数参数的行为,我们称为借用

&:只传递值,不传递所有权

* :解引用

fn main() {

    let s2 = String::from("勇士");//s2被创建

    let s3 = s_send(&s2);//s2被函数传递,所有权没有被传递,只传递了其值
}//s2,s3作用域结束

fn s_send(s_str : &str)->usize{//s_str不拥有s2的所有权,只拥有s2的值,
    s_str.len()//返回s2的长度
}

引用默认也是不可变的,但是可以用通过mut变成可变的。

fn main() {

    let mut s2 = String::from("勇士");//s2被创建

    let s3 = s_send(&mut s2);//s2被函数传递,所有权没有被传递,只传递了其值
}//s2,s3作用域结束

fn s_send(s_str : &mut String)->usize{//s_str不拥有s2的所有权,只拥有s2的值,
    s_str.push_str(", 好久不见");
    println!("push result: {}",s_str);
    s_str.len()//返回s2的长度
}
输出:
push result: 勇士, 好久不见

有一个限制:在特定的作用域中,一块数据,只能同时有一个可变的引用,防止在编译时发生数据竞争

fn main() {

    let mut s2 = String::from("勇士");//s2被创建
    let s3 = &mut s2;
    let s4 = &mut s2;
    println!("result : {},{}",s3,s4);
}

编译报错:
4 |     let s3 = &mut s2;
  |              ------- first mutable borrow occurs here
5 |     let s4 = &mut s2;
  |              ^^^^^^^ second mutable borrow occurs here
6 |     println!("result : {},{}",s3,s4);
  |                               -- first borrow later used here

在不同的作用域中可以有多个可变引用

fn main() {

    let mut s2 = String::from("勇士");//s2被创建
    {
        let s3 = &mut s2;
        println!("result : {}",s3);
    }
    let s4 = &mut s2;
    println!("result : {}",s4);
}

输出:
result : 勇士
result : 勇士

另一个限制,不可同时拥有可变引用和不可变引用

fn main() {

    let mut s2 = String::from("勇士");//s2被创建
     let s3 = &s2;
    let s4 = &mut s2;
    println!("result : {},{}",s3,s4);
}

编译报错:
4 |      let s3 = &s2;
  |               --- immutable borrow occurs here
5 |     let s4 = &mut s2;
  |              ^^^^^^^ mutable borrow occurs here
6 |     println!("result : {},{}",s3,s4);
  |                               -- immutable borrow later used here
悬空引用(Dangling References)

一个指针指向了heap上一个地址,但是这个指针的其他人正在使用,但是此内存地址已经被释放了。就会引起悬空引用。

fn main() {
    let mut s3 =back();//s3获取到str的引用,但是此时str已经离开了作用域,已经被释放掉
    println!("result : {}",s3);
}

fn back()-> &str{//返回一个引用
    let str = String::from("勇士");
   &str//返回str的引用
}//str离开作用域,所有权已失效

编译报错:
error[E0106]: missing lifetime specifier
切片(slice)

切片是Rust另一种不持有所有权的数据类型

切片规则可以理解为左闭右开,形式:[开始索引..结束索引]

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

    let hello = &str[0..5];
    let hello2 = &str[..5];//hello等同于hello2
    let world = &str[6..11];
    let world2 = &str[6..str.len()];//world等同于world2
    let world3 = &str[6..];//world等同于world2 等同于world3

    let all = &str[..];
    let all2 = &str[0..str.len()];//all等同于all2

    println!("hello : {}",hello);
    println!("hello2 : {}",hello2);
    println!("world : {}",world);
    println!("world2 : {}",world2);
    println!("world3 : {}",world3);
    println!("all : {}",all);
    println!("all2 : {}",all2);
}
输出:
hello : hello
hello2 : hello
world : world
world2 : world
world3 : world
all : hello world
all2 : hello world

 

  • 26
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值