自有类型与借用类型

发布时间:2022-07-04 11:03
最后更新:2022-07-04 11:03
所属分类:
Rust

在根据Rust的所有权系统使用变量的时候,值通常会发生移动来改变其拥有者,这通常总是适用于Rust自有类型,但是Rust中的借用类型的表现就不一样了,因为在这些类型中存放的实际上是另一个内存区域的地址,而借用类型的变量一般并不拥有它所指向的那一片内存区域。这种行为上的区别,就构成了Rust中两种不同的类型:自有类型和借用类型。

自有类型

自有类型其实比较好理解,这种类型是完全遵守Rust中的所有权机制的。自有类型变量所拥有的内存区域中保存的就是实实在在的值。如果发生转移,那么这一块内存区域的所有权就将被直接转移到新的变量名下。

1
2
3
4
5
6
7
8
9
fn main() {
  let person1: String = String::from("John");

  // 使用赋值语句将会使person1所拥有的内存区域转移给person_alias。
  let person_alias = person1;

  // 发生转移以后再使用person1就会失败。
  println!("Person name: {}", person1);
}

在程序中使用自有类型是不会有任何歧义的,因为任何操作都必须遵循Rust的所有权机制。但是使用自有类型会对编程过程中的自由性产生一定的影响。

借用类型

Rust中的借用类型就不是那么遵守所有权机制了,在代码中,借用类型一般都非常好辨认,在借用类型的前面通常都会带着一个&符号。当然这种类型在其他语言中通常被称作引用类型。

同样是上面的示例,采用借用类型以后,运行效果就会变得不一样。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
  let person1: &String = &String::from("John");

  // 这里同样使用一个赋值语句,但是需要注意的是person_alias现在的类型也是&Stirng
  let person_alias = person1;

  println!("Person alias: {}", person_alias);
  // 这会儿再使用person1的时候并不会出现任何错误。
  println!("Person name: {}", person1);
}

就像是之前提到过的,借用类型变量所直接拥有的内存区域中保存的实际上是另一块内存区域的地址,程序中所需要使用的值实际上是被放置在这一块内存区域中的,借用类型变量是通过指针指向来访问所需要使用的值的。

在上面的这个示例中,赋值语句的是使用并没有发生任何的所有权转移。这也就是借用类型并不是那么遵守所有权机制的原因。

如何选择使用

在了解自有类型和借用类型的区别以后,就能理解为什么从函数中不能返回借用类型的原因了,因为从函数中返回值通常是所有权的转移,但是借用类型对于其所指向的目标内存区域是没有所有权的。所以即便是在函数中使用了借用类型,在返回其指向的值的时候,也应该借助于.to_owned()方法将其转换为自有类型,但是要谨记在调用函数的地方还是要处理所有权的。

不管在任何时候,使用自有类型作为函数的参数类型总不是一个好的选择。函数实参的所有权会被转移到函数中,如果这一段内训区域在使用完以后没有把所有权转移回来,那么在调用完函数以后,用作函数实参的变量就不能再用了。所以在选择函数参数类型的时候,应该首选借用类型。

另外,选择可切片类型和胖指针类型作为函数参数类型,还可以避免类型带有强制隐式类型转换带来的代码复杂度。例如传递字符串的时候使用&str而不是&String,传递数组使用&[T]而不是&Vec<T>,选择&T代替&Box<T>。例如有函数fn str_length(word: &String): i32,在传入一个引用的时候,是可以正常运行的,例如str_length(&person_name),但是直接传入一个字符串字面量就不行了,例如str_length("John")。这是因为在调用函数的时候,表示字符串字面量的&str类型不能被强制转换成函数参数所要求的&String类型。但是如果将函数定义为fn str_length(word: &str): i32,那么就可以避免这个强制类型转换,而解决这个问题。

隐式类型转换规则

在Rust中发生这种强制隐式类型转换是有一定规则可循的,一般情况下强制隐式类型转换的目的都是弱化指针或者解引用,例如常见的有以下形式。

  1. 弱化指针:
    • &mut T转换为&T
    • *mut T转换为*const T
    • &T转换为*const T
    • &mut T转换为*mut T
  2. 解引用:
    • 如果T: Deref<U>,那么可以将&T通过表达式&*x转换为&U

这种隐式类型转换通常会发生在letconststatic、函数调用传参、从函数返回值等位置。


索引标签
Rust
类型系统
所有权
借用
引用