Lowent 매뉴얼←↑→

11 답을 담는 타입 — option 과 result

먼저 알아야 할 것

7장 흐름 · guard 와 panic
10장 묶음 · 값을 지닌 enum 과 match

돌아보기

10장의 case rect w h 는 무엇을 하는가? 그리고 match 가 갈래 하나를 빠뜨리면 어떻게 되는가?

답. 갈래가 rect 이면 그 갈래가 지닌 두 값을 w 와 h 에 묶는다. 갈래를 빠뜨리면 E-MATCH-INEXHAUSTIVE 로 거절된다. 이 장의 option 과 result 는 언어가 미리 만들어 둔 두 갈래짜리 열거형처럼 행동한다 — 값이 있거나 없거나, 성공이거나 실패거나.

이 장의 필요성과 맥락

실패를 −1 이나 널 포인터 같은 특별한 값으로 알리는 관습은 “이 −1 은 오류인가 그냥 −1 인가” 를 소스만 보고 알 수 없게 만든다. 그리고 확인을 빠뜨려도 아무도 모른다. option 과 result 는 그 물음을 타입으로 옮긴다. 데이터를 다루는 제3부의 한가운데에 이 장이 있는 것은, 조회·변환·파싱처럼 데이터를 꺼내는 거의 모든 op 이 “없을 수 있음” 이나 “실패할 수 있음” 을 돌려주기 때문이다.

이 장이 끝나면

option 을 some·none 으로 만들고, 묻고 꺼내거나 value_or 로 대신할 값을 주거나 match 로 가르는 법을 익힌다. 확인 없이 꺼내면 멈춘다는 것을 알게 된다. result 와 errors 절이 짝을 이루고, try 가 실패를 위로 넘기며, else_none·else_error 꼬리가 둘 사이를 건넌다는 것도 보게 된다. 실패를 말하는 세 길 — result·option·panic — 을 가르는 기준을 세우고, or 패턴·겹친 패턴·번역 시점에 접히는 match 도 보게 된다.

이 장에서 답할 질문

  1. value_or 의 기본값 자리에 비싼 계산을 적으면 매번 계산되는가?

11.1 option — 값이 있거나 없거나#

option t 는 t 값이 있거나(some v. .) 없거나(none) 둘 중 하나다. 뒤에서 볼 result 와 함께, 값을 담는 상자 두 종류로 그려 두면 구별이 쉽다.

 option u64         ┌──────────┐              result u64 e     ┌──────────────┐
                    │ some 20  │  값이 있다                     │ ok 20        │  값이 있다
                    ├──────────┤                               ├──────────────┤
                    │ none     │  비어 있다                     │ error bad    │  실패했다 ---
                    └──────────┘  (찾는 것이 없었을 뿐)          └──────────────┘  왜(bad)를 함께 든다

none 은 이상한 일이 아니라 «없었다» 는 정상적인 답이고, error 는 «하려던 일이 실패했다» 는 답이다. 어느 상자든 열어 보기 전에는 안의 값을 쓸 수 없다.

examples/ch11/lookup.low

module lookup .
rem run: find 2
rem run: find 7
rem run: find_or 7
rem run: find_asked 2
rem run: find_match 7
rem trap: find_raw 7

fn find input k u8 . output option u8 .
do
  guard lt k. 3 . else return none . .
  return some mul k. 10 . . .
end .

fn find_or input k u8 . output u8 .
do
  return value_or find k. . 99 . .
end .

fn find_asked input k u8 . output u8 .
do
  let r option u8 find k. . .
  guard is_some r. . else return 255 . .
  return some_value r. . .
end .

fn find_match input k u8 . output u8 .
do
  match find k. . do
    case some v do return v. . end .
    case none do return 0 . end .
  end .
end .

fn find_raw input k u8 . output u8 .
do
  return some_value find k. . . .
end .

실행 결과

$ lowentc --run find lookup.low 2
find(2) = some 20
$ lowentc --run find lookup.low 7
find(7) = none
$ lowentc --run find_or lookup.low 7
find_or(7) = 99
$ lowentc --run find_asked lookup.low 2
find_asked(2) = 20
$ lowentc --run find_match lookup.low 7
find_match(7) = 0
$ lowentc --run find_raw lookup.low 7
== ir diagnostics (1) ==
0:0 E-VM-NONE: some_value of none (panic)

