E4015
E4015 — Aspawnregion that moves a value from the code around it:let x = t;orf(t)with a parameter that takestby value, wheretis declared before the region. The region only borrows what it uses, and the function dropstafter the region. Borrow it (&t), or clone it (t.clone()).
What the compiler reports
Error[E4015] at {{.*}}.vx:[[@LINE+4]]:14: this `spawn` region moves 't', which belongs to the code around the region
Error[E4015] at {{.*}}.vx:[[@LINE+4]]:19: this `spawn` region moves 't'
Error[E4015] at {{.*}}.vx:[[@LINE+5]]:10: this `spawn` region moves 't'
Error[E4015] at {{.*}}.vx:[[@LINE+5]]:9: this `spawn` region moves 't'
Error[E4015] at {{.*}}.vx:[[@LINE+6]]:14: this `spawn` region moves 'n'
The fragments the test suite holds the compiler to. An ellipsis marks text the test does not constrain; the emitted message also carries a source location.
A program that triggers it
//
// Part of the Vx Project, under the Apache License v2.0 with LLVM Exceptions.
// See LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// A `spawn` region only borrows the values that it uses from the code around it. The function
// drops them after the region. So the region must not give such a value a new owner: a
// variable, a struct field, or a parameter that takes it by value. That owner would drop it
// too.
//
struct Noisy {
id : i32,
}
impl Drop for Noisy {
fn drop(self : &mut Noisy) -> void {
println!("dropped {}", self.id);
}
}
struct Holder {
t : Tensor<f32, [4]>,
}
fn sink(t : Tensor<f32, [4]>) -> i32 {
return 1;
}
fn look(t : &Tensor<f32, [4]>) -> i32 {
return 1;
}
fn into_a_variable() -> i32 {
let t = Tensor<f32, [4]>::fill(1.0);
spawn on(Topology::Current) {
let _x = t;
}
return 0;
}
fn into_a_parameter() -> i32 {
let t = Tensor<f32, [4]>::fill(1.0);
spawn on(Topology::Current) {
let _n = sink(t);
}
return 0;
}
fn into_a_struct_field() -> i32 {
let t = Tensor<f32, [4]>::fill(1.0);
spawn on(Topology::Current) {
let _h = Holder {
t: t
};
}
return 0;
}
fn into_a_variable_that_exists() -> i32 {
let t = Tensor<f32, [4]>::fill(1.0);
let mut x = Tensor<f32, [4]>::fill(2.0);
spawn on(Topology::Current) {
x = t;
}
return 0;
}
fn a_value_with_a_drop() -> i32 {
let n = Noisy {
id: 1
};
spawn on(Topology::Current) {
let _m = n;
}
return 0;
}
// A borrow, a clone, and a value made in the region are fine.
fn borrows_and_clones() -> i32 {
let t = Tensor<f32, [4]>::fill(1.0);
spawn on(Topology::Current) {
let _r = &t;
let _n = look(&t);
let _c = t.clone();
let u = Tensor<f32, [4]>::fill(2.0);
let _v = u;
}
return 0;
}
fn main() -> i32 {
return into_a_variable() + into_a_parameter() + into_a_struct_field() + into_a_variable_that_exists() + a_value_with_a_drop() + borrows_and_clones();
}
From tests/frontend/fail/spawn_region_that_moves_a_value_from_outside_it.vx, which asserts this diagnostic on every commit.
Related
Ownership and borrowingThe chapter covering the rule this code enforces.
All diagnosticsEvery code the compiler can emit, grouped by stage.
E4014A task that can still run when a variable that it uses ends or changes. A task in a loop that the same pass does not join runs at the same time as the next pass, so it must not write a variable from outside the loop, or read one that the loop writes. A task must not use a variable that ends before its
tasks block, such as one of the loop body. Join the task in the same block: let h = spawn { .. }; and then h.join();. A return, break or continue that leaves two tasks blocks waits only for the tasks of the inner one, so it cannot drop a value from outside the inner block yet.
E4016A borrow clause that is not well formed, or one that the body does not keep. In ensures !points_into(return, *p) the result cannot point into the memory under *p, and in ensures !reaches(return, *p) it also cannot point where a reference in *p points. In ensures !keeps(p) the body stores p, or a reference read through it, nowhere that outlives the call. p is a reference parameter. Each borrow clause is an ensures of its own.