authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-05-28 01:45:15+01:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-06-13 12:55:22+01:00
logbcb673d94ac09ec381e5b1ad1edf64b603ac68f1
tree7624448f2c157b81b003935583deeaae5ec264df
parent85e94fed1e058720460560823ac09d7b64e49b97
signaturelock-open Commit is signed but in an unrecognized format.

Sema: resolve union payload switch captures with peer type resolution

This is a bit harder than it seems at first glance. Actually resolving the type is the easy part: the interesting thing is actually getting the capture value. We split this into three cases: * If all payload types are the same (as is required in status quo), we can just do what we already do: get the first field value. * If all payloads are in-memory coercible to the resolved type, we still fetch the first field, but we also emit a `bitcast` to convert to the resolved type. * Otherwise, we need to handle each case separately. We emit a nested `switch_br` which, for each possible case, gets the corresponding union field, and coerces it to the resolved type. As an optimization, the inner switch's 'else' prong is used for any peer which is in-memory coercible to the target type, and the bitcast approach described above is used. Pointer captures have the additional constraint that all payload types must be in-memory coercible to the resolved type. Resolves: #2812

3 files changed, 347 insertions(+), 37 deletions(-)

src/Sema.zig+252-37
...@@ -2392,6 +2392,34 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {...@@ -2392,6 +2392,34 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError {
2392 return error.AnalysisFail;2392 return error.AnalysisFail;
2393}2393}
23942394
2395/// Given an ErrorMsg, modify its message and source location to the given values, turning the
2396/// original message into a note. Notes on the original message are preserved as further notes.
2397/// Reference trace is preserved.
2398fn reparentOwnedErrorMsg(
2399 sema: *Sema,
2400 block: *Block,
2401 src: LazySrcLoc,
2402 msg: *Module.ErrorMsg,
2403 comptime format: []const u8,
2404 args: anytype,
2405) !void {
2406 const mod = sema.mod;
2407 const src_decl = mod.declPtr(block.src_decl);
2408 const resolved_src = src.toSrcLoc(src_decl, mod);
2409 const msg_str = try std.fmt.allocPrint(mod.gpa, format, args);
2410
2411 const orig_notes = msg.notes.len;
2412 msg.notes = try sema.gpa.realloc(msg.notes, orig_notes + 1);
2413 std.mem.copyBackwards(Module.ErrorMsg, msg.notes[1..], msg.notes[0..orig_notes]);
2414 msg.notes[0] = .{
2415 .src_loc = msg.src_loc,
2416 .msg = msg.msg,
2417 };
2418
2419 msg.src_loc = resolved_src;
2420 msg.msg = msg_str;
2421}
2422
2395const align_ty = Type.u29;2423const align_ty = Type.u29;
23962424
2397fn analyzeAsAlign(2425fn analyzeAsAlign(
...@@ -10082,6 +10110,8 @@ const SwitchProngAnalysis = struct {...@@ -10082,6 +10110,8 @@ const SwitchProngAnalysis = struct {
10082 operand: Air.Inst.Ref,10110 operand: Air.Inst.Ref,
10083 /// May be `undefined` if no prong has a by-ref capture.10111 /// May be `undefined` if no prong has a by-ref capture.
10084 operand_ptr: Air.Inst.Ref,10112 operand_ptr: Air.Inst.Ref,
10113 /// The switch condition value. For unions, `operand` is the union and `cond` is its tag.
10114 cond: Air.Inst.Ref,
10085 /// If this switch is on an error set, this is the type to assign to the10115 /// If this switch is on an error set, this is the type to assign to the
10086 /// `else` prong. If `null`, the prong should be unreachable.10116 /// `else` prong. If `null`, the prong should be unreachable.
10087 else_error_ty: ?Type,10117 else_error_ty: ?Type,
...@@ -10315,61 +10345,245 @@ const SwitchProngAnalysis = struct {...@@ -10315,61 +10345,245 @@ const SwitchProngAnalysis = struct {
10315 const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, mod).?);10345 const first_field_index = @intCast(u32, operand_ty.unionTagFieldIndex(first_item_val, mod).?);
10316 const first_field = union_obj.fields.values()[first_field_index];10346 const first_field = union_obj.fields.values()[first_field_index];
1031710347
10318 for (case_vals[1..], 0..) |item, i| {10348 const field_tys = try sema.arena.alloc(Type, case_vals.len);
10349 for (case_vals, field_tys) |item, *field_ty| {
10319 const item_val = sema.resolveConstValue(block, .unneeded, item, "") catch unreachable;10350 const item_val = sema.resolveConstValue(block, .unneeded, item, "") catch unreachable;
10351 const field_idx = @intCast(u32, operand_ty.unionTagFieldIndex(item_val, sema.mod).?);
10352 field_ty.* = union_obj.fields.values()[field_idx].ty;
10353 }
1032010354
10321 const field_index = operand_ty.unionTagFieldIndex(item_val, mod).?;10355 // Fast path: if all the operands are the same type already, we don't need to hit
10322 const field = union_obj.fields.values()[field_index];10356 // PTR! This will also allow us to emit simpler code.
10323 if (!field.ty.eql(first_field.ty, mod)) {10357 const same_types = for (field_tys[1..]) |field_ty| {
10324 const msg = msg: {10358 if (!field_ty.eql(field_tys[0], sema.mod)) break false;
10325 const capture_src = raw_capture_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .none);10359 } else true;
1032610360
10327 const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{});10361 const capture_ty = if (same_types) field_tys[0] else capture_ty: {
10328 errdefer msg.destroy(sema.gpa);10362 // We need values to run PTR on, so make a bunch of undef constants.
10363 const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
10364 for (dummy_captures, field_tys) |*dummy, field_ty| {
10365 dummy.* = try sema.addConstUndef(field_ty);
10366 }
10367
10368 const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
10369 @memset(case_srcs, .unneeded);
1032910370
10371 break :capture_ty sema.resolvePeerTypes(block, .unneeded, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) {
10372 error.NeededSourceLocation => {
10330 // This must be a multi-prong so this must be a `multi_capture` src10373 // This must be a multi-prong so this must be a `multi_capture` src
10331 const multi_idx = raw_capture_src.multi_capture;10374 const multi_idx = raw_capture_src.multi_capture;
10375 const src_decl_ptr = sema.mod.declPtr(block.src_decl);
10376 for (case_srcs, 0..) |*case_src, i| {
10377 const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(u32, i) } };
10378 case_src.* = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none);
10379 }
10380 const capture_src = raw_capture_src.resolve(mod, src_decl_ptr, switch_node_offset, .none);
10381 _ = sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err1| switch (err1) {
10382 error.AnalysisFail => {
10383 const msg = sema.err orelse return error.AnalysisFail;
10384 try sema.reparentOwnedErrorMsg(block, capture_src, msg, "capture group with incompatible types", .{});
10385 return error.AnalysisFail;
10386 },
10387 else => |e| return e,
10388 };
10389 unreachable;
10390 },
10391 else => |e| return e,
10392 };
10393 };
1033210394
10333 const raw_first_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_idx, .item = 0 } };10395 // By-reference captures have some further restrictions which make them easier to emit
10334 const first_item_src = raw_first_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .first);
10335 const raw_item_src = Module.SwitchProngSrc{ .multi = .{ .prong = multi_idx, .item = 1 + @intCast(u32, i) } };
10336 const item_src = raw_item_src.resolve(mod, mod.declPtr(block.src_decl), switch_node_offset, .first);
10337 try sema.errNote(block, first_item_src, msg, "type '{}' here", .{first_field.ty.fmt(mod)});
10338 try sema.errNote(block, item_src, msg, "type '{}' here", .{field.ty.fmt(mod)});
10339 break :msg msg;
10340 };
10341 return sema.failWithOwnedErrorMsg(msg);
10342 }
10343 }
10344
10345 if (capture_byref) {10396 if (capture_byref) {
10346 const field_ty_ptr = try Type.ptr(sema.arena, mod, .{10397 const operand_ptr_info = operand_ptr_ty.ptrInfo(mod);
10347 .pointee_type = first_field.ty,10398 const capture_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{
10348 .@"addrspace" = .generic,10399 .pointee_type = capture_ty,
10349 .mutable = operand_ptr_ty.ptrIsMutable(mod),10400 .@"addrspace" = operand_ptr_info.@"addrspace",
10401 .mutable = operand_ptr_info.mutable,
10402 .@"volatile" = operand_ptr_info.@"volatile",
10403 // TODO: alignment!
10350 });10404 });
1035110405
10406 // By-ref captures of hetereogeneous types are only allowed if each field
10407 // pointer type is in-memory coercible to the capture pointer type.
10408 if (!same_types) {
10409 for (field_tys, 0..) |field_ty, i| {
10410 const field_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{
10411 .pointee_type = field_ty,
10412 .@"addrspace" = operand_ptr_info.@"addrspace",
10413 .mutable = operand_ptr_info.mutable,
10414 .@"volatile" = operand_ptr_info.@"volatile",
10415 // TODO: alignment!
10416 });
10417 if (.ok != try sema.coerceInMemoryAllowed(block, capture_ptr_ty, field_ptr_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) {
10418 const multi_idx = raw_capture_src.multi_capture;
10419 const src_decl_ptr = sema.mod.declPtr(block.src_decl);
10420 const capture_src = raw_capture_src.resolve(mod, src_decl_ptr, switch_node_offset, .none);
10421 const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(u32, i) } };
10422 const case_src = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none);
10423 const msg = msg: {
10424 const msg = try sema.errMsg(block, capture_src, "capture group with incompatible types", .{});
10425 errdefer msg.destroy(sema.gpa);
10426 try sema.errNote(block, case_src, msg, "pointer type child '{}' cannot cast into resolved pointer type child '{}'", .{
10427 field_ty.fmt(sema.mod),
10428 capture_ty.fmt(sema.mod),
10429 });
10430 try sema.errNote(block, capture_src, msg, "this coercion is only possible when capturing by value", .{});
10431 break :msg msg;
10432 };
10433 return sema.failWithOwnedErrorMsg(msg);
10434 }
10435 }
10436 }
10437
10352 if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| {10438 if (try sema.resolveDefinedValue(block, operand_src, spa.operand_ptr)) |op_ptr_val| {
10353 return sema.addConstant(field_ty_ptr, (try mod.intern(.{ .ptr = .{10439 if (op_ptr_val.isUndef(mod)) return sema.addConstUndef(capture_ptr_ty);
10354 .ty = field_ty_ptr.toIntern(),10440 return sema.addConstant(
10355 .addr = .{ .field = .{10441 capture_ptr_ty,
10356 .base = op_ptr_val.toIntern(),10442 (try mod.intern(.{ .ptr = .{
10357 .index = first_field_index,10443 .ty = capture_ptr_ty.toIntern(),
10358 } },10444 .addr = .{ .field = .{
10359 } })).toValue());10445 .base = op_ptr_val.toIntern(),
10446 .index = first_field_index,
10447 } },
10448 } })).toValue(),
10449 );
10360 }10450 }
10451
10361 try sema.requireRuntimeBlock(block, operand_src, null);10452 try sema.requireRuntimeBlock(block, operand_src, null);
10362 return block.addStructFieldPtr(spa.operand_ptr, first_field_index, field_ty_ptr);10453 return block.addStructFieldPtr(spa.operand_ptr, first_field_index, capture_ptr_ty);
10363 }10454 }
1036410455
10365 if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| {10456 if (try sema.resolveDefinedValue(block, operand_src, spa.operand)) |operand_val| {
10366 return sema.addConstant(10457 if (operand_val.isUndef(mod)) return sema.addConstUndef(capture_ty);
10367 first_field.ty,10458 const union_val = mod.intern_pool.indexToKey(operand_val.toIntern()).un;
10368 mod.intern_pool.indexToKey(operand_val.toIntern()).un.val.toValue(),10459 if (union_val.tag.toValue().isUndef(mod)) return sema.addConstUndef(capture_ty);
10369 );10460 const active_field_idx = @intCast(u32, operand_ty.unionTagFieldIndex(union_val.tag.toValue(), sema.mod).?);
10461 const field_ty = union_obj.fields.values()[active_field_idx].ty;
10462 const uncoerced = try sema.addConstant(field_ty, union_val.val.toValue());
10463 return sema.coerce(block, capture_ty, uncoerced, operand_src);
10370 }10464 }
10465
10371 try sema.requireRuntimeBlock(block, operand_src, null);10466 try sema.requireRuntimeBlock(block, operand_src, null);
10372 return block.addStructFieldVal(spa.operand, first_field_index, first_field.ty);10467
10468 if (same_types) {
10469 return block.addStructFieldVal(spa.operand, first_field_index, capture_ty);
10470 }
10471
10472 // We may have to emit a switch block which coerces the operand to the capture type.
10473 // If we can, try to avoid that using in-memory coercions.
10474 const first_non_imc = in_mem: {
10475 for (field_tys, 0..) |field_ty, i| {
10476 if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) {
10477 break :in_mem i;
10478 }
10479 }
10480 // All fields are in-memory coercible to the resolved type!
10481 // Just take the first field and bitcast the result.
10482 const uncoerced = try block.addStructFieldVal(spa.operand, first_field_index, first_field.ty);
10483 return block.addBitCast(capture_ty, uncoerced);
10484 };
10485
10486 // By-val capture with heterogeneous types which are not all in-memory coercible to
10487 // the resolved capture type. We finally have to fall back to the ugly method.
10488
10489 // However, let's first track which operands are in-memory coercible. There may well
10490 // be several, and we can squash all of these cases into the same switch prong using
10491 // a simple bitcast. We'll make this the 'else' prong.
10492
10493 var in_mem_coercible = try std.DynamicBitSet.initFull(sema.arena, field_tys.len);
10494 in_mem_coercible.unset(first_non_imc);
10495 {
10496 const next = first_non_imc + 1;
10497 for (field_tys[next..], next..) |field_ty, i| {
10498 if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, sema.mod.getTarget(), .unneeded, .unneeded)) {
10499 in_mem_coercible.unset(i);
10500 }
10501 }
10502 }
10503
10504 const capture_block_inst = try block.addInstAsIndex(.{
10505 .tag = .block,
10506 .data = .{
10507 .ty_pl = .{
10508 .ty = try sema.addType(capture_ty),
10509 .payload = undefined, // updated below
10510 },
10511 },
10512 });
10513
10514 const prong_count = field_tys.len - in_mem_coercible.count();
10515
10516 const estimated_extra = prong_count * 6; // 2 for Case, 1 item, probably 3 insts
10517 var cases_extra = try std.ArrayList(u32).initCapacity(sema.gpa, estimated_extra);
10518 defer cases_extra.deinit();
10519
10520 {
10521 // Non-bitcast cases
10522 var it = in_mem_coercible.iterator(.{ .kind = .unset });
10523 while (it.next()) |idx| {
10524 var coerce_block = block.makeSubBlock();
10525 defer coerce_block.instructions.deinit(sema.gpa);
10526
10527 const uncoerced = try coerce_block.addStructFieldVal(spa.operand, @intCast(u32, idx), field_tys[idx]);
10528 const coerced = sema.coerce(&coerce_block, capture_ty, uncoerced, .unneeded) catch |err| switch (err) {
10529 error.NeededSourceLocation => {
10530 const multi_idx = raw_capture_src.multi_capture;
10531 const src_decl_ptr = sema.mod.declPtr(block.src_decl);
10532 const raw_case_src: Module.SwitchProngSrc = .{ .multi = .{ .prong = multi_idx, .item = @intCast(u32, idx) } };
10533 const case_src = raw_case_src.resolve(mod, src_decl_ptr, switch_node_offset, .none);
10534 _ = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
10535 unreachable;
10536 },
10537 else => |e| return e,
10538 };
10539 _ = try coerce_block.addBr(capture_block_inst, coerced);
10540
10541 try cases_extra.ensureUnusedCapacity(3 + coerce_block.instructions.items.len);
10542 cases_extra.appendAssumeCapacity(1); // items_len
10543 cases_extra.appendAssumeCapacity(@intCast(u32, coerce_block.instructions.items.len)); // body_len
10544 cases_extra.appendAssumeCapacity(@enumToInt(case_vals[idx])); // item
10545 cases_extra.appendSliceAssumeCapacity(coerce_block.instructions.items); // body
10546 }
10547 }
10548 const else_body_len = len: {
10549 // 'else' prong uses a bitcast
10550 var coerce_block = block.makeSubBlock();
10551 defer coerce_block.instructions.deinit(sema.gpa);
10552
10553 const first_imc = in_mem_coercible.findFirstSet().?;
10554 const uncoerced = try coerce_block.addStructFieldVal(spa.operand, @intCast(u32, first_imc), field_tys[first_imc]);
10555 const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
10556 _ = try coerce_block.addBr(capture_block_inst, coerced);
10557
10558 try cases_extra.appendSlice(coerce_block.instructions.items);
10559 break :len coerce_block.instructions.items.len;
10560 };
10561
10562 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).Struct.fields.len +
10563 cases_extra.items.len +
10564 @typeInfo(Air.Block).Struct.fields.len +
10565 1);
10566
10567 const switch_br_inst = @intCast(u32, sema.air_instructions.len);
10568 try sema.air_instructions.append(sema.gpa, .{
10569 .tag = .switch_br,
10570 .data = .{ .pl_op = .{
10571 .operand = spa.cond,
10572 .payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
10573 .cases_len = @intCast(u32, prong_count),
10574 .else_body_len = @intCast(u32, else_body_len),
10575 }),
10576 } },
10577 });
10578 sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
10579
10580 // Set up block body
10581 sema.air_instructions.items(.data)[capture_block_inst].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
10582 .body_len = 1,
10583 });
10584 sema.air_extra.appendAssumeCapacity(switch_br_inst);
10585
10586 return Air.indexToRef(capture_block_inst);
10373 },10587 },
10374 .ErrorSet => {10588 .ErrorSet => {
10375 if (capture_byref) {10589 if (capture_byref) {
...@@ -11099,6 +11313,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r...@@ -11099,6 +11313,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_r
11099 .parent_block = block,11313 .parent_block = block,
11100 .operand = raw_operand.val,11314 .operand = raw_operand.val,
11101 .operand_ptr = raw_operand.ptr,11315 .operand_ptr = raw_operand.ptr,
11316 .cond = operand,
11102 .else_error_ty = else_error_ty,11317 .else_error_ty = else_error_ty,
11103 .switch_block_inst = inst,11318 .switch_block_inst = inst,
11104 .tag_capture_inst = tag_capture_inst,11319 .tag_capture_inst = tag_capture_inst,
test/behavior/switch.zig+68
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1const builtin = @import("builtin");1const builtin = @import("builtin");
2const std = @import("std");2const std = @import("std");
3const assert = std.debug.assert;
3const expect = std.testing.expect;4const expect = std.testing.expect;
4const expectError = std.testing.expectError;5const expectError = std.testing.expectError;
5const expectEqual = std.testing.expectEqual;6const expectEqual = std.testing.expectEqual;
...@@ -717,3 +718,70 @@ test "comptime inline switch" {...@@ -717,3 +718,70 @@ test "comptime inline switch" {
717718
718 try expectEqual(u32, value);719 try expectEqual(u32, value);
719}720}
721
722test "switch capture peer type resolution" {
723 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
724
725 const U = union(enum) {
726 a: u32,
727 b: u64,
728 fn innerVal(u: @This()) u64 {
729 switch (u) {
730 .a, .b => |x| return x,
731 }
732 }
733 };
734
735 try expectEqual(@as(u64, 100), U.innerVal(.{ .a = 100 }));
736 try expectEqual(@as(u64, 200), U.innerVal(.{ .b = 200 }));
737}
738
739test "switch capture peer type resolution for in-memory coercible payloads" {
740 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
741
742 const T1 = c_int;
743 const T2 = @Type(@typeInfo(T1));
744
745 comptime assert(T1 != T2);
746
747 const U = union(enum) {
748 a: T1,
749 b: T2,
750 fn innerVal(u: @This()) c_int {
751 switch (u) {
752 .a, .b => |x| return x,
753 }
754 }
755 };
756
757 try expectEqual(@as(c_int, 100), U.innerVal(.{ .a = 100 }));
758 try expectEqual(@as(c_int, 200), U.innerVal(.{ .b = 200 }));
759}
760
761test "switch pointer capture peer type resolution" {
762 if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
763
764 const T1 = c_int;
765 const T2 = @Type(@typeInfo(T1));
766
767 comptime assert(T1 != T2);
768
769 const U = union(enum) {
770 a: T1,
771 b: T2,
772 fn innerVal(u: *@This()) *c_int {
773 switch (u.*) {
774 .a, .b => |*ptr| return ptr,
775 }
776 }
777 };
778
779 var ua: U = .{ .a = 100 };
780 var ub: U = .{ .b = 200 };
781
782 ua.innerVal().* = 111;
783 ub.innerVal().* = 222;
784
785 try expectEqual(U{ .a = 111 }, ua);
786 try expectEqual(U{ .b = 222 }, ub);
787}
test/cases/compile_errors/switch_capture_incompatible_types.zig created+27
...@@ -0,0 +1,27 @@
1export fn f() void {
2 const U = union(enum) { a: u32, b: *u8 };
3 var u: U = undefined;
4 switch (u) {
5 .a, .b => |val| _ = val,
6 }
7}
8
9export fn g() void {
10 const U = union(enum) { a: u64, b: u32 };
11 var u: U = undefined;
12 switch (u) {
13 .a, .b => |*ptr| _ = ptr,
14 }
15}
16
17// error
18// backend=stage2
19// target=native
20//
21// :5:20: error: capture group with incompatible types
22// :5:20: note: incompatible types: 'u32' and '*u8'
23// :5:10: note: type 'u32' here
24// :5:14: note: type '*u8' here
25// :13:20: error: capture group with incompatible types
26// :13:14: note: pointer type child 'u32' cannot cast into resolved pointer type child 'u64'
27// :13:20: note: this coercion is only possible when capturing by value