Uploaded avatar of iHiD

Mechanical March가 시작됐어요!

@iHiD
3년 초과 전
동영상

두 번째 테마의 달, Mechanical March에 오신 것을 환영해요. 이번 달에는 기계어로 컴파일되는 시스템 언어에 집중해요.

이 글은 Mechanical March 영상의 일부를 정리한 글이고, 일부는 녹취록이에요. 이번 달을 간단히 소개한 다음, 시스템 언어와 그 역사적 발전, 기계어로 컴파일하는 것의 장단점, 그리고 특집 언어들을 하나씩 간단히 살펴볼게요. 이번에도 Erik이 함께해요. 후반부는 Erik이 대부분 이야기할 거예요. 그럼 저는 실용적인 정보부터 시작해볼게요.

먼저 이번 달 특집 언어예요. C, C++, D, Go, Nim, Rust, V, Zig예요. Mechanical March 배지를 받으려면 이 언어들 중 하나로 연습 문제 다섯 개를 완료해야 해요. 우리 Go 트랙은 Exercism에서 가장 좋은 실라버스 중 하나를 갖고 있어서 정말 한번 해볼 만해요. Exercism에서는 Nim도 아주 좋아하는데, 시작하기에 비교적 단순한 언어이고 쓰기도 아주 쉬워서 이것도 꼭 한번 해봐요.

해볼 만한 특집 연습 문제도 다섯 개 있어요:

  • linked-list 또는 simple-linked-list(언어에 따라 달라요): 메모리 할당과 해제, 포인터
  • secret-handshake: 비트 연산
  • pangram: for문, 문자열과 문자
  • sieve: 배열, for문
  • binary-search: 배열, 루프

며칠 전 업데이트 영상에서 새 배지를 발표했는데, 이달의 테마 언어로 특집 연습 문제 다섯 개를 완료하면 받는 배지예요. 그 배지를 받으려면 올해 안에 시스템 언어로 그 문제들을 모두 완료해야 해요.

재미있는 것들도 많이 준비 중이에요. Go 코어 팀에서 몇 분과의 인터뷰, 가능하면 Rust와 다른 언어들의 인터뷰도요. 이번 달 내내 라이브 스트리밍도 많이 진행할 거예요. Mechanical March 굿즈도 곧 나올 예정이에요!

그럼 기술적인 부분을 조금 더 파고들어볼까요.

이 언어들은 어디에 쓰나요?

Exercism 전반에서 이 언어들을 사용해요. CLI는 Go로, 내부 트랙 관리 도구인 configlet은 Nim으로, 제출한 풀이의 코드 줄 수를 세는 핵심 라이브러리는 Rust로 작성했어요. Erik, 그 도구들을 만들 때 왜 그 언어들을 골랐나요?

CLI:

  • Katrina가 가장 익숙한 언어라서 Go를 쓴 것 같아요.
  • Go는 이런 작은 규모의 명령줄 도구에 아주 좋아요.
  • Go 코드는 비교적 단순해서 기여하기 쉬워요
  • Go 바이너리는 런타임이 필요 없어서 배포하기 쉬워요
  • Go는 크로스 컴파일을 잘해요

Nim

  • Nim은 Go와 거의 같은 장점을 갖고 있어요
  • CLI를 유지보수할 만큼 Go를 아는 사람이 많지 않아서 Nim으로 바꿨어요

이 언어들은 또 어떤 곳에서 쓰이는 걸 볼 수 있을까요?

  • 성능이 중요한 모든 곳(드라이버, 게임, 운영체제, 빌드 시스템/컴파일러)
  • 자원이 제한된 모든 곳(예: 임베디드 소프트웨어)
  • 높은 이식성이 필요한 것, 즉 여러 플랫폼에서 실행되어야 하는 것

기계어란 무엇인가요?

앞서 말했듯이, Mechanical March의 이 언어들은 기계어로 컴파일된다는 점이 특징이에요. 기계어가 무엇인지, 그리고 이와 대조되는 바이트코드가 무엇인지 조금 설명해 줄 수 있나요?

  • 기계어는 기계에서 직접 실행할 수 있는 코드예요.
  • 반대로 바이트코드는 바이트코드를 기계어로 해석하거나 컴파일하는 다른 코드가 필요해요. 그래서 바이트코드는 실행되기 전에 중간 단계를 거쳐야 해요.

바이트코드에 비해 기계어가 갖는 장단점은 무엇인가요?