만드는 쪽인 find 는 return none . 과 return some mul k. 10 . . . 으로 값을 싼다. VM 은 결과를 some 20 . 과 none 으로 보여 준다. 받는 쪽은 세 가지로 쓸 수 있다.

find_raw 는 묻지 않고 바로 꺼낸다. 번역은 통과하지만 값이 없는 7 에서 실행이 멈춘다(E-VM-NONE). 꺼내는 연산은 부분 연산이다. 어느 길에서 값이 있는지는 저자가 아는 것이고 처리기가 언제나 알 수는 없으므로 번역이 막지 않는다. 대신 틀렸을 때 조용하지 않다 — 0 을 내주고 계속 가지 않는다.

문. value_or 의 기본값 자리에 비싼 계산을 적으면 매번 계산되는가?

답. 아니다. 기본값은 값이 없을 때만 계산된다. value_or some 7 . div 1 0 . . 은 7 이고 0 나누기는 일어나지 않는다. 그래서 기본값 자리에 실패할 수 있는 계산을 적어도 된다. 이 동작은 한때 반대였다가 명세에 맞게 고쳐졌다.

11.2 result 와 errors 절#

result t e 는 성공한 값(ok v. .) 또는 오류(error <갈래>) 중 하나다. 오류 타입 e 는 보통 enum 이다. 그리고 result 를 돌려주는 op 은 언제 어떤 오류를 내는지 errors 절에 적는다.

examples/ch11/halve.low

module halving .
rem run: halve 100
rem run: halve 250
rem run: halve 101
rem run: halve_plus_one 100
rem run: halve_plus_one 250
rem run: halve_or_zero 250

enum io_error do
  too_big .
  odd .
end .

fn halve input a u8 . output result u8 io_error .
  errors too_big gt a. 200 . .
  errors odd ne mod a. 2 . 0 . .
do
  guard le a. 200 . else return error too_big . . .
  guard eq mod a. 2 . 0 . else return error odd . . .
  return ok div a. 2 . . .
end .

fn halve_plus_one input a u8 . output result u8 io_error .
  errors too_big gt a. 200 . .
  errors odd ne mod a. 2 . 0 . .
do
  let v u8 try halve a. . . .
  return ok add v. 1 . . .
end .

fn halve_or_zero input a u8 . output u8 .
do
  let r result u8 io_error halve a. . .
  guard not is_error r. . . else return 0 . .
  return ok_value r. . .
end .

실행 결과

$ lowentc --run halve halve.low 100
halve(100) = ok 50
$ lowentc --run halve halve.low 250
halve(250) = err too_big
$ lowentc --run halve halve.low 101
halve(101) = err odd
$ lowentc --run halve_plus_one halve.low 100
halve_plus_one(100) = ok 51
$ lowentc --run halve_plus_one halve.low 250
halve_plus_one(250) = err too_big
$ lowentc --run halve_or_zero halve.low 250
halve_or_zero(250) = 0

halve 의 머리에는 오류가 둘 적혀 있다. errors too_big gt a 200 . 은 “a 가 200 보다 크면 too_big 을 낸다” 는 약속이다. 이 절은 나가는 쪽의 계약이다. 조건이 참인데 op 이 정상으로 돌아오거나, 적지 않은 오류를 돌려주면 계약 위반이다. 적지 않은 오류를 돌려주는 코드는 번역에서 거절된다.

examples/ch11/undeclared.low

module undeclared .
rem expect: E-ERR-UNDECLARED

enum parse_error do
  empty .
  too_long .
end .

fn first_byte input s slice u8 . output result u8 parse_error .
  errors empty eq len s. . 0 . .
do
  guard gt len s. . 0 . else return error empty . . .
  guard le len s. . 16 . else return error too_long . . .
  return ok idx s. 0 . . .
end .

실행 결과

$ lowentc --check undeclared.low
undeclared.low:13:0 E-ERR-UNDECLARED: op returns an error not in its errors clause

