07_rust的引用和借用

发布时间 2023-10-14 01:38:50作者: UFOFCZ

rust的引用和借用

fn main() {
    let s1 = String::from("hello");
    let len = test_func(&s1);
    println!("{} {}", s1, len);
}
fn test_func(s: &String) -> usize {
    s.len();
}

从这个例子开始,函数参数传递的时s1的引用&String,这样就不会转移所有权。

引用

& 符号表示引用,允许引用某些值而不取得所有权。
上述例子,参数s其实是指向s1的指针,通过s访问s1的内存,进而访问字符串。
image

fn main() {
    let s1 = String::from("hello");
    let len = test_func(&s1); // 创建了一个s1的引用,但不拥有s1,之后进入函数,s1的所有权不会被清理掉
    // let len2 = test_func2(s1); // 作为对比,这种会转移s1的所有权,s1会被清理掉
    println!("{} {}", s1, len);
}
 // 参数s变量,因为未获得所有权,所以函数返回后,
 // 退出了s的作用域,也不会清除所有权,这种情况称之为借用
fn test_func(s: &String) -> usize {
    s.len();
}
fn test_func2(s: String) -> usize {
    s.len();
}

借用

把引用作为函数参数的行为叫做借用。
如果想试图修改s的内容,则会报编译错误。

fn main() {
    let s1 = String::from("hello");
    let len = test_func(&s1);
    println!("{} {}", s1, len);
}
fn test_func(s: &String) -> usize {
    s.push_str("world"); // 编译报错:cannot borrow '*s' as mutable
    s.len();
}

可见无法修改借用的东,和变量一样,引用默认也是不可变的。

可变引用

如果要修改,则应使用可变引用,加上mut关键字。如下

fn main() {
    let mut s1 = String::from("hello");
    let len = test_func(&mut s1);
    println!("{} {}", s1, len);
}
fn test_func(s: &mut String) -> usize {
    s.push_str("world"); // 编译通过,且对s1修改成果
    s.len();
}

可变引用有个重要的限制:在特定作用域内,对某一块数据,只能有一个可变引用。
如下例子则会报错:

fn main() {
    let mut s = String::from("hello");
    let s1 = &mut s;
    let s2 = &mut s; // 编译报错:cannot borrow 's' as mutable more than once at a time
}

这样做的好处是可在编译时防止数据竞争。
以下三种行为下会发生数据竞争

  • 两个或多个指针同时访问同一个数据
  • 至少有一个指针用于写入数据
  • 没有用任何机制来同步对数据的访问
    数据竞争问题在运行时很难排查。如果满足这三种情况,rust会在编译时报错,从根本上接近数据竞争的问题。

而在rust中,可通过创建新的作用域,来允许非同时的创建多个可变引用。

fn main() {
    let mut s = String::from("hello");
    {
        let s1 = &mut s;
    }
    let s2 = &mut s; // 至此s1的作用域已经结束
}

另外,不可同时拥有一个可变引用和一个不可变引用。因为当可变引用把值改变后,不可变引用就失效了,产生语法二义。
多个不可变引用时可以的。

fn main() {
    let mut s = String::from("hello");
    let s1 = &s;
    let s2 = &s;
    let s2 = &mut s; // 编译报错:cannot borrow 's' as mutable because it is alse borrowed as immutable
}

悬空引用 Dangling References

跟悬空指针(Dangling Pointer)类似,一个指针引用了内存中的某个地址,但这内存可能已经释放或分配给其它地方使用。
在rust里,编译器可保证引用永远都不是悬空引用,如果引用了某些数据,编译器将保证引用离开作用域之前,数据不会离开作用域。

fn main() {
    let r = dangle_test();
}
fn dangle_test() -> &String {//编译报错:missing lefetime specifier,避免悬空引用
    let s = String::from("test");
    &s;
}

引用规则

在任何给定的时刻,只能满足条件之一:

  • 一个可变的引用
  • 任意数量不可变的应用
    引用必须一直有效。