< 返回版块

Zhanghailin1995 发表于 2020-08-06 20:44

Tags:rust,reborrow

fn main() {
    let mut a = 1;
    let b = &mut a;
    let foo = |state: &mut i32| {
        Box::new(state);
    };

    foo(b);
    foo(b);
}

如果把闭包换成自动类型推断的则无法编译,这个是因为什么呢?和reborrow有关系?

fn main() {
    let mut a = 1;
    let b = &mut a;
    let foo = |state| {
        Box::new(state);
    };

    foo(b);
    foo(b);
}

评论区

写评论
xjkdev 2020-08-07 16:08

https://gist.github.com/xjkdev/209ab1986a50010857ecf034b71fb70c 这是我的源码和mir,确实在我这边调用是没有不同的。

--
👇
Zhanghailin1995: 我在windows 10下连个mir分别是

        _7 = _2;                         // bb0[10]: scope 3 at ./examples/test_reborrow.rs:5:9: 5:10
        (_6.0: &mut i32) = move _7;      // bb0[11]: scope 3 at ./examples/test_reborrow.rs:5:5: 5:11
        _4 = const <[closure@./examples/test_reborrow.rs:4:15: 4:31] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // bb0[12]: scope 3 at ./examples/test_reborrow.rs:5:5: 5:11
        _7 = move _2;                    // bb0[10]: scope 3 at ./examples/test_reborrow2.rs:5:9: 5:10
        (_6.0: &mut i32) = move _7;      // bb0[11]: scope 3 at ./examples/test_reborrow2.rs:5:5: 5:11
        _4 = const <[closure@./examples/test_reborrow2.rs:4:15: 4:21] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // bb0[12]: scope 3 at ./examples/test_reborrow2.rs:5:5: 5:11

--
👇
xjkdev: 奇怪的是在我的电脑上,两者的mir都是这个形式的

_8 = move _2;                    // scope 4 at main.rs:21:9: 21:10
        (_7.0: &mut i32) = move _8;      // scope 4 at main.rs:21:5: 21:11
        _5 = const <[closure@main.rs:12:15: 14:6] as std::ops::Fn<(&mut i32,)>>::call(move _6, move _7) -> bb1; // scope 4 at main.rs:21:5: 21:11

_8 = move _2;,并没有_7 = &mut (*_2);这样的语句。 并且在函数、闭包声明时,我印象中也没有显式地把reference move进函数内的语法吧,这样的不确定性行为似乎违反了rust语法设计的一些基础思想。

--
👇
Hyuuko: 确实和 reborrow 有关系,reborrow 不仅仅发生在闭包的参数、函数的参数指定类型的时候。变变量绑定指定类型时也会发生 reborrow:

let mut a = 1;
let b = &mut a;

let c: &mut i32 = b; // 指定类型时,发生 reborrow
println!("{}", b);

let c = &mut *b; // 显式 reborrow
println!("{}", b);

let c = b; // 自动类型推断时是 move
println!("{}", b); // error! borrow of moved value: `b`

总之这两种情况都可以触发 reborrow:

  1. 指定形参类型为&mut i32
  2. &mut *b作为实参

所以你的第二段代码不能编译通过确实是因为没有指定类型,而导致没有进行 reborrow。可以用rustc --emit=mir .\src\main.rs命令看一下 MIR

例 1 指定类型

fn main() {
    let mut a = 1; // 1
    let b = &mut a; // 2
    let foo = |i: &mut i32| {};
    foo(b);
}

发生了 reborrow

_7 = &mut (*_2);                 // 通过对 b 进行 reborrow 产生的 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:31] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11

例 2 自动类型推断

fn main() {
    let mut a = 1;
    let b = &mut a; // 2
    let foo = |i| {};
    foo(b);
}

可以看到未发生 reborrow,b 被 move 进去了

_7 = move _2;                    // b 被 move 进 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:21] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11
作者 Zhanghailin1995 2020-08-07 15:40