too_long 은 parse_error 의 갈래이지만 first_byte 의 errors 절에는 없다. 부르는 쪽은 머리만 보고 empty 만 다루면 된다고 믿을 것이다. 적은 것과 내는 것이 어긋나면 한쪽은 못 본 실패를, 다른 쪽은 오지 않을 실패를 다루게 된다.

11.3 try — 실패를 위로 넘긴다#

실패를 확인하는 코드를 매번 손으로 쓰면 길어지고, 길어지면 빼먹는다. try 는 result 를 받아 성공이면 값을 꺼내고, 실패면 그 오류를 그대로 돌려주며 op 을 떠난다. halve.low 의 halve_plus_one 이 그 모양이다.

let v u8 try halve a. . . .
return ok add v. 1 . . .

halve_plus_one 이 자기 머리에도 errors 를 적은 것에 주목한다. try 로 오류를 넘기려면 자기도 그 오류를 돌려줄 수 있어야 하고, 그 사실을 계약에 적어야 한다. 실패가 조용히 사라지는 길이 없다. 실패를 위로 넘기지 않고 이 자리에서 다루려면 halve_or_zero 처럼 is_error 로 묻고 ok_value 로 꺼낸다.

흔한 오해. try 는 예외 처리의 try 다

Java 나 C++ 의 try 는 블록 안 어디서든 던져진 예외를 잡는 자리다. Lowent 의 try 는 식 하나에 붙어서 그 식의 실패를 위로 넘기는 표시이고, Rust 의 ? 에 가깝다. 던지고 잡는 제어 흐름은 없다. 실패는 언제나 값으로 돌아오고, 어느 식에서 넘어갈 수 있는지가 소스에 적혀 있다.

11.4 바인딩 else — 꺼내거나 떠난다#

실패를 위로 넘기지 않고 이 자리에서 다룰 때, 확인하고 꺼내는 두 줄을 한 줄로 적을 수 있다. 바인딩의 타입을 알맹이의 타입으로 적고, 비었을 때 할 일을 else 로 붙인다.

examples/ch11/bindelse.low

module bindelse .
rem run: lookup 7
rem run: lookup 4
rem run: doubled 30
rem run: doubled 300

enum perr do
  too_big .
end .

fn find input xs slice u64 . input k u64 . output option u64 . do
  var i u64 0 .
  for x xs. do
    if eq x. k. . do
      return some i. . .
    end .
    set i. add i. 1 . .
  end .
  return none .
end .

fn twice input a u64 . output result u64 perr .
  errors too_big ge a. 100 . .
do
  guard lt a. 100 . else return error too_big . . .
  return ok mul a. 2 . . .
end .

rem option 에서 알맹이를 꺼내 묶는다 --- 비었으면 else 로 떠난다
export fn lookup input k u64 . output u64 .
do
  let xs slice u64 lit slice u64 5 6 7 . .
  let at u64 find xs. k. . else return 99 . .
  return add at. 100 . .
end .

rem result 도 같다 --- 오류면 else 로
export fn doubled input a u64 . output u64 .
do
  let v u64 twice a. . else return 0 . .
  return add v. 1 . .
end .

실행 결과

$ lowentc --run lookup bindelse.low 7
lookup(7) = 102
$ lowentc --run lookup bindelse.low 4
lookup(4) = 99
$ lowentc --run doubled bindelse.low 30
doubled(30) = 61
$ lowentc --run doubled bindelse.low 300
doubled(300) = 0

else 뒤는 세 가지로 적는다. 떠나는 문장 하나면 else return 99 . — 문장의 마침표가 맨 끝에 온다. 여러 문장이면 else do … end — 블록이 end 로 끝나므로 마침표를 찍지 않는다. result 가 왜 실패했는지 보려면 else error <이름> do … end 로 오류 값을 이름에 묶는다.

examples/ch11/elseerror.low

module elseerror .
rem run: port 8080
rem run: port 0
rem run: port 70000

enum port_error do
  zero .
  too_big .
end .

fn check input n u64 . output result u64 port_error .
  errors zero eq n. 0 . .
  errors too_big gt n. 65535 . .
do
  if eq n. 0 . do return error zero . . end .
  if gt n. 65535 . do return error too_big . . end .
  return ok n. . .
end .

