# assert를 반드시 고쳐야 한다

> Clean Markdown view of GeekNews topic #30059. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=30059](https://news.hada.io/topic?id=30059)
- GeekNews Markdown: [https://news.hada.io/topic/30059.md](https://news.hada.io/topic/30059.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-06-01T09:22:07+09:00
- Updated: 2026-06-01T09:22:07+09:00
- Original source: [kristoff.it](https://kristoff.it/blog/fix-your-asserts/)
- Points: 1
- Comments: 1

## Topic Body

- **assert**는 사전 조건·사후 조건·불변식을 코드로 명시하는 장치이며, 타입 시스템으로 강제 가능한 제약은 언어 기능으로 표현하는 편이 바람직함
- Zig의 `std.debug.assert`는 매크로가 아닌 **일반 함수**로, `unreachable`을 통해 도달 불가능한 경로를 표시하고 최적화에도 활용됨
- Debug·ReleaseSafe에서는 실패한 assert가 panic으로 크래시하지만, ReleaseFast·ReleaseSmall에서는 **unchecked illegal behavior**로 잘못 동작할 수 있음
- 프로덕션에서 assert를 끄면 잘못된 가정을 빨리 발견할 기회를 잃고, 이후 코드가 **틀린 assert**에 의존해 취약점으로 이어질 수 있음
- ReleaseSafe와 ReleaseFast 중 무엇을 고를지는 프로그램 우선순위에 달렸지만, 핵심은 assert를 덮어 끄지 말고 **잘못된 assert를 고쳐야 한다**는 데 있음

---

### assert의 역할과 Zig의 기본 동작
- **assert**는 “이 인자는 null일 수 없음”, “이 정수는 짝수일 수 없음” 같은 조건이 항상 참이어야 한다는 사실을 코드로 표현하는 장치임
  - 예: `assert(my_arg != null);`, `assert(my_num % 2 != 0);`
  - 타입 시스템으로 제약을 강제할 수 있다면 assert보다 언어 기능을 쓰는 편이 나음
  - Zig에서는 일반 포인터 `*Foo`가 null이 될 수 없고, 선택 포인터 `?*Foo`는 null일 수 있지만 값에 접근하기 전 확인을 강제함
- assert는 **사전 조건**, **사후 조건**, **불변식**을 명시하는 데 적합함
  - 좋은 assert는 프로그래밍 실수를 잡는 데 단위 테스트보다 강력할 수 있음
  - 퍼징과 함께 쓰면 assert의 효과가 더 커질 수 있음

### Zig의 `unreachable`과 assert
- Zig의 assert는 잘못된 코드 경로를 표시하는 언어 기능인 **`unreachable`** 에 기반함
  - `switch`에서 도달할 수 없는 분기를 `.a => unreachable`처럼 표시할 수 있음
  - `unreachable`은 문(statement)으로도, 어떤 타입의 표현식이 필요한 위치에서도 사용할 수 있음
  - 도달 불가능한 경우에 임시 값을 억지로 만들 필요가 없음
- Zig 표준 라이브러리의 `std.debug.assert`는 다음처럼 구현됨
  ```zig
  pub fn assert(ok: bool) void {
    if (!ok) unreachable; // assertion failure
  }
  ```
- `unreachable` 정보는 **최적화**에 활용될 수 있음
  - 컴파일러는 도달 불가능한 경로를 제거할 수 있고, 이 정보가 전파되며 비지역적인 최적화가 가능해짐
  - 모든 assert가 성능 향상으로 이어지지는 않지만, 프로그래머가 쉽게 예상하지 못하는 최적화도 가능함

### 빌드 모드와 런타임 안전성
- Zig에는 **Debug**, **ReleaseSafe**, **ReleaseFast**, **ReleaseSmall** 빌드 모드가 있음
  - 이 설정은 프로그램 전체에 반드시 전역으로만 적용되지 않음
  - 각 의존성을 서로 다른 모드로 빌드할 수 있고, [`@setRuntimeSafety`](https://ziglang.org/documentation/0.16.0/#setRuntimeSafety)를 쓰면 함수 내부 블록 단위로도 런타임 안전성을 조정할 수 있음
- assert 실패는 Zig에서 “illegal behavior”가 됨
  - Checked 모드인 Debug, ReleaseSafe, `@setRuntimeSafety(true)`에서는 panic으로 프로그램이 크래시됨
  - Unchecked 모드인 ReleaseFast, ReleaseSmall, `@setRuntimeSafety(false)`에서는 “unchecked illegal behavior”가 발생해 프로그램이 잘못 동작함
- unchecked illegal behavior의 결과는 보장되지 않음
  - 예시의 `switch`에서는 현재 생성되는 머신 코드 특성상 다른 분기로 넘어가는 것처럼 보일 수 있음
  - 다른 컴파일러 버전에서는 전혀 다른 잘못된 동작이 나올 수 있음
  - 관련 동작은 [godbolt 예시](https://godbolt.org/z/jssEfx4Pv)에서 확인할 수 있음
- assert와 이후 `switch`가 ReleaseSafe와 ReleaseFast에서 어떻게 달라지는지는 [또 다른 godbolt 예시](https://zig.godbolt.org/z/7sMjofYsK)로 확인 가능함
  - ReleaseFast에서는 함수가 모든 비교를 건너뛰고 `true`를 반환하는 형태가 나타남
  - 이런 최적화는 비디오게임과 기타 실시간 미디어 애플리케이션이 크게 의존하는 종류의 동작임

### Zig assert는 매크로가 아님
- Zig의 `std.debug.assert`는 **매크로가 아니라 일반 함수**임
  - Zig에는 매크로가 없음
  - C/C++ 개발자가 Zig에 접근할 때 특히 놀라는 지점임
- C/C++에서는 assert를 비활성화하면 assert 호출 전체와 전달된 표현식이 주석 처리된 것처럼 동작하는 방식이 흔함
  - 그래서 C/C++에서는 assert에 부작용이 있는 표현식을 넣으면 안 됨
  - assert가 비활성화될 때 해당 연산 자체가 사라질 수 있기 때문임
- Zig에서는 함수 호출 규칙에 따라 인자가 함수 호출 전에 평가됨
  - `std.debug.assert` 내부 로직과 무관하게 인자 표현식은 평가됨
  - 따라서 다음처럼 부작용이 있는 표현식도 assert에 넣을 수 있음
  ```zig
  // assert that the remove operation is not a noop:
  assert(my_map.remove("expected-to-exist"));
  ```
- 반대로 assert 조건을 계산하기 위해 복잡한 연산이 필요하면 unchecked 모드에서도 그 계산이 반드시 제거되지 않을 수 있음
  - 이런 경우에는 `comptime if`로 코드를 보호해야 함
  ```zig
  const builtin = @import("builtin");
  
  if (builtin.mode == .Debug) {
    var condition = ...;
    // whatever bookkeeping is necessary
    // to compute the condition
    assert(condition == .ok);
  }
  ```
- C/C++ 의미론에 익숙하면 낯설 수 있지만, Zig에서는 assert를 일반적으로 비활성화하지 않는다는 전제가 깔려 있음

### 프로덕션에서 assert를 끄는 문제
- assert에는 크게 세 가지 선택지가 있음
  - 런타임 체크로 유지하고 실패 시 프로세스를 panic으로 크래시하게 함
  - assert를 성능 최적화에 사용하되, assert가 틀렸을 때 프로그램 오동작을 감수함
  - assert를 완전히 비활성화함
- `std.debug.assert`는 assert 완전 비활성화를 기본 지원하지 않음
  - 빌드 시점 플래그를 내부에서 확인하는 자체 assert를 구현하면 C/C++ 방식에 가까운 동작을 만들 수 있음
- assert를 끄고 싶어지는 이유는 보통 두 가지가 결합된 결과임
  - 성능 비용이나 애플리케이션 크래시가 싫어서 런타임 체크를 유지하고 싶지 않음
  - assert가 항상 올바르다고 믿기 어려워, 최적화에 쓰였을 때 발생할 수 있는 오동작을 두려워함
- [matklad가 관련 논의에서 상기시킨 것처럼](https://ziggit.dev/t/bun-is-being-ported-from-zig-to-rust/15330/129?u=kristoff), 크래시를 피해야 할 정당한 엔지니어링 이유가 있는 상황은 존재함
  - 하지만 일반적인 소프트웨어에서 크래시 회피를 기본값으로 삼는 것은 나쁜 선택으로 평가됨
- assert를 비활성화하면 불가능하다고 가정한 조건이 실제로 발생해도 프로그램이 계속 실행됨
  - 프로그램은 틀린 가정 아래 계속 실행되고, 이는 unchecked illegal behavior가 아니더라도 오동작의 한 형태임
- unchecked illegal behavior나 C의 undefined behavior가 위험한 이유는 프로그램을 [weird machine](https://en.wikipedia.org/wiki/Weird_machine)으로 바꾸는 경로가 될 수 있기 때문임
  - 충분히 복잡한 소프트웨어에서는 UIB가 없어도 프로그램이 의도하지 않은 방식으로 비틀릴 수 있음
  - 런타임에 assert가 거짓이 되는 것은 명세에서 벗어나는 일이며, 그 자체로 의도하지 않은 작업을 수행하게 만들 수 있음
  - SQL injection은 UIB 없이도 weird-machine급 오동작을 일으키는 구체적이고 널리 퍼진 예임
- 프로그램 오동작 비용이 너무 높다면 assert를 켜 두는 편이 맞음
  - 성능이 매우 중요해 오동작 위험을 감수할 수 있다면 assert를 최적화 기회로 쓰는 편이 맞음
  - assert를 비활성화하면 성능을 놓치면서도 실제보다 더 안전하다고 착각하기 쉬움

### 잘못된 assert가 코드베이스를 속이는 방식
- 핵심 위험은 **틀린 assert**가 테스트에서는 드러나지 않고 프로덕션에서만 실패할 수 있다는 데 있음
  - 모든 assert가 항상 참이라고 보장할 수 있다면 assert를 최적화에 쓰는 것은 논란이 되지 않음
  - 테스트가 모든 잘못된 assert를 잡는다고 보장할 수 있다면 프로덕션 최적화도 안전해짐
  - 실제로는 잘못된 assert를 작성할 수 있고, 테스트가 반드시 잡아주지도 않음
- assert를 프로덕션에서 끄면 잘못된 assert를 최대한 빨리 발견할 기회를 잃음
  - 더 심각한 문제는 이후 코드가 그 잘못된 assert에 의존해 계속 작성된다는 데 있음
- 예시 코드에서는 `processThing`이 이미 시작된 `thing`에서만 호출되어야 한다는 가정을 assert로 둠
  ```zig
  fn processThing(thing: Thing) void {
     // this function must always be invoked on
     // a thing that has already been started
     assert(thing.is_started);
  
     // ...
  }
  ```
- 이 assert가 테스트에서는 실패하지 않고, 프로덕션에서는 비활성화되어 실제로 거짓이 될 수 있다는 사실을 놓칠 수 있음
  - 사용자에게 관찰되는 오동작이 없으면 문제가 없는 것처럼 보이고 개발이 계속됨
- 이후 누군가 `thing`이 이미 시작되었으므로 추가 준비 없이 `baz`를 호출해도 된다고 보고 코드를 추가할 수 있음
  ```zig
  fn processThing(thing: Thing) void {
     // this function must always be invoked on
     // a thing that has already been started
     assert(thing.is_started);
  
     // ...
  
     // Since thing is already started, we don't
     // need to foo the bar before bazzing the qux.
     // It would be really bad to baz the qux otherwise,
     // so we add an assert for good measure.
     assert(thing.is_fooed);
     thing.baz(qux);
  }
  ```
- 두 번째 assert 자체가 논리적으로 맞더라도, 첫 번째 assert가 실제로는 거짓이 될 수 있다면 위험이 생김
  - 테스트에서는 첫 번째 assert가 실패하지 않으므로 두 번째 assert도 실패하지 않음
  - 프로덕션에서는 assert가 비활성화되어 취약점이 코드베이스에 들어오는 순간을 알아차리지 못할 수 있음
- 코드 안의 assert가 개발자를 속이는 상태라면 올바른 코드를 작성하는 일이 불합리하게 어려워짐

### 선택지는 프로그램의 우선순위에 따라 달라짐
- 프로그램마다 우선순위가 다르며, 어떤 프로그램은 오동작 위험 최소화보다 성능을 우선하는 것이 정당할 수 있음
  - 이 경우 assert를 최적화 기회로 바꾸는 선택은 자연스러움
- 프로덕션에서 assert를 관성적으로 비활성화하는 것은 assert를 켜 두는 것보다도, 성능 최적화를 적극 활용하는 것보다도 열등한 선택으로 평가됨
  - ReleaseFast에 대해 [매우](https://lobste.rs/s/lapqbz/bun_s_rust_rewrite_has_been_merged#c_j8afau) [비판적](https://lobste.rs/s/lapqbz/bun_s_rust_rewrite_has_been_merged#c_dusv0t)이면서 assert 비활성화는 무비판적으로 받아들이는 태도는 모순적임
- [Zine](https://zine-ssg.io)은 정적 사이트 생성기이며, 현재 주로 개인 블로그 빌드에 사용됨
  - 위협 모델이 정의되어 있지 않고, 그것이 최우선순위도 아님
  - Hugo보다 한 자릿수(order of magnitude) 빠르게 실행되는 점을 선호해 ReleaseFast 빌드를 배포함
- [Awebo](https://codeberg.org/awebo-chat/awebo/)는 pre-alpha 단계의 셀프호스팅 가능한 Discord 대안임
  - 개인 정보를 다루고 인터넷에 노출될 소프트웨어라는 점이 이미 명확함
  - 배포 시점에는 ReleaseSafe 빌드를 제공할 계획임
  - 다만 FFmpeg, Xiph Opus, SQLite 같은 핵심 의존성 일부는 ReleaseFast로 빌드할 예정임
  - 해당 의존성에서는 성능 향상이 프로그램 오동작 위험을 더 줄이는 것보다 명확히 더 중요하다고 판단됨

### 실제 프로젝트들의 선택과 보안 사례
- [TigerBeetle](https://tigerbeetle.com)은 금융 데이터베이스이며 assert를 항상 켜 둠
- [Ghostty](https://ghostty.org)는 터미널 에뮬레이터이며 macOS용 ReleaseFast 빌드를 배포함
  - downstream 소비자, 예를 들어 Linux 배포판 관리자에게도 [같은 방식을 권장함](https://github.com/ghostty-org/ghostty/blob/2c62d182cec246764ff725096a70b9ef44996f7f/PACKAGING.md?plain=1#L111-L115)
- Ghostty에 공개된 비교적 심각한 CVE 두 건은 모두 **메모리 손상 없이** 임의 명령 실행이 가능했던 사례임
  - <https://github.com/ghostty-org/ghostty/security/advisories/GHSA-4jxv-xgrp-5m3r>
  - <https://github.com/ghostty-org/ghostty/security/advisories/GHSA-5hcq-3j4q-4v6p>
- 메모리 손상이나 UIB만이 위험의 전부는 아님

### Zig에서 완전히 사라지지 않는 암묵적 assert
- 자체 assert는 비활성화할 수 있더라도, Zig 언어 자체가 코드에 암묵적으로 추가하는 assert는 비활성화할 수 없음
  - 정수 오버플로, 0으로 나누기, 배열 범위 초과 등이 여기에 해당함
  - 이런 조건은 런타임 panic을 일으키거나 최적화 목적으로 사용됨
- 프로덕션 assert 비활성화 관행은 잘못된 assert가 코드베이스 안에서 썩고 늘어나게 만들 수 있음
  - 그 결과 UIB에 대한 편집증이 커지고, 개발자들이 assert를 다시 켜서 결과를 마주하기를 무의식적으로 두려워하게 될 수 있음
- 피할 수 없는 결론은 assert를 비활성화해 덮는 것이 아니라 **잘못된 assert를 고쳐야 한다**는 것임
  - 프로그램 정확성은 일부 하위 집합이 아니라 전체를 대상으로 추구해야 함

## Comments



### Comment 58691

- Author: neo
- Created: 2026-06-01T09:22:08+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/ycplhh/you_must_fix_your_asserts) 
- `assert`에서 그냥 크래시를 내는 것, 또는 Rust의 패닉처럼 작업만 크래시시키는 동작이 대체로 최선이라는 데는 동의함. 하지만 `assert`를 최적화 힌트로 쓰는 것이 그냥 빼버리는 것보다 항상 낫다는 데는 동의하기 어려움  
  첫째, 임의의 `assert`는 최적화에 별 도움이 안 되는 경우가 많고, 최적화기가 바로 활용할 수 없는 조건도 많음. “이 분기는 절대 타지 않는다”처럼 직접적인 가정을 넣는 게 아니라면 코드 곳곳에 무작위 가정을 뿌려 얻는 성능 이득은 크지 않을 가능성이 높음  
  둘째, `assert`를 가정으로 바꾸면 실수의 **피해 반경**이 크게 넓어짐. 예를 들어 프로젝트나 사용자별로 분리된 데이터를 처리하는 시스템에서, 원래 불가능해야 하는 상태를 잡는 `assert`가 계산 함수 중간에 있다고 해보면, 릴리스 빌드에서 비용이 커서 끄는 경우 단순 비활성화라면 피해가 한 프로젝트나 사용자에 국한되고 이후 검사에서 잡힐 수도 있음. 반면 이를 **정의되지 않은 동작**으로 만들면 계산이 엉뚱한 코드로 점프해 메모리를 임의로 망가뜨리고 모든 프로젝트의 데이터를 손상시킬 수 있음  
  결국 릴리스 빌드의 기본값으로 안전하지 않은 `assert`를 택하면, 문제가 생겼을 때 피해를 국소화할 가능성을 줄이는 대가로 코드의 임의 지점을 성급하게 최적화하는 셈임. Rust는 `assert!()`는 항상 패닉, `debug_assert!()`는 디버그 모드에서만 패닉, `assert_unchecked()`는 디버그에서 패닉하고 릴리스에서 최적화 힌트가 되는 식이라 잘 설계됐다고 봄
  - 실수의 피해 반경이 걱정된다면 `ReleaseFast`가 아니라 **`ReleaseSafe`** 를 써야 함
  - 개별 `assert`를 끄는 것에 반대하는 게 아니라, 일반적인 권장 관행처럼 **일괄적으로 꺼버리는 것**에 반대함  
    성능 영향이 너무 커서 릴리스 빌드에 유지할 수 없다는 판단은 완전히 합리적임. 게다가 계산 비용이 큰 `assert`는 앞서 말한 것처럼 성능 개선으로 이어질 가능성도 거의 없음  
    Zine에도 그런 예가 몇 개 있음:  
    https://github.com/kristoff-it/zine/blob/a16c9f1d3f3166337da47fda2de0f4addc719b92/src/worker.zig#L249-L252  
    https://github.com/kristoff-it/zine/blob/a16c9f1d3f3166337da47fda2de0f4addc719b92/src/PathTable.zig#L51-L55  
    Zig에는 “기본 릴리스 모드”가 없음. `assert`를 어떻게 다룰지 항상 직접 골라야 하고, 전체 적용 옵션은 크래시 또는 최적화이며 어느 쪽도 더 기본값이라고 할 수 없음

- Ghostty에서 지금까지 공개된 비교적 심각한 CVE 두 건이 모두 메모리 손상 없이 임의 명령 실행으로 이어졌다는 사실은 매우 이상하게 느껴짐. **ReleaseFast**로 배포됐는데도 그렇다는 점이 세상이 돌아가는 방식에 대한 내 이해와 정면으로 어긋남
  - 그다지 이상하진 않다고 봄. 심각한 취약점의 70%가 메모리 관련이라는 보고를 믿더라도, 그건 C와 C++ 기준이고 Zig는 메모리 안전성에서 조금 나을 수 있음. 게다가 **표본 수 2개**라면 대략 프로젝트 10개 중 하나에서는 이런 결과가 나와도 이상하지 않음  
    터미널 에뮬레이터를 다뤄본 입장에서 이 취약점들은 딱 예상 가능한 종류의 골치 아픈 문제임. 개발자나 연구자를 폄하하려는 건 아니고, 이런 엉뚱한 위치의 **명령 주입**은 이 분야에서 거의 따라오는 문제이며, 다른 영역에서 다른 주입 취약점이 따라오는 것과 비슷함

- 프로덕션에서 “성능 때문에” `assert`와 경계 검사를 끄자는 주장을 거의 **40년째** 듣고 있다는 게 재미있음. 그동안 컴퓨터는 몇 자릿수나 빨라졌고 소프트웨어는 모두의 삶에 훨씬 더 깊이 들어왔으니, 런타임의 정확성은 그 어느 때보다 중요해졌음  
  더 생산적인 얘기를 하자면, 예전 Microsoft에는 일반적인 `assert`, `check` 같은 것 외에 다른 곳에서는 잘 못 본 **보고용 assert**가 있었음. 내가 완전히 통제하지 못하는 조건이 있고, 참이라고 가정하지만 거짓인 경우도 방어적으로 처리하며, 실제 현장에서 거짓이 되는지 로그나 원격 측정으로 알고 싶을 때 사용함. 예를 들어 사용자가 어떤 목록에 1000개 넘게 넣지 않을 거라 가정해 이차 알고리즘을 쓰거나, 네트워크 지연이 200ms 미만일 거라 보고 왕복이 많은 프로토콜을 쓰는 경우임
  - 그건 `check`와 뭐가 다른가?

- 여기 연결된 사람 중 하나로서, 이건 `assert`에 대한 내 생각을 우스꽝스러운 **거짓 양자택일**과 캐리커처로 만든 것임. [다른 댓글](https://lobste.rs/c/6t6q9e)에서도 썼듯이, 정의되지 않은 동작으로 전환할지는 `assert`별로 결정하는 편을 선호함. ReleaseFast에 대한 내 비판은 그 선택을 특정 범위의 모든 `assert`뿐 아니라 모든 안전성 검사와 묶어버린다는 데 있음  
  고치지 않은 `assert`가 크래시를 낸다는 이유만으로 꺼버리는 건 어리석다는 kristoff의 말에는 동의함. 다만 “크래시 아니면 정의되지 않은 동작”만이 합리적인 대안이라는 데는 동의하지 않음. 형제 댓글의 goldstein 쪽이 내 생각에 더 가까움

- `assert_unchecked()` 동작을 전역 기본값으로 만드는 건 변호하기 어렵지만, 성능 최적화 기법으로는 합리적일 수 있음. 모든 `assert`를 가정으로 바꿨을 때 프로덕션 빌드가 크게 빨라진다면, 성능 개선 대부분을 만들어내는 **소수의 가정**, 바라건대 하나의 가정이 있을 수 있고, 이분 탐색 같은 방법으로 찾아낼 수 있을 것임
  - 기본값은 없고, 사용자가 명시적으로 **ReleaseSafe**와 `ReleaseFast`/`ReleaseSmall` 중 하나를 선택하는 구조임

- 프로그램 분석 문헌에는 코드 안의 주장 또는 `assert`를 두 형태로 나누는 **쌍대성**이 있음. 하나는 코드 주변 문맥, 함수라면 호출자가 만족해야 하는 조건이고, 다른 하나는 코드 자체, 함수라면 함수가 만족해야 하는 조건임  
  이 구분은 계약과 점진적 타입 문헌에서 표준적인 학술 개념인 “책임(blame)”으로 보면 명확함. 문맥에 대한 주장이 실패하면 우리 잘못이 아니라 문맥이나 호출자에게 책임이 있지만, 호출자가 맞고 주장 자체가 버그일 수도 있음. 코드 자체에 대한 주장이 실패하면 우리 책임이지만, 코드가 맞고 주장 자체가 버그일 수도 있음  
  함수 수준에서는 전제 조건이 문맥에 대한 주장이고, 사후 조건이 코드에 대한 주장임. 다만 둘 다 코드 중간에도 넣을 수 있음. 어떤 검증 프레임워크는 `assert`를 코드에 대한 주장에, `assume`을 문맥에 대한 주장에 사용함. 일부 테스트 프레임워크, 특히 무작위 테스트 프레임워크가 이를 해석하는 방식과도 연결됨. `assert`가 실패하면 테스트 실패로 표시하고, `assume`이 실패하면 테스트를 건너뜀
  - BIND9는 매크로 **REQUIRE()** 로 호출자가 만족해야 하는 전제 조건을, **ENSURE()** 로 함수가 보장하는 사후 조건을 검사하는 [계약에 의한 설계](https://en.wikipedia.org/wiki/Design_by_contract)에 가까운 스타일을 따름. 중간 검사용으로 INSIST(), 루프나 자료구조용으로 INVARIANT()도 있음. 함수 문서에는 전제 조건과 사후 조건에 대응하는 “requires”와 “ensures” 메모가 있어야 함

- 이건 Bun을 암시하는 것 같아서, 연결을 좀 더 공식적으로 해두고 싶음. Bun 제작자인 Jarred Sumner가 2024년에 `unreachable`이 **ReleaseFast**에서 패닉해야 한다고 제안한 Zig 이슈가 있음. 그 스레드의 Andrew Kelley와 Matthew Lugg 댓글이 이 논의와 관련 있음  
  => https://github.com/ziglang/zig/issues/19664  
  Bun은 자체 `assert` 함수들을 쓰며, 릴리스 모드에서 패닉하거나 제거되지만 정의되지 않은 동작을 도입하지는 않음. 다만 Loris의 각주도 기억해야 함. “언어로서 Zig는 비활성화할 수 없는 많은 `assert`를 코드에 암묵적으로 추가한다”는 점임  
  Bun 얘기를 너무 길게 하고 싶진 않음. 작은 팀의 단일 프로젝트니까. 핵심은 걱정이 조금이라도 있으면 **ReleaseSafe**를 쓰라는 것임. ReleaseSafe는 느리다는 평판이 있지만, 내 소규모 Zig 프로젝트들에서는 ReleaseSafe와 ReleaseFast 사이의 벤치마크 차이를 재지 못했음. 그래도 여전히 많은 다른 언어보다 빠를 가능성이 큼
  - 걱정이 조금이라도 있으면 ReleaseSafe를 쓰라는 건 맞음. 더 흥미로운 전략도 가능함. 코드를 수정하는 동안, 즉 버그를 넣을 가능성이 있을 때는 **ReleaseSafe**로 두고, 코드가 안정화되고 실전 검증을 거친 뒤 성능 향상이 가치 있으면 **ReleaseFast**로 전환할 수 있음  
    또는 맥락상 말이 된다면 ReleaseFast 실행 파일을 배포하다가, 정의되지 않은 동작 때문에 비결정적 버그 보고가 들어오기 시작하면 ReleaseSafe로 되돌릴 수도 있음. 그러면 어떤 `assert`가 실패했는지, 범위 밖 접근이나 오버플로 같은 것도 포함해 실행 가능한 버그 보고를 모으고 코드를 고칠 수 있음. 애초에 ReleaseFast로 배포하면 안 되는 맥락에서 그렇게 결정했더라도 이 접근을 추천할 정도임 :^)  
    의존성을 조정하고 `@setRuntimeSafety`를 써서 프로젝트 일부에만 같은 방식을 적용할 수도 있음. 결국 똑똑하게 하려는 의지만 있다면 필요한 도구는 다 갖춰져 있음

- `assert` 호출 안에 **부작용 있는 표현식**을 넣어도 된다는 식으로 쓰면 안 됨. 나쁜 관행임. 에러 검사에 `assert`를 쓰는 것도 피해야 함. 공정하게 말하면 글쓴이가 그렇게 주장하는 것 같지는 않음  
  반대로 `assert`가 복잡한 계산에 의존하면 unchecked 모드에서도 그 계산이 꼭 제거되지는 않으니, `comptime if`로 보호해야 한다는 설명도 나옴  
  “매크로가 남긴 트라우마를 버리고 단순함을 받아들일 좋은 기회”라는 말의 아이러니를 글쓴이가 놓치지 않았길 바람. “프로그램의 빌드 모드를 고려하고 방어적인 `comptime if`를 곳곳에 뿌려야 하는 단순함”을 받아들이라는 셈임
  - 왜 나쁜 관행인가?

- C#으로 수치 계산 코드를 조금 작성하고, 릴리스에서는 꺼지는 `assert`를 많이 씀. 빽빽한 루프마다 실행하기엔 너무 비싸지만, 단위 테스트에서는 루틴이 처음 **NaN 입력**을 보는 순간 바로 터지는 게 유용함  
  이런 NaN은 사용자 입력에서 오기보다, 최적화기가 가면 안 되는 곳으로 가는 등의 코드 버그 때문에 생기는 경우가 많고 더 나은 경계 제약 같은 게 필요함. 물론 사용자 입력에 정제가 필요할 수도 있지만, 그건 알고리즘 깊은 곳이 아니라 가장 바깥 경계에서 해야 함. 사용자 입력 정제의 결과로 알고리즘 내부 불변식을 `assert` 없이 증명할 수 있는 증명 시스템이 있으면 좋겠지만, 이건 사이드 프로젝트이고 터져도 아무도 죽지 않음

- `assert`에 대한 의견 불일치의 90%는 그 단어의 정의가 부실하고 여러 가지라서 생기며, 그 때문에 사고와 소통이 흐려짐. 그래서 개념을 다음 **세 가지 이름**으로 나누고 엄격히 써야 함  
  `assert(bool)` 또는 Rust라면 `assert_unchecked()`는 프로그래머가 항상 참이라고 믿고, 컴파일러도 항상 참이라고 가정해 최적화에 쓰는 것임. 오래된 언어의 검사형 assert와의 연상을 피하려면 `assume()`이라고 부르는 편이 나을 수도 있음  
  `check(bool)`는 조건이 거짓이면 패닉하고 참이면 계속 진행하며, 항상 그렇게 동작함  
  `debug_check(bool)`는 디버그 모드에서는 `check()`와 같고 릴리스 모드에서는 항상 계속 진행함. 실제로는 디버그 모드에서 기본으로 켜지는 `--debug_checks` 플래그로 제어됨  
  여기에 `assert()`를 `check()`로 바꾸는 컴파일러 플래그 `--check_asserts`도 필요함. 자기 `assert`가 의심스러워 검증하고 싶을 때 쓰며, 디버그 모드에서는 기본으로 켜짐. “assert”라고 말할 때 무엇을 뜻하는지 아주 명확하지 않으면 성숙한 논의가 불가능하고, 말만 낭비하게 됨
