1P by GN⁺ 8일전 | ★ favorite | 댓글 1개
  • AdaRust 두 언어를 활용해 Advent of Code 문제를 풀며 생긴 차이점과 특징을 비교함
  • 안전성과 신뢰성을 중심으로 하는 두 언어의 언어 설계와 실질적인 프로그램 작성 방식의 차이점을 분석함
  • 각 언어의 표준 라이브러리, 기능 내장 여부, 성능 차이, 오류 처리 스타일 등 다양한 관점에서 차별성이 드러남
  • 모듈성과 제네릭, 반복문, 에러 핸들링 등 실전 코드 예시로 실제 작성·운용 시 겪는 구체적 사례를 설명함
  • 정적 타입 지정 방식, 배열 처리, 에러 처리 인터페이스 차이로 인한 개발 경험의 차별성이 두드러짐

소개 및 목적

  • Advent of Code(이하 AoC) 문제 해결 과정에서 Ada만 사용하다가, 2023년부터 RustModula-2로도 해답을 작성하며 직접 비교하게 됨
  • 기존 Ada-중심 솔루션을 Rust로 옮기며 두 언어의 구조적 차이와 고유한 접근법을 체감함
  • 코드의 안전성, 신뢰성, 언어 설계 관점에서 실제 사용상 차이를 명확하게 하려는 목적을 가짐

비교에 사용한 언어 버전

  • Ada 2022(필요에 따라 Spark 2014 일부 규칙 참조)
  • Rust 2021 (주요 비교 시 Rust 1.81.0 버전 기반)

제외된 기능 및 비교 기준

  • 각 언어의 대표적인 기능(=킬러 피처)은 본문 중 코멘트로 간략히 언급됨
  • 개인적 경험과 솔루션별 현실적인 필요성에 따라 다루지 않은 기능도 일부 있음
  • 가능 최대한 사견은 배제하고 주요 특징에 집중

저자의 배경 및 관점

  • Ada, Rust 모두 비원어민 사용자로서, C/C++, Pascal, Modula-2 등의 1980년대 언어 경험이 베이스임
  • 결과적으로 코드 스타일이 현대적/이디엄과 다를 수 있음
  • 최적 구현이 아닐 수도 있고, 문제 상황에 따라 직관적이거나 비관례적 해법을 선택한 경우도 있음

Ada와 Rust의 포지셔닝

  • 여전히 Ada는 아주 안전하고 신뢰성 높은 시스템/임베디드 개발 언어로, 코드 가독성을 중시
  • Rust는 메모리 안전성과 시스템 프로그래밍의 강점을 가져, Stack Overflow 개발자 조사에서 다년간 ‘가장 선호하는 언어’에 지명됨
  • Ada는 범용 고수준 언어로, 리딩 · 유지보수에 특화한 스펙트럼 제공
  • Rust는 저수준 시스템 프로그램 개발을 지향하며, 명시적 메모리 관리와 오류/옵션 타입 기반 안전 프로그래밍 문화 확립

안전성 및 구조적 특징 비교

  • Ada

    • ISO 표준(엄밀한 스펙)
    • 문제 특성에 맞는 타입(범위, 자릿수 등을 선언)이 쉬움
    • 배열 인덱스가 숫자가 아니어도 허용
    • Spark라는 더욱 엄격한 사양 존재
  • Rust

    • 명세가 공식 문서(Reference)와 컴파일러 중심
    • 타입 선언이 머신 타입(예: f64, u32)에 의존
    • 배열 인덱싱은 수치형만 자연스러움

기능/내장 여부 테이블 주요 요약

  • 배열 범위 체크, 제네릭 컨테이너, 동시성, 라벨 루프, 패턴 매칭 지원 등에 차이
  • Ada는 Exception(예외) 기반 오류 처리를, Rust는 Result/Option 타입을 통한 반환 기반 처리 방식
  • Rust는 매크로, 패턴 매칭, 함수형 순수성 지원 등에서 차별점 두드러짐
  • Ada는 계약 기반 설계 및 DBC(Design By Contract) 컴파일 타임 검증 Spark에서 지원
  • 메모리 안전성 측면에서는 Rust와 Spark가 강제, Ada는 Null 포인터 활용 허용