rem 실패했을 때 *왜* 실패했는지 본다 --- `e` 는 이 블록 안에서만 산다
export fn port input n u64 . output u64 .
do
  let p u64 check n. . else error e do
    if eq e. zero. . do return 80 . end .
    return 65535 .
  end .
  return p. .
end .

실행 결과

$ lowentc --run port elseerror.low 8080
port(8080) = 8080
$ lowentc --run port elseerror.low 0
port(0) = 80
$ lowentc --run port elseerror.low 70000
port(70000) = 65535

11.5 두 채널 사이를 건넌다#

부르는 op 과 불리는 op 의 채널이 다를 때가 있다. try 뒤에 꼬리를 붙이면 담는 그릇을 바꾼다.

examples/ch11/tails.low

module tails .
rem run: maybe_half 100
rem run: maybe_half 250
rem run: must_find 2
rem run: must_find 9

enum lookup_error do
  too_big .
  not_found .
end .

fn halve input a u8 . output result u8 lookup_error .
  errors too_big gt a. 200 . .
do
  guard le a. 200 . else return error too_big . . .
  return ok div a. 2 . . .
end .

fn find input k u8 . output option u8 .
do
  guard lt k. 3 . else return none . .
  return some mul k. 10 . . .
end .

fn maybe_half input a u8 . output option u8 .
do
  return try halve a. . else_none . .
end .

fn must_find input k u8 . output result u8 lookup_error .
  errors not_found .
do
  return try find k. . else_error not_found . .
end .

실행 결과

$ lowentc --run maybe_half tails.low 100
maybe_half(100) = some 50
$ lowentc --run maybe_half tails.low 250
maybe_half(250) = none
$ lowentc --run must_find tails.low 2
must_find(2) = ok 20
$ lowentc --run must_find tails.low 9
must_find(9) = err not_found
모양방향무엇을 잃거나 얻나
try <식> else_noneresult → option오류를 버린다. 왜 실패했는지 더는 말하지 않는다
try <식> else_error <갈래>option → result없음에 이름을 붙인다

표 11.1 — try 의 꼬리로 채널을 바꾼다

꼬리를 붙인 try 는 타입도 바꾼다. try halve a. . else_none . 의 타입은 option u8 이지 u8 이 아니다. 그래서 maybe_half 는 그것을 그대로 돌려주고, let v u8 try … else_error … 처럼 값 타입에 담으려 하면 거절된다.

else_none 은 정보를 버리는 선택이다. 편해서 습관이 되기 쉬운데, 그 순간부터 호출자는 “왜” 를 물을 수 없다. 버릴 만한 자리에서만 버린다.

11.6 실패를 말하는 세 길#

무엇무엇을 말하나부르는 쪽이 하는 일
result t e고칠 수 있는 실패어느 쪽인지 묻고 다룬다. 안 다루면 위로 넘긴다
option t값이 없음있는지 묻고 꺼내거나, 대신 쓸 값을 준다
panic · 계약 위반약속이 깨졌다다룰 수 없다. 프로그램이 멈춘다

표 11.2 — 실패를 말하는 세 길

셋을 가르는 물음은 “부르는 쪽이 무엇을 할 수 있는가” 다. 파일이 없으면 다른 파일을 열어 볼 수 있으니 result 다. 찾는 것이 목록에 없으면 그냥 없는 것이니 option 이다. 부르는 쪽이 계약을 어겼다면 이미 약속이 깨진 것이라 고칠 수 있는 일이 아니고, 그래서 멈춘다. panic 은 되돌아 풀리지 않는다. 중간에 잡아 이어 가는 길은 없다. 이 기준으로 op 의 실패를 설계하는 법은 17장에서 다시 다룬다.

11.7 패턴을 묶고 겹친다#

option·result·enum 을 모두 보았으니 match 의 패턴을 더 넓게 쓸 수 있다.

examples/ch11/patterns.low

module patterns .
rem run: warm 1
rem run: combine 1
rem run: pick 1
rem run: choose
rem run: half 200

enum color do
  red .
  green .
  blue .
end .

enum node do
  num v u32 .
  plus l u32 r u32 .
  times l u32 r u32 .
end .

