理解Rust变量的所有权、引用与Copy,Clone的关系

变量的可变与不变

rust中,使用let声明的变量是不可变的。例如:

fn main() {
    let i = 100;
    //编译失败
    i = 200;
}

不同于java中的final,rust变量的不可变包括对象属性值的不可变。例如在Java中:

final Map<String, Integer> map = new HashMap<>();
//虽然map是final,但对象内部值仍然是可变的。
map.put("one", 1);
//编译失败,变量map是final,不能修改。
map = new HashMap<>();

而在rust中:

use std::collections::HashMap;
let map: HashMap<String, i32> = HashMap::new();
//编译失败,变量map是不可变的,内部值也不允许修改。
map.insert("one", 1);

使用let mut声明变量是可变的。

变量所有权与Copy,Clone的关系

rust中,所有的"值"都是有"所有者"的。这里的"值"代表一块内存空间,数据就存放于这块内存空间中。"值"既可以位于栈,也可以位于堆。而变量就是这块内存空间的"所有者",拥有这块内存空间的"所有权"。例如:

//可变变量i 拥有 一块i32类型所需大小的内存空间 的所有权, 这块内存空间存储的值 是 101
let mut i: i32 = 101;

当拥有"所有权"的变量被赋值给其他变量时,"所有权"也就交给了被赋值的变量。只有拥有"所有权"的变量才能对内存空间中存储的值进行读/写操作。
需要强调的是,"值"的类型是否实现了trait Copy 或trait Clone,或都没有实现,直接影响赋值行为。
Copy和Clone是rust中的2个trait,都表示"复制一份"的意思。Copy继承于Clone。不同点可以简单的理解为存储在栈的"值"需要实现Copy,存储在堆的"值"则需要实现Clone。
具体看Copy、Clone和普通类型赋值行为的不同,以及理解清楚它们的所有权关系。

//Copy类型,rust中,基本数据类型都实现了Copy。
//可变变量i 拥有 一块i32类型所需大小的内存空间 的所有权, 这块内存空间存储的值 是 101
let mut i: i32 = 101;
//将 可变变量i 拥有 的值101 Copy一份,存储在 不可变变量a 所拥有的内存空间中。
let a: i32 = i;
println!("{:?}, {:?}", a, i);
//这时内存中,有2个值为101的空间,所有权分别属于可变变量i和不可变变量a。i和a各自独立,互不影响。
//Clone类型,rust中,String类型实现了trait Clone,但没有实现trait Copy
//可变变量i 拥有 一块内存堆的空间 的所有权, 这块内存空间存储的值 是 hello
let mut i: String = String::from("hello");
//将 可变变量i 拥有 的值hello Clone一份,存储在 不可变变量a 所拥有的内存空间中。
let a: String = i.clone();
println!("{:?}, {:?}", a, i);
//这时内存中,有2个值为hello的空间,所有权分别属于可变变量i和不可变变量a。i和a各自独立,互不影响。

可以看出,Copy和Clone都产生了"复制一份"的目的, 区别在于Copy类型是隐式实现的。Clone类型需要调用clone()方法。
如果1个"值"的类型,既不是Copy,也不是Clone,会怎么样呢?

//自定义结构体Foo,既没有实现Copy,也没有实现Clone
#[derive(Debug)]
struct Foo {
    age: i32,
}

fn main() {
    let mut i: Foo = Foo {age: 20};
    //i的值,类型是Foo,Foo不是Copy,也不是Clone,没有clone()方法可以调用,没有发生"复制一份"的行为,这时候就进行内存空间所有权的交接
    let a = i;
    //编译失败, 可变变量i 的 所有权 交给了 不可变变量a,不再拥有内存空间的所有权。没有所有权就不能进行读/写操作了
    println!("{:?}, {:?}", a, i);
}

变量与引用

变量可以将值"借"给其他的变量使用,但不交出所有权。得到这些变量被叫做"引用"。语法上,使用&符号表示"借"的行为。
"引用"同样受可变性的控制。

let i: String = String::from("hello");
//将 不可变变量i 的值,借给 不可变变量a 使用
let a: &String = &i;
//将 不可变变量i 的值,借给 不可变变量b 使用
let b: &String = &i;
println!("{:?}, {:?}, {:?}", a, b, i);
//可变引用,可以修改值,但拥有所有权的变量也必须是可变的。
let mut i: String = String::from("hello");
let a: &mut String = &mut i;
a.push_str(" world");
println!("{:?}", a);
println!("{:?}", i);

从代码中可以看出,声明变量a时,数据类型是&String,说明引用本身就是一种数据类型。
通过引用,也可以操作"值"。

引用的限制:
可变引用与不可变引用不能同时存在。
在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
引用必须总是有效的。

解引用与Copy,Clone的关系

解引用是一种行为。
引用本身是一种数据类型,解引用就是通过引用类型获取真实的"值”。比如对1个&String类型的变量解引用,得到String类型的值。对&i32类型的变量解引用,得到i32类型的值。
解引用也受到"值"类型是否实现了Copy/Clone的影响。
语法上,使用*符号进行解引用操作

//实现了Copy的情况
let i: i32 = 101;
let a: &i32 = &i;
//解引用操纵, 发生了数据类型转换,从&i32转换成了i32。发生了"复制一份"的动作。a和b各自独立,互不影响
let b: i32 = *a;
println!("{}, {}", a, b);
//实现了Clone的情况
let i: String = String::from("hello");
let a: &String = &i;
//解引用操纵, 发生了数据类型转换,从&String转换成了String。发生了"复制一份"的动作。a和b各自独立,互不影响
let b: String = a.clone();
println!("{}, {}", a, b);
//自定义结构体Foo,既没有实现Copy,也没有实现Clone, 无法解引用。
#[derive(Debug)]
struct Foo {
    age: i32,
}

fn main() {
    let i: Foo = Foo {age: 20};
    let a: &Foo = &i;
    //编译失败,Foo类型没有实现Copy, 也无法调用clone()方法。
    let b: Foo = *a;
    println!("{:?}, {:?}", a, b);
}

由于引用没有所有权,解引用实际还是执行的"复制一份"的逻辑。如果不能完成"复制一份"的行为,则编译失败。

通过解引用的语法赋值
#[derive(Debug)]
struct Foo {
    age: i32,
}

fn main() {
    let mut i: Foo = Foo {age: 20};
    let a: &mut Foo = &mut i;
    //将一个新值赋值给a, 由于a可以使用”值", 所以也会导致拥有所有权的i也被赋予相同的值。
    *a = Foo {age: 30};
    println!("{:?}", a);
    println!("{:?}", i);
}

引用就是指针, 对引用的操作会传导给拥有所有权的变量。

后续再补充所有权/引用的特殊情况。例如闭包,共享所有权等操作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容