장점:

  • 시작이 빨라요(JIT 컴파일 단계가 없어요)
  • 메모리 사용량이 적어요(런타임을 로드하지 않고, 메모리에 바이트코드도 없어서 임베디드 시스템에 아주 좋아요)
  • 대상 기계에 런타임을 설치할 필요가 없어요(docker 컨테이너를 작게 유지하는 데 중요해요)

단점:

  • 이식성이 없어요. 바이트코드는 이식성이 있지만, 컴파일된 기계어는 특정 플랫폼에 맞춰져요
  • 프로파일 기반 최적화 같은 고급 최적화는 할 수 없어요(한동안 실행해 본 뒤 (바이트)코드를 가장 잘 컴파일하는 방법을 정하는 것)

참고: 하이브리드 방식도 가능해요. 언어가 바이트코드로 컴파일한 다음, 다른 도구로 그 바이트코드를 기계어로 컴파일하는 방식이에요.

시스템 프로그래밍의 발전

자, 그럼 이번 달 언어 몇 가지의 발전 과정을 조금 살펴볼까요. 먼저 C로 거슬러 올라가서, C++이 어떻게 발전했는지 봐요. 이 언어들에 대해 조금 이야기해 주세요

C는 아주 저수준 언어예요. 기계어보다 살짝 위에 있는 느낌이에요. 그래서 아주 강력하고 최적화하기도 좋지만, 버그가 생기기 쉽기도 해요(예: 널 포인터 예외와 버퍼 오버플로). 메모리 관리는 전부 수동이라 프로그래머의 책임이고, 버그나 메모리 누수로 이어질 수 있어요. C++은 C와 비슷하지만 객체 지향 프로그래밍을 지원해요. 여전히 꽤 저수준이고 수동 메모리 관리를 해야 해요. C와 C++ 모두 인라인 어셈블리(ASM)를 작성할 수 있어요!

그럼 더 새로운 시스템 언어들은 어떤가요? 어떻게 발전해 왔나요?

모든 현대 시스템 프로그래밍 언어는 참조 카운팅, 가비지 컬렉터 또는 다른 어떤 방식으로든 자동 메모리 관리를 지원해요.

초기 시스템 프로그래밍 언어는 모두 널 포인터를 지원해요. Tony Hoare는 이를 자신의 '10억 달러짜리 실수'라고 불렀죠. 널 포인터는 런타임 오류와 취약점으로 이어지는 것으로 악명 높아요. 많은 현대 언어는 null을 없애거나, 최소한 사용하려면 노력이 필요하게 만들어요.

또 다른 변화는 기본값이 가변 값에서 기본적으로 불변 값으로 옮겨간 것이에요. 예를 들어 Rust와 Vlang은 둘 다 기본적으로 값이 불변이고, 가변성을 쓰려면 직접 선택해야 해요.

모든 최신 언어는 C(또는 C++)와의 상호 운용을 지원해요. 그 언어들로 작성된 코드가 워낙 많으니까요.

또 흥미로운 점은 일부 최신 언어가 기계어로 직접 컴파일하지 않고 다른 도구를 사용한다는 거예요. 예를 들어 Rust와 Zig는 LLVM을 사용하고, Nim은 다양한 컴파일러를 사용할 수 있어요. 이를 트랜스파일링이라고 해요.

매크로와 메타프로그래밍 같은 건 어떤가요?

매크로와 메타프로그래밍에는 흥미로운 차이가 있어요. C/C++의 매크로는 강력하지만, 다루기 어렵다는 다소 안 좋은 평판이 있어요. Rust, Nim, D는 모두 강력한 메타프로그래밍을 제공하지만, 훨씬 더 깔끔한 방식으로 해요. 반대로 VLang과 Zig는 매크로가 없다는 점을 언어의 특징으로 내세우고, Go는 go generate라는 다른 접근 방식을 써요.

시스템 언어는 꽤 저수준이라는 평판이 있어요. 아직도 그럴까요?

최신 언어들은 모두 C/C++보다 더 높은 추상화 수준에서 동작해요. 예를 들어 Rust, D, Nim은 아주 함수형에 가까운 방식으로 코드를 작성할 수도 있어요. Nim과 D에는 '순수' 함수라는 개념까지 있어요.

이달의 언어 소개

그럼 언어들을 하나씩 차례로 살펴보면 좋겠어요. 모든 언어에는 공통점이 있어요. 전부 강타입이고 정적 타입이에요. 그럼 어떻게 다른지 살펴볼까요? C부터 시작해볼까요?