我在windows 10下连个mir分别是

        _7 = _2;                         // bb0[10]: scope 3 at ./examples/test_reborrow.rs:5:9: 5:10
        (_6.0: &mut i32) = move _7;      // bb0[11]: scope 3 at ./examples/test_reborrow.rs:5:5: 5:11
        _4 = const <[closure@./examples/test_reborrow.rs:4:15: 4:31] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // bb0[12]: scope 3 at ./examples/test_reborrow.rs:5:5: 5:11
        _7 = move _2;                    // bb0[10]: scope 3 at ./examples/test_reborrow2.rs:5:9: 5:10
        (_6.0: &mut i32) = move _7;      // bb0[11]: scope 3 at ./examples/test_reborrow2.rs:5:5: 5:11
        _4 = const <[closure@./examples/test_reborrow2.rs:4:15: 4:21] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // bb0[12]: scope 3 at ./examples/test_reborrow2.rs:5:5: 5:11

--
👇
xjkdev: 奇怪的是在我的电脑上,两者的mir都是这个形式的

_8 = move _2;                    // scope 4 at main.rs:21:9: 21:10
        (_7.0: &mut i32) = move _8;      // scope 4 at main.rs:21:5: 21:11
        _5 = const <[closure@main.rs:12:15: 14:6] as std::ops::Fn<(&mut i32,)>>::call(move _6, move _7) -> bb1; // scope 4 at main.rs:21:5: 21:11

_8 = move _2;,并没有_7 = &mut (*_2);这样的语句。 并且在函数、闭包声明时,我印象中也没有显式地把reference move进函数内的语法吧,这样的不确定性行为似乎违反了rust语法设计的一些基础思想。

--
👇
Hyuuko: 确实和 reborrow 有关系,reborrow 不仅仅发生在闭包的参数、函数的参数指定类型的时候。变变量绑定指定类型时也会发生 reborrow:

let mut a = 1;
let b = &mut a;

let c: &mut i32 = b; // 指定类型时,发生 reborrow
println!("{}", b);

let c = &mut *b; // 显式 reborrow
println!("{}", b);

let c = b; // 自动类型推断时是 move
println!("{}", b); // error! borrow of moved value: `b`

总之这两种情况都可以触发 reborrow:

  1. 指定形参类型为&mut i32
  2. &mut *b作为实参

所以你的第二段代码不能编译通过确实是因为没有指定类型,而导致没有进行 reborrow。可以用rustc --emit=mir .\src\main.rs命令看一下 MIR

例 1 指定类型

fn main() {
    let mut a = 1; // 1
    let b = &mut a; // 2
    let foo = |i: &mut i32| {};
    foo(b);
}

发生了 reborrow

_7 = &mut (*_2);                 // 通过对 b 进行 reborrow 产生的 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:31] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11

例 2 自动类型推断

fn main() {
    let mut a = 1;
    let b = &mut a; // 2
    let foo = |i| {};
    foo(b);
}

可以看到未发生 reborrow,b 被 move 进去了

_7 = move _2;                    // b 被 move 进 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:21] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11
xjkdev 2020-08-07 15:31

奇怪的是在我的电脑上,两者的mir都是这个形式的

_8 = move _2;                    // scope 4 at main.rs:21:9: 21:10
        (_7.0: &mut i32) = move _8;      // scope 4 at main.rs:21:5: 21:11
        _5 = const <[closure@main.rs:12:15: 14:6] as std::ops::Fn<(&mut i32,)>>::call(move _6, move _7) -> bb1; // scope 4 at main.rs:21:5: 21:11

_8 = move _2;,并没有_7 = &mut (*_2);这样的语句。 并且在函数、闭包声明时,我印象中也没有显式地把reference move进函数内的语法吧,这样的不确定性行为似乎违反了rust语法设计的一些基础思想。

--
👇
Hyuuko: 确实和 reborrow 有关系,reborrow 不仅仅发生在闭包的参数、函数的参数指定类型的时候。变变量绑定指定类型时也会发生 reborrow:

let mut a = 1;
let b = &mut a;

let c: &mut i32 = b; // 指定类型时,发生 reborrow
println!("{}", b);

let c = &mut *b; // 显式 reborrow
println!("{}", b);

let c = b; // 自动类型推断时是 move
println!("{}", b); // error! borrow of moved value: `b`

总之这两种情况都可以触发 reborrow:

  1. 指定形参类型为&mut i32
  2. &mut *b作为实参

所以你的第二段代码不能编译通过确实是因为没有指定类型,而导致没有进行 reborrow。可以用rustc --emit=mir .\src\main.rs命令看一下 MIR

例 1 指定类型

