Rust之所有权(二):引用和借用

开发环境

  • Windows 10
  • Rust 1.55.0

 

  • VS Code 1.60.2

 

项目工程

这里继续沿用上次工程rust-demo

 引用和借用

上述章节中的元组代码的问题是,我们必须将String返回给调用函数,这样我们仍然可以在调用之后使用Stringcalculate_length,因为String被移动到计calculate_length中。

下面是如何定义和使用具有对对象的引用作为参数的calculate_length函数,而不是获取该值的所有权:

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

首先,注意变量声明和函数返回值中的所有元组代码都不见了。其次,请注意,我们将&s1传递到calculate_length中,在其定义中,我们采用&string而不是String

这些符号是引用,它们允许您引用某些值,而不需要拥有它的所有权。指向String s1的&String s的关系图如下:

 注意:

与使用&的引用相反的是取消引用,这是通过取消引用操作符*完成的。

 让我们仔细看看这里的函数调用

let s1 = String::from("hello");

let len = calculate_length(&s1);   // 引用

&s1语法允许我们创建引用,引用s1的值,但不拥有它。因为它不拥有它,所以当引用超出范围时,它所指向的值不会被删除。

同样,函数的签名使用&指示参数s的类型是引用。让我们添加一些解释性注释:

fn calculate_length(s: &String) -> usize { // s是字符串引用
    s.len()
} // 这里,s超出了范围。 但是因为它对它所指代的东西没有所有权,所以什么都不会发生。

变量s有效的作用域与任何函数参数的作用域相同,但当它超出作用域时,我们不会删除引用指向的值,因为我们没有所有权。当函数将引用作为参数而不是实际值时,我们将不需要返回值来返回所有权,因为我们从来没有所有权。

我们称有引用为函数参数借用。在现实生活中,如果一个人拥有某物,你可以向他们借。当你完成的时候,你必须把它还给我。

那么,如果我们试图修改我们正在借来的东西,会发生什么呢?尝试上例中的代码。出现警报:它不起作用!

fn main() {
    let s = String::from("hello");

    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

运行测试

 正如变量在默认情况下是不可变的一样,引用也是不可变的。我们不允许修改我们所引用的东西。

可变引用

我们只需稍作调整,就可以修复上述示例中的代码的错误。

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {    // 关键字mut
    some_string.push_str(", world");
}

首先,我们必须改变s的类型为mut。然后,我们必须使用&mut s创建一个可变引用,并接受some_string:&mut字符串的可变引用。

但是可变引用有一个很大的限制:对特定范围内的特定数据只能有一个可变的引用。下面的代码会出错:

fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;      // r1引用可变字符串s
    let r2 = &mut s;      // r2引用可变字符串s
 
    println!("{}, {}", r1, r2);
}

运行测试 

 这一限制允许可变性,但以一种非常可控的方式。这是新Rust程序员们苦苦挣扎的地方,因为大多数语言都允许你随时变异。

拥有这一限制的好处是,Rust可以在编译时防止数据竞争。数据竞争类似于竞争条件,并在这三种行为发生时发生。

  • 两个或多个指针同时访问相同的数据。
  • 至少有一个指针被用来写入数据。
  • 没有用于同步数据访问的机制。

数据竞赛会导致未定义的行为,当您试图在运行时跟踪它们时,很难诊断和修复它们;Rust防止了这个问题的发生,因为它甚至不会用数据竞争来编译代码!

与往常一样,我们可以使用花括号创建一个新的范围,允许多个可变引用,而不是同时引用:

fn main() {
    let mut s = String::from("hello");

    {
        let r1 = &mut s;
    } // r1超出了这里的范围,所以我们可以在没有问题的情况下做一个新的参考

    let r2 = &mut s;
}

对于组合可变的和不可变的引用,也存在类似的规则。参考如下示例:

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    let r3 = &mut s; // 这里会出错

    println!("{}, {}, and {}", r1, r2, r3);
}

 运行测试

 当我们有一个不变的引用时,我们也不能有一个可变的引用。不可变引用的用户不会期望值突然从它们下面改变!但是,多个不可变的引用是可以的,因为任何刚刚读取数据的人都没有能力影响其他人对数据的读取。

请注意,引用的作用域从引入它的位置开始,并一直持续到上次使用该引用。例如,这段代码将编译,因为最后一次使用不可变引用发生在引入可变引用之前。

fn main() {
    let mut s = String::from("hello");

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    println!("{} and {}", r1, r2); // 变量r1和r2在此之后将不再使用

    let r3 = &mut s; // 没问题
    println!("{}", r3);
}

运行测试 

 不可变引用的作用域r1r2println!之后结束!最后使用它们的位置,这是在创建可变引用r3之前。这些作用域不重叠,因此允许使用此代码。

尽管借用错误有时可能令人沮丧,但请记住,是Rust编译器及早指出了潜在的错误(在编译时而不是在运行时),并向您展示了问题所在。然后你就不需要找出为什么你的数据不是你想象的那样了。

悬空引用

在有指针的语言中,很容易错误地创建一个悬空指针,该指针通过释放一些内存,同时保留指向该内存的指针,来引用内存中可能给其他人的位置。相比之下,在Rust中,编译器保证引用永远不会是悬空引用:如果您有对某些数据的引用,编译器将确保数据在引用数据之前不会超出范围。 

让我们尝试创建一个悬空引用,Rust将通过编译时错误来防止这种引用。 

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {    // dangle返回对字符串的引用。
    let s = String::from("hello"); // s是个新字符串

    &s                          // 返回对字符串s的引用
}                               // s超出了范围,被丢弃了。它的内存信息消失了。

运行测试

此错误消息引用了我们尚未讨论的一个特性:lifetime。我们将在后面论lifetime。

因为s是在dangle中创建的,所以当dangle完成时,s将被释放。但我们试图返回一个引用。这意味着此引用将指向无效String

这里的解决方案是直接返回String。如下

fn main() {
    let string = no_dangle();
}

fn no_dangle() -> String {                 // 返回类型为字符串
    let s = String::from("hello");

    s                                      // 直接返回字符串
}

这没有任何问题。所有权被移除,任何东西都不会被释放。

引用规则

  • 在任何给定的时间,您可以有一个可变引用或任意数量的不可变引用。
  • 引用必须始终有效。

本章重点

  • 引用
  • 借用
  • 可变引用
  • 引用的规则和注意事项
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值