make it illegal to implicitly capture mutable variables

this is the final part of #1273
This commit is contained in:
Niko Matsakis 2012-05-07 11:31:57 -07:00
parent d709ed2542
commit 8a9df5aa38
32 changed files with 258 additions and 122 deletions

View File

@ -362,7 +362,7 @@ type capture_item = {
};
#[auto_serialize]
type capture_clause = [capture_item];
type capture_clause = @[capture_item];
/*
// Says whether this is a block the user marked as

View File

@ -1187,7 +1187,8 @@ fn parse_fn_expr(p: parser, proto: ast::proto) -> @ast::expr {
let body = parse_block(p);
ret mk_expr(p, lo, body.span.hi,
ast::expr_fn(proto, decl, body, capture_clause + cc_old));
ast::expr_fn(proto, decl, body,
@(*capture_clause + cc_old)));
}
fn parse_fn_block_expr(p: parser) -> @ast::expr {
@ -1679,7 +1680,7 @@ fn parse_ty_params(p: parser) -> [ast::ty_param] {
}
// FIXME Remove after snapshot
fn parse_old_skool_capture_clause(p: parser) -> ast::capture_clause {
fn parse_old_skool_capture_clause(p: parser) -> [ast::capture_item] {
fn expect_opt_trailing_semi(p: parser) {
if !eat(p, token::SEMI) {
if p.token != token::RBRACKET {
@ -1739,7 +1740,7 @@ fn parse_fn_decl(p: parser, purity: ast::purity,
parse_arg_fn, p).node;
let inputs = either::lefts(args_or_capture_items);
let capture_clause = either::rights(args_or_capture_items);
let capture_clause = @either::rights(args_or_capture_items);
// Use the args list to translate each bound variable
// mentioned in a constraint to an arg index.
@ -1778,7 +1779,7 @@ fn parse_fn_block_decl(p: parser) -> (ast::fn_decl, ast::capture_clause) {
purity: ast::impure_fn,
cf: ast::return_val,
constraints: []},
either::rights(inputs_captures));
@either::rights(inputs_captures));
}
fn parse_fn_header(p: parser) -> {ident: ast::ident, tps: [ast::ty_param]} {

View File

@ -1018,7 +1018,7 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
// head-box, will be closed by print-block at start
ibox(s, 0u);
word(s.s, proto_to_str(proto));
print_fn_args_and_ret(s, decl, cap_clause);
print_fn_args_and_ret(s, decl, *cap_clause);
space(s.s);
print_block(s, body);
}
@ -1028,7 +1028,7 @@ fn print_expr(s: ps, &&expr: @ast::expr) {
// head-box, will be closed by print-block at start
ibox(s, 0u);
word(s.s, "{");
print_fn_block_args(s, decl, cap_clause);
print_fn_block_args(s, decl, *cap_clause);
print_possibly_embedded_block(s, body, block_block_fn, indent_unit);
}
ast::expr_loop_body(body) {

View File

@ -16,8 +16,8 @@ enum fn_kind {
fk_item_fn(ident, [ty_param]), //< an item declared with fn()
fk_method(ident, [ty_param], @method),
fk_res(ident, [ty_param], region_param),
fk_anon(proto), //< an anonymous function like fn@(...)
fk_fn_block, //< a block {||...}
fk_anon(proto, capture_clause), //< an anonymous function like fn@(...)
fk_fn_block(capture_clause), //< a block {||...}
fk_ctor(ident, [ty_param], node_id /* self id */,
def_id /* parent class id */) // class constructor
}
@ -26,7 +26,7 @@ fn name_of_fn(fk: fn_kind) -> ident {
alt fk {
fk_item_fn(name, _) | fk_method(name, _, _) | fk_res(name, _, _)
| fk_ctor(name, _, _, _) { name }
fk_anon(_) | fk_fn_block { "anon" }
fk_anon(*) | fk_fn_block(*) { "anon" }
}
}
@ -34,7 +34,7 @@ fn tps_of_fn(fk: fn_kind) -> [ty_param] {
alt fk {
fk_item_fn(_, tps) | fk_method(_, tps, _) | fk_res(_, tps, _)
| fk_ctor(_, tps, _, _) { tps }
fk_anon(_) | fk_fn_block { [] }
fk_anon(*) | fk_fn_block(*) { [] }
}
}
@ -381,11 +381,13 @@ fn visit_expr<E>(ex: @expr, e: E, v: vt<E>) {
v.visit_expr(x, e, v);
for arms.each {|a| v.visit_arm(a, e, v); }
}
expr_fn(proto, decl, body, _) {
v.visit_fn(fk_anon(proto), decl, body, ex.span, ex.id, e, v);
expr_fn(proto, decl, body, cap_clause) {
v.visit_fn(fk_anon(proto, cap_clause), decl, body,
ex.span, ex.id, e, v);
}
expr_fn_block(decl, body, _) {
v.visit_fn(fk_fn_block, decl, body, ex.span, ex.id, e, v);
expr_fn_block(decl, body, cap_clause) {
v.visit_fn(fk_fn_block(cap_clause), decl, body,
ex.span, ex.id, e, v);
}
expr_block(b) { v.visit_block(b, e, v); }
expr_assign(a, b) { v.visit_expr(b, e, v); v.visit_expr(a, e, v); }

View File

@ -368,7 +368,7 @@ fn mk_test_wrapper(cx: test_ctxt,
let wrapper_expr: ast::expr = {
id: cx.sess.next_node_id(),
node: ast::expr_fn(ast::proto_bare, wrapper_decl,
wrapper_body, []),
wrapper_body, @[]),
span: span
};

View File

@ -223,8 +223,7 @@ fn visit_ids(item: ast::inlined_item, vfn: fn@(ast::node_id)) {
vfn(m.self_id);
vec::iter(tps) {|tp| vfn(tp.id)}
}
visit::fk_anon(_) |
visit::fk_fn_block {
visit::fk_anon(*) | visit::fk_fn_block(*) {
}
}

View File

@ -31,12 +31,11 @@ type capture_map = map::hashmap<ast::def_id, capture_var>;
// errors for any irregularities which we identify.
fn check_capture_clause(tcx: ty::ctxt,
fn_expr_id: ast::node_id,
fn_proto: ast::proto,
cap_clause: ast::capture_clause) {
let freevars = freevars::get_freevars(tcx, fn_expr_id);
let seen_defs = map::int_hash();
let check_capture_item = fn@(cap_item: ast::capture_item) {
for (*cap_clause).each { |cap_item|
let cap_def = tcx.def_map.get(cap_item.id);
if !vec::any(*freevars, {|fv| fv.def == cap_def}) {
tcx.sess.span_warn(
@ -52,22 +51,6 @@ fn check_capture_clause(tcx: ty::ctxt,
#fmt("variable '%s' captured more than once",
cap_item.name));
}
};
alt fn_proto {
ast::proto_any | ast::proto_block {
if vec::is_not_empty(cap_clause) {
let cap_item0 = vec::head(cap_clause);
tcx.sess.span_err(
cap_item0.span,
"cannot capture values explicitly with a block closure");
}
}
ast::proto_bare | ast::proto_box | ast::proto_uniq {
for cap_clause.each { |cap_item|
check_capture_item(cap_item);
}
}
}
}
@ -80,7 +63,7 @@ fn compute_capture_vars(tcx: ty::ctxt,
// first add entries for anything explicitly named in the cap clause
for cap_clause.each { |cap_item|
for (*cap_clause).each { |cap_item|
let cap_def = tcx.def_map.get(cap_item.id);
let cap_def_id = ast_util::def_id_of_def(cap_def).node;
if cap_item.is_move {

View File

@ -5,6 +5,7 @@ import ty::{kind, kind_copyable, kind_sendable, kind_noncopyable};
import driver::session::session;
import std::map::hashmap;
import syntax::print::pprust::expr_to_str;
import freevars::freevar_entry;
// Kind analysis pass. There are three kinds:
//
@ -56,17 +57,61 @@ fn check_crate(tcx: ty::ctxt, method_map: typeck::method_map,
ret ctx.rval_map;
}
type check_fn = fn@(ctx, option<@freevar_entry>, bool, ty::t, sp: span);
// Yields the appropriate function to check the kind of closed over
// variables. `id` is the node_id for some expression that creates the
// closure.
fn with_appropriate_checker(cx: ctx, id: node_id,
b: fn(fn@(ctx, ty::t, sp: span))) {
fn with_appropriate_checker(cx: ctx, id: node_id, b: fn(check_fn)) {
fn check_for_uniq(cx: ctx, fv: option<@freevar_entry>, is_move: bool,
var_t: ty::t, sp: span) {
// all captured data must be sendable, regardless of whether it is
// moved in or copied in
check_send(cx, var_t, sp);
// check that only immutable variables are implicitly copied in
if !is_move {
for fv.each { |fv|
check_imm_free_var(cx, fv.def, fv.span);
}
}
}
fn check_for_box(cx: ctx, fv: option<@freevar_entry>, is_move: bool,
var_t: ty::t, sp: span) {
// copied in data must be copyable, but moved in data can be anything
if !is_move { check_copy(cx, var_t, sp); }
// check that only immutable variables are implicitly copied in
if !is_move {
for fv.each { |fv|
check_imm_free_var(cx, fv.def, fv.span);
}
}
}
fn check_for_block(cx: ctx, fv: option<@freevar_entry>, _is_move: bool,
_var_t: ty::t, sp: span) {
// only restriction: no capture clauses (we would have to take
// ownership of the moved/copied in data).
if fv.is_none() {
cx.tcx.sess.span_err(
sp,
"cannot capture values explicitly with a block closure");
}
}
fn check_for_bare(cx: ctx, _fv: option<@freevar_entry>, _is_move: bool,
_var_t: ty::t, sp: span) {
cx.tcx.sess.span_err(sp, "attempted dynamic environment capture");
}
let fty = ty::node_id_to_type(cx.tcx, id);
alt ty::ty_fn_proto(fty) {
proto_uniq { b(check_send); }
proto_box { b(check_copy); }
proto_bare { b(check_none); }
proto_any | proto_block { /* no check needed */ }
proto_uniq { b(check_for_uniq) }
proto_box { b(check_for_box) }
proto_bare { b(check_for_bare) }
proto_any | proto_block { b(check_for_block) }
}
}
@ -75,59 +120,54 @@ fn with_appropriate_checker(cx: ctx, id: node_id,
fn check_fn(fk: visit::fn_kind, decl: fn_decl, body: blk, sp: span,
fn_id: node_id, cx: ctx, v: visit::vt<ctx>) {
// n.b.: This could be the body of either a fn decl or a fn expr. In the
// former case, the prototype will be proto_bare and no check occurs. In
// the latter case, we do not check the variables that in the capture
// clause (as we don't have access to that here) but just those that
// appear free. The capture clauses are checked below, in check_expr().
//
// We could do this check also in check_expr(), but it seems more
// "future-proof" to do it this way, as check_fn_body() is supposed to be
// the common flow point for all functions that appear in the AST.
// Find the check function that enforces the appropriate bounds for this
// kind of function:
with_appropriate_checker(cx, fn_id) { |chk|
with_appropriate_checker(cx, fn_id) { |checker|
// Begin by checking the variables in the capture clause, if any.
// Here we slightly abuse the map function to both check and report
// errors and produce a list of the def id's for all capture
// variables. This list is used below to avoid checking and reporting
// on a given variable twice.
let cap_clause = alt fk {
visit::fk_anon(_, cc) | visit::fk_fn_block(cc) { cc }
visit::fk_item_fn(*) | visit::fk_method(*) |
visit::fk_res(*) | visit::fk_ctor(*) { @[] }
};
let captured_vars = (*cap_clause).map { |cap_item|
let cap_def = cx.tcx.def_map.get(cap_item.id);
let cap_def_id = ast_util::def_id_of_def(cap_def).node;
let ty = ty::node_id_to_type(cx.tcx, cap_def_id);
chk(cx, none, cap_item.is_move, ty, cap_item.span);
cap_def_id
};
// Iterate over any free variables that may not have appeared in the
// capture list. Ensure that they too are of the appropriate kind.
for vec::each(*freevars::get_freevars(cx.tcx, fn_id)) {|fv|
let id = ast_util::def_id_of_def(fv.def).node;
if checker == check_copy {
// skip over free variables that appear in the cap clause
if captured_vars.contains(id) { cont; }
// if this is the last use of the variable, then it will be
// a move and not a copy
let is_move = {
let last_uses = alt check cx.last_uses.find(fn_id) {
some(last_use::closes_over(vars)) { vars }
none { [] }
};
if option::is_some(
vec::position_elem(last_uses, id)) { cont; }
}
last_uses.contains(id)
};
let ty = ty::node_id_to_type(cx.tcx, id);
checker(cx, ty, fv.span);
chk(cx, some(fv), is_move, ty, fv.span);
}
}
visit::visit_fn(fk, decl, body, sp, fn_id, cx, v);
}
fn check_fn_cap_clause(cx: ctx,
id: node_id,
cap_clause: capture_clause) {
// Check that the variables named in the clause which are not free vars
// (if any) are also legal. freevars are checked above in check_fn().
// This is kind of a degenerate case, as captured variables will generally
// appear free in the body.
let freevars = freevars::get_freevars(cx.tcx, id);
let freevar_ids = vec::map(*freevars, { |freevar|
ast_util::def_id_of_def(freevar.def).node
});
//log("freevar_ids", freevar_ids);
with_appropriate_checker(cx, id) { |checker|
for cap_clause.each { |cap_item|
let cap_def = cx.tcx.def_map.get(cap_item.id);
let cap_def_id = ast_util::def_id_of_def(cap_def).node;
if !vec::contains(freevar_ids, cap_def_id) {
let ty = ty::node_id_to_type(cx.tcx, cap_def_id);
checker(cx, ty, cap_item.span);
}
}
}
}
fn check_block(b: blk, cx: ctx, v: visit::vt<ctx>) {
alt b.node.expr {
some(ex) { maybe_copy(cx, ex); }
@ -225,9 +265,6 @@ fn check_expr(e: @expr, cx: ctx, v: visit::vt<ctx>) {
}
}
}
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
check_fn_cap_clause(cx, e.id, cap_clause);
}
_ { }
}
visit::visit_expr(e, cx, v);
@ -307,6 +344,35 @@ fn check_copy_ex(cx: ctx, ex: @expr, _warn: bool) {
}
}
fn check_imm_free_var(cx: ctx, def: def, sp: span) {
let msg = "mutable variables cannot be implicitly captured; \
use a capture clause";
alt def {
def_local(_, is_mutbl) {
if is_mutbl {
cx.tcx.sess.span_err(sp, msg);
}
}
def_arg(_, mode) {
alt ty::resolved_mode(cx.tcx, mode) {
by_ref | by_val { /* ok */ }
by_mutbl_ref | by_move | by_copy {
cx.tcx.sess.span_err(sp, msg);
}
}
}
def_upvar(_, def1, _) {
check_imm_free_var(cx, *def1, sp);
}
def_binding(*) | def_self(*) { /*ok*/ }
_ {
cx.tcx.sess.span_bug(
sp,
#fmt["unknown def for free variable: %?", def]);
}
}
}
fn check_copy(cx: ctx, ty: ty::t, sp: span) {
if !ty::kind_can_be_copied(ty::type_kind(cx.tcx, ty)) {
cx.tcx.sess.span_err(sp, "copying a noncopyable value");
@ -319,10 +385,6 @@ fn check_send(cx: ctx, ty: ty::t, sp: span) {
}
}
fn check_none(cx: ctx, _ty: ty::t, sp: span) {
cx.tcx.sess.span_err(sp, "attempted dynamic environment capture");
}
//
// Local Variables:
// mode: rust

View File

@ -159,7 +159,7 @@ fn visit_expr(ex: @expr, cx: ctx, v: visit::vt<ctx>) {
// n.b.: safe to ignore copies, as if they are unused
// then they are ignored, otherwise they will show up
// as freevars in the body.
for cap_clause.each { |ci|
for (*cap_clause).each { |ci|
if ci.is_move {
clear_def_if_local(cx, cx.def_map.get(ci.id), false);
}

View File

@ -200,7 +200,7 @@ fn visit_expr(ex: @expr, &&cx: @ctx, v: visit::vt<@ctx>) {
check_lval(cx, dest, msg_assign);
}
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
for cap_clause.each { |cap_item|
for (*cap_clause).each { |cap_item|
if cap_item.is_move {
let def = cx.tcx.def_map.get(cap_item.id);
alt is_illegal_to_modify_def(cx, def, msg_move_out) {

View File

@ -455,7 +455,7 @@ fn resolve_names(e: @env, c: @ast::crate) {
}
ast::expr_fn(_, _, _, cap_clause) |
ast::expr_fn_block(_, _, cap_clause) {
for cap_clause.each { |ci|
for (*cap_clause).each { |ci|
resolve_capture_item(e, sc, ci);
}
}
@ -615,10 +615,12 @@ fn visit_fn_with_scope(e: @env, fk: visit::fn_kind, decl: ast::fn_decl,
for decl.constraints.each {|c| resolve_constr(e, c, sc, v); }
let scope = alt fk {
visit::fk_item_fn(_, tps) | visit::fk_res(_, tps, _) |
visit::fk_method(_, tps, _) | visit::fk_ctor(_, tps, _, _)
{ scope_bare_fn(decl, id, tps) }
visit::fk_anon(ast::proto_bare) { scope_bare_fn(decl, id, []) }
visit::fk_anon(_) | visit::fk_fn_block { scope_fn_expr(decl, id, []) }
visit::fk_method(_, tps, _) | visit::fk_ctor(_, tps, _, _) {
scope_bare_fn(decl, id, tps) }
visit::fk_anon(ast::proto_bare, _) {
scope_bare_fn(decl, id, []) }
visit::fk_anon(_, _) | visit::fk_fn_block(_) {
scope_fn_expr(decl, id, []) }
};
visit::visit_fn(fk, decl, body, sp, id, cons(scope, @sc), v);

View File

@ -343,12 +343,12 @@ fn find_pre_post_expr(fcx: fn_ctxt, e: @expr) {
expr_fn(_, _, _, cap_clause) | expr_fn_block(_, _, cap_clause) {
find_pre_post_expr_fn_upvars(fcx, e);
for cap_clause.each { |cap_item|
for (*cap_clause).each { |cap_item|
let d = local_node_id_to_local_def_id(fcx, cap_item.id);
option::iter(d, { |id| use_var(fcx, id) });
}
for cap_clause.each { |cap_item|
for (*cap_clause).each { |cap_item|
if cap_item.is_move {
log(debug, ("forget_in_postcond: ", cap_item));
forget_in_postcond(fcx, e.id, cap_item.id);

View File

@ -363,7 +363,7 @@ fn find_pre_post_state_cap_clause(fcx: fn_ctxt, e_id: node_id,
let ccx = fcx.ccx;
let pres_changed = set_prestate_ann(ccx, e_id, pres);
let post = tritv_clone(pres);
for cap_clause.each { |cap_item|
for (*cap_clause).each { |cap_item|
if cap_item.is_move {
forget_in_poststate(fcx, post, cap_item.id);
}

View File

@ -3565,7 +3565,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
}
ast::expr_fn(proto, decl, body, cap_clause) {
check_expr_fn(fcx, expr, proto, decl, body, false, expected);
capture::check_capture_clause(tcx, expr.id, proto, cap_clause);
capture::check_capture_clause(tcx, expr.id, cap_clause);
}
ast::expr_fn_block(decl, body, cap_clause) {
// Take the prototype from the expected type, but default to block:
@ -3573,7 +3573,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
alt sty { ty::ty_fn({proto, _}) { some(proto) } _ { none } }
}).get_default(ast::proto_box);
check_expr_fn(fcx, expr, proto, decl, body, false, expected);
capture::check_capture_clause(tcx, expr.id, proto, cap_clause);
capture::check_capture_clause(tcx, expr.id, cap_clause);
}
ast::expr_loop_body(b) {
// a loop body is the special argument to a `for` loop. We know that
@ -3605,7 +3605,7 @@ fn check_expr_with_unifier(fcx: @fn_ctxt,
ast::expr_fn_block(decl, body, cap_clause) {
check_expr_fn(fcx, b, proto, decl, body, true, some(inner_ty));
demand::suptype(fcx, b.span, inner_ty, fcx.expr_ty(b));
capture::check_capture_clause(tcx, b.id, proto, cap_clause);
capture::check_capture_clause(tcx, b.id, cap_clause);
}
}
let block_ty = structurally_resolved_type(

View File

@ -1,5 +1,5 @@
// error-pattern:variable 'x' captured more than once
fn main() {
let x = 5;
let y = fn~[move x; copy x]() -> int { x };
let y = fn~(move x, copy x) -> int { x };
}

View File

@ -1,5 +1,5 @@
// error-pattern:variable 'x' captured more than once
fn main() {
let x = 5;
let y = fn~[copy x, x]() -> int { x };
let y = fn~(copy x, copy x) -> int { x };
}

View File

@ -1,5 +1,5 @@
// error-pattern:variable 'x' captured more than once
fn main() {
let x = 5;
let y = fn~[move x, x]() -> int { x };
let y = fn~(move x, move x) -> int { x };
}

View File

@ -3,7 +3,7 @@
fn to_lambda2(b: fn(uint) -> uint) -> fn@(uint) -> uint {
// test case where copy clause specifies a value that is not used
// in fn@ body, but value is illegal to copy:
ret fn@[copy b](u: uint) -> uint { 22u };
ret fn@(u: uint, copy b) -> uint { 22u };
}
fn main() {

View File

@ -1,6 +1,6 @@
// error-pattern:unresolved name: z
fn main() {
let x = 5;
let y = fn~[copy z, x]() {
let y = fn~(copy z, copy x) {
};
}
}

View File

@ -1,6 +1,6 @@
// error-pattern:unresolved name: z
fn main() {
let x = 5;
let y = fn~[move z, x]() {
let y = fn~(move z, move x) {
};
}
}

View File

@ -2,6 +2,6 @@
fn main() {
let x = 5;
let _y = fn~[move x]() { };
let _y = fn~(move x) { };
let _z = x; //< error: Unsatisfied precondition constraint
}

View File

@ -0,0 +1,14 @@
fn foo(_f: fn()) {}
fn bar(_f: @int) {}
fn main() {
let x = @3;
foo {|| bar(x); }
let x = @3;
foo {|copy x| bar(x); } //! ERROR cannot capture values explicitly with a block closure
let x = @3;
foo {|move x| bar(x); } //! ERROR cannot capture values explicitly with a block closure
}

View File

@ -0,0 +1,35 @@
fn use(_i: int) {}
fn foo() {
// Here, i is *moved* into the closure: OK
let mut i = 0;
task::spawn {||
use(i);
}
}
fn bar() {
// Here, i would be implicitly *copied* but it
// is mutable: bad
let mut i = 0;
while i < 10 {
task::spawn {||
use(i); //! ERROR mutable variables cannot be implicitly captured
}
i += 1;
}
}
fn car() {
// Here, i is mutable, but *explicitly* copied:
let mut i = 0;
while i < 10 {
task::spawn {|copy i|
use(i);
}
i += 1;
}
}
fn main() {
}

View File

@ -0,0 +1,8 @@
fn foo(_x: @uint) {}
fn main() {
let x = @3u;
let _ = fn~() { foo(x); }; //! ERROR not a sendable value
let _ = fn~(copy x) { foo(x); }; //! ERROR not a sendable value
let _ = fn~(move x) { foo(x); }; //! ERROR not a sendable value
}

View File

@ -0,0 +1,17 @@
fn foo(_x: r) {}
resource r(_x: ()) {}
fn main() {
let x = r(());
let _ = fn~() {
// Error even though this is the last use:
foo(x); //! ERROR not a sendable value
};
let x = r(());
let _ = fn@() {
// OK in fn@ because this is the last use:
foo(x);
};
}

View File

@ -0,0 +1,4 @@
// xfail-test
fn foo(x) { //! ERROR expecting ':' but found ')'
}

View File

@ -0,0 +1,11 @@
// xfail-test
fn let_in<T>(x: T, f: fn(T)) {}
fn main() {
let_in(3u, fn&(i) { assert i == 3; });
//!^ ERROR expected `uint` but found `int`
let_in(3, fn&(i) { assert i == 3u; });
//!^ ERROR expected `int` but found `uint`
}

View File

@ -1,6 +0,0 @@
// error-pattern: not a sendable value
fn main() {
let x = @3u;
let _f = fn~(y: uint) -> uint { ret *x+y; };
}

View File

@ -10,7 +10,7 @@ fn main() {
let mut i = 10;
while (i > 0) {
log(debug, i);
task::spawn {|| child(i, ch); };
task::spawn {|copy i| child(i, ch); };
i = i - 1;
}

View File

@ -34,7 +34,7 @@ fn test00() {
while i < number_of_tasks {
let builder = task::builder();
results += [task::future_result(builder)];
task::run(builder) {||
task::run(builder) {|copy i|
test00_start(ch, i, number_of_messages)
}
i = i + 1;

View File

@ -43,7 +43,9 @@ fn test00() {
i = i + 1;
let builder = task::builder();
results += [task::future_result(builder)];
task::run(builder) {|| test00_start(ch, i, number_of_messages);}
task::run(builder) {|copy i|
test00_start(ch, i, number_of_messages);
}
}
let mut sum: int = 0;
for results.each {|r|
@ -128,7 +130,9 @@ fn test06() {
i = i + 1;
let builder = task::builder();
results += [task::future_result(builder)];
task::run(builder) {|| test06_start(i);};
task::run(builder) {|copy i|
test06_start(i);
};
}

View File

@ -5,7 +5,7 @@ import task;
fn main() {
let mut i = 10;
while i > 0 { task::spawn {|| child(i); }; i = i - 1; }
while i > 0 { task::spawn {|copy i| child(i); }; i = i - 1; }
#debug("main thread exiting");
}