rem 어느 하나라도 맞으면 그 갈래 --- red·green·blue 를 모두 덮으므로 _ 가 필요 없다
fn warm input k u8 . output u8 . do
  let c color green. .
  match c. do
    case red or green do return 1 . end .
    case blue do return 2 . end .
  end .
end .

rem or 로 묶은 갈래는 같은 이름을 묶어야 한다
fn combine input k u8 . output u32 . do
  let e node node.times 6 7 . .
  match e. do
    case plus l r or times l r do return add l. r. . . end .
    case num v do return v. . end .
  end .
end .

rem result 안의 option 을 한 번에 가른다
fn pick input k u8 . output u32 . do
  let r result option u32 color ok some 42 . . .
  match r. do
    case ok (some x) do return x. . end .
    case ok none do return 0 . end .
    case error do return 99 . end .
  end .
end .

rem 가르는 값이 번역 시점 상수면 맞는 갈래 하나로 접힌다
fn choose output u32 . do
  match comptime add 2 3 . do
    case 0 do return 100 . end .
    case 5 do return 500 . end .
    case _ do return 999 . end .
  end .
end .

rem 두 범위가 u8 전체를 빈틈 없이 덮으므로 _ 가 필요 없다
fn half input b u8 . output u8 . do
  match b. do
    case 0 to 127 do return 0 . end .
    case 128 to 255 do return 1 . end .
  end .
end .

실행 결과

$ lowentc --run warm patterns.low 1
warm(1) = 1
$ lowentc --run combine patterns.low 1
combine(1) = 13
$ lowentc --run pick patterns.low 1
pick(1) = 42
$ lowentc --run choose patterns.low
choose() = 500
$ lowentc --run half patterns.low 200
half(200) = 1

_ 뒤에 갈래를 두면 거절된다.

examples/ch11/arm_after_wild.low

module arm_after_wild .
rem expect: E-MATCH-REDUNDANT

enum light do
  bright .
  dark .
end .

fn name input k u8 . output u8 . do
  let s light bright. .
  match s. do
    case _ do return 0 . end .
    case bright do return 1 . end .
  end .
end .

실행 결과

$ lowentc --check arm_after_wild.low
arm_after_wild.low:13:0 E-MATCH-REDUNDANT: this `case` comes AFTER a `_` (wildcard) arm — the wildcard already matched, so this arm can never run. A dead arm is an error here, not a warning (RFC-0020 §6.4)

_ 가 이미 모두 받았으므로 뒤의 갈래는 영영 돌지 않는다. 죽은 갈래는 경고가 아니라 오류다 — 읽을 때 각 경우가 한 번씩이 아닌 match 는 결함을 숨긴다.

11.8 흔한 실수#

반례. option 을 수처럼 계산에 넣는다

examples/ch11/mistake_optarith.low

module mistake_optarith .
rem expect: E-TYPE-RETURN

fn find input k u64 . output option u64 .
do
  guard lt k. 5 . else return none . .
  return some mul k. 10 . . .
end .

fn plus_one input k u64 . output u64 .
do
  rem ✘ `option u64` 는 수가 아니다 --- 있는지 먼저 묻고 꺼내야 더할 수 있다
  return add find k. . 1 . .
end .

실행 결과

$ lowentc --check mistake_optarith.low
mistake_optarith.low:13:0 E-TYPE-RETURN: the returned value does not match the op's `output` — expected `u64`, found `option …`

find k 가 돌려주는 것은 u64 가 아니라 “u64 가 있을 수도 없을 수도 있는 상자” 다. 상자에 1 을 더할 수는 없다. 다른 언어라면 없음(null)이 계산 속으로 흘러 들어가 한참 뒤에 터지지만, Lowent 는 여기서 E-TYPE-RETURN 으로 멈춰 세운다. 고치는 길은 셋이다. value_or find k. . 0 . 으로 대신할 값을 주거나, is_some 으로 묻고 some_value 로 꺼내거나, match 로 가른다. 어느 것을 고를지는 “없을 때 무엇을 해야 하는가” 가 정한다.

반례. option 을 돌려주는 op 에서 some 을 빠뜨린다

examples/ch11/mistake_nosome.low

module mistake_nosome .
rem expect: E-TYPE-RETURN

