Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

4.3 所有权与函数

4.3.0 写在正文之前

在学习了 Rust 的通用编程概念后,就来到了整个 Rust 的重中之重——所有权。它跟其他语言都不太一样,很多初学者觉得学起来很难。这个章节就旨在让初学者能够完全掌握这个特性。

本章有五小节:

4.3.1 把值传递给函数

在语义上,把值传递给函数和把值赋给变量是类似的,所以一句话总结:函数参数传递跟赋值操作是一样的

接下来详细解释一下:把值传递给函数将会发生移动或者复制

  • 对于实现了 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 函数时,发生了移动,即所有权从变量 machine 转移到了函数参数 some_string
    • 此时,machine 的所有权已被转移。函数 wjq 可以正常使用它,但原来的变量 machine 不再可用。如果之后尝试使用 machine,编译器会报错。
  • 对于变量 x

    • i32 是一种基本数据类型,大小固定,分配在栈上,并且实现了 Copy trait。
    • x 被传递给 wjq_copy 函数时,发生了复制,即变量 x 的值被复制一份传递给函数参数 some_number
    • 由于只是值的复制,原变量 x 不受影响,可以在函数调用之后继续使用。
  • 对于变量 some_string

    • 其作用域从第 10 行被声明开始,到第 12 行的 } 时结束。
    • 离开作用域时,Rust 会自动调用 drop 函数,释放 some_string 所占的内存。
  • 对于变量 some_number

    • 其作用域从第 14 行被声明开始,到第 16 行的 } 时结束。
    • 离开作用域时不会有特殊事情发生,因为实现了 Copy trait 的类型在离开作用域时不会调用 Drop

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 的作用域后不会被 drop,因为它的所有权已交给 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 的类型(如基本类型 i32f64 等),在赋值时才会进行复制
  • 当一个包含堆数据的变量离开作用域时,它的值就会被 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, usize) {
    let length = s.len();
    (s, length)
}

在这个例子中,s1 不得不把所有权交给 s,但这个函数在返回时又把 s 原封不动地返回,把数据所有权交给了 s2。这样一来,数据所有权又回到了 main 函数里的变量,使得 s1 下的数据又能在 main 中使用(虽然换了个变量名)。

这种做法太麻烦,也太笨了。 Rust 针对这种场景提供了一个叫做引用的特性,让函数使用某个值而不获得其所有权。这个特性将会在下一篇文章 4.4. 引用与借用 中讲解。