拷贝类型
Rust中的一些类型非常简单。它们被称为拷贝类型。这些简单的类型都在栈中,编译器知道它们的大小。这意味着它们非常容易复制,所以当你把它发送到一个函数时,编译器总是会复制。它总是复制,因为它们是如此的小而简单,没有理由不复制。所以你不需要担心这些类型的所有权问题。
这些简单的类型包括:整数、浮点数、布尔值(true
和false
)和char
。
如何知道一个类型是否实现复制?(实现 = 能够使用)你可以查看文档。例如,这里是 char 的文档:
https://doc.rust-lang.org/std/primitive.char.html
在左边你可以看到Trait Implementations。例如你可以看到Copy, Debug, 和 Display。所以你知道,当你把一个char
:
- 当你把它发送到一个函数(Copy)时,它就被复制了。
- 可以用
{}
打印(Display) - 可以用
{:?}
打印(Debug)
fn prints_number(number: i32) { // There is no -> so it's not returning anything // If number was not copy type, it would take it // and we couldn't use it again println!("{}", number); } fn main() { let my_number = 8; prints_number(my_number); // Prints 8. prints_number gets a copy of my_number prints_number(my_number); // Prints 8 again. // No problem, because my_number is copy type! }
但是如果你看一下String的文档,它不是拷贝类型。
https://doc.rust-lang.org/std/string/struct.String.html
在左边的Trait Implementations中,你可以按字母顺序查找。A、B、C......C中没有Copy,但是有Clone。Clone和Copy类似,但通常需要更多的内存。另外,你必须用.clone()
来调用它--它不会自己克隆。
在这个例子中,prints_country()
打印的是国家名称,一个String
。我们想打印两次,但我们不能。
fn prints_country(country_name: String) { println!("{}", country_name); } fn main() { let country = String::from("Kiribati"); prints_country(country); prints_country(country); // ⚠️ }
但现在我们明白了这个信息。
error[E0382]: use of moved value: `country`
--> src\main.rs:4:20
|
2 | let country = String::from("Kiribati");
| ------- move occurs because `country` has type `std::string::String`, which does not implement the `Copy` trait
3 | prints_country(country);
| ------- value moved here
4 | prints_country(country);
| ^^^^^^^ value used here after move
重要的部分是which does not implement the Copy trait
。但是在文档中我们看到String实现了Clone
的特性。所以我们可以在代码中添加.clone()
。这样就创建了一个克隆,然后我们将克隆发送到函数中。现在 country
还活着,所以我们可以使用它。
fn prints_country(country_name: String) { println!("{}", country_name); } fn main() { let country = String::from("Kiribati"); prints_country(country.clone()); // make a clone and give it to the function. Only the clone goes in, and country is still alive prints_country(country); }
当然,如果String
非常大,.clone()
就会占用很多内存。一个String
可以是一整本书的长度,我们每次调用.clone()
都会复制这本书。所以,如果可以的话,使用&
来做引用是比较快的。例如,这段代码将&str
推送到String
上,然后每次在函数中使用时都会进行克隆。
fn get_length(input: String) { // Takes ownership of a String println!("It's {} words long.", input.split_whitespace().count()); // splits to count the number of words } fn main() { let mut my_string = String::new(); for _ in 0..50 { my_string.push_str("Here are some more words "); // push the words on get_length(my_string.clone()); // gives it a clone every time } }
它的打印。
It's 5 words long.
It's 10 words long.
...
It's 250 words long.
这就是50个克隆。这里是用引用代替更好:
fn get_length(input: &String) { println!("It's {} words long.", input.split_whitespace().count()); } fn main() { let mut my_string = String::new(); for _ in 0..50 { my_string.push_str("Here are some more words "); get_length(&my_string); } }
不是50个克隆,而是0个。
无值变量
一个没有值的变量叫做 "未初始化"变量。未初始化的意思是 "还没有开始"。它们很简单:只需写上let
和变量名。
fn main() { let my_variable; // ⚠️ }
但是你还不能使用它,如果任何东西都没有被初始化,Rust就不会编译。
但有时它们会很有用。一个很好的例子是当:
- 你有一个代码块,而你的变量值就在里面,并且
- 变量需要活在代码块之外。
fn loop_then_return(mut counter: i32) -> i32 { loop { counter += 1; if counter % 50 == 0 { break; } } counter } fn main() { let my_number; { // Pretend we need to have this code block let number = { // Pretend there is code here to make a number // Lots of code, and finally: 57 }; my_number = loop_then_return(number); } println!("{}", my_number); }
这将打印出 100
。
你可以看到 my_number
是在 main()
函数中声明的,所以它一直活到最后。但是它的值是在循环里面得到的。然而,这个值和my_number
一样长,因为my_number
有这个值。而如果你在块里面写了let my_number = loop_then_return(number)
,它就会马上死掉。
如果你简化代码,对想象是有帮助的。loop_then_return(number)
给出的结果是100,所以我们删除它,改写100
。另外,现在我们不需要 number
,所以我们也删除它。现在它看起来像这样:
fn main() { let my_number; { my_number = 100; } println!("{}", my_number); }
所以说let my_number = { 100 };
差不多。
另外注意,my_number
不是mut
。我们在给它50之前并没有给它一个值,所以它的值一直没有改变。最后,my_number
的真正代码只是let my_number = 100;
。