使用元祖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
}

有关参考的规则:

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

子康
10 声望0 粉丝