< 返回版块

QingJuBaiTang 发表于 2024-01-30 13:55

上一个帖子里面 请教一个生命周期的问题,我的问题是self.target的生命周期为什么是'b,看了大佬们的回答,我大致明白了。现在我强制把返回的生命周期跟'a挂钩,那对于target的借用就会扩大到dbg!(foo_out)这个地方,这时如果中间有一个对于target的mutable借用,编译器应该会报错,实际测试并没有,这是为什么呢?

use std::mem::transmute;

pub struct Writer<'a> {
    target: &'a mut String,
}

impl<'a> Writer<'a> {
    fn indent<'b>(&'b mut self) -> &'a String {
        unsafe {
            transmute (&*self.target)
        }
    }
}

fn main() {
    let mut foo_in = String::from("abc");
    let mut foo = Writer {
        target : &mut foo_in
    };
    
    let foo_out = foo.indent();   
    foo.target.push('d');  // 这儿对foo.target做了reborrow,应该会报错啊
    dbg!(foo_out);
}

评论区

写评论
zylthinking 2024-01-30 20:45

这个编译不过是因为 'a 不可能超过 foo_in 的 liveness scope

作者 QingJuBaiTang 2024-01-30 19:35

请看下面的改法:

pub struct Writer<'a> {
    target: &'a String,  // 这里的mut我去掉了
}

impl<'a> Writer<'a> {
    // 这里可以用'a标注了,而且正常也应该用'a标注
    fn indent<'b>(&'b self) -> &'a String {
        self.target
    }
}

fn main() {
    let foo_out;

    {
        let foo_in = String::from("abc");
        let foo = Writer {
            target : &foo_in
        };

        // 这儿target的生命周期扩大了,所以编译报错了
        // 我的疑问是如果target被声明称&mut,并且indent()强制返回了'a的生命周期,target按理说应该会一直被借用啊
        foo_out = foo.indent();
    }

    dbg!(foo_out);
}
Bai-Jinlin 2024-01-30 19:29

你要不要猜一猜为什么transmute是unsafe函数?

--
👇
QingJuBaiTang: ```rust // 完整的函数原型应该是 impl<'a> Writer<'a> { fn indent<'b>(writer: &'b mut Writer<'a>) -> &'a String { unsafe { transmute (&*writer.target) } } }


因为有'a outlives writer.target,所以我理解函数对writer.target的借用会持续到函数结束,但是中间又发生了一次对target的借用(foo.target.push('d')),这不是违反借用规则了吗
zylthinking 2024-01-30 18:51

编译时对函数调用只看签名, 编译器不会知道你返回值借用了啥。

其实它也不关心。 在你这个例子里其实是 indent 的实参决定了 foo.target 能不能访问。

这个我再前面已经说了啊

作者 QingJuBaiTang 2024-01-30 18:39
// 完整的函数原型应该是
impl<'a> Writer<'a> {
    fn indent<'b>(writer: &'b mut Writer<'a>) -> &'a String {
        unsafe {
            transmute (&*writer.target)
        }
    }
}

因为有'a outlives writer.target,所以我理解函数对writer.target的借用会持续到函数结束,但是中间又发生了一次对target的借用(foo.target.push('d')),这不是违反借用规则了吗

zylthinking 2024-01-30 18:37
 fn indent<'b>(&'b mut self) -> &'a String {
         ......
    }

你这个函数的原型只说明了 foo_out outlives 'b

也就是说, 'b 在 foo_out 之前销毁。

那么, 'b 当然可以在 foo.target.push('d') 前销毁

既然 'b 销毁了, 那么 foo.target.push('d')自然可以编译成功

换句话说, 编译函数时只看函数原型, 编译器不知道返回的引用借用的是什么。 决定 foo.target 什么时候允许访问的是 &'b mut self

为何

 fn indent<'b>(&'b mut self) -> &'b String {
         ......
    }

不行也很显然了

因为 'b 一致延续到最后, 那么自然 &'b mut self 也还在, 因此 foo.target.push('d')编译不了

作者 QingJuBaiTang 2024-01-30 17:52

补充一下,如果我把'a换成'b,结果是符合预期的,编译会报错。

impl<'a> Writer<'a> {
    fn indent<'b>(&'b mut self) -> &'b String {
        unsafe {
            transmute (&*self.target)
        }
    }
}
作者 QingJuBaiTang 2024-01-30 17:29

忽略transmute的那个写法,那不是我想问的。 我的意思是我返回了一个'a生命周期的引用,这个引用来自于Writer<'a>,也就是Writer.{&'a mut target}。 也就是说let foo_out = foo.indent()这个调用会将target会被借用到最后一行(dbg!(foo_out);),但是中间又对target做了一次借用(foo.target.push('d');),也就是说对target可变借用了2次,理论上编译会报错,实际上并没有报错

--
👇
苦瓜小仔: > 现在我强制把返回的生命周期跟'a挂钩

不知道你在问些什么,但至少这句话与代码里的 transmute 背道而驰。但凡看一看 transmute 的文档(别告诉我你只看那些文字,里面的超链接都不点进去看)

The nomicon has additional documentation.

Transmuting to a reference without an explicitly provided lifetime produces an unbounded lifetime.

transmute 可以转化任何生命周期 —— 所以 foo.target.push('d') 不会在你写的情况下报错不是完全在意料之中吗。

甚至大胆点,对于以下 unbounded lifetime 签名

fn indent<'b, 'c>(&'b mut self) -> &'c String

代码可以编译,我也不会感到奇怪。

毕竟 safe 编译器要你写标注契约,你偏要以 unsafe 绕道不写,后果自负咯。

苦瓜小仔 2024-01-30 16:49

现在我强制把返回的生命周期跟'a挂钩

不知道你在问些什么,但至少这句话与代码里的 transmute 背道而驰。但凡看一看 transmute 的文档(别告诉我你只看那些文字,里面的超链接都不点进去看)

The nomicon has additional documentation.

Transmuting to a reference without an explicitly provided lifetime produces an unbounded lifetime.

transmute 可以转化任何生命周期 —— 所以 foo.target.push('d') 不会在你写的情况下报错不是完全在意料之中吗。

甚至大胆点,对于以下 unbounded lifetime 签名

fn indent<'b, 'c>(&'b mut self) -> &'c String

代码可以编译,我也不会感到奇怪。

毕竟 safe 编译器要你写标注契约,你偏要以 unsafe 绕道不写,后果自负咯。

1 共 9 条评论, 1 页