성능 및 실행 시간 비교

  • Rust는 일반적으로 런타임이 빠르나 컴파일 속도가 느리고, Ada는 반대로 컴파일 타임이 빠르고 런타임은 검증 체크에 따라 다소 느리다는 평판
  • 벤치마크 결과, Rust가 day24 문제에서 f64 타입 한계로 오버플로우 발생했으나 Ada는 digits 18과 같이 고수준 타입 지정이 가능해, 기계 타입 자동 선택 및 오버플로우 회피로 뛰어난 성능을 보임
  • Rust는 안정적이지 않은 f128 또는 외부 라이브러리를 사용해야 하며, Ada는 컴파일러스펙에 적합한 타입 지정만으로 우위 확보 가능

파일 처리 및 에러 핸들링(Case Study 1)

Ada의 파일 처리

  • 기본적으로 Ada.Text_IO 사용
  • 명시적으로 파일 오픈, 라인별 읽기, 원하는 범위, 위치 기반 Line 처리 등 상대적으로 직관적으로 가능
  • 에러 발생 시 명확한 오류 메시지보다 예외로 처리되고, 함수 서명에 에러 발생 가능성이 드러나지 않음

Rust의 파일 처리

  • std::fs::FileBufReader 활용
  • 파일 오픈 시 Result 타입으로 반환, 에러 가능성이 명확하게 드러남
  • 직접적인 문자 인덱스 접근 지원 없음, 반드시 Iterator로 처리
  • map, filter, collect, sum 등 함수형·반복형 도구가 중심이며, 다양한 매크로(예: include_str!) 지원
  • 반환 타입에 명시적으로 에러를 선언해 함수 수준에서 에러 전파 명확성 확보

모듈성과 제네릭(Case Study 2)

Ada의 모듈성

  • 패키지(package) 기반 명확한 명세(인터페이스)와 구현 분리
  • 모듈화 강화 위해 하위 패키지, use/rename 문법 조합으로 가독성 조정
  • 패키지의 generic 지원: 타입/상수/하위 패키지 전체를 일반화

Rust의 모듈성

  • mod/crate 체계로 모듈 구성, 명세와 구현 구분은 문서 생성기가 자동화
  • pub/private 접근 지정, 선언적 접근 권한 부여
  • use/as로 임포트/이름 변경 조합
  • 내장 테스트 지원으로 코드에 직접 테스트 모듈 선언 및 빌드, 자동 실행 가능

제네릭

  • Ada는 패키지/프로시저 단위 제네릭만 지원(타입 단독 지원 불가)
  • Rust는 타입 자체에 제네릭 적용 가능(템플릿 기반)
  • Ada는 타입 범위 등 추가 특성을 범위 타입, 서브타입 등으로 명확히 표현 가능, Rust는 인스턴스 상수 활용

열거형 타입 비교(Case Study 3)

  • Ada는 간결한 선언과 동시에 자동적으로 이산형, 순서형, 반복 루프/인덱스 활용을 지원
  • Rust enum도 선언은 비슷하나, 패턴 매칭 및 반복 등 접근은 더욱 명시적 방식이 필요

결론

  • 고수준 명세 타입 및 검증성, 실행 시 체크 등에서 Ada가 더 엄격한 통제력 제공
  • 함수형 프로그래밍 스타일, 매크로 프로그래밍, 컴파일러 보조 에러 처리 등에서 Rust가 개발 편의성과 안전성 모두에서 우위
  • 실전 문제 해결에서 Ada는 오래된 코드 호환성과 유지보수 강점을, Rust는 현대적 개발 도구생태계와 안전/병렬성 지원에 이점을 보임