C

  • Dennis Ritchie가 개발했어요
  • 세계에서 가장 오래된 언어 중 하나이고, 아마 가장 많이 쓰이는 언어일 거예요
  • Unix와 Linux처럼 수많은 소프트웨어가 C로 작성되었어요
  • 영향력이 아주 커요(C와 비슷한 언어라는 개념을 떠올려 보세요)
  • 수동 메모리 관리
  • 성능이 아주 뛰어나요("기계"에 가까워요)
  • 어디서나 실행돼요
  • 임베디드 시스템에 완벽해요
  • 문법이 꽤 작은 언어예요

C++

  • Bjarne Stroustrup이 개발했어요
  • C의 후속 언어지만 객체 지향을 더했어요(C with classes)
  • 객체 지향 프로그래밍을 대중화하는 데 기여했어요
  • C보다 고수준 언어 기능이 많아요
  • 템플릿을 통한 제네릭 프로그래밍을 지원해요
  • 네임스페이스를 통한 모듈 지원을 더했어요
  • 많은 게임(엔진)이 C++로 작성되었고, Windows의 상당 부분도 마찬가지예요
  • 수동 메모리 관리
  • 지금도 꾸준히 새로운 기능이 추가되며 발전하고 있어요(방대한 명세)

D

  • Walter Bright가 개발했고, 이후 Andrei Alexandrescu가 합류했어요
  • 원래는 C++를 다시 설계한 언어(C++의 "실수"에서 배운)로 여겨졌고, 여러 다른 언어에서 영감을 받았어요
  • 멀티 패러다임으로, 명령형/객체 지향/함수형 프로그래밍을 지원해요
  • C/C++와의 상호 운용이 쉬워요
  • 통일된 함수 호출 문법
  • 컴파일 타임 함수 평가(예: 컴파일 타임에 정규식 상태 머신 생성)
  • 함수형 프로그래밍과 "순수" 함수를 지원해요
  • 다양한 안전 기능
  • @safe를 통한 메모리 안전성
  • 계약(사전/사후 조건, 불변식)
  • 순수 함수
  • 단위 테스트에 중점을 두고, 테스트를 테스트 대상 소스 코드 옆에 둬요(Exercism 웹사이트에서는 예외를 만들어야 했어요 :))
  • 유니코드

Rust

  • Mozilla Research의 직원 Graydon Hoare가 개발했고, 이후 Mozilla가 공식적으로 채택했으며 지금은 Rust Foundation의 일부예요
  • 멀티 패러다임으로, 객체 지향(다만 주관이 뚜렷해요. 예를 들어 상속이 없어요), 명령형, 함수형 프로그래밍을 지원해요(Option/Result 타입, 패턴 매칭)
  • Rust로 작성된 새로운 도구가 많아요(예: SWC, Gleam, 그리고 Linux 커널이 두 번째로 지원하는 언어예요. 우리는 코드 줄 수 세는 도구에 Rust를 사용해요)
  • 신뢰성과 성능에 중점을 둬요
  • LLVM 위에 만들어졌어요
  • 지난 7년 동안 StackOverflow 설문에서 가장 사랑받는 언어였어요
  • 빠른데, 부분적으로는 최소한의 코어와 표준 라이브러리 덕분이에요
  • 안전해요. 소유권과 수명을 통한 메모리 안전성과 스레드 안전성을 모두 갖췄고, 기본적으로 불변이에요
  • 강력한 타입 시스템으로 컴파일 타임에 많은 버그를 잡아요(특히 메모리 관련). 컴파일러는 정말 도움이 되는 오류를 출력해요
  • 필요한 도구가 모두 포함되어 있어요: 컴파일러, 빌드 도구, 포매터, 패키지 관리자, IDE 통합
  • 훌륭한 문서(컴파일러가 어떻게 동작하는지 다룬 방대한 문서도 있어요)
  • 이식성이 좋아요: 단일 정적 바이너리로 컴파일되며 런타임을 설치할 필요가 없어요
  • C 코드와 상호 운용이 쉬워요
  • 제로 코스트 추상화
  • 두려움 없는 동시성
  • 매크로

