本文主要介绍一下Rust的所有权与函数之间的关系,我们知道将值传递给函数在语义上和给变量赋值时相似的,像函数传递值也可能会移动或者复制,就像变量与数据的两种交互方式一样,下面通过代码示例对Rust所有权和函数之间的关系进行解释说明。
所有权与函数
以下述代码示例对变量何时进入和离开作用域进行说明:
fn main() {
let s = String::from("hello"); // 变量s进入作用域
move_variable(s); // s 的值被移动到move_variable函数中
// 因此在这里s已经不再有效
let x = 1; // 变量x进入作用域
makes_copy(x); // x应该移动到函数中,但是i32类型时copy的,因此x变量的作用域中还可以继续使用x变量
} // 这里 x 先移出了作用域,然后是s,但是因为s的值已经被移走,所以对s没有任何操作
fn move_variable(string_str: String) { // string_str进入作用域
println!("str is {}", string_str);
} // string_str移出作用域 Rust调用drop方法回收被string_str占用的内存
fn makes_copy(some_integer: i32) { // some_integer 进入作用域
println!("{}", some_integer);
} // 这里,some_integer 移出作用域。不会有特殊操作
当尝试在调用 move_variable
后使用 s
时,Rust 会抛出一个编译时错误。这些静态检查使我们免于犯错。小伙伴们可以试试在 main
函数中添加使用 s
和 x
的代码来看看哪里能使用他们,以及所有权规则会在哪里阻止我们这么做。
函数返回值和作用域
函数的返回值也可以转移其所有权,以下述代码为例进行说明:
fn main() {
let s1 = gives_ownership(); // gives_ownership 将返回值
// 移给 s1
let s2 = String::from("hello"); // s2 进入作用域
let s3 = takes_and_gives_back(s2); // s2 被移动到
// takes_and_gives_back 中,
// 它也将返回值移给 s3
} // 这里, s3 移出作用域并被丢弃。s2 也移出作用域,但已被移走,
// 所以什么也不会发生。s1 移出作用域并被丢弃
fn gives_ownership() -> String { // gives_ownership 将返回值移动给
// 调用它的函数
let some_string = String::from("hello"); // some_string 进入作用域.
some_string // 返回 some_string 并移出给调用的函数
}
// takes_and_gives_back 将传入字符串并返回该值
fn takes_and_gives_back(a_string: String) -> String { // a_string 进入作用域
a_string // 返回 a_string 并移出给调用的函数
}
变量的所有权总是遵循相同的模式:将值赋给另一个变量时移动它。当持有堆中数据值的变量离开作用域时,其值将通过 drop
被清理掉,除非数据被移动为另一个变量所有。
在每一个函数中都获取所有权并接着返回所有权有些啰嗦。如果我们想要函数使用一个值但不获取所有权该怎么办呢?如果我们还要接着使用它的话,每次都传进去再返回来就有点烦人了,除此之外,我们也可能想返回函数体中产生的一些数据。这种情况下我们可以使用元组来返回多个值:
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, usize) {
let length = s.len(); // len() 返回字符串的长度
(s, length)
}
用官方的话来说这未免有些形式主义,而且这种场景应该很常见。因此,Rust 对此提供了一个功能,叫做 引用(references)。