R11 이 데이터가 데이터를 가리킬 때 내놓는 답이다. 아레나가 값을 소유하고 핸들은 숫자라서 참조가 아니고, R4 가 반대할 것이 없다. mem.Arena 옆에 두되 다른 자료구조다 -- 저쪽은 크기가 제각각인 것을 오프셋으로 나눠주고, 이쪽은 슬롯마다 고정된 T 를 담고 슬롯을 되받는다. 핸들은 믿는 것이 아니라 검사받는다. 슬롯의 세대, 아레나의 epoch, 어느 아레나에서 왔는지를 8바이트에 담아서, 놓아준 뒤에 쓰거나 reset 뒤에 쓰거나 다른 아레나에 물어보면 옆값이 아니라 null 이 나온다. 여덟 바이트는 빌드마다 같다 -- --no-checks 는 비교를 건너뛸 뿐 배치를 바꾸지 않는다. 세대가 다 닳은 슬롯은 재사용 목록에 넣지 않고 버린다. 한 바퀴 돌면 옛 핸들이 다시 유효해지는데, 그것이 세대가 막으려던 바로 그 일이다. get 은 사본을 돌려준다. 대여가 문장을 넘기지 않는 이유가 그것이고, 그래서 한 슬롯을 읽으면서 다른 슬롯에 쓸 수 있다. get_mut 은 두지 않았다 -- 아레나 하나를 통째로 잠그는 참조를 오래 들고 있게 하는 API 다. 길에서 고친 것: 메서드 호출이 시그니처를 호출자 유닛에서 풀고 있었다. 그래서 Arena(T) 안의 Handle(T) 를 부르는 쪽 유닛에서 찾다가 실패했다. 필드 타입에 쓰던 enter_declaring_unit 을 method_type 에도 물렸다. 241/241, 36/36.
82 lines
2.7 KiB
Plaintext
82 lines
2.7 KiB
Plaintext
// EXIT:0
|
|
// OUTPUT:alloc 10 20 30 len 3
|
|
// OUTPUT:freed len 2 stale -1 live 30
|
|
// OUTPUT:reused index 1 old -1 new 99
|
|
// OUTPUT:swap 99 10
|
|
// OUTPUT:set 77 take 77 after 5
|
|
// OUTPUT:reset len 0 before -1
|
|
// OUTPUT:other -1
|
|
// OUTPUT:full yes
|
|
// OUTPUT:balanced
|
|
unit arenat;
|
|
|
|
import std.io;
|
|
import std.sys;
|
|
import std.arena;
|
|
|
|
// SPEC R11: the arena owns the values and a handle is a number. The point of
|
|
// the number carrying a generation is that using it after the slot was given
|
|
// back answers `null` rather than whatever moved in afterwards.
|
|
|
|
fn run() -> !void {
|
|
var a: arena.Arena(i32) = try arena.Arena(i32).with_capacity(1, 3);
|
|
let x: arena.Handle(i32) = try a.alloc(10);
|
|
let y: arena.Handle(i32) = try a.alloc(20);
|
|
let z: arena.Handle(i32) = try a.alloc(30);
|
|
@print("alloc {} {} {} len {}\n", a.get(x) orelse -1, a.get(y) orelse -1,
|
|
a.get(z) orelse -1, a.len());
|
|
|
|
// Give one back. Its handle stops meaning anything; the others do not.
|
|
let gone: bool = a.free(y);
|
|
@print("freed len {} stale {} live {}\n", a.len(), a.get(y) orelse -1,
|
|
a.get(z) orelse -1);
|
|
|
|
// The slot comes back on the free list, and the old handle still does not
|
|
// match the new occupant.
|
|
let again: arena.Handle(i32) = try a.alloc(99);
|
|
@print("reused index {} old {} new {}\n", again.index,
|
|
a.get(y) orelse -1, a.get(again) orelse -1);
|
|
|
|
let ok: bool = a.swap(x, again);
|
|
@print("swap {} {}\n", a.get(x) orelse -1, a.get(again) orelse -1);
|
|
|
|
let wrote: bool = a.set(x, 77);
|
|
let took: i32 = a.take(x, 5) orelse -1;
|
|
@print("set {} take {} after {}\n", 77, took, a.get(x) orelse -1);
|
|
|
|
// Reset moves the epoch, so every handle made before it is stale without
|
|
// a single slot being touched.
|
|
a.reset();
|
|
@print("reset len {} before {}\n", a.len(), a.get(x) orelse -1);
|
|
|
|
// A handle from one arena means nothing to another.
|
|
var b: arena.Arena(i32) = try arena.Arena(i32).with_capacity(2, 2);
|
|
let h: arena.Handle(i32) = try b.alloc(41);
|
|
@print("other {}\n", a.get(h) orelse -1);
|
|
|
|
// Running out of room is an error, not a trap.
|
|
var full: bool = false;
|
|
fill(&mut b) catch |e| { full = true; };
|
|
@print("full {}\n", yesno(full));
|
|
return;
|
|
}
|
|
|
|
/// Two more into an arena that has room for one.
|
|
fn fill(b: &mut arena.Arena(i32)) -> !void {
|
|
let p: arena.Handle(i32) = try b.alloc(1);
|
|
let q: arena.Handle(i32) = try b.alloc(2);
|
|
return;
|
|
}
|
|
|
|
fn yesno(b: bool) -> []u8 {
|
|
if b { return "yes"; }
|
|
return "no";
|
|
}
|
|
|
|
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;
|
|
}
|