Nim

  • Andreas Rumpf가 개발했어요(원래 이름은 Nimrod)
  • 문법은 Python에서 영감을 받았어요
  • 멀티 패러다임
  • Exercism에서는 configlet에 사용해요
  • 뛰어난 성능: 오버헤드 없는 반복자, 값 기반 타입을 스택에 할당하는 선호
  • 현대적이고 표현력이 뛰어난 타입 시스템: 타입 추론, 튜플, 제네릭, 합 타입, async/await
  • 가비지 컬렉션이지만 결정적 메모리 관리를 지원해요(여러 가지 메모리 관리 옵션)
  • 코드의 컴파일 타임 실행
  • 통일된 호출 문법
  • 매크로: 언어를 쉽게 확장할 수 있어요
  • 이펙트 시스템: 부수 효과를 타입 시스템에 인코딩해요

Go

  • Google의 Robert Griesemer, Rob Pike, Ken Thompson이 개발했어요
  • 주로 명령형/절차형이고, 객체 지향과 비슷한 접근을 지원해요(다만 상속은 없어요)
  • Docker, Kubernetes 같은 큰 프로젝트에 사용돼요. 백엔드와 CLI에도 좋아요(예: esbuild)
  • 머릿속에 담을 수 있을 만큼 단순한 것을 목표로 해요(문법이 적어요)
  • 가비지 컬렉터를 통한 메모리 안전성
  • 빠른 속도: 컴파일이 빠르고, 테스트가 빠르고, 런타임이 빨라요. 벤치마크 작성도 기본으로 지원해요!
  • 주관이 뚜렷해요: Go 코드 스타일에 영향을 주기 위한 많은 노력이 담겨 있어요. 문법이 적고, go fmt로 코드를 포맷하며, 관용적인 사용을 확인하는 도구가 있고, 다른 언어에서는 경고인 것들(예: 사용하지 않는 변수)을 오류로 처리하며, 사용할 Go 관용구를 정리한 문서도 있어요
  • Go를 매우 유연하게 만들어 주는 가벼운 타입 시스템(타입 추론을 지원해요)
  • 이식성이 좋아요: 단일 정적 바이너리로 컴파일되며 런타임을 설치할 필요가 없어요. 크로스 컴파일도 쉬워요. 고루틴(경량)을 통한 동시성과 채널을 통한 통신
  • 인터페이스를 통한 구조적 타이핑(덕 타이핑과 비슷하지만 정적으로 검사해요)
  • 오류 처리: 오류를 확인하고 다루도록 언어가 권장해요

VLang

  • Alexander Medvednikov와 Delyan Angelov가 개발했어요
  • Go에서 영감을 받았어요:
  • "일을 하는 방법은 하나뿐"이라는 전략도 같아요
  • "최소한의 문법"이라는 전략도 같아요
  • 코루틴
  • Go와 다른 점:
  • nil/null이 없고, 대신 결과 타입을 사용해요
  • 기본적으로 불변
  • 합 타입(함수형)
  • 문자열 보간
  • 더 작은 런타임/바이너리
  • 패턴 매칭
  • GC를 선택 사항으로 만들기 위한 작업(autofree)
  • 제로 코스트 C 상호 운용
  • 코드에서 문서를 생성해요
  • 메모리를 적게 사용하는 빠른 컴파일러

Zig

  • Andrew Kelley가 개발했어요
  • 문법이 꽤 적어요(500줄짜리 PEG 문법 파일)
  • 컴파일 타임 코드 실행과 리플렉션
  • "명확한" 언어를 목표로 해요. 숨겨진 제어 흐름이나 할당, 매크로/메타프로그래밍이 없어요
  • 수동 메모리 할당
  • 다양한 할당자를 지원해요
  • 할당하는 표준 라이브러리 함수에는 할당자 매개변수가 있어요
  • 테스트 프레임워크가 메모리 누수를 감지할 수 있어요
  • 안전성:
  • 오류는 값이며 반드시 처리해야 해요
  • null이 없고, 옵셔널 타입을 사용해요
  • 테스트를 소스와 같은 파일에 작성할 수 있어요(D처럼)
  • 백엔드로 LLVM을 사용해요
  • C/C++ 코드를 컴파일할 수 있어요
  • C 상호 운용이 쉬워요
  • 크로스 컴파일이 쉬워요

마무리

이쯤에서 마무리할게요. 지금쯤이면 다들 머리가 꽤 가득 찼을 테니까요.

이번 달 특집 언어들에 대한 유용하고 재미있는 소개가 되었으면 좋겠어요. 이 언어들을 탐험하면서 정말 즐거운 시간을 보내길 바라요. Erik과 저는 어떤 언어를 골랐는지, 그리고 어떻게 느꼈는지 듣고 싶어요. 댓글이나 포럼에 꼭 글을 남겨 주세요!

시청해 주셔서 감사해요!

2023년 03월 01일 · 유용했나요?