대여는 변수가 아니라 place 단위다

p.a 와 p.b 는 서로 다른 자리인데 한쪽을 대여하면 다른 쪽까지 잠겼다. 메서드가
하는 일의 대부분이 한 필드에 쓰면서 다른 필드를 읽는 것이라, std.map 의 keep 은
그것 때문에 함수 둘로 갈라져 있었고 파서도 같은 자리에서 걸렸다.

FeOwnState 가 필드별 칸을 넷 갖는다. 값으로 복사되는 구조체라 흐름 병합과
스냅샷은 손댈 것이 없었다. 전체를 대여하면 모든 필드와 충돌하고, 필드를
대여하면 전체 및 같은 필드와 충돌한다. 칸이 모자라면 전체 대여로 되돌아가
더 많이 보고할 뿐 놓치지 않는다.

읽기는 식별자에서 일어나는데 그 자리에서는 자기가 무엇의 밑동인지 알 수 없다.
그래서 투영이 내려가는 길에 어느 필드인지 적어두고 식별자가 그것을 집는다.
인덱스는 갈라지지 않는다 -- xs[i] 의 i 는 상수가 아닐 수 있고, 필드 이름은
상수다.

길에서 나온 것: mem.replace 가 목적지 대여를 가져가고 돌려주지 않았다. 일반
호출 인자는 문장 끝에 돌려주는데 intrinsic 경로에만 그것이 없었다. 전에는
그 자리가 어차피 거부돼서 드러나지 않았다.

  var p = Pair{ a: 1, b: 2 };
  let r = &mut p.a;
  p.b = 3;      // ok -- 전에는 에러
  p.a = 3;      // 에러
  take(p);      // 에러

SPEC §5 R6 을 고쳤고, 옛 규칙을 그대로 적어둔 문단과 예제를 갈아치웠다.
own/badrfld 는 이제 허용되는 코드였으므로 같은 필드를 건드리도록 다시 겨눴고
okrfld·badrall·badrsame·exec/fieldbrw 를 더했다.

228/228, 32/32.
This commit is contained in:
2026-08-17 15:29:12 +09:00
parent 120a36eef5
commit f7e667652e
15 changed files with 510 additions and 40 deletions
+81
View File
@@ -0,0 +1,81 @@
// EXIT:0
// OUTPUT:a 40 b 3
// OUTPUT:used 5 room 8
// OUTPUT:first 9
// OUTPUT:balanced
unit fieldbrw;
import std.io;
import std.sys;
import std.mem;
// Borrowing is per field. `p.a` and `p.b` are different places, so lending one
// out has to leave the other readable -- otherwise a method cannot write
// through one field while reading another, which is most of what a method
// does.
struct Pair { a: i32, b: i32, }
fn scale(v: &mut i32, by: i32) -> void {
v.^ = v.^ * by;
return;
}
struct Box {
bytes: ^[]mut u8,
used: usize,
room: usize,
fn with_capacity(n: usize) -> !Self {
let room: ^[]mut u8 = try mem.alloc_slice(u8, n);
return Self{ bytes: room, used: 0, room: n };
}
/// Move to a bigger buffer, then go on reading the other fields. The
/// borrow that hands over the buffer covers `bytes` and nothing else.
fn grow(self: &mut Self, want: usize) -> !void {
let bigger: ^[]mut u8 = try mem.alloc_slice(u8, want);
var i: usize = 0;
while i < self.used {
bigger.^[i] = self.bytes.^[i];
i = i + 1;
}
let old: ^[]mut u8 = mem.replace(&mut self.bytes, bigger);
mem.destroy(old);
self.room = want;
return;
}
fn push(self: &mut Self, v: u8) -> !void {
if self.used == self.room { try self.grow(self.room * 2); }
self.bytes.^[self.used] = v;
self.used = self.used + 1;
return;
}
}
fn run() -> !void {
var p: Pair = Pair{ a: 4, b: 2 };
let left: &mut i32 = &mut p.a;
// Writing another field while `a` is lent out.
p.b = 3;
scale(left, 10);
@print("a {} b {}\n", p.a, p.b);
var box: Box = try Box.with_capacity(4);
try box.push(9);
try box.push(8);
try box.push(7);
try box.push(6);
try box.push(5);
@print("used {} room {}\n", box.used, box.room);
@print("first {}\n", box.bytes.^[0]);
return;
}
fn main() -> i32 {
run() catch |e| { @print("failed\n"); return 1; };
if sys.allocs() == sys.frees() { @print("balanced\n"); }
else { @print("leaked\n"); }
return 0;
}