< 返回版块

viruscamp 发表于 2023-06-19 15:46

语法特性叫 reborrowing, the book 和 the reference 都没写清楚, 实际人人都用,根本离不开的。

// 已有 I 和 X1 以及 `impl From<&mut I> for X1`
struct I(i32);

struct X1;
impl From<&mut I> for X1 {
    fn from(p: &mut I) -> X1 {
        p.0 = 1;
        X1
    }
}
// 必须引入这个中间函数
fn x1(p: &mut I) -> X1 {
    X1::from(p)
}

// 我想要 `impl From<&mut I> for XT`
struct XT(X1, X1);
impl From<&mut I> for XT {
    fn from(p: &mut I) -> XT {
        XT(x1(p), x1(p))
    }
}

// value used here after move
fn from_twice_fail(p: &mut I) {
    let x11 = X1::from(p);
    let x12 = X1::from(p);
}
// 跟上面的函数有啥区别,为什么可以?
fn from_twice(p: &mut I) {
    let x11 = x1(p);
    let x12 = x1(p);
}

评论区

写评论
PeishuaiLee 2024-02-06 08:37

XT(x1(p), x1(p)): p在第一个 x1(p)已经被move了。

TinusgragLin 2024-01-29 15:07

结合我自己的搜寻和对下面代码的观察,我有这么几个想法:

  1. reborrow 是一种 type coercion,结合 type coercion 的文档,type coercion 可能只能在源类型和目的类型都知道的情况下进行。
  2. 在考虑对引用的 reborrow 时,rustc 的类型推断进程很可能没有 100% 完成,所以一部分值的类型是不是引用仍然是不知道的,于是此时 reborrow 就不会发生。
  3. 对参数的分析很可能是从第一个参数到最后一个参数依次进行的,如果对前面参数的分析推断到了更多的类型信息,那么对后面参数的分析就会利用前面得到的类型信息。
struct A;
struct B;

trait Hey<T> {
    fn hey(p: T, q: T);
}

impl Hey<u32> for B {
    fn hey(p: u32, q: u32) { todo!() }
}
impl Hey<&mut A> for B {
    fn hey(p: &mut A, q: &mut A) { todo!() }
}

fn hey_hey(p: &mut A, q: &mut A) {
    // <B as Hey<_>> 中的 _ 类型有两种可能,需要被推断:
    <B as Hey<_>>::hey(p, q);
    // 编译器抱怨 p 已被移走,但是 q 却依然可以使用:
    <B as Hey<_>>::hey(p, q);
}

--
👇
NoReligion: 这个重借用问题似乎只和 From<_> 有关……

use std::ops::Add;

struct I(i32);
struct S;

trait Tr<T> {
    fn g(p: T) -> Self;
}

impl Tr<&mut I> for S {
    fn g(p: &mut I) -> S {
        p.0 = 1;
        S
    }
}

impl Add<&mut I> for S {
    type Output = S;
    fn add(self, p: &mut I) -> S {
        p.0 = 3;
        self
    }
}

impl From<&mut I> for S {
    fn from(p: &mut I) -> S {
        p.0 = 2;
        S
    }
}

fn _test(p: &mut I) {
    let _x11 = S::g(p);
    let _x12 = S::g(p); // 可自动重借用
}

fn _test2(p: &mut I) {
    let _x11 = S::add(S, p);
    let _x12 = S::add(S, p); // 可自动重借用
}

fn _fail(p: &mut I) {
    let _x11 = S::from(p);
    let _x12 = S::from(p); // 不可自动重借用,"use of moved value: `p`"
}

--
👇
viruscamp: 稍稍测试了下,应该是泛型函数或trait定义就有 &mut 的,可以自动重借用,而泛型参数被替换成 &mut X 的不能自动重借用

struct X;
impl From<&mut i32> for X {
    fn from(_: &mut i32) -> X {
        X
    }
}

fn from<F, T: From<F>>(f: F) -> T {
  T::from(f)
}
let mut i = 4;
let x = &mut i;
let _: X = from(x); // 此处不会自动重借用
let _: X = from(x); // 第二次调用失败

fn from2<'a, F, T: From<&'a mut F>>(f: &'a mut F) -> T {
  T::from(f)
}
let _: X = from2(x); // 可以自动重借用
let _: X = from2(x);
NoReligion 2023-07-05 18:04

这个重借用问题似乎只和 From<_> 有关……

use std::ops::Add;

struct I(i32);
struct S;

trait Tr<T> {
    fn g(p: T) -> Self;
}

impl Tr<&mut I> for S {
    fn g(p: &mut I) -> S {
        p.0 = 1;
        S
    }
}

impl Add<&mut I> for S {
    type Output = S;
    fn add(self, p: &mut I) -> S {
        p.0 = 3;
        self
    }
}

impl From<&mut I> for S {
    fn from(p: &mut I) -> S {
        p.0 = 2;
        S
    }
}

fn _test(p: &mut I) {
    let _x11 = S::g(p);
    let _x12 = S::g(p); // 可自动重借用
}

fn _test2(p: &mut I) {
    let _x11 = S::add(S, p);
    let _x12 = S::add(S, p); // 可自动重借用
}

fn _fail(p: &mut I) {
    let _x11 = S::from(p);
    let _x12 = S::from(p); // 不可自动重借用,"use of moved value: `p`"
}

--
👇
viruscamp: 稍稍测试了下,应该是泛型函数或trait定义就有 &mut 的,可以自动重借用,而泛型参数被替换成 &mut X 的不能自动重借用

