In C++ (please correct me if wrong), a temporary bound via constant reference is supposed to outlive the expression it is bound to. I assumed the same was true in Rust, but I get two different behaviors in two different cases.
Consider:
struct A;
impl Drop for A { fn drop(&mut self) { println!("Drop A.") } }
struct B(*const A);
impl Drop for B { fn drop(&mut self) { println!("Drop B.") } }
fn main() {
let _ = B(&A as *const A); // B is destroyed after this expression itself.
}
The output is:
Drop B.
Drop A.
This is what you would expect. But now if you do:
fn main() {
let _b = B(&A as *const A); // _b will be dropped when scope exits main()
}
The output is:
Drop A.
Drop B.
This is not what I expected.
Is this intended and if so then what is the rationale for the difference in behavior in the two cases?
I am using Rust 1.12.1.
Temporaries are dropped at the end of the statement, just like in C++. However, IIRC, the order of destruction in Rust is unspecified (we'll see the consequences of this below), though the current implementation seems to simply drop values in reverse order of construction.
There's a big difference between let _ = x; and let _b = x;. _ isn't an identifier in Rust: it's a wildcard pattern. Since this pattern doesn't find any variables, the final value is effectively dropped at the end of the statement.
On the other hand, _b is an identifier, so the value is bound to a variable with that name, which extends its lifetime until the end of the function. However, the A instance is still a temporary, so it will be dropped at the end of the statement (and I believe C++ would do the same). Since the end of the statement comes before the end of the function, the A instance is dropped first, and the B instance is dropped second.
To make this clearer, let's add another statement in main:
fn main() {
let _ = B(&A as *const A);
println!("End of main.");
}
This produces the following output:
Drop B.
Drop A.
End of main.
So far so good. Now let's try with let _b; the output is:
Drop A.
End of main.
Drop B.
As we can see, Drop B is printed after End of main.. This demonstrates that the B instance is alive until the end of the function, explaining the different destruction order.
Now, let's see what happens if we modify B to take a borrowed pointer with a lifetime instead of a raw pointer. Actually, let's go a step further and remove the Drop implementations for a moment:
struct A;
struct B<'a>(&'a A);
fn main() {
let _ = B(&A);
}
This compiles fine. Behind the scenes, Rust assigns the same lifetime to both the A instance and the B instance (i.e. if we took a reference to the B instance, its type would be &'a B<'a> where both 'a are the exact same lifetime). When two values have the same lifetime, then necessarily we need to drop one of them before the other, and as mentioned above, the order is unspecified. What happens if we add back the Drop implementations?
struct A;
impl Drop for A { fn drop(&mut self) { println!("Drop A.") } }
struct B<'a>(&'a A);
impl<'a> Drop for B<'a> { fn drop(&mut self) { println!("Drop B.") } }
fn main() {
let _ = B(&A);
}
Now we're getting a compiler error:
error: borrowed value does not live long enough
--> <anon>:8:16
|
8 | let _ = B(&A);
| ^ does not live long enough
|
note: reference must be valid for the destruction scope surrounding statement at 8:4...
--> <anon>:8:5
|
8 | let _ = B(&A);
| ^^^^^^^^^^^^^^
note: ...but borrowed value is only valid for the statement at 8:4
--> <anon>:8:5
|
8 | let _ = B(&A);
| ^^^^^^^^^^^^^^
help: consider using a `let` binding to increase its lifetime
--> <anon>:8:5
|
8 | let _ = B(&A);
| ^^^^^^^^^^^^^^
Since both the A instance and the B instance have been assigned the same lifetime, Rust cannot reason about the destruction order of these objects. The error comes from the fact that Rust refuses to instantiate B<'a> with the lifetime of the object itself when B<'a> implements Drop (this rule was added as the result of RFC 769 before Rust 1.0). If it was allowed, drop would be able to access values that have already been dropped! However, if B<'a> doesn't implement Drop, then it's allowed, because we know that no code will try to access B's fields when the struct is dropped.
Raw pointers themselves do not carry any sort of lifetime so the compiler might do something like this:
Example:
B is created (so that it can hold an *const A in it)
A is created
B is not bound to a binding and thus gets dropped
A is not needed and thus gets dropped
Let's check out the MIR:
fn main() -> () {
let mut _0: (); // return pointer
let mut _1: B;
let mut _2: *const A;
let mut _3: *const A;
let mut _4: &A;
let mut _5: &A;
let mut _6: A;
let mut _7: ();
bb0: {
StorageLive(_1); // scope 0 at <anon>:8:13: 8:30
StorageLive(_2); // scope 0 at <anon>:8:15: 8:29
StorageLive(_3); // scope 0 at <anon>:8:15: 8:17
StorageLive(_4); // scope 0 at <anon>:8:15: 8:17
StorageLive(_5); // scope 0 at <anon>:8:15: 8:17
StorageLive(_6); // scope 0 at <anon>:8:16: 8:17
_6 = A::A; // scope 0 at <anon>:8:16: 8:17
_5 = &_6; // scope 0 at <anon>:8:15: 8:17
_4 = &(*_5); // scope 0 at <anon>:8:15: 8:17
_3 = _4 as *const A (Misc); // scope 0 at <anon>:8:15: 8:17
_2 = _3; // scope 0 at <anon>:8:15: 8:29
_1 = B::B(_2,); // scope 0 at <anon>:8:13: 8:30
drop(_1) -> bb1; // scope 0 at <anon>:8:31: 8:31
}
bb1: {
StorageDead(_1); // scope 0 at <anon>:8:31: 8:31
StorageDead(_2); // scope 0 at <anon>:8:31: 8:31
StorageDead(_3); // scope 0 at <anon>:8:31: 8:31
StorageDead(_4); // scope 0 at <anon>:8:31: 8:31
StorageDead(_5); // scope 0 at <anon>:8:31: 8:31
drop(_6) -> bb2; // scope 0 at <anon>:8:31: 8:31
}
bb2: {
StorageDead(_6); // scope 0 at <anon>:8:31: 8:31
_0 = (); // scope 0 at <anon>:7:11: 9:2
return; // scope 0 at <anon>:9:2: 9:2
}
}
As we can see drop(_1) is indeed called before drop(_6) as presumed, thus you get the output above.
Example
In this example B is bound to a binding
B is created (for the same reason as above)
A is created
A is not bound and gets dropped
B goes out of scope and gets dropped
The corresponding MIR:
fn main() -> () {
let mut _0: (); // return pointer
scope 1 {
let _1: B; // "b" in scope 1 at <anon>:8:9: 8:10
}
let mut _2: *const A;
let mut _3: *const A;
let mut _4: &A;
let mut _5: &A;
let mut _6: A;
let mut _7: ();
bb0: {
StorageLive(_1); // scope 0 at <anon>:8:9: 8:10
StorageLive(_2); // scope 0 at <anon>:8:15: 8:29
StorageLive(_3); // scope 0 at <anon>:8:15: 8:17
StorageLive(_4); // scope 0 at <anon>:8:15: 8:17
StorageLive(_5); // scope 0 at <anon>:8:15: 8:17
StorageLive(_6); // scope 0 at <anon>:8:16: 8:17
_6 = A::A; // scope 0 at <anon>:8:16: 8:17
_5 = &_6; // scope 0 at <anon>:8:15: 8:17
_4 = &(*_5); // scope 0 at <anon>:8:15: 8:17
_3 = _4 as *const A (Misc); // scope 0 at <anon>:8:15: 8:17
_2 = _3; // scope 0 at <anon>:8:15: 8:29
_1 = B::B(_2,); // scope 0 at <anon>:8:13: 8:30
StorageDead(_2); // scope 0 at <anon>:8:31: 8:31
StorageDead(_3); // scope 0 at <anon>:8:31: 8:31
StorageDead(_4); // scope 0 at <anon>:8:31: 8:31
StorageDead(_5); // scope 0 at <anon>:8:31: 8:31
drop(_6) -> [return: bb3, unwind: bb2]; // scope 0 at <anon>:8:31: 8:31
}
bb1: {
resume; // scope 0 at <anon>:7:1: 9:2
}
bb2: {
drop(_1) -> bb1; // scope 0 at <anon>:9:2: 9:2
}
bb3: {
StorageDead(_6); // scope 0 at <anon>:8:31: 8:31
_0 = (); // scope 1 at <anon>:7:11: 9:2
drop(_1) -> bb4; // scope 0 at <anon>:9:2: 9:2
}
bb4: {
StorageDead(_1); // scope 0 at <anon>:9:2: 9:2
return; // scope 0 at <anon>:9:2: 9:2
}
}
As we can see drop(_6) does get called before drop(_1) so we get the behavior you have seen.
Related
This question already has an answer here:
How can I pass a reference to a stack variable to a thread?
(1 answer)
Closed last month.
How do I move a vector reference into threads? The closest I get is the (minimized) code below. (I realize that the costly calculation still isn't parallel, as it is locked by the mutex, but one problem at a time.)
Base problem: I'm calculating values based on information saved in a vector. Then I'm storing the results as nodes per vector element. So vector in vector (but only one vector in the example code below). The calculation takes time so I would like to divide it into threads. The structure is big, so I don't want to copy it.
use std::sync::{Arc, Mutex};
use std::thread;
fn main() {
let n = Nodes::init();
n.calc();
println!("Result: nodes {:?}", n);
}
#[derive(Debug)]
struct Nodes {
nodes: Vec<Info>,
}
impl Nodes {
fn init() -> Self {
let mut n = Nodes { nodes: Vec::new() };
n.nodes.push(Info::init(1));
n.nodes.push(Info::init(2));
n
}
fn calc(&self) {
Nodes::calc_associative(&self.nodes);
}
fn calc_associative(nodes: &Vec<Info>) {
let mut handles = vec![];
let arc_nodes = Arc::new(nodes);
let counter = Arc::new(Mutex::new(0));
for _ in 0..2 {
let arc_nodes = Arc::clone(&arc_nodes);
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut idx = counter.lock().unwrap();
// costly calculation
arc_nodes[*idx].set_length(arc_nodes[*idx].get_length() * 2);
*idx += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
}
}
#[derive(Debug)]
struct Info {
length: u32,
}
impl Info {
fn init(length: u32) -> Self {
Info { length }
}
fn get_length(&self) -> u32 {
self.length
}
fn set_length(&mut self, x: u32) {
self.length = x;
}
}
The compiler complains that life time of the reference isn't fulfilled, but isn't that what Arc::clone() should do? Then Arc require a deref, but maybe there are better solutions before starting to dig into that...?
Compiling threads v0.1.0 (/home/freefox/proj/threads)
error[E0596]: cannot borrow data in an `Arc` as mutable
--> src/main.rs:37:17
|
37 | arc_nodes[*idx].set_length(arc_nodes[*idx].get_length() * 2);
| ^^^^^^^^^ cannot borrow as mutable
|
= help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<&Vec<Info>>`
error[E0521]: borrowed data escapes outside of associated function
--> src/main.rs:34:26
|
25 | fn calc_associative(nodes: &Vec<Info>) {
| ----- - let's call the lifetime of this reference `'1`
| |
| `nodes` is a reference that is only valid in the associated function body
...
34 | let handle = thread::spawn(move || {
| __________________________^
35 | | let mut idx = counter.lock().unwrap();
36 | | // costly calculation
37 | | arc_nodes[*idx].set_length(arc_nodes[*idx].get_length() * 2);
38 | | *idx += 1;
39 | | });
| | ^
| | |
| |______________`nodes` escapes the associated function body here
| argument requires that `'1` must outlive `'static`
Some errors have detailed explanations: E0521, E0596.
For more information about an error, try `rustc --explain E0521`.
error: could not compile `threads` due to 2 previous errors
You wrap a reference with Arc. Now the type is Arc<&Vec<Info>>. There is still a reference here, so the variable could still be destroyed before the thread return and we have a dangling reference.
Instead, you should take a &Arc<Vec<Info>>, and on the construction of the Vec wrap it in Arc, or take &[Info] and clone it (let arc_nodes = Arc::new(nodes.to_vec());). You also need a mutex along the way (either Arc<Mutex<Vec<Info>>> or Arc<Vec<Mutex<Info>>>), since you want to change the items.
Or better, since you immediately join() the threads, use scoped threads:
fn calc_associative(nodes: &[Mutex<Info>]) {
let counter = std::sync::atomic::AtomicUsize::new(0); // Changed to atomic, prefer it to mutex wherever possible
std::thread::scope(|s| {
for _ in 0..2 {
s.spawn(|| {
let idx = counter.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
let node = &mut *nodes[idx].lock().unwrap();
// costly calculation
node.set_length(node.get_length() * 2);
});
}
});
}
I'm trying to write a program that generates mathematical expressions and then evaluates them. Expressions can contain primitive operations (plus, minus, etc.) or other sub-expressions that consist of primitive operations.
The problem is that sub-expressions are local for the created expression and won't be needed anywhere else except their parent expression (so, I should not make them static), but I can't move parent expression somewhere, because local sub-expressions considered as dropped even if I move them to the parent expression struct.
Minimal example:
Rust Playground
trait Operation {
fn compute(&self) -> i32;
}
struct SumNumbers {
pub numbers: Vec<i32>,
}
impl Operation for SumNumbers {
fn compute(&self) -> i32 {
let mut result = 0;
for x in &self.numbers {
result += x;
}
result
}
}
struct Expression<'a> {
pub operations: Vec<&'a dyn Operation>,
sub_expr_holder: Vec<Expression<'a>>,
}
// Not working attempt to ensure sub_expr_holder will live longer than operations
// struct Expression<'a, 'b : 'a> {
// pub operations: Vec<&'a dyn Operation>,
// sub_expr_holder: Vec<Expression<'a, 'b>>,
// }
impl Operation for Expression<'_> {
fn compute(&self) -> i32 {
let mut result = 0;
for operation in &self.operations {
result += operation.compute();
}
result
}
}
fn build_single_expression() {
let static_sum2 = SumNumbers {
numbers: vec![1, 1],
};
let static_sum4 = SumNumbers {
numbers: vec![1, 3],
};
let static_sum6 = SumNumbers {
numbers: vec![4, 2],
};
let root_expr = {
let local_expr_sum10 = Expression {
operations: vec![&static_sum4 as &dyn Operation,
&static_sum6 as &dyn Operation],
sub_expr_holder: vec![]
};
let mut root_expr = Expression {
operations: vec![],
sub_expr_holder: vec![local_expr_sum10]
};
root_expr.operations = vec![&root_expr.sub_expr_holder[0], &static_sum2];
// Ideal option would be to keep it immutable:
// let root_expr = Expression {
// operations: vec![&local_expr_sum10, &static_sum2],
// sub_expr_holder: vec![local_expr_sum10]
// };
root_expr
};
let root_expr = &root_expr as &dyn Operation;
let result = root_expr.compute();
assert_eq!(result, 12)
}
The errors I get are:
error[E0597]: `root_expr.sub_expr_holder` does not live long enough
--> src/gep_tools/test.rs:61:38
|
50 | let root_expr = {
| --------- borrow later stored here
...
61 | root_expr.operations = vec![&root_expr.sub_expr_holder[0], &static_sum2];
| ^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
...
70 | };
| - `root_expr.sub_expr_holder` dropped here while still borrowed
Even though I move the local_expr_sum10 into the root_expr, the compiler says it doesn't live long enough to be borrowed from root_expr.operations. It seems the second error explains why:
error[E0505]: cannot move out of `root_expr` because it is borrowed
--> src/gep_tools/test.rs:69:9
|
61 | root_expr.operations = vec![&root_expr.sub_expr_holder[0], &static_sum2];
| ------------------------- borrow of `root_expr.sub_expr_holder` occurs here
...
69 | root_expr
| ^^^^^^^^^
| |
| move out of `root_expr` occurs here
| borrow later used here
As I understand, the compiler assumes that I can move the root_expr.sub_expr_holder out of the root_expr struct and it will be dropped while still borrowed by root_expr.operations.
Is there a way how I can force the compiler to forbid move of root_expr.sub_expr_holder out of the struct and therefore let me borrow it? Or any other approach that will let me borrow the local sub-expressions this way?
Edit:
I want to use references specifically, because there may be a lot primitive expressions which are reused among all the expressions and it would be waste of memory to copy them each time. Also, the sub-expressions can be used in the root expression several times:
root_expr.operations = vec![&root_expr.sub_expr_holder[0], &static_sum2, &root_expr.sub_expr_holder[0]];
This doesn't make a lot of sense in this minimal example, but in my full code arguments can be passed to the sub-expressions, so it makes sense to put them in a single expression several times. So, again, it would be a waste of memory to copy them multiple times.
Someone marked this question as a duplicate of "self-referential struct problem" (it was re-opened since then), but as I said, this is just 1 of my attempts to solve my problem. As it turned out, the actual problem is called "Multiple Ownership" and as Chayim Friedman suggested, it can be solved with Rc.
As a simple conclusion: if you need to use a local struct that will be dropped at the end of the scope, but you can not move it and have to use references, then go with a reference counter.
A good place to read about Rc and multiple ownership is the Rust Book.
My solution:
Rust Playground
use std::rc::Rc;
trait Operation {
fn compute(&self) -> i32;
}
struct SumNumbers {
pub numbers: Vec<i32>,
}
impl Operation for SumNumbers {
fn compute(&self) -> i32 {
let mut result = 0;
for x in &self.numbers {
result += x;
}
result
}
}
struct Expression {
pub operations: Vec<Rc<dyn Operation>>,
}
impl Operation for Expression<> {
fn compute(&self) -> i32 {
let mut result = 0;
for operation in &self.operations {
result += operation.compute();
}
result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_single_expression() {
let static_sum2 = SumNumbers {
numbers: vec![1, 1],
};
let static_sum4 = SumNumbers {
numbers: vec![1, 3],
};
let static_sum6 = SumNumbers {
numbers: vec![4, 2],
};
let root_expr = {
let local_expr_sum10: Rc<dyn Operation> = Rc::new(
Expression {
operations: vec![Rc::new(static_sum4),
Rc::new(static_sum6)]
});
Expression {
operations: vec![Rc::clone(&local_expr_sum10),
Rc::clone(&local_expr_sum10),
Rc::new(static_sum2)],
}
};
let root_expr = &root_expr as &dyn Operation;
let result = root_expr.compute();
assert_eq!(result, 22)
}
}
I'm not sure how good this solution is, but it completely solves my problem, and it should not affect performance.
I'm trying to learn assembly through compiling Rust. I have found a way to compile Rust code to binary machine code and be able to objdump it to view the assembly. However if I write the following:
#![no_main]
#[link_section = ".text.entry"]
#[no_mangle]
pub extern "C" fn _start() -> ! {
let a: u64 = 4;
let b: u64 = 7;
let c: u64 = a * b;
loop {}
}
The assembly I get is:
0000000000000000 <.data>:
0: 1101 addi sp,sp,-32
2: 4511 li a0,4
4: e42a sd a0,8(sp)
6: 451d li a0,7
8: e82a sd a0,16(sp)
a: 4571 li a0,28
c: ec2a sd a0,24(sp)
e: a009 j 0x10
10: a001 j 0x10
So it looks like rust is collapsing the mul to a constant. I'm using the following compile options:
Cargo.toml:
[profile.dev]
opt-level = 0
mir-opt-level = 0
Is there a way to stop Rust from optimizing this?
The LLVM emitted looks like this:
; Function Attrs: noreturn nounwind
define dso_local void #_start() unnamed_addr #0 section ".text.entry" !dbg !22 {
start:
%c.dbg.spill = alloca i64, align 8
%b.dbg.spill = alloca i64, align 8
%a.dbg.spill = alloca i64, align 8
store i64 4, i64* %a.dbg.spill, align 8, !dbg !36
call void #llvm.dbg.declare(metadata i64* %a.dbg.spill, metadata !28, metadata !DIExpression()), !dbg !37
store i64 7, i64* %b.dbg.spill, align 8, !dbg !38
call void #llvm.dbg.declare(metadata i64* %b.dbg.spill, metadata !31, metadata !DIExpression()), !dbg !39
store i64 28, i64* %c.dbg.spill, align 8, !dbg !40
call void #llvm.dbg.declare(metadata i64* %c.dbg.spill, metadata !33, metadata !DIExpression()), !dbg !41
So it looks like the optimization is before the LLVM pass.
$ rustc --version
rustc 1.60.0-nightly (c5c610aad 2022-02-14)
Command to build:
RUSTFLAGS="--emit=llvm-bc" cargo build --target riscv64imac-unknown-none-elf --no-default-features
build.rs
fn main() {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-link-arg=-Tlink.ld");
}
link.ld
ENTRY(_start)
SECTIONS {
.text : { *(.text); *(.text.*) }
}
There is one compiler pass before the generation of LLVM-IR, which is the generation of MIR, the Rust intermediate representation. If you emit this for the given code with a command such as this one:
cargo rustc -- --emit mir
You will see in the .mir file generated that the optimization already took place there.
fn _start() -> ! {
let mut _0: !; // return place in scope 0 at src\main.rs:5:31: 5:32
let _1: u64; // in scope 0 at src\main.rs:6:9: 6:10
scope 1 {
debug a => _1; // in scope 1 at src\main.rs:6:9: 6:10
let _2: u64; // in scope 1 at src\main.rs:7:9: 7:10
scope 2 {
debug b => _2; // in scope 2 at src\main.rs:7:9: 7:10
let _3: u64; // in scope 2 at src\main.rs:8:9: 8:10
scope 3 {
debug c => _3; // in scope 3 at src\main.rs:8:9: 8:10
}
}
}
bb0: {
_1 = const 4_u64; // scope 0 at src\main.rs:6:18: 6:19
_2 = const 7_u64; // scope 1 at src\main.rs:7:18: 7:19
_3 = const 28_u64; // scope 2 at src\main.rs:8:18: 8:23
goto -> bb1; // scope 3 at src\main.rs:10:5: 10:12
}
bb1: {
goto -> bb1; // scope 3 at src\main.rs:10:5: 10:12
}
}
This is happening because the mir-opt-level option currently only exists as an unstable compiler option. It is not available as a profile property in Cargo. Set it manually on a direct call to the compiler:
cargo rustc -- -Z mir-opt-level=0 --emir mir
And this optimization will disappear:
fn _start() -> ! {
let mut _0: !; // return place in scope 0 at src\main.rs:5:31: 5:32
let mut _1: !; // in scope 0 at src\main.rs:5:33: 11:2
let _2: u64; // in scope 0 at src\main.rs:6:9: 6:10
let mut _5: u64; // in scope 0 at src\main.rs:8:18: 8:19
let mut _6: u64; // in scope 0 at src\main.rs:8:22: 8:23
let mut _7: (u64, bool); // in scope 0 at src\main.rs:8:18: 8:23
let mut _8: !; // in scope 0 at src\main.rs:10:5: 10:12
let mut _9: (); // in scope 0 at src\main.rs:5:1: 11:2
scope 1 {
debug a => _2; // in scope 1 at src\main.rs:6:9: 6:10
let _3: u64; // in scope 1 at src\main.rs:7:9: 7:10
scope 2 {
debug b => _3; // in scope 2 at src\main.rs:7:9: 7:10
let _4: u64; // in scope 2 at src\main.rs:8:9: 8:10
scope 3 {
debug c => _4; // in scope 3 at src\main.rs:8:9: 8:10
}
}
}
bb0: {
StorageLive(_1); // scope 0 at src\main.rs:5:33: 11:2
StorageLive(_2); // scope 0 at src\main.rs:6:9: 6:10
_2 = const 4_u64; // scope 0 at src\main.rs:6:18: 6:19
StorageLive(_3); // scope 1 at src\main.rs:7:9: 7:10
_3 = const 7_u64; // scope 1 at src\main.rs:7:18: 7:19
StorageLive(_4); // scope 2 at src\main.rs:8:9: 8:10
StorageLive(_5); // scope 2 at src\main.rs:8:18: 8:19
_5 = _2; // scope 2 at src\main.rs:8:18: 8:19
StorageLive(_6); // scope 2 at src\main.rs:8:22: 8:23
_6 = _3; // scope 2 at src\main.rs:8:22: 8:23
_7 = CheckedMul(_5, _6); // scope 2 at src\main.rs:8:18: 8:23
assert(!move (_7.1: bool), "attempt to compute `{} * {}`, which would overflow", move _5, move _6) -> bb1; // scope 2 at src\main.rs:8:18: 8:23
}
bb1: {
_4 = move (_7.0: u64); // scope 2 at src\main.rs:8:18: 8:23
StorageDead(_6); // scope 2 at src\main.rs:8:22: 8:23
StorageDead(_5); // scope 2 at src\main.rs:8:22: 8:23
StorageLive(_8); // scope 3 at src\main.rs:10:5: 10:12
goto -> bb2; // scope 3 at src\main.rs:10:5: 10:12
}
bb2: {
_9 = const (); // scope 3 at src\main.rs:10:10: 10:12
goto -> bb2; // scope 3 at src\main.rs:10:5: 10:12
}
}
And this is probably as far as you can go without touching LLVM directly. Some optimisations in specific parts of the code can also be prevented through constructs such as black_box.
See also:
Rustc dev guide book on MIR optimizations
a is a Vec<i32> which can be mutably and immutably referenced in one expression:
fn main() {
let mut a = vec![0, 1];
a[0] += a[1]; // OK
}
I thought this compiled because i32 implements Copy, so I created another type that implements Copy and compiled it like the first example, but it fails:
use std::ops::AddAssign;
#[derive(Clone, Copy, PartialEq, Debug, Default)]
struct MyNum(i32);
impl AddAssign for MyNum {
fn add_assign(&mut self, rhs: MyNum) {
*self = MyNum(self.0 + rhs.0)
}
}
fn main() {
let mut b = vec![MyNum(0), MyNum(1)];
b[0] += b[1];
}
playground
error[E0502]: cannot borrow `b` as immutable because it is also borrowed as mutable
--> src/main.rs:14:13
|
14 | b[0] += b[1];
| --------^---
| | |
| | immutable borrow occurs here
| mutable borrow occurs here
| mutable borrow later used here
Why does my MyNum not behave in the same way as i32 even though it implements Copy?
Why can the vector be mutably and immutably referenced in one expression?
I believe the thing you're seeing here is that primitive types do not actually call their std::ops equivalents. Those std::ops may just be included for seamless trait extensions, etc. I think the blog post Rust Tidbits: What Is a Lang Item? partially explains this.
I exported the MIR of your example that works with primitive types. I got:
bb5: {
StorageDead(_9); // bb5[0]: scope 1 at src/main.rs:6:8: 6:9
_10 = CheckedAdd((*_8), move _5); // bb5[1]: scope 1 at src/main.rs:6:5: 6:17
assert(!move (_10.1: bool), "attempt to add with overflow") -> [success: bb6, unwind: bb4]; // bb5[2]: scope 1 at src/main.rs:6:5: 6:17
}
I had a lot of difficulty exporting the MIR for the code that was erroring. Outputting MIR without borrow checking is new to me and I couldn't figure out how to do it.
This playground has a very similar thing, but compiles :)
It gives me an actual call to add_assign:
bb3: {
_8 = _9; // bb3[0]: scope 1 at src/main.rs:14:5: 14:9
StorageDead(_10); // bb3[1]: scope 1 at src/main.rs:14:8: 14:9
StorageLive(_11); // bb3[2]: scope 1 at src/main.rs:14:14: 14:22
(_11.0: i32) = const 1i32; // bb3[3]: scope 1 at src/main.rs:14:14: 14:22
// ty::Const
// + ty: i32
// + val: Value(Scalar(0x00000001))
// mir::Constant
// + span: src/main.rs:14:20: 14:21
// + literal: Const { ty: i32, val: Value(Scalar(0x00000001)) }
_7 = const <MyNum as std::ops::AddAssign>::add_assign(move _8, move _11) -> [return: bb5, unwind: bb4]; // bb3[4]: scope 1 at src/main.rs:14:5: 14:22
// ty::Const
// + ty: for<'r> fn(&'r mut MyNum, MyNum) {<MyNum as std::ops::AddAssign>::add_assign}
// + val: Value(Scalar(<ZST>))
// mir::Constant
// + span: src/main.rs:14:5: 14:22
// + literal: Const { ty: for<'r> fn(&'r mut MyNum, MyNum) {<MyNum as std::ops::AddAssign>::add_assign}, val: Value(Scalar(<ZST>)) }
}
How does the primitive case pass the borrow checker? Since add_assign is not called, the immutable reference can be dropped before the mutable reference is required. The MIR simply dereferences the needed location earlier on and passes it through by value.
bb3: {
_5 = (*_6); // bb3[0]: scope 1 at src/main.rs:6:13: 6:17
StorageDead(_7); // bb3[1]: scope 1 at src/main.rs:6:16: 6:17
...
}
It seems that shadowing a variable does not release the borrowed reference it holds. The following code does not compile:
fn main() {
let mut a = 40;
let r1 = &mut a;
let r1 = "shadowed";
let r2 = &mut a;
}
With the error message:
error[E0499]: cannot borrow `a` as mutable more than once at a time
--> src/main.rs:5:19
|
3 | let r1 = &mut a;
| - first mutable borrow occurs here
4 | let r1 = "shadowed";
5 | let r2 = &mut a;
| ^ second mutable borrow occurs here
6 | }
| - first borrow ends here
I would expect the code to compile because the first reference r1 is shadowed before borrowing the second reference r2. Obviously, the first borrow lives until the end of the block although it is no longer accessible after line 4. Why is that the case?
TL;DR: Shadowing is about name-lookup, borrowing is about lifetimes.
From a compiler point of view, variables have no name:
fn main() {
let mut __0 = 40;
let __1 = &mut __0;
let __2 = "shadowed";
let __3 = &mut __0;
}
This is not very readable for a human being, so the language allows us to use descriptive names instead.
Shadowing is an allowance on reusing names, which for the lexical scope of the "shadowing" variable will resolve the name to the "shadowing" one (__2 here) instead of the "original" one (__1 here).
However just because the old one can no longer be accessed does not mean it no longer lives: Shadowing != Assignment. This is especially notable with different scopes:
fn main() {
let i = 3;
for i in 0..10 {
}
println!("{}", i);
}
Will always print 3: once the shadowing variable's scope ends, the name resolves to the original again!
It's not like the original r1 ceases to exist after it becomes shadowed; consider the MIR produced for your code without the last line (r2 binding):
fn main() -> () {
let mut _0: (); // return pointer
scope 1 {
let mut _1: i32; // "a" in scope 1 at src/main.rs:2:9: 2:14
scope 2 {
let _2: &mut i32; // "r1" in scope 2 at src/main.rs:3:9: 3:11
scope 3 {
let _3: &str; // "r1" in scope 3 at src/main.rs:4:9: 4:11
}
}
}
bb0: {
StorageLive(_1); // scope 0 at src/main.rs:2:9: 2:14
_1 = const 40i32; // scope 0 at src/main.rs:2:17: 2:19
StorageLive(_2); // scope 1 at src/main.rs:3:9: 3:11
_2 = &mut _1; // scope 1 at src/main.rs:3:14: 3:20
StorageLive(_3); // scope 2 at src/main.rs:4:9: 4:11
_3 = const "shadowed"; // scope 2 at src/main.rs:4:14: 4:24
_0 = (); // scope 3 at src/main.rs:1:11: 5:2
StorageDead(_3); // scope 2 at src/main.rs:5:2: 5:2
StorageDead(_2); // scope 1 at src/main.rs:5:2: 5:2
StorageDead(_1); // scope 0 at src/main.rs:5:2: 5:2
return; // scope 0 at src/main.rs:5:2: 5:2
}
}
Note that when "shadowed" becomes bound (_3), it doesn't change anything related to the original r1 binding (_2); the name r1 no longer applies to the mutable reference, but the original variable still exists.
I wouldn't consider your example a very useful case of shadowing; its usual applications, e.g. bodies of loops, are much more likely to utilize it.