Hacker News 의견
  • Ada에는 정말 괜찮은 아이디어들이 많았음에도, 대부분 안전이 매우 중요한 분야에만 쓰인 점이 아쉬움으로 남음. 특히 숫자 타입의 값 범위를 제한하는 기능은 특정 버그 예방에 아주 유용함. Spark Ada는 배우기도, 실제로 SIL 4(가장 엄격한 소프트웨어 안전 기준) 준수 소프트웨어 개발에도 적용하기 쉬웠음. 지난 수십 년간 소프트웨어 산업이 ‘성장 우선, 안정성은 뒷전’으로 치달았지만, 이제 다시 안전한 소프트웨어 개발로 돌아가려는 흐름이 느껴짐. 그간 축적된 안전 관련 교훈들이 더 나은 언어로 이어질 수 있었으면 하는 바람임. 현실은 좋은 아이디어들이 마이너 언어 속에 숨어 사라지기 일쑤였음
    • 소프트웨어 개발을 오래 하다 보면 ‘바퀴의 재발명’이 정말 많음을 느끼게 됨. Ada와 Rust 모두 안전성 추구라는 점에선 닮았지만, 그 정의와 적용 범위는 다름. Rust는 굉장히 집중된 형태의 중요한 안전을 아주 강렬히 추구하고, Ada는 더 넓고 구체적인 안전의 정의를 지님. 내가 90년대 초에 Ada를 배웠을 때 가장 흔한 비판이 언어가 지나치게 크고 복잡해서 개발 속도를 떨어뜨린다는 점이었음(당시 Ada 83 검증 컴파일러가 한 명당 오늘날 가치로 약 2만 달러였음). 그런데 시대가 변해 Rust처럼 크고 복잡한 언어가 실제 안전한 동시성 프로그래밍에 필요하다는 것을 다들 인정하게 됨
    • Nim도 Ada와 Modula에서 영감을 받아 타입의 값 범위를 제한하는 subrange를 지원함
      type
        Age = range[0..200]
      
      let ageWorks = 200.Age
      let ageFails = 201.Age
      
      컴파일할 때 201은 Age 타입으로 변환이 안 된다는 오류가 뜸
      Nim Subranges 설명 링크
    • Ada(GNAT 기준)는 컴파일 타임에서 물리 단위/치수 분석(즉, 단위 체크)를 지원함. 엔지니어링 현장에선 아주 실용적인데, 왜 다른 언어들은 이런 중요한 기능을 서드파티 라이브러리로만 제공하는지 의문임
      관련 문서
    • C++에서도 값 범위가 제한된 숫자 타입을 코드로 쉽게 만들 수 있음(표준 라이브러리에는 없지만 직접 구현은 매우 쉬움). 일부 안전 체크는 런타임이 아니라 컴파일 타임에 할 수 있음. 이런 기능은 모든 언어가 표준으로 지원했으면 좋겠음
    • Ada에서 제일 아쉬운 부분은 객체지향(OOP)에 대한 명확한 접근임. 대부분 언어는 OOP 개념을 “클래스”라는 한 덩어리에 집어넣는데, Ada는 메시지 전달, 동적 디스패치, 서브타이핑, 제네릭 등을 개별적으로 선택 적용할 수 있게 해 줌. 이 각각의 기능들이 예쁘게 결합되는 방식이 아주 마음에 들었음
  • 저자는 Ada에는 공식 명세가 있고 Rust에는 없다는 점 등을 차이로 언급하지만, 실제로 사용자 입장에선 그보다 언어의 채택/생태계(툴링, 라이브러리, 커뮤니티)가 더 중요함. Ada가 항공우주/안전 분야 등에서 성공적이었고 AOC나 임베디드 저수준 작업에 적합하긴 하지만, 실제 현실 프로젝트(분산 시스템, OS 컴포넌트 등)에서는 데이터 포맷, 프로토콜, IDE 지원, 동료들과의 협업 같은 요소들이 비중이 큼. 결국 처음 언어를 고를 때 이런 환경적인 부분이 결정적임
    • 최근 Rust도 Ferrocene에서 Ada 명세 스타일을 참고해 스펙 문서를 기증함. 공개되어 있으니 참고 가능함
      Rust 스펙
    • Rust와 Ada 모두 엄밀한 의미의 “형식적(formal) 명세”(기계적으로 증명 가능한 문서)로는 약함. Spark Ada조차 언어 의미론적 가정들이 자리 잡고 있지만, 이조차 완전히 공식적이고 기계가 읽을 수 있는 방식은 아님
    • 항공기 제어 소프트웨어의 개발자들도 “현실 실제 환경에서 중요하지 않은 일이라면 저희 프로세스는 과한 거 맞겠네요”라고 답할 것임. 실제로 안전이 중요한 분야에서 Ada처럼 엄격한 언어와 프로세스가 오히려 표준임
  • Ada가 러스트보다 타입 관련 기능은 부족해도 코드의 가독성 면에서는 Ada가 오히려 뛰어날 때가 많다는 점이 인상적이었음. 비교 글에는 컴파일러 속도 측면 언급이 빠졌는데, Ada가 복잡한 언어라 느껴졌던 건 과거 이야기고 요즘 Rust와 비교하면 꼭 그렇진 않을 수 있음. 이 글을 보고 Ada로 진짜 프로젝트 해보고 싶어졌음
    • "타입 관련 단점"이 정확히 무엇을 의미하는지 궁금함. 내 경험상 Ada는 타입 시스템이 엄청나게 표현력이 강함. 사용자 정의 값 범위 타입, 임의의 나열형으로 인덱스 가능한 배열, 타입별 연산자 정의, 컴파일 런타임 체크/사전조건/사후조건 등 타입에 다양한 보조 기능 추가 가능. 변별 레코드, 구조체 표현 클라우스 등도 있음. 오히려 단점이 아니라 막강한 기능임
  • Ada와 Rust의 문자열 차이에 대해 얘기하고 싶음. Ada는 1980년대 초 설계 당시 문자(char) 배열을 “문자열”로 했기에 그냥 바이트 배열로 인덱싱이 쉬움. Rust는 기본적으로 유니코드를 의식한 설계라, Rust의 문자열은 UTF-8 인코딩된, 즉 진짜 ‘텍스트’임. 그래서 Ada는 배열처럼 랜덤 인덱싱이 되지만 Rust는 문자열 개념이 달라서 그냥 바이트 배열로 바꾸는 선택지도 있음
    • Ada의 내장 유니코드 문자열은 일반적으로 UTF-32 배열임. Rust와 달리 UTF-8 리터럴을 직접 제공하지 않고, 8/16/32비트 배열로부터 변환해야 함
    • Rust 문자열에도 인덱싱은 가능함. 다만 Rust는 문자열을 일반 배열처럼 취급하지 않고 sub-string 슬라이스를 주로 사용함. 특정 문자 중간을 잘라 인덱싱하면 panic이 발생함(유니코드 인코딩 값의 경계를 어기는 케이스). AoC 처럼 항상 ASCII만 쓴다면 [u8] 또는 str::as_bytes 메서드로 바이트 슬라이스를 쓰는 게 맞음
  • Rust가 “동시성(concurrent) 프로그래밍을 기본으로 지원하지 않는다”는 저자의 말이 이상하게 느껴짐. Rust는 스레드(thread) 기능이 언어에 내장되어 있고, 오히려 async보다 사용이 쉬움. 아주 많은 스레드가 필요해서 리소스 한계에 부딪힐 때만 문제가 될 뿐, 대다수 소프트웨어에는 built-in 스레드가 충분함
    • (Rust 비사용자로서 진심 궁금함) Rust에서 스레드와 async에서의 중단(cancel) 처리가 어떻게 다른지, 그리고 타언어의 async와 어떤 차이가 있는지 알고 싶음. C++, Python, C#에서는 async의 중단 관리가 스레드보다 훨씬 나았음. Rust에서는 이런 취소/중단 관리를 예외로 처리하지 않아 오히려 좀 더 어렵다고 들었는데 실제 현업 경험이 궁금함. Ada에서는 이런 중단 관리가 어떨지도 듣고 싶음
    • Tokio와 같은 워크 스틸링 스케줄러가 단순히 스레드를 여러 개 돌리는 것보다 실제로 얼마나 빠른지 경계선이 궁금함. 단순 배열(예: VecMap)도 원소 수가 작을 땐 빠르지만 결국 일정 수 넘어가면 다른 자료구조가 더 효율적인 것과 비슷하다고 생각함. 실제 어느 지점부터 워크 스틸링이 이득인지 궁금함
    • 현실적으로 Async를 쓰는 주된 이유는 사용하는 서드파티 crate가 Async이기 때문임. (예: Reqwest는 Tokrio 필요) 높은 수준의 앱 개발에서 비Async만 고집하다 보면 결국 한계에 부딪히게 됨
    • 플랫폼이 스레드 지원이 약한 환경(WASM, 임베디드 등)에서는 Async가 차라리 더 적합함. 수십만 명이 블로그에 동시에 접속하는 상황은 비현실적인데 오히려 이런 환경에서 Async의 필요성 주장하는 건 과장인 듯함
  • Ada에도 오픈소스 컴파일러가 있다는 점이 흥미로웠음. 예전엔 모두 독점 컴파일러만 있다고 생각해서 아예 Ada에 관심을 두지 않았는데, 다시 한번 확인해 봐야겠음
    • GNAT 컴파일러는 출시된 지 30년 이상 됐고, 한때 GPL 런타임 예외가 없어 컴파일 결과물도 GPL이 되어야 한다는 오해가 있었으나, 현재는 이슈가 해결됨
    • GNAT는 90년대부터 GCC에 기반해서 만들어졌고, 일부 대학에서는 실시간 프로그래밍처럼 실습 중심 과목에 GNAT를 직접 활용했음. Ada를 프로그래밍 입문용으로 쓰려다 Pascal, C++로 곧 갈아탔던 경험도 있음
  • 3D프린팅 분야에서 관심 갔던 프로젝트 중 최근엔 Prunt라는 프린터 컨트롤 보드와 펌웨어가 있었음. Ada로 펌웨어를 개발하는데, 꽤 색다르면서도 개념적으로 잘 어울리는 선택임
    Prunt 홈페이지
    Prunt GitHub
  • Case Study 2 끝부분에 “클라이언트가 SIDE_LENGTH를 알아야 한다면 반환하는 함수를 추가하라”고 했는데, 굳이 함수가 아니라 pub const SIDE_LENGTH: usize = ROW_LENGTH;처럼 상수 선언을 쓰는 게 더 직접적인 방법임
  • 두 언어가 모두 스택 중심 프로그래밍을 장려한다는 주장에 동의하지 않음. Ada는 오히려 정적 할당 방식을 적극적으로 권장함
  • Ada의 배열 인덱스가 임의 타입일 수 있다는 점이 큰 장점처럼 소개된 것이 신기했음. 거의 모든 언어에 딕셔너리(해시맵)가 표준 라이브러리로 있는데, Rust에는 두 가지가 제공됨
    • 여기서 말하는 것은 언어 내장 배열 얘기임. 예를 들어 Ada에서는 “eggs”라는 배열의 인덱스를 BirdSpecies 타입으로 지정하면 eggs[Robin], eggs[Seagull] 등은 말이 되지만 eggs[5]는 허용이 안 됨. Rust에서도 원하는 자료구조(예시로 Index<BirdSpecies> 구현)를 만들 수 있고 eggs[Robin]은 되지만 eggs[5]는 에러임. 다만 Rust는 언어 자체적으로 이걸 “배열”로 직접 지원하지는 않음. Ada처럼 “사용자 정의 타입이 부분집합인 정수형으로 선언 가능”할 때 이런 인덱싱이 진가를 발휘함. Rust에선 아직 순수 사용자 정의 타입으로 범위제한 인티저 등은 만들 수 없음(내부적으로만 NonZeroI16 등 제공). 이런 수준까지 Rust가 지원하면 진짜 좋을 것임
    • Ada에도 해시맵, 집합 구조체 기본 지원함. Ada 컨테이너 관련 표준(A.18 절 참고). 배열 인덱스 타입에 전형적인 ‘연속된 값’(예: 0~N-1) 범위를 쓸 수 있다는 점은 빽빽한 맵이나 연속된 메모리 접근이 중요한 상황에선 딕셔너리보다 훨씬 빠르고 캐시 효율까지 좋아서 장점임
    • Ada에서의 배열 인덱스 타입 제한(subtype)은 딕셔너리와는 구조적으로 완전히 다른 개념임. 배열 인덱스의 값 종류까지 언어 차원에서 제한 가능함