struct X;
impl From<&mut i32> for X {
    fn from(_: &mut i32) -> X {
        X
    }
}

fn from<F, T: From<F>>(f: F) -> T {
  T::from(f)
}
let mut i = 4;
let x = &mut i;
let _: X = from(x); // 此处不会自动重借用
let _: X = from(x); // 第二次调用失败

fn from2<'a, F, T: From<&'a mut F>>(f: &'a mut F) -> T {
  T::from(f)
}
let _: X = from2(x); // 可以自动重借用
let _: X = from2(x);
作者 viruscamp 2023-06-23 23:08

稍稍测试了下,应该是泛型函数或trait定义就有 &mut 的,可以自动重借用,而泛型参数被替换成 &mut X 的不能自动重借用

struct X;
impl From<&mut i32> for X {
    fn from(_: &mut i32) -> X {
        X
    }
}

fn from<F, T: From<F>>(f: F) -> T {
  T::from(f)
}
let mut i = 4;
let x = &mut i;
let _: X = from(x); // 此处不会自动重借用
let _: X = from(x); // 第二次调用失败

fn from2<'a, F, T: From<&'a mut F>>(f: &'a mut F) -> T {
  T::from(f)
}
let _: X = from2(x); // 可以自动重借用
let _: X = from2(x);
lengyijun 2023-06-20 19:01

我是看 miri 的 stacked borrow 才搞懂 reborrow 的

zhylmzr 2023-06-19 19:41

正如issue里所说,reborrow并没有文档记录,哪些情况下会有reborrow全部在引擎盖之下,幸运的是借助rust-analyzer可以一窥究竟:

"rust-analyzer.inlayHints.expressionAdjustmentHints.enable": "always", // 开启reborrow提示
"rust-analyzer.inlayHints.lifetimeElisionHints.enable": "always", // 开启生命周期提示

通过实验会发现直接调用 trait 方法时,其中的 Self 类型并不会发生reborrow,所以两次调用 X1::from(p) 会失败。

苦瓜小仔 2023-06-19 18:10

嗯,基本思路是对的,只不过注意:

  1. 几乎可以把任何引用(无论独占还是共享)视为 reborrow 之后的引用
  2. reborrow 概念最初是作为 coercion 提出的,不过目前的 coercion 文档并不明确提及它
  3. 独占引用的 reborrow 实际上是在这个独占引用的生命周期内分化成多个不相交的、较小范围(生命周期)的独占引用:范围不相交意味着遵守了 引用的规则之一:“At any given time, you can have either one mutable reference or any number of immutable references”
  4. 共享引用也存在 reborrow,只不过大部分情况都很少注意到它,更多时候关注到它实现的 Copy

对于你的代码,显式的重借可以解决

fn from_twice_fail(p: &mut I) {
    let x11 = X1::from(&mut *p);
    let x12 = X1::from(&mut *p);
}

这涉及 reborrow 和泛型的交互,见 https://github.com/rust-lang/rust/issues/85161,而且据此并不被认为是一个错误。

Bai-Jinlin 2023-06-19 17:35

https://alabaster-linen-4dc.notion.site/Rust-86f927bca1794b3b95e3b5ab5f81b9c4

作者 viruscamp 2023-06-19 17:10

有名字就好查了 better documentation of reborrowing

差不多让我理解的是这段:

One of the less obvious but more important coercions is what I call reborrowing, though it's really a special case of autoborrow. The idea here is that when we see a parameter of type &'a T or &'a mut T we always "reborrow" it, effectively converting to &'b T or &'b mut T. While both are borrowed pointers, the reborrowed version has a different (generally shorter) lifetime.

我的理解是:

fn x2(p: &mut I) {
    {
        let p = &mut *p; // 隐式自动生成的 reborrow, 最终不会违反借用规则
        let x11 = x1(p);
    }
    {
        let p = &mut *p;
        let x12 = x1(p);
    }
}

fn x2_from(p: &mut I) {
    // 应该 reborrow 的,但没有实现
    {
        let p = &mut *p;
        let x11 = X1::from(p);
    }
    {
        let p = &mut *p;
        let x12 = X1::from(p);
    }
}

--
👇
苦瓜小仔: reborrow 规则而已。

苦瓜小仔 2023-06-19 16:40

reborrow 规则而已。

作者 viruscamp 2023-06-19 16:01
// 在发现多包一层函数可以绕过之前,写过这种东西
trait MyFrom<T> {
    fn new(&mut T) -> (Self, &mut T);
}
// 可以用,完全符合借用规则,就是感觉非常恶心,有种吃了吐,吐了吃,人*蜈蚣的感觉。
作者 viruscamp 2023-06-19 15:54
// 简单思考下,这里报错是符合一般借用规则的
fn from_twice_fail(p: &mut I) {
    let x11 = X1::from(p);
    let x12 = X1::from(p);
}
// 这里不报错,应该是某条例外规则,哪里有这个规则的说明?
fn from_twice(p: &mut I) {
    let x11 = x1(p);
    let x12 = x1(p);
}

// 要是没有例外规则,这个都跑不起来
fn set_pos(p: &mut Pos) {
    p.set_x(3);
    p.set_y(4);
}
struct Pos(i32, i32);
impl Pos {
    fn set_x(&mut self, x: i32) { self.0 = x; }
    fn set_y(&mut self, y: i32) { self.1 = y; }
}
1 共 12 条评论, 1 页