fn find input k u64 . output option u64 .
do
  guard lt k. 5 . else return none . .
  rem ✘ 값을 `some` 으로 싸지 않았다
  return mul k. 10 . .
end .

실행 결과

$ lowentc --check mistake_nosome.low
mistake_nosome.low:8:0 E-TYPE-RETURN: the returned value does not match the op's `output` — expected `option …`, found `u64`

머리에 output option u64 라고 적었으면 돌려주는 값도 상자여야 한다. none 은 상자이지만 mul k. 10 . 은 맨 수라서 E-TYPE-RETURN 이다. 몇몇 언어는 값을 알아서 감싸 주지만 Lowent 는 감싸지 않는다. return some mul k. 10 . . . 처럼 “있다” 를 적어야 읽는 사람이 두 갈래를 모두 본다.

반례. result 를 돌려주지 않는 op 에서 try 를 쓴다

examples/ch11/mistake_trynoresult.low

module mistake_trynoresult .
rem expect: E-TRY-NORESULT

enum half_error do
  too_big .
end .

fn halve input a u8 . output result u8 half_error .
errors too_big gt a. 200 . .
do
  guard le a. 200 . else return error too_big . . .
  return ok div a. 2 . . .
end .

rem ✘ `try` 를 썼는데 이 op 은 `result` 를 돌려주지 않고 `errors` 절도 없다
fn plus input a u8 . output u8 .
do
  let v u8 try halve a. . . .
  return add v. 1 . .
end .

실행 결과

$ lowentc --check mistake_trynoresult.low
mistake_trynoresult.low:18:0 E-TRY-NORESULT: `try` may only be used in an op that can RETURN that error (§6.5.8(2)): this op's output is not a `result`, so there is nowhere for the failure to go. It used to pass, and the VM then returned the ERROR VALUE in the plain output slot while the native build would not compile at all. Declare `output result <T> <E> .` (and the matching `errors` clause), or handle the failure here (`guard is_ok …` / `value_or` / a `case error` arm)

try 는 실패를 위로 넘긴다. 그러려면 이 op 도 실패를 돌려줄 수 있어야 한다(§6.5.8(2)). 그런데 plus 는 u8 만 돌려주고 errors 절도 없어서 E-TRY-NORESULT 로 거절된다. 2026-09-16 까지는 통과했고, plus 250 은 u8 자리에 err too_big 을 냈으며 네이티브는 C 컴파일 단계에서 지어지지도 않았다. 꼬리를 붙여 채널을 바꾸거나(else_none·else_error), is_ok 처럼 여기서 다루면 result 를 돌려주지 않아도 된다. 실패를 위로 넘길 생각이라면 머리를 result 와 errors 로 맞춘다.

examples/ch11/trynoresult_fixed.low

module trynoresult_fixed .
rem run: plus 10
rem run: plus 250

enum half_error do
  too_big .
end .

fn halve input a u8 . output result u8 half_error .
errors too_big gt a. 200 . .
do
  guard le a. 200 . else return error too_big . . .
  return ok div a. 2 . . .
end .

rem 실패를 위로 넘기는 op 은 자기도 result 를 돌려주고, 넘길 수 있는 오류를 errors 에 적는다
fn plus input a u8 . output result u8 half_error .
errors too_big gt a. 200 . .
do
  let v u8 try halve a. . . .
  return ok add v. 1 . . .
end .

실행 결과

$ lowentc --run plus trynoresult_fixed.low 10
plus(10) = ok 6
$ lowentc --run plus trynoresult_fixed.low 250
plus(250) = err too_big

겹친 패턴도 망라에 센다. 바깥 꼬리표는 안쪽이 스스로 빠짐없을 때 덮인 것으로 세어진다.

examples/ch11/nestedwild.low

module nestedwild .
rem run: via_match

enum read_error do
  broken .
end .

fn pick input r result (option u64) read_error . output u64 .
do
  rem 겹친 패턴 셋이 모든 경우다 --- `_` 를 따로 두지 않는다
  match r. do
    case ok (some x) do return x. . end .
    case ok none do return 0 . end .
    case error e do return 1 . end .
  end .
end .

fn via_match output u64 .
do
  return pick ok some 42 . . . .
end .

실행 결과

$ lowentc --run via_match nestedwild.low
via_match() = 42

