< 返回版块

ziyouwa 发表于 2022-01-13 19:28

Tags:生命周期,可变借用,不可变借用

代码不长,直接贴这里了。我的疑惑在下面做了标注,主要还是生命周期的问题。

fn strtok<'a>(s: &'a mut &str, pat: char) -> &'a str {
    match s.find(pat) {
        Some(i) => {
            let prefix = &s[..i];
            let suffix = &s[i + pat.len_utf8()..];
            *s = suffix;
            prefix
        }
        None => {
            let prefix = *s;
            *s = "";
            prefix
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn it_works() {
        let mut s = "hello world";
        let s2 = &mut s;
        let s1 = strtok(s2, ' ');  // a
        assert_eq!(s1,"hello");    // b   b、c两句交换位置就会报错,说是在a处 
        assert_eq!(*s2,"world");   // c   可变借用后,又在c处存在不可变借用
    }
}

评论区

写评论
Grainspring 2022-01-17 16:59

严格说来,你遇到的问题与Non-lexical lifetimes简称NLL相关,与Copy Trait没太大关系。

具体可参阅LRFRC系列:全面理解Rust生命周期: https://mp.weixin.qq.com/s?__biz=MzIxMTM0MjM4Mg==&mid=2247483826&idx=1&sn=5b3fe0e8b51d5cafb01db1f4a441be66中

4.Non-lexical lifetimes 部分

👇
苦瓜小仔: 是的,

https://doc.rust-lang.org/nightly/std/marker/trait.Copy.html

https://doc.rust-lang.org/std/mem/fn.drop.html

苦瓜小仔 2022-01-15 21:32

是的,

https://doc.rust-lang.org/nightly/std/marker/trait.Copy.html

https://doc.rust-lang.org/std/mem/fn.drop.html

作者 ziyouwa 2022-01-15 20:42

我了个去,我这才突然反应过来了 共享引用(&T)具有 Copy trait ,所以每次使用 &T 都是使用 &T 的复制品 这句话的意思,其实drop(a),这里传进去的就是a的复制品,真正的a其实没有动到。那么是不是所有Copy trait的,手动drop其实没用?

苦瓜小仔 2022-01-14 22:45

别想得太复杂了。

我举 Copy trait 的例子是想说:

共享引用(&T)具有 Copy trait ,所以每次使用 &T 都是使用 &T 的复制品

把这句话换成任何具有 Copy trait 的类型都成立。如果你能理解以下代码为什么能通过,那么能理解 s1 为什么一直存在。

    let a = 1i32;
    drop(a);
    { let _ = a; }
    dbg!(a);

又是什么原因,让栈上的 s1 一直有效的呢?

Copy trait 只让共享引用一直复制,不会保持引用一直有效。

所以 s1 一直有效是因为:原数据存在(即变量 s 的生命周期至少比 s1 要长)。


总而言之,fn strtok<'a>(s: &mut &'a str, pat: char) -> &'a str { ... } 这个函数签名可以联系起共享引用的 Copy trait,让生命周期 'a 的含义更清晰 —— strtok 所返回的引用就是直接指向原数据的引用,你那种标注达不到这种效果。

作者 ziyouwa 2022-01-14 20:53

对s、s1、s2,我理解的是在栈上,它们都是各自的区域,哪怕这个区域存储的都是同一块堆上内存的地址。drop(s1)如果不能标记栈上的s1无效,又不能释放堆上的内存,那么它什么都没做?!又是什么原因,让栈上的s1一直有效的呢。

作者 ziyouwa 2022-01-14 20:46

谢谢瓜仔大佬的认真回复,说说我的理解,也算是交作业了:

&mut str:指向str的可变引用,意思是可变的&str

mut &str:可变的字符串,&str指向的内容可变

&mut &'a str:指向&str的可变引用,保证&str的生命周期为'a并周期内有效

&'a &mut str:指向&str的可变引用,保证&mut str的生命周期为'a并周期内有效

例子中,我只需要&str,即s本身和函数返回具有同样生命周期即可,之前的错误是我搞成了保证可变引用本身的生命周期了,当下一句*s2时,就在原有&mut &s2的基础上多了一个&&s2,导致编译器报错。

不过这里还是不明白为啥drop之后还能访问s1。

    let s1 = strtok(s2, ' '); // a
    assert_eq!(*s2, "world"); // c     
    ......

    // drop 函数可以替换成任何具有 move 语义的场景
    drop(s1);
    { let _ = s1; } // s1 依然可用,因为它是共享引用,具有 Copy trait
    // *********************************
    
    assert_eq!(s1, "hello"); // b 

就算s2传入strtok时,函数是把堆上s0的数据复制了一份,处理完了让s1指向新复制的这份数据。那么,drop时发生了什么?我的理解是至少释放了堆上s1指向的那份新数据吧?也应该s1无效了啊。 我以为的drop(s1)如下,不管是否支持Copy Trait,我认为drop都是释放了堆内存的。不过显然不对,否则上面最后两个调用就该出错: 1、释放s1指向的堆内存 2、标记栈上的s1为无效(猜的) 所以,

let s1 = strtok(s2, ' ');

和drop时,到底发生了什么?希望知道的大佬指点一下。

苦瓜小仔 2022-01-13 23:23

延伸的细节(关于 Copy trait)见:

playground

生命周期这种概念就是多思考才会理解,尤其像这样从很小的例子中理解透彻才会掌握。

lan 2022-01-13 22:36

有道理!学到了。

👇
苦瓜小仔:

因为你标注的生命周期在这里比所需的要严格一些。更合适的标注应该是这样

// 这种标注意味着:若想使用 &'a str,那么传入的 &'a str 必须存在且有效,而且必须维持这个 &'a str
// 在我的例子中,我一直使用的是原数据的共享引用(见上述链接的 s0 变量),所以这个 &'a str 一直可用
fn strtok1<'a>(s: &mut &'a str, pat: char) -> &'a str { ... }

// 这种标注意味着:若想使用 &'a str,那么 &'a mut &str 必须存在且有效,而且必须维持这个 &'a mut (交换 b c 两行破坏了 &'a mut,因为这个独占引用之外还存在 *s2 这个共享引用)
fn strtok2<'a>(s: &'a mut &str, pat: char) -> &'a str { ... }
苦瓜小仔 2022-01-13 21:06

因为你标注的生命周期在这里比所需的要严格一些。更合适的标注应该是这样

// 这种标注意味着:若想使用 &'a str,那么传入的 &'a str 必须存在且有效,而且必须维持这个 &'a str
// 在我的例子中,我一直使用的是原数据的共享引用(见上述链接的 s0 变量),所以这个 &'a str 一直可用
fn strtok1<'a>(s: &mut &'a str, pat: char) -> &'a str { ... }

// 这种标注意味着:若想使用 &'a str,那么 &'a mut &str 必须存在且有效,而且必须维持这个 &'a mut (交换 b c 两行破坏了 &'a mut,因为这个独占引用之外还存在 *s2 这个共享引用)
fn strtok2<'a>(s: &'a mut &str, pat: char) -> &'a str { ... }
作者 ziyouwa 2022-01-13 20:30

这只是一次练习,是没想明白为什么。

--
👇
lan: 你可以用一个大括号把ab两行括起来,但如果互换bc两行就不行了。因为借用的生命周期止于最后一次被使用时。

lan 2022-01-13 19:58

你可以用一个大括号把ab两行括起来,但如果互换bc两行就不行了。因为借用的生命周期止于最后一次被使用时。

1 共 11 条评论, 1 页