# Steering Zig Fmt

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

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=29340](https://news.hada.io/topic?id=29340)
- GeekNews Markdown: [https://news.hada.io/topic/29340.md](https://news.hada.io/topic/29340.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-05-10T09:04:51+09:00
- Updated: 2026-05-10T09:04:51+09:00
- Original source: [matklad.github.io](https://matklad.github.io/2026/05/08/steering-zig-fmt.html)
- Points: 1
- Comments: 1

## Topic Body

- `zig fmt`는 파일에 이미 있는 구문 형태를 반영해 같은 코드도 여러 레이아웃으로 배치할 수 있는 **조종 가능한 포매터**로 쓰일 수 있음
- 함수 호출에서는 **trailing comma** 유무가 결과를 바꾸며, 쉼표가 없으면 한 줄로 합쳐지고 쉼표가 있으면 인자를 줄마다 배치함
- 실제 흐름은 원하는 코드 배치를 먼저 정하고 쉼표를 몇 개 추가한 뒤 포맷 단축키를 눌러 `zig fmt`가 나머지를 처리하게 하는 방식임
- 배열에서는 trailing comma뿐 아니라 **첫 번째 줄바꿈 위치**도 반영되어, 첫 줄바꿈이 세 번째 항목 뒤에 있으면 항목 3개씩 맞춰 정렬됨
- `++` 배열 연결을 신중히 쓰면 줄마다 항목 수를 다르게 배치할 수 있고, subprocess에 `--key`와 `value` 쌍을 넘길 때 고정 인자 배열과 옵션 쌍 배열을 연결해 정렬할 수 있음

---

### `zig fmt`를 조종하는 방식
- `zig fmt`는 현재 파일에 이미 있는 구문 형태를 보고 같은 구문도 여러 방식으로 배치할 수 있어 **조종 가능**한 포매터로 쓰일 수 있음
- 함수 호출에서 **trailing comma** 유무가 레이아웃을 바꿈
  ```zig
  f(1, 2,
        3);
  
  // -> zig fmt ->
  
      f(1, 2, 3);
  ```
  ```zig
  f(1, 2,
        3,);
  
  // -> zig fmt ->
  
      f(
          1,
          2,
          3,
      );
  ```
- 실제 사용 흐름은 원하는 코드 배치를 먼저 정하고 `,`를 몇 개 추가한 뒤, 포맷 단축키를 눌러 `zig fmt`가 나머지를 처리하게 하는 방식임
- 포매터가 레이아웃을 추측하게 하기보다, 사용자가 핵심 선택을 직접 남기는 방식이 더 잘 맞을 수 있음
- 좋은 포매팅의 90%는 논리 블록 사이의 빈 줄과 적절한 중간 변수 선택에 달려 있으므로, 이런 선택을 제거하기보다 활용하는 편이 낫다는 결론임

### 배열의 열 맞춤 레이아웃
- 배열에서는 trailing comma만으로 한 줄에 하나씩 배치되는 것이 아니라, **첫 번째 줄바꿈 위치**도 `zig fmt`가 반영함
  ```zig
  .{ 1, 2, 3,
        4, 5, 6, 7, 8, 9, 10, 11,  };
  ```
- 첫 줄바꿈이 세 번째 항목 뒤에 있으면, 결과도 항목 3개씩 맞춰 정렬됨
  ```zig
  .{
          1,  2,  3,
          4,  5,  6,
          7,  8,  9,
          10, 11,
      };
  ```
- [`++`](https://ziglang.org/documentation/0.16.0/#:~:text=a%20++%20b) 배열 연결을 신중히 쓰면 줄마다 항목 수를 다르게 배치할 수 있음
- subprocess에 `--key`와 `value` 쌍을 넘길 때는 고정 인자 배열과 옵션 쌍 배열을 연결해 다음처럼 정렬할 수 있음
  ```zig
  try run(&(.{ "aws", "s3", "sync", path, url } ++ .{
      "--include",            "*.html",
      "--include",            "*.xml",
      "--metadata-directive", "REPLACE",
      "--cache-control",      "max-age=0",
  }));
  ```

## Comments



### Comment 57139

- Author: neo
- Created: 2026-05-10T09:04:52+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/auxtwd/steering_zig_fmt) 
- `gofmt`에도 비슷한 **서식 유도 동작**이 있었던 것으로 기억하고, 그런 포매터가 `rustfmt`보다 더 마음에 듦  
  그래도 포매터가 아예 없는 것보다는 어떤 형태의 서식 자동화라도 있는 편이 낫다고 봄
  - “포매터가 없는 것보다 뭐든 있는 게 낫다”는 말은 그냥 넘기기 어렵다  
    **자동 포매터**는 평범함을 강제하고, 서식을 못 쓰는 사람들을 끌어올리지만 잘 쓰는 사람들도 끌어내린다  
    협업할 때 다른 사람들이 원하거나 그들의 개인 서식 규율을 신뢰하기 어렵다면 쓰겠지만, 혼자 작업할 때는 절대 쓰지 않음  
    내 취향도 있고 포매터의 취향도 있는데, 둘은 화해 불가능하게 다르다  
    다만 이 글에서 보여준 내용 자체는 인상적임  
    그 문장은 *Fiddler on the Roof*에서 중매쟁이 Yente가 “나쁜 남편이라도—신이여 막아주소서—남편이 없는 것보다는 낫다!”라고 한 대사를 떠올리게 함
  - 기억하기로 **Elm 포매터**도 비슷하게 동작했고, 원래 서식을 고려하지 않는 포매터들에 비해 꽤 좋게 느껴졌음
  - 일부 C++ 프로젝트에서 `clang-format`을 쓰는데 끔찍함  
    버전 간 **안정성**이 너무 낮아서 `clang-format` 업그레이드가 코드 모든 줄을 건드리는 서식 커밋으로 이어짐  
    포매터가 없는 것보다 나은지 정말 확신이 안 듦
  - 예전에는 “어떤 포매터든 없는 것보다 낫다”고 오래 생각했지만, 최근에는 완전히 생각이 바뀜  
    자동 포매터는 주로 풀 리퀘스트에서 **자전거 헛간 논쟁**을 없애는 사람 문제를 해결함  
    그런데 이제 **에이전트형 개발**로 넘어가면서 그 문제는 점점 덜 중요해짐  
    지금 여러 프로젝트에서 기계가 대부분의 작업을 하고 있는데, 그렇게 되면 포매터를 돌리지 않는 편이 더 낫다는 쪽으로 느껴짐

- Python에서 명령줄 인자를 만들 때는 튜플을 리스트에 **스플랫**하는 방식을 좋아해서, 글의 마지막 예제는 이렇게 쓸 것 같음  
  ```python  
  [  
    "aws",  
    "s3",  
    "sync",  
    path,  
    url,  
          *("--include", "*.html"),  
    *("--include", "*.xml"),  
    *("--metadata-directive", "REPLACE"),  
    *("--cache-control", "max-age=0"),  
  ]  
  ```

- 마지막으로 봤을 때 `zig fmt`에는 **80열 제한**을 100열 제한 대신 쓰도록 설정하는 방법이 없었는데, 아직도 그런가?  
  하루에 여러 시간 작업하면 눈이 덜 피로해서 터미널 글꼴 크기를 키워 쓰는데, 80열과 100열 차이는 `vim` 분할 두 개와 `nerd tree`를 나란히 둘 수 있느냐를 가름함
  - `zig fmt`에는 **열 제한**이 없음

- 포매터가 전혀 없던 팀에 [rigid formatter](https://github.com/Facebook/ktfmt)를 도입한 사람으로서, 가끔은 수동으로 서식에 영향을 줄 수 있는 능력이 그리움  
  그런 면에서 Zig가 유연한 건 정말 멋짐

- 훌륭하다!  
  이런 식의 **TS/JS 포매터**가 있을까?  
  `maplibre-gl`을 쓰는 프로젝트가 있는데 스타일 명세 표현식이 가끔 너무 과하게 서식화돼서 아무것도 안 보임  
  지금은 포매터 사용을 멈췄지만, 디버깅하고 복사하고 주석 처리하다 보니 코드가 지저분해지고 있음  
  어쩌면 Zig 포매터를 다른 언어도 포매팅하도록 만들 수 있을지도 :)
  - Prettier에도 비슷한 기능이 있지만, 구체적으로는 **객체 리터럴**에 한정되고 “한 줄에 전부”와 “각 요소를 다른 줄에” 중에서만 고를 수 있음  
    예를 들어 한 줄에 네 요소씩 두라고 포매터에 지시할 방법은 없음  
    또한 객체 리터럴이 너무 길어지면 입력 텍스트가 어떻게 되어 있든 Prettier가 결국 “각 요소를 다른 줄에” 형식으로 바꿔버림

- 가벼운 방식의 포매터, 사실상 **줄바꿈만 하는 포매터**에는 실망한 적이 있어서, 더 엄격한 예시 안에서 이런 유연성을 갖는다는 발상이 부럽고 마음에 듦 :p  
  최근 fedi에서 비례폭 글꼴로 Lisp를 작성하고 서식화하는 질문에 답하면서, 의미 있는 공백을 쓰는 s-expression 변형들, 즉 wisp, Readable/Sweet expressions, SRFI 119와 110을 짚었음  
  이 문법 계열은 선택적 중위 표기 확장을 활용해 줄바꿈에 대한 제어권을 일부 돌려준다는 관찰도 덧붙였음

- 흥미로운 설계지만 마음에 드는지는 잘 모르겠음  
  내 포매터에서는 다르게 처리함: 포매터가 **후행 쉼표**를 무시하고 서식을 결정한 뒤, 여러 줄로 나뉘면 항상 후행 쉼표를 추가하고 한 줄이면 항상 후행 쉼표를 제거함  
  그래서 “유도”는 못 하지만 `f(1, 2, 3)`은 후행 쉼표 유무나 토큰 사이 공백의 양과 종류와 관계없이 일관되게 포매팅됨  
  어느 정도의 유도는 필요함  
  예를 들어 긴 리스트 리터럴 `[&lt;expr1&gt;, &lt;expr2&gt;, ..., &lt;expr100&gt;]`이 있으면 대부분의 포매터는 각 표현식을 한 줄에 하나씩 두겠지만, 가능한 한 많이 한 줄에 넣고 싶을 수 있음  
  이 둘을 후행 쉼표로 결정하는 건 이상하다고 보고, 일반적으로는 선택지가 2개가 아니라 N개일 수 있음  
  이런 목적에는 속성이 더 잘 맞는다고 생각함  
  예를 들어 이미 있을지도 모르지만, 문장 앞에 `#[rustfmt::list_layout(flow)]` 같은 것을 붙여 해당 문장 안의 리스트 리터럴 서식에 영향을 주는 식이 가능함  
  유도가 너무 많으면 전체 생태계의 코드 서식을 일관되게 만들고 코드 리뷰를 쉽게 하는 **포매터의 목적**을 해치므로 제한된 경우에만 해야 함  
  긴 리스트 리터럴은 정말 필요한 예라고 봄  
  내 프로젝트에도 테스트 기대값 리뷰에 서식이 도움이 되는 예가 있는데, [여기][1]가 그렇다  
  Dart 포매터에도 다른 “유도” 동작이 하나 떠오름: 긴 리스트 리터럴에서 주석 줄을 추가해 줄들을 그룹화할 수 있음  
  예를 들어 `[1, 2, 3, ..., 1000]`이면 각 요소를 한 줄에 하나씩 두지만, 수동으로 이렇게 그룹화할 수 있음  
  ```dart  
  [1, 2, 3, 4, 5,  //  
   6, 7, 8, 9, 10, //  
   ...]  
  ```  
  이런 기능을 의도적으로 넣은 것인지, 주석 처리 방식에서 나온 부산물인지는 모르겠음
  
  [1]: https://github.com/fir-lang/fir/blob/1b63e18703c18adcb63babe86fb1668a169b4ac4/src/scanner.rs#L458-L501
  - 그 방식이 정확히 `rustfmt`의 동작이고, 그게 미치게 만듦  
    가끔은 **줄 길이 제한**을 넘기더라도 함수 호출을 나누지 않는 편이 더 읽기 좋은 경우가 있고, 거기에 대한 내 판단을 반영할 수 있으면 좋겠음  
    떠오르는 한 가지 사례는 OpenGL임  
    보통 하나의 리소스를 수정하거나 사용하면서, 예를 들어 텍스처 초기화처럼 `gl.*` 호출을 연속으로 많이 하게 되는데 `rustfmt`는 “줄이 너무 김. 줄을 쪼개야 함”이라는 로봇 같은 목적 말고는 아무 감각 없이 밀어버림  
    이 예제는 동작을 보여주기 위한 인위적인 것이고 실제 `rustfmt` 동작과 완전히 같지는 않음  
    줄도 그렇게 길지는 않음  
    지금 휴대폰으로 쓰고 있어서 100% 정확한 예제를 만들 도구가 없음  
    ```rust  
    gl.bind_texture(gl::TEXTURE_2D, tex);  
    gl.tex_parameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST);  
    gl.tex_parameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST);
    
    // -->
    
    gl.bind_texture(gl::TEXTURE_2D, tex);  
    gl.tex_parameteri(  
        gl::TEXTURE_2D,  
        gl::TEXTURE_MIN_FILTER,  
        gl::NEAREST,  
    );  
    gl.tex_parameteri(  
        gl::TEXTURE_2D,  
        gl::TEXTURE_MAG_FILTER,  
        gl::NEAREST,  
    );  
    ```  
    이런 식으로 이어지는 `gl.tex_parameteri` 호출을 여러 줄로 쪼개는데, 사실 각 호출은 한 줄에 완전히 펼쳐 두는 편이 더 낫다  
    열이 맞춰지면 두 줄의 차이를 훨씬 쉽게 찾을 수 있기 때문임  
    쪼갠 버전은 시각적 근접성이 떨어지고 읽기 더 어렵다  
    눈으로 두 줄을 쉽게 비교할 수 없게 됨  
    또 줄을 문자 수 제한 안에 맞게 포매팅할 수 없을 때 완전히 실패하는 우스운 일도 생김  
    컴파일러 코드를 쓰며 문자열 리터럴로 진단 메시지를 만들 때 이런 일이 자주 일어나는데, 메시지가 꽤 길어질 수 있음  
    `rustfmt`는 이를 어떻게 나눌지 몰라서 포기하고 해당 문장 전체를 포매팅하지 않음  
    흔히 이런 식임  
    ```rust  
    match something {  
        // ... match arms above this one ...  
        _ => emit_diagnostic(&mut state, "This is a very long message to try and illustrate the problem. Help: please consult a doctor.")  
    }  
    ```  
    여기서 `emit_diagnostic` 호출이 표현식일 뿐이라는 이유로 **전체 `match` 문**의 포매팅을 포기하는데, 그냥 어리석다  
    내 코드를 최대 100열로 밀어붙이려 하지 않았다면 전부 피할 수 있었음

- 끝부분의 코멘트를 보고 나처럼 찾아봐야 했던 사람을 위해 적자면, `++`는 [배열 연결 연산자](https://ziglang.org/documentation/master/#:~:text=Array%20Concatenation)임  
  그래서 배열을 두 개로 나누면 서로 다르게 포매팅할 수 있음