ok some x. . . 와 ok none . 이 함께 ok 를 덮고 error e . 가 나머지를 덮으므로 _ 가 필요 없다. 하나라도 빠지면 E-MATCH-INEXHAUSTIVE 로 거절된다 — 쓸모없는 _ 를 두는 것보다 낫다. _ 는 나중에 갈래가 늘어도 아무 말을 하지 않는다.

흔한 오해. value_or 를 쓰면 없는 경우도 알 수 있다

examples/ch11/valueor_blind.low

module valueor_blind .
rem run: find_or_zero 0
rem run: find_or_zero 7
rem run: found 0
rem run: found 7

fn find input k u8 . output option u8 .
do
  guard lt k. 3 . else return none . .
  return some mul k. 10 . . .
end .

rem 0 번 칸에는 진짜 0 이 있고, 7 번 칸은 없다 --- 그런데 둘 다 0 으로 보인다
fn find_or_zero input k u8 . output u8 .
do
  return value_or find k. . 0 . .
end .

rem 있는지를 알고 싶으면 대신할 값으로 덮기 전에 묻는다
fn found input k u8 . output bool .
do
  return is_some find k. . . .
end .

실행 결과

$ lowentc --run find_or_zero valueor_blind.low 0
find_or_zero(0) = 0
$ lowentc --run find_or_zero valueor_blind.low 7
find_or_zero(7) = 0
$ lowentc --run found valueor_blind.low 0
found(0) = 1
$ lowentc --run found valueor_blind.low 7
found(7) = 0

value_or 는 없음을 대신할 값으로 덮는다. 덮는 값이 진짜 값과 겹치면 둘을 가를 수 없다. 위에서 0 번 칸의 진짜 0 과 7 번 칸의 없음이 똑같이 0 이다. “없으면 0 으로 쳐도 된다” 가 맞는 자리에서만 value_or 를 쓰고, 있는지가 중요하면 덮기 전에 is_some 이나 match 로 묻는다.

11.9 이 장의 문법을 한눈에#

모양뜻왜 이렇게
output option u8 .값이 있거나 없다없음(null)을 타입에 드러낸다
some v. . · none있다 · 없다“있다” 도 적어야 두 갈래가 모두 보인다
output result u8 e .값이거나 오류(e 의 갈래)실패도 값으로 돌려준다 — 예외가 없다
ok v. . · error too_big .성공 · 실패어느 갈래인지 소스에 적힌다
errors too_big <조건> .어떤 오류가 언제 나는지 약속계약에 적어 부르는 쪽이 대비한다
is_some r. . · some_value r. .있는지 묻기 · 꺼내기꺼내기는 부분 연산 — 묻고 꺼낸다
is_error r. . · ok_value r. .실패인지 묻기 · 성공 값 꺼내기같은 이유
value_or r. 99 .없으면 대신할 값한 줄로 끝나지만 없음을 덮는다
try <식>실패면 그 오류를 돌려주며 떠난다확인 코드를 빼먹지 않게 — Rust 의 ?
let n u64 find xs. k. . else return 0 . .알맹이를 꺼내 묶거나 else 로 떠난다확인 없이 쓰는 실수가 없다
try <식> else_none · else_error eresult → option · option → result채널을 바꿀 때 무엇을 잃는지 드러낸다
case ok (some x) · case a or b겹친 패턴 · 여러 갈래 묶기한 번에 가르되 모든 경우를 덮는다

표 11.3 — 답을 담는 타입의 문법 — 모양 · 뜻 · 왜 이렇게 생겼나

복습 정리

option 은 some·none, result 는 ok·error 로 만든다. 받는 쪽은 묻고 꺼내거나(is_some·some_value · is_error·ok_value), value_or 로 대신할 값을 주거나, match 로 가른다. 꺼내기는 부분 연산이라 없는 쪽을 꺼내면 멈춘다. result 를 돌려주는 op 은 errors 절로 오류를 약속하고, try 는 실패를 위로 넘기며, else_none·else_error 꼬리는 채널과 타입을 바꾼다. 패턴은 or 로 묶고(같은 이름을 묶는다) 겹쳐 쓸 수 있으며, 상수를 가르는 match 는 번역에서 접힌다.