lang: 배타 대여를 호출에 넘기는 것은 이동이 아니라 재대여다

&mut T 를 &mut T 파라미터에 넘기면 호출이 끝날 때 돌려받는다. 호출이 도는
동안 호출자는 그 값에 손댈 수 없으므로 별칭이 생기지 않는다. 이것이 없으면
배타 파라미터를 다시 넘기는 일이 함수당 한 번만 가능해서 &mut 가 사실상 쓸 수
없었다 -- 재귀 하강 파서를 쓰다가 걸렸다.

페이로드 없는 enum 은 이름 붙은 수라서 수로 읽을 수 있다. 반대 방향은 안 된다:
임의의 수는 변이가 아니다.

calc 프로그램: 재귀 하강 수식 계산기. 우선순위, 괄호, 오류 전파.

  1+2*3 = 7   (1+2)*3 = 9   2*(3+4)-5 = 9   10/3 = 3
  1+ = error  (1+2 = error
This commit is contained in:
2026-08-17 06:45:19 +09:00
parent 206799d1cb
commit 432d073104
3 changed files with 146 additions and 1 deletions
+1
View File
@@ -110,6 +110,7 @@ and or not orelse
- **배열은 포인터로 붕괴하지 않는다.** 함수에 넘기려면 `arr[..]`로 슬라이스를 만들거나 `&arr` / `^[N]T`를 쓴다.
- 슬라이싱: `arr[..]`, `arr[a..b]`(반개구간, 경계 검사), `arr[a..]`, `slice[a..b]`. `let` 배열·공유 슬라이스에서는 `[]T`, `var` 배열·배타 슬라이스에서는 `[]mut T`가 생긴다.
- `[]mut T``[]T`로, `&mut T``&T`**호출 인자 위치에서만** 암묵 재대여할 수 있다. 이것은 호출 동안의 read-only view이며 원래 배타 대여는 원래 마지막 사용까지 유지된다. 일반 `let`/대입에는 이 암묵 약화를 적용하지 않는다. 장기 shared borrow가 필요하면 root/place에서 명시적으로 새 `&` 또는 shared slice를 만들고 R6 검사를 받는다.
- **배타 대여를 호출에 넘기는 것은 이동이 아니라 그 호출 동안의 재대여다.** `&mut T``&mut T` 파라미터에, `[]mut T``[]mut T` 파라미터에 넘기면 호출이 끝날 때 돌려받는다. 호출이 도는 동안 호출자는 그 값에 손댈 수 없으므로 별칭이 생기지 않는다. 이것이 없으면 배타 파라미터를 다시 넘기는 일이 함수당 한 번만 가능해져서 `&mut`가 사실상 쓸 수 없게 된다.
- `^[]T`는 "슬라이스를 가리키는 포인터"가 아니라 길이를 함께 소유하는 독립 타입이다. R4의 일반 `^T` 대상 제한의 예외이며 `?^[]T`도 허용한다. `*[]T`/`*[]mut T`는 계속 금지한다. `mem.alloc_slice(T, n)`가 반환하고 drop 시 버퍼를 해제한다.
- `str`은 nominal 타입이 아니라 미리 선언된 `const str = []u8;` type alias다. UTF-8 검증을 보장하지 않으며 문자열 리터럴은 정적 읽기 전용 `[]u8`이다. 따라서 별도 변환 규칙이나 별도 C 표현은 없다.
+25 -1
View File
@@ -156,9 +156,27 @@ static int compatible(FeType *want, FeType *got, FeNode *value)
value->text[0] != '\'' && value->text[0] != '"';
}
/* Does passing `arg` to a parameter of type `param` lend it rather than give
it away? An exclusive borrow handed to a call comes back when the call
returns, so it is not a move. */
static int call_reborrows(const FeType *param, const FeType *arg)
{
if (!param || !arg) return 0;
if (param->kind==FE_TYPE_REF && arg->kind==FE_TYPE_REF &&
param->ref_mut && arg->ref_mut) return 1;
if (param->kind==FE_TYPE_SLICE && arg->kind==FE_TYPE_SLICE &&
param->ref_mut && arg->ref_mut) return 1;
return 0;
}
static int explicit_castable(FeType *a, FeType *b)
{
if (!a || !b) return 0;
/* An enum without a payload is a number with names on it, so reading it
as one is a widening or narrowing and nothing more. The other direction
is not allowed: an arbitrary number is not a variant. */
if (a->kind == FE_TYPE_ENUM && !a->fields &&
(fe_type_is_integer(b) || b->kind == FE_TYPE_CHAR)) return 1;
return (fe_type_is_integer(a) || a->kind == FE_TYPE_CHAR) &&
(fe_type_is_integer(b) || b->kind == FE_TYPE_CHAR);
}
@@ -2820,6 +2838,11 @@ static FeType *check_call_args(FeCheckerState *s, FeNode *n, FeSym *sym,
} else if (b && a && b->kind==FE_TYPE_SLICE && !b->ref_mut &&
a->kind==FE_TYPE_SLICE && a->ref_mut) {
/* Call-only []mut -> [] weakening is a temporary view. */
} else if (call_reborrows(b, a)) {
/* Handing an exclusive borrow to a call lends it for the length of
that call and takes it back after: the caller cannot touch it
meanwhile, so nothing is aliased. Without this an exclusive
parameter could be passed onwards exactly once. */
} else mark_moved(s,arg,a);
if (!compatible(b, a, arg) &&
!(b && a && b->kind==FE_TYPE_SLICE && a->kind==FE_TYPE_SLICE &&
@@ -2916,7 +2939,8 @@ static FeType *check_call(FeCheckerState *s, FeNode *n)
if (root && root->borrow_root) root=root->borrow_root;
if (root) fe_own_call_shared_view(c->diags,&root->own,arg->loc);
} else if (!(b && a && b->kind==FE_TYPE_SLICE &&
a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut))
a->kind==FE_TYPE_SLICE && !b->ref_mut && a->ref_mut) &&
!call_reborrows(b, a))
mark_moved(s,arg,arg->sem_type ? arg->sem_type : a);
if (!fe_type_equal(b,a) && !m7_actual_compatible(b,a,arg) &&
!(b && a && b->kind==FE_TYPE_SLICE && a->kind==FE_TYPE_SLICE &&
+120
View File
@@ -0,0 +1,120 @@
// EXIT:0
// OUTPUT:1+2*3 = 7
// OUTPUT:(1+2)*3 = 9
// OUTPUT:2*(3+4)-5 = 9
// OUTPUT:10/3 = 3
// OUTPUT:1+ = error
// OUTPUT:(1+2 = error
unit calc;
import std.io;
import std.fmt;
// A recursive-descent evaluator over a byte slice.
//
// The position travels in a `&mut usize` rather than in a struct beside the
// text: a struct cannot hold a slice, because a slice is a borrowed view and
// R4 keeps borrows out of aggregate storage. Passing both is the honest way
// to say "this text, and how far we have read".
fn done(src: []u8, at: usize) -> bool { return at >= src.n; }
fn peek(src: []u8, at: usize) -> u8 {
if done(src, at) { return 0; }
return src[at];
}
fn skip_spaces(src: []u8, at: &mut usize) -> void {
while not done(src, at.^) {
if src[at.^] != 32 { break; }
at.^ = at.^ + 1;
}
}
fn number(src: []u8, at: &mut usize) -> !i32 {
var value: i32 = 0;
var digits: usize = 0;
while not done(src, at.^) {
let c: u8 = src[at.^];
if c < 48 or c > 57 { break; }
value = value * 10 + ((c - 48) as i32);
digits = digits + 1;
at.^ = at.^ + 1;
}
if digits == 0 { return error.BadNumber; }
return value;
}
fn factor(src: []u8, at: &mut usize) -> !i32 {
skip_spaces(src, at);
if peek(src, at.^) == 40 {
at.^ = at.^ + 1;
let inner: i32 = try expr(src, at);
skip_spaces(src, at);
if peek(src, at.^) != 41 { return error.Unbalanced; }
at.^ = at.^ + 1;
return inner;
}
return number(src, at);
}
fn term(src: []u8, at: &mut usize) -> !i32 {
var left: i32 = try factor(src, at);
while true {
skip_spaces(src, at);
let op: u8 = peek(src, at.^);
if op != 42 and op != 47 { break; }
at.^ = at.^ + 1;
let right: i32 = try factor(src, at);
if op == 42 { left = left * right; }
else {
if right == 0 { return error.DivideByZero; }
left = left / right;
}
}
return left;
}
fn expr(src: []u8, at: &mut usize) -> !i32 {
var left: i32 = try term(src, at);
while true {
skip_spaces(src, at);
let op: u8 = peek(src, at.^);
if op != 43 and op != 45 { break; }
at.^ = at.^ + 1;
let right: i32 = try term(src, at);
if op == 43 { left = left + right; }
else { left = left - right; }
}
return left;
}
fn evaluate(text: []u8) -> !i32 {
var at: usize = 0;
let value: i32 = try expr(text, &mut at);
skip_spaces(text, &mut at);
if not done(text, at) { return error.Trailing; }
return value;
}
fn show(text: []u8) -> void {
var buf: [16]u8 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
io.print(text);
io.print(" = ");
let value: i32 = evaluate(text) catch |e| {
io.print("error\n");
return;
};
let n: usize = fmt.fmt_i32(buf[..], value);
io.print(buf[0..n]);
io.print("\n");
}
fn main() -> i32 {
show("1+2*3");
show("(1+2)*3");
show("2*(3+4)-5");
show("10/3");
show("1+");
show("(1+2");
return 0;
}