关于rust:rust学习所有权之参考和借阅

42次阅读

共计 4204 个字符,预计需要花费 11 分钟才能阅读完成。

应用元祖 tuple 接管函数的返回值,在所有权问题中会产生:咱们必须将入参返回给调用函数,略显繁冗。

rust 中一种新的援用形式:

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()
}

与应用 tuple 的形式相比,上例:
1、变量申明和函数返回值中的所有元组代码都隐没了;
2、将 &s1 传递给 calculate_length,并且在其定义中,采纳 &String 而不是 String。

这种形式(& 变量 )被称为参考,它们使咱们能够援用某些值而无需领有其所有权。
留神:应用 & 进行援用的反义词是解援用,这是通过解援用运算符 * 实现的。

在上例中:

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

let len = calculate_length(&s1);

&s1 语法使咱们能够创立一个援用 s1 的参考,但该援用不领有它。因为不领有它,所以当援用超出作用域时,它所指向的值将不会被删除。
同样,函数的签名应用 & 示意参数 s 的类型是援用。其解释为:

fn calculate_length(s: &String) -> usize { // s 是对字符串的一个援用
    s.len()} // 这里,变量 s 超出了作用域,然而因为其自身并不具备所有权(也就是它所指向的内容),所以什么也不会产生

变量 s 无效的作用域与任何函数参数的作用域雷同,然而当它超出作用域时,因为没有所有权,rust 不会删除援用指向的内容。当函数应用援用作为参数而不是理论值作为参数时,rust 将不须要返回这些值来偿还所有权,因为在此模块中 rust 从未领有过所有权。

rust 中称援用(参考)为函数参数借用。与现实生活中一样,如果某人领有某物,则能够向他们借用。实现后,咱们必须将其偿还。

那么,借用的内容是否可被批改呢?是不能够的,例如:

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

    change(&s);
}

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

$ cargo run
  |
7 | fn change(some_string: &String) {
  |                        ------- help: consider changing this to be a mutable reference: `&mut std::string::String`
8 |     some_string.push_str(", world");
  |     ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable

error: aborting due to previous error

For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership`.

To learn more, run the command again with --verbose.

正如变量在默认状况下是不可变的一样,参考也是如此。rust 不容许批改参考的内容。

可变援用
如果我肯定要批改某一个援用的值呢,先来看一个例子:

fn main() {let mut s = String::from("hello");
    printfln!("{}",s);

    change(&mut s);
    printfln!("{}",s);
}

fn change(some_string: &mut String) {some_string.push_str(", world");
}
// 运行后果
cargo run
    hello
    hello, world

在下面的例子中:首先,咱们必须将 s 更改为 mut。而后,咱们必须应用 &mut s 创立一个可变援用,并应用 some_string:&mut String 承受一个可变援用。

然而可变援用有一个很大的限度:就是只能对一个特定范畴内的特定数据进行一个可变援用。

比方以下代码将失败:

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

    let r1 = &mut s;
    let r2 = &mut s;

    println!("{}, {}", r1, r2);
}
$ cargo run
  |
4 |     let r1 = &mut s;
  |              ------ first mutable borrow occurs here
5 |     let r2 = &mut s;
  |              ^^^^^^ second mutable borrow occurs here
6 | 
7 |     println!("{}, {}", r1, r2);
  |                        -- first borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership`.

To learn more, run the command again with --verbose.

为什么会存在这样的限度呢?此限度使得 Rust 能够避免在编译时产生数据争用。数据争用相似于争用条件,并且在以下三种行为产生时产生:

* 两个或多个指针同时拜访雷同的数据。* 至多有一个指针用于写入数据。* 没有用于同步拜访数据的机制。

然而有的时候咱们想要有多个可变援用,这个时候就要借助于作用域了———{}:

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

    {let r1 = &mut s;} // r1 在此处超出作用域,所以咱们能够创立一个新的可变援用而不会有任何问题

    let r2 = &mut s;
}

援用一个变量通常有可变援用与不可变援用,如果即存在可变援用又存在不可变援用,那么此时 rust 仍旧有相似的规定存在

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

    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    let r3 = &mut s; // 有问题

    println!("{}, {}, and {}", r1, r2, r3);
}
$ cargo run
  |
4 |     let r1 = &s; // 没问题
  |              -- immutable borrow occurs here
5 |     let r2 = &s; // 没问题
6 |     let r3 = &mut s; // 有问题
  |              ^^^^^^ mutable borrow occurs here
7 | 
8 |     println!("{}, {}, and {}", r1, r2, r3);
  |                                -- immutable borrow later used here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership`.

To learn more, run the command again with --verbose.

当咱们领有不变的参考时,咱们不能同时领有可变的参考。不变援用的 user 绝不心愿援用的值从援用的上面忽然扭转!然而,能够应用多个不可变的援用,因为没有人具备仅读取数据还能影响他人读取数据的能力。

援用的范畴从引入它的中央开始,始终继续到最初一次应用该援用。例如:

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);
}

空指针——悬挂参考

在带有指针的语言中,很容易谬误地创立一个悬空指针,即通过在保留指向该内存的指针的同时开释一些内存来援用可能已调配给别人的内存中某个地位的指针。相比之下,在 Rust 中,编译器保障援用永远不会悬挂援用:如果咱们对某些数据有援用,则编译器将确保数据不会超出对数据的援用范畴。(js 语言中并没有指针的概念)

例如:

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

fn dangle() -> &String {  // dangle 返回对字符串的援用

    let s = String::from("hello"); // s 是一个新的 String
    
    &s   // 咱们返回对 String 的援用——s
} // 在此,s 超出范围,并被抛弃,它的贮存隐没了。十分危险

$ cargo run

  |
5 | fn dangle() -> &String {
  |                ^ help: consider giving it a 'static lifetime: `&'static`
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

error: aborting due to previous error

For more information about this error, try `rustc --explain E0106`.
error: could not compile `ownership`.

To learn more, run the command again with --verbose.

因为 s 是在 dangle 外部创立的,所以当 dangle 的代码实现时,s 将被开释。然而咱们试图返回对它的援用。这意味着此援用将指向有效的 String。这回造成空指针问题,Rust 不容许咱们这样做。

解决方案是间接返回 String:

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

// 这能够失常运行。所有权被移出,没有任何货色被开释。fn no_dangle() -> String {let s = String::from("hello");

    s
}

无关参考的规定:

* 在任何给定工夫,都能够具备一个可变援用或任意数量的不可变援用。* 援用必须始终无效。

正文完
 0