fn main() {
    let mut a = 1; // 1
    let b = &mut a; // 2
    let foo = |i: &mut i32| {};
    foo(b);
}

发生了 reborrow

_7 = &mut (*_2);                 // 通过对 b 进行 reborrow 产生的 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:31] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11

例 2 自动类型推断

fn main() {
    let mut a = 1;
    let b = &mut a; // 2
    let foo = |i| {};
    foo(b);
}

可以看到未发生 reborrow,b 被 move 进去了

_7 = move _2;                    // b 被 move 进 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:21] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11
Hyuuko 2020-08-07 11:38

确实和 reborrow 有关系,reborrow 不仅仅发生在闭包的参数、函数的参数指定类型的时候。变变量绑定指定类型时也会发生 reborrow:

let mut a = 1;
let b = &mut a;

let c: &mut i32 = b; // 指定类型时,发生 reborrow
println!("{}", b);

let c = &mut *b; // 显式 reborrow
println!("{}", b);

let c = b; // 自动类型推断时是 move
println!("{}", b); // error! borrow of moved value: `b`

总之这两种情况都可以触发 reborrow:

  1. 指定形参类型为&mut i32
  2. &mut *b作为实参

所以你的第二段代码不能编译通过确实是因为没有指定类型,而导致没有进行 reborrow。可以用rustc --emit=mir .\src\main.rs命令看一下 MIR

例 1 指定类型

fn main() {
    let mut a = 1; // 1
    let b = &mut a; // 2
    let foo = |i: &mut i32| {};
    foo(b);
}

发生了 reborrow

_7 = &mut (*_2);                 // 通过对 b 进行 reborrow 产生的 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:31] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11

例 2 自动类型推断

fn main() {
    let mut a = 1;
    let b = &mut a; // 2
    let foo = |i| {};
    foo(b);
}

可以看到未发生 reborrow,b 被 move 进去了

_7 = move _2;                    // b 被 move 进 _7
(_6.0: &mut i32) = move _7;      // scope 3 at .\src\main.rs:5:5: 5:11
_4 = const <[closure@.\src\main.rs:4:15: 4:21] as std::ops::Fn<(&mut i32,)>>::call(move _5, move _6) -> bb1; // scope 3 at .\src\main.rs:5:5: 5:11
作者 Zhanghailin1995 2020-08-07 08:46

感谢回复,我看看

--
👇
xjkdev: 我找到一个类似的问题:https://github.com/rust-lang/rust/issues/62640

xjkdev 2020-08-06 23:46

我找到一个类似的问题:https://github.com/rust-lang/rust/issues/62640

xjkdev 2020-08-06 23:43

我检查了一下,发现类型推断与非类型推断的foo的类型貌似是一模一样的,就连mir中似乎都看不出区别。这可能是一个bug?建议往stackoverflow或github的issues里面发一下。

--
👇
xjkdev: rust的类型推断是会考虑上下文的,所以这么写语法是没有问题的。

这里的问题在于未标注类型的时候,第一个foo(b)似乎将&mut i32当成一个值类型,move进了foo这个闭包的作用域,而不是引用类型的传递。而标注了state的类型时,就是直接按引用传递了。

--
👇
Neutron3529: 定义闭包时本来就是要指定类型的吧

除了iter里面(比如foreach(|x|...)这里可以不指定类型)的闭包可以不指定类型之外,我是真的不知道还有哪个闭包可以不指定类型

xjkdev 2020-08-06 23:19

rust的类型推断是会考虑上下文的,所以这么写语法是没有问题的。

这里的问题在于未标注类型的时候,第一个foo(b)似乎将&mut i32当成一个值类型,move进了foo这个闭包的作用域,而不是引用类型的传递。而标注了state的类型时,就是直接按引用传递了。

--
👇
Neutron3529: 定义闭包时本来就是要指定类型的吧

除了iter里面(比如foreach(|x|...)这里可以不指定类型)的闭包可以不指定类型之外,我是真的不知道还有哪个闭包可以不指定类型

Neutron3529 2020-08-06 21:20

定义闭包时本来就是要指定类型的吧

除了iter里面(比如foreach(|x|...)这里可以不指定类型)的闭包可以不指定类型之外,我是真的不知道还有哪个闭包可以不指定类型

1 共 9 条评论, 1 页