4.3.0 写在正文之前
在学习了Rust的通用编程概念后,就来到了整个Rust的重中之重——所有权,它跟其他语言都不太一样,很多初学者觉得学起来很难。这个章节就旨在让初学者能够完全掌握这个特性。
本章有三小节:
- 所有权:栈内存 vs. 堆内存
- 所有权规则、内存与分配
- 所有权与函数(本文)
喜欢的话记得点赞、收藏加关注哦,想要跟着学下去记得关注专栏哦
4.3.1. 把值传递给函数
在语义上,把值传递给函数和把值赋给变量是类似的,所以一句话总结:函数参数传递跟赋值操作是一样的
接下来详细解释一下:把值传递给函数将会发生移动(Move)或者复制(Copy)
- 对于实现了Copy trait的数据类型,会发生复制,所以原本的变量不受影响,能够继续使用
- 对于没有实现Copy trait的数据类型,会发生复制,所以原本的变量会被弃用,不可使用
Copy trait、移动、复制的详细介绍在上一篇文章4.2. 所有权规则、内存与分配有讲,这里不再作阐述
fn main(){
let machine = String::from("6657");
wjq(machine);
let x = 6657;
wjq_copy(x);
println!("x is:{}", x);
}
fn wjq(some_string::String){
println!("{}", some_string);
}
fn wjq_copy(some_number::i32){
println!("{}", some_number);
}
对于变量
machine
:String
是一种复杂数据类型,分配在堆上,并且没有实现Copy trait。- 当
machine
被传递给wjq
函数时,发生了移动(Move),即所有权从变量machine
转移到了函数参数some_string
。 - 此时,
machine
的所有权被转移,函数wjq
可以正常使用它,但原来的变量machine
不再可用。如果尝试在之后使用machine
,编译器会报错。
对于变量
x
:i32
是一种基本数据类型,大小固定,分配在栈上,并且实现了 Copy trait。- 当
x
被传递给wjq_copy
函数时,发生了复制(Copy),即变量x
的值被复制了一份传递给了函数参数some_number
。 - 由于是值的复制,原变量
x
不受影响,可以在函数调用之后继续使用。
对于变量
some_string
:- 其作用域从第10行被声明开始,到第12行的
}
时就离开了作用域 - 在离开作用域时Rust会自动调用
drop
函数释放变量some_string
所占的内存
- 其作用域从第10行被声明开始,到第12行的
对于变量
some_number
:- 其作用域是从第14行被声明开始,到第16行的
}
时就离开了作用域 - 离开作用域时不会有特殊的事情发生,因为实现了Copy trait的类型在离开作用域时不会调用
Drop
- 其作用域是从第14行被声明开始,到第16行的
4.3.2. 返回值与作用域
函数在返回值的过程中同样也会发生所有权的转移。
fn main(){
let s1 = give_ownership();
let s2 = String::from("6657");
let s3 = takes_and_gives_back(s2);
}
fn give_ownership() -> String {
let some_string = String::from("machine");
some_string
}
fn takes_and_gives_back(a_string:String) -> String {
a_string
}
函数
give_ownership
的行为:give_ownership
函数创建了一个String
类型的变量some_string
,它的所有权属于give_ownership
函数。- 当
some_string
作为返回值返回时,其所有权被转移到调用者,即变量s1
。 - 结果是,
some_string
离开give_ownership
的作用域后不会被释放,因为它的所有权已交给s1
。
函数
takes_and_gives_back
的行为:takes_and_gives_back
函数接受一个String
类型的参数a_string
。调用该函数时,传入的参数(s2
)的所有权被转移到函数的参数a_string
。- 函数将
a_string
返回时,其所有权从a_string
再次转移给调用者,即变量s3
。 - 此时,变量
s2
不再可用,因为其所有权已被转移给takes_and_gives_back
,而函数的返回值赋给了s3
。
一个变量的所有权总是遵循同样的模式:
- 把一个值赋给其它变量时就会发生移动,只有实现了Copy trait 的类型(如基本类型
i32
,f64
等),在赋值时才会进行复制 - 当一个包含堆数据的变量离开作用域时,它的值就会被
drop
函数清理掉,除非数据的所有权被移动到另一个变量上。
4.3.3. 让函数使用某个值而不获得其所有权
有的时候代码的本意是让函数使用变量,但不想因此失去对数据的使用权,这时候就可以这么写:
fn main(){
let s1 = String::from("Hello");
let (s2, len) = calculate_length(s1);
println!("The length of '{}' is {}", s2, len);
}
fn calculate_length(s:String) -> (String, uszie) {
let length = s.len();
(s, length)
}
在这个例子中,s1
不得不把所有权交给s
,但这个函数在返回时把s
也原封不动地返回,把数据所有权交给了s2
,这样做就把数据所有权又交给了main
函数里的变量,使得s1
下的数据又能够在main
函数中使用(虽然换了个变量名)。
这种做法太麻烦,也太笨了。 Rust针对这种场景有一个特性叫引用(Reference),让函数使用某个值而不获得其所有权。 这个特性将会在下篇文章中讲。