디시인사이드 갤러리

갤러리 이슈박스, 최근방문 갤러리

갤러리 본문 영역

Ada vs. Rust: 동일 안전성 수준 코드 비교

루비갤로그로 이동합니다. 2025.07.04 00:14:53
조회 44 추천 0 댓글 0

최근 Rust 커뮤니티에서 메모리 안전성 보장 방식과 관련하여 코드 컴파일 논란이 있었습니다. 필자가 제시한 Rust 코드 예시(node-fail.rs)가 "컴파일이 되지 않는다"고 설명했음에도 불구하고, 일부에서는 "아무 이상 없이 잘 된다"며 필자를 비방하는 주장이 나왔습니다. 이 논란은 Rust의 **보로 체커(Borrow Checker)**가 메모리 안전성을 어떻게 강제하는지, 그리고 이 강제성이 개발 경험에 어떤 영향을 미치는지를 여실히 보여주는 사례입니다.


이 게시글에서는 Rust의 컴파일 실패 코드를 다시 한번 명확히 제시하고, 이와 대비되는 컴파일 성공 코드를 통해 Rust가 메모리 안전성을 어떻게 달성하는지 보여드리겠습니다. 나아가, 동일한 **'최고 수준의 안전성'**을 목표로 할 때, Ada는 Rust와 어떻게 다른 접근 방식을 취하는지 코드를 통해 비교 분석하며, 단순한 기술 논쟁을 넘어선 언어 철학의 차이를 조명하고자 합니다.


1. 러스트 (Rust): '규칙'이 '의도'를 가로막는 경우

Rust의 보로 체커는 메모리 안전성을 위해 개발자에게 특정 규칙을 강제합니다. 이는 직관적인 코드를 작성하려 할 때 컴파일 실패로 이어지기도 합니다.


1.1. 직관적이지만 '실패'하는 Rust 코드 (컴파일 불가)

아래 코드는 C/C++ 사용자라면 자연스럽게 생각할 수 있는 부모-자식 참조 구조입니다. 하지만 Rust의 보로 체커는 이 코드를 절대로 컴파일하지 않습니다.


Rust


// node-fail.rs

// 이 코드는 컴파일되지 않습니다! (에러 발생)


struct Node<'a> {

    parent: Option<&'a Node<'a>>, // 부모를 가리키는 참조

    // ... 다른 필드들 (생략)

}


fn main() {

    let mut root = Node { parent: None, /* ... */ };


    // 컴파일 에러: `root`의 소유권과 빌림 규칙이 충돌합니다.

    // 보로 체커는 가변 참조와 불변 참조의 동시 존재를 허용하지 않습니다.

    let child = Node { parent: Some(&root), /* ... */ };

}

컴파일 실패 원인:


이 코드를 컴파일하면 error[E0502]: cannot borrow ... 에러가 발생합니다. let mut root로 선언된 root 변수가 가변(mutable) 상태인데, child를 생성하며 parent: Some(&root)를 통해 root에 대한 **불변 참조(immutable reference)**를 동시에 만들려 하기 때문입니다. Rust의 보로 체커는 이러한 동시 참조를 막아 **데이터 경쟁(Data Race)**과 같은 런타임 메모리 오류를 컴파일 시점에 방지합니다.


이 실패는 '자식이 부모를 가리킨다'는 개발자의 단순한 의도가 '데이터는 단 하나의 가변 소유자만 가져야 하며, 빌림 규칙은 순환을 만들 수 없다'는 컴파일러의 엄격한 규칙과 정면으로 충돌하는 상황을 보여줍니다.


1.2. '성공'하지만 복잡한 Rust 코드 (컴파일 가능)

이 문제를 안전한(safe) Rust 코드로 해결하려면, 개발자는 자신의 직관적인 의도를 포기하고 컴파일러의 규칙을 통과하기 위해 **Rc, RefCell, Weak**라는 복잡한 스마트 포인터 개념들을 동원한 '우회로'를 설계해야 합니다.


Rust


// node.rs

// 이 코드는 정상적으로 컴파일되고 실행됩니다.


use std::rc::{Rc, Weak};

use std::cell::RefCell;

use std::fmt;


// Node 구조체 정의

// Rc, Weak, RefCell을 사용하여 공유 소유권 및 내부 가변성을 관리합니다.

struct Node {

    value: String,

    parent: RefCell<Weak<Node>>,     // 부모는 약한 참조 (순환 참조 방지), RefCell로 내부 가변성

    children: RefCell<Vec<Rc<Node>>>, // 자식들은 강한 참조 (공유 소유권), RefCell로 내부 가변성

}


// Node의 소멸자 구현: 객체가 해제될 때 메시지 출력

impl Drop for Node {

    fn drop(&mut self) {

        println!("Node '{}' destroyed.", self.value);

    }

}


impl Node {

    fn new(value: &str) -> Rc<Self> {

        let node = Rc::new(Node {

            value: value.to_string(),

            parent: RefCell::new(Weak::new()),

            children: RefCell::new(Vec::new()),

        });

        println!("Node '{:?}' created.", node);

        node

    }


    fn add_child(parent_node: Rc<Node>, child_node: Rc<Node>) {

        *child_node.parent.borrow_mut() = Rc::downgrade(&parent_node);

        parent_node.children.borrow_mut().push(child_node);

    }


    fn get_parent_value(&self) -> Option<String> {

        self.parent.borrow().upgrade().map(|p| p.value.clone())

    }


    fn get_child_values(&self) -> Vec<String> {

        self.children.borrow().iter().map(|c| c.value.clone()).collect()

    }

}


fn main() {

    println!("\n--- Rust 노드 생성 및 관계 설정 ---");

    let root = Node::new("root_node");

    let child1 = Node::new("child_1");

    let child2 = Node::new("child_2");


    Node::add_child(Rc::clone(&root), Rc::clone(&child1));

    Node::add_child(Rc::clone(&root), Rc::clone(&child2));


    let grandchild1 = Node::new("grandchild_1");

    Node::add_child(Rc::clone(&child1), Rc::clone(&grandchild1));


    println!("\n--- 현재 Rust 트리 상태 ---");

    println!("루트 자식들: {:?}", root.get_child_values());

    println!("자식_1 부모: {:?}", child1.get_parent_value());

    println!("손자_1 부모: {:?}", grandchild1.get_parent_value());

    println!("자식_1 자식들: {:?}", child1.get_child_values());


    println!("\n--- Rust 객체 수명 주기 시연 ---");

    // root 변수를 drop하여, root와 그에 종속된 강한 참조들(child1, child2)을 해제합니다.

    drop(root); 

    

    println!("\nroot 변수를 drop() 한 후:");

    println!("손자_1 부모 (이제 없음): {:?}", grandchild1.get_parent_value());

    println!("손자_1 자식들: {:?}", grandchild1.get_child_values());

    // main 함수 종료 시 남아있는 grandchild1 노드가 파괴됩니다.

}

Rust 방식 분석:


단순한 부모-자식 관계를 표현하기 위해, 개발자는 Rc (참조 카운팅 포인터), RefCell (내부 가변성), Weak (약한 참조), borrow_mut() (가변 빌림), downgrade() (약한 참조로 변환) 등 수많은 난해한 개념과 마주해야 합니다. 개발자의 초점은 '트리 구조'라는 원래 문제에서 벗어나, '어떻게 하면 보로 체커를 통과할까'라는 Rust의 규칙 자체를 해결하는 문제로 변질됩니다. Rust의 안전성은 컴파일러가 강제하지만, 그 대가로 개발자는 비직관적인 코드와 씨름해야 합니다.


2. Ada/SPARK: '의도'를 코드로 직접 표현하는 경우

Ada/SPARK 생태계는 개발자의 의도를 존중하고, 안전성을 다층적으로 확보하는 길을 제시합니다. 이들은 Rust와는 다른 철학으로 동일한 안전성 목표에 도달합니다.


2.1. Ada: 직관적인 구조 표현과 런타임 안전성

Ada 언어 자체는 access라는 '포인터' 타입을 사용하여 개발자의 의도를 직접적으로 표현하도록 돕습니다. Rust처럼 컴파일 타임에 모든 메모리 안전성을 강제하지는 않지만, 런타임에 강력한 안전망을 제공합니다.


Ada


-- Ada 코드: node.ads (패키지 사양 - 정의)

package Node_Types is

   -- `Node` 타입을 미리 선언하여 자기 참조 구조가 가능하게 합니다.

   type Node;

   -- `Node` 타입을 가리킬 수 있는 '포인터' 타입을 정의합니다.

   type Node_Access is access all Node;


   -- 실제 `Node` 타입을 정의합니다.

   type Node is record

      Parent   : Node_Access;       -- 부모를 가리키는 포인터

      Children : Node_Access_Array; -- 자식들을 담는 배열 (예시용)

   end record;


   -- (여기서는 Children에 대한 자세한 구현은 생략하고 개념만 제시)

   type Node_Access_Array is array (Positive range <>) of Node_Access;


   -- 프로시저 선언

   procedure Create_And_Link_Nodes;

   procedure Print_Node_Info (Item : in Node_Access);


end Node_Types;



-- Ada 코드: node.adb (패키지 본문 - 구현)

with Ada.Text_IO; use Ada.Text_IO;

with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;


package body Node_Types is


   -- Create_And_Link_Nodes 프로시저 구현

   procedure Create_And_Link_Nodes is

      Root  : Node_Access := new Node;

      Child : Node_Access := new Node;

   begin

      Put_Line ("--- Ada 노드 생성 및 관계 설정 ---");

      Root.all.Parent := null; -- 루트 노드의 부모는 없음

      Root.all.Value := To_Unbounded_String("root_node"); -- 값 설정 (예시 추가)


      Child.all.Parent := Root; -- 자식 노드의 Parent 필드가 부모 노드(Root)를 직접 가리킵니다.

      Child.all.Value := To_Unbounded_String("child_node"); -- 값 설정 (예시 추가)

      

      -- 실제 Children 목록에 추가하는 로직은 복잡하여 간단히 생략

      -- Root.all.Children.Append(Child);


      Put_Line ("Root created.");

      Put_Line ("Child created and linked to Root.");

      

      Print_Node_Info(Root);

      Print_Node_Info(Child);

   end Create_And_Link_Nodes;


   -- 노드 정보 출력 프로시저

   procedure Print_Node_Info (Item : in Node_Access) is

   begin

      if Item /= null then

         Put_Line ("Node Value: " & To_String(Item.all.Value));

         if Item.all.Parent /= null then

            Put_Line ("  Parent Value: " & To_String(Item.all.Parent.all.Value));

         else

            Put_Line ("  Parent: None");

         end if;

      end if;

   end Print_Node_Info;


end Node_Types;



-- Ada 코드: main.adb (메인 프로그램)

with Node_Types; use Node_Types;

with Ada.Text_IO; use Ada.Text_IO;


procedure Main is

begin

   Node_Types.Create_And_Link_Nodes;

   -- 메모리 해제는 Ada의 접근 타입(access type)과 힙 관리자에 의해 자동으로 처리되거나

   -- 개발자가 Unchecked_Deallocation을 통해 명시적으로 처리할 수 있습니다.

   -- 여기서 할당된 Node들은 Main 프로시저 종료 시 암묵적으로 접근 불가능하게 됩니다.

   -- SPARK와 함께 사용 시 런타임 오류나 메모리 릭이 없음을 증명할 수 있습니다.

end Main;

Ada 방식 분석:


Ada는 'access' 타입을 통해 포인터를 직접적으로 다룰 수 있게 하여 개발자의 의도("노드는 다른 노드를 가리킬 수 있다")를 매우 간결하게 표현합니다. Rust와 달리 컴파일 시점에 엄격한 보로 체커를 강제하지 않습니다. 대신, 개발자가 Parent가 null인 상태에서 Parent.all과 같이 역참조를 시도하면, 컴파일러는 이 코드를 막는 대신 런타임에 Constraint_Error라는 예외를 발생시키는 코드를 기본적으로 삽입합니다. 즉, 런타임에서 안전장치가 작동하는 방식입니다. 이는 시스템이 예외를 처리한 후에도 임무를 지속하는 **'회복력(Resilience)'**을 가질 수 있게 합니다.


2.2. SPARK: 명시적 계약을 통한 '증명된' 컴파일 타임 안전성

SPARK는 Ada의 런타임 검사조차 필요 없게 만듭니다. 개발자가 자신의 '의도'를 '계약(Contract)'으로 명시하면, 정적 분석 도구(Prover)가 "이 코드는 런타임 에러가 절대 발생하지 않는다"는 것을 수학적으로 증명합니다.


Ada


-- SPARK 코드 (Ada 패키지 사양에 추가된 계약)

package Node_Types is

   -- ... (Node 타입, Node_Access 등 위와 동일)


   -- 노드 정보 출력 프로시저 (SPARK 계약 추가)

   procedure Print_Node_Info (Item : in Node_Access)

      with

         -- 사전 조건(Pre-condition)으로 "Item은 null이 아니다"라고 명시합니다.

         -- 이것이 개발자의 '의도'이자, Prover에게 증명을 요청하는 '계약'입니다.

         Pre => Item /= null; -- null 포인터 역참조 방지 계약

   

   -- 부모 노드 값을 출력하는 함수 (SPARK 계약 추가)

   function Get_Parent_Value (Item : in Node_Access) return String

      with

         Pre => Item /= null and then Item.all.Parent /= null; -- Item과 부모 모두 null이 아님을 계약

end Node_Types;

SPARK 방식 분석:


SPARK는 개발자에게 자신의 코드가 안전하다는 것을 **'스스로 증명'**하도록 요구합니다. 도구는 개발자의 이 선언이 타당한지를 수학적으로 검증해 줄 뿐입니다. 이 방식은 정수 오버플로우, 배열 인덱스 초과, 0으로 나누기 등 모든 종류의 산술적 오류까지 포함하여 런타임 오류 자체가 원천적으로 발생하지 않음을 증명할 수 있습니다.


3. 결론: '안전성'을 향한 서로 다른 길



구분

러스트 (Rust)

Ada/SPARK

핵심 철학

'규칙'이 '의도'를 제약

'도구'가 '의도'를 지원하고 검증

안전성 확보

컴파일러가 암묵적 규칙자동으로 강제

개발자가 명시적 계약을 작성하고 도구가 증명

개발자 경험

컴파일러의 규칙을 통과하기 위한 '싸움'

자신의 의도를 코드로 표현하고, 그 안전성을 계약으로 증명

주요 어려움

언어의 고유한 패러다임(소유권)에 대한 적응

문제에 대한 형식적이고 논리적인 사전 분석 및 '계약' 작성

안전성의 기본값

성능 우선 (안전은 Opt-in)

안전 우선 (런타임 체크가 기본, 증명은 Opt-in)



Rust의 방식은 '개발자의 단순한 의도'와 '컴파일러의 엄격한 규칙'이 정면으로 충돌하는 상황에서 개발자에게 복잡한 '우회로'를 강제합니다. 하지만 그 대가로 C/C++에서 발생하는 치명적인 메모리 오류를 컴파일 타임에 '자동으로' 막아냅니다.


반면 Ada/SPARK는 '개발자의 의도'를 코드로 직접 표현하게 하고, 그 의도에 대한 안전성을 런타임 검사 또는 수학적 증명을 통해 보장합니다. 이는 Rust와는 다른 차원의 '안전성'과 '회복력'을 제공합니다.


이 비교를 통해, 우리는 두 언어가 '안전성'에 도달하는 길이 근본적으로 다름을 알 수 있습니다. Rust는 개발자의 실수를 원천적으로 막는 엄격한 '자동화된 규칙'을 선택했고, Ada/SPARK는 개발자의 '명시적인 의도와 증명'을 통해 안전성을 확보하는 길을 선택했습니다. 어느 쪽이 더 합리적인지는, 결국 프로젝트의 요구사항과 개발자의 철학에 달려있을 것입니다.


-------------


러스트의 결계 결함으로 볼 수 있는 지점입니다.

바로 이점이 러스트 빠돌이들의 발작 포인트입니다.

극대노를 넘어서 비판자를 향해 허위사실 유포, 인신공격 등을 서슴치 않죠.

추천 비추천

0

고정닉 0

0

댓글 영역

전체 댓글 0
본문 보기

하단 갤러리 리스트 영역

왼쪽 컨텐츠 영역

갤러리 리스트 영역

갤러리 리스트
번호 제목 글쓴이 작성일 조회 추천
설문 현역으로 군대 안 간게 의아한 스타는? 운영자 25/06/30 - -
AD 휴대폰 바꿀까? 특가 구매 찬스! 운영자 25/07/02 - -
공지 프로그래밍 갤러리 이용 안내 [88] 운영자 20.09.28 45203 65
2869965 '백종원 이혼하네요... ㅇㅇ(106.244) 20:59 0 0
2869964 나는조현병이야 나는내향적이야 손발이시립디다갤로그로 이동합니다. 20:58 0 0
2869962 코볼은 현재 얼마나 쓰임? ㅇㅇ갤로그로 이동합니다. 20:53 8 0
2869961 누가 멍유 좀 내쫓아봐 [1] ♥냥덩이♥갤로그로 이동합니다. 20:51 4 0
2869960 중요한 것은 내가 러스트 언어의 잠재력을 봤다는거지 프갤러(27.177) 20:34 10 0
2869958 겜 좀 했더니 전략적 판단력이 올라가네 프갤러(211.234) 20:21 10 0
2869956 오랜만에 겜 좀 했더니 러스트가 그렇게 중요한가 싶다. 프갤러(211.234) 20:18 14 0
2869953 엄마냥과 아가냥❤+ [1] ♥냥덩이♥갤로그로 이동합니다. 20:13 13 0
2869951 러스트쟁이들이 많아져야한다 [1] 뒷통수한방(1.213) 20:06 13 0
2869950 좇센만큼살기좋은나라가어딨다고 에휴 ㅉㅉ [2] 뒷통수한방(1.213) 19:55 16 0
2869949 집으로 가장~ ♥냥덩이♥갤로그로 이동합니다. 19:54 8 0
2869948 새끼냥이 울고 있어양.. 주울깡..? [1] ♥냥덩이♥갤로그로 이동합니다. 19:49 15 0
2869947 폭똥 쌌당.. [1] ♥냥덩이♥갤로그로 이동합니다. 19:47 12 0
2869946 로또 당첨됐다. ㅇㅇ(211.235) 19:45 13 0
2869944 ❤✨☀⭐나님 시작합니당⭐☀✨❤ [1] ♥냥덩이♥갤로그로 이동합니다. 19:13 15 0
2869943 3장에서는 러스트 설계 철학의 모순에 대해 루비갤로그로 이동합니다. 19:03 13 0
2869942 이재명씨는 왜 스윗식스티가 됐을까? [4] 헬마스터갤로그로 이동합니다. 19:01 34 0
2869941 러빠 왜 안 보임 gg침?? [1] 루비갤로그로 이동합니다. 18:59 15 0
2869940 나경원 이 사진이 안웃기냐? [1] 헬마스터갤로그로 이동합니다. 18:59 27 0
2869939 동아시아 문화에 최적화된 AI 기반 조직관리 서비스 어떨 것 같음? 프갤러(211.204) 18:54 10 0
2869938 다른 에.드랑 급이다름..이건 꼭해야돼 2분에 7만눤이????? ㅇㅅㅇ(211.36) 18:35 14 0
2869937 mz빙고...jpg [2] ㅇㅇ갤로그로 이동합니다. 18:30 32 0
2869935 프부이들 치킨 ㄱㄱ? [1] 프갤러(14.45) 18:07 32 0
2869933 서민 대변한다던 의원들, 알보고니 최고 고소득자…극우정당 내로남불에 난리 [1] 발명도둑잡기(118.216) 17:56 19 1
2869930 남한테 설명한단 사실 하나만으로 실력이 느는듯 [4] ㅆㅇㅆ찡갤로그로 이동합니다. 17:49 44 0
2869928 휴지통에서 영구삭제한 파일 레큐바에도 안뜨는데 이거 걍 못찾는거임? [2] 프갤러(115.143) 17:38 22 0
2869927 오늘의 작사 실마리: 100만원으로 한 달 살기 발명도둑잡기(118.216) 17:38 15 0
2869924 세계 동물 수 비교 [1] 발명도둑잡기(118.216) 17:18 19 0
2869923 교정기 때문에 입안이 다 헐어서 다이어트가 저절로 된다 발명도둑잡기(118.216) 17:13 14 0
2869921 지금 갤에 뭐가 있길래 [1] 프갤러(113.59) 17:04 41 1
2869920 굳이 공부안해도 살아남는사람들은 뭐냐 [1] ㅇㅇ(39.118) 17:04 27 0
2869919 젤렌스키는 왜 중립을 포기했나? 우크라이나 전쟁, 다른 시선 심용환 [1] 발명도둑잡기(118.216) 17:03 21 0
2869918 ‘핵무기 야망’ 이유로 이란 비난한 서방, 자신들 안보는 핵무기로? 발명도둑잡기(118.216) 16:46 17 0
2869917 에어장 목사 사건 발명도둑잡기(118.216) 16:39 19 0
2869916 파이썬은 자바에 비하면 병신 언어 같음 ㅇㅇ갤로그로 이동합니다. 16:38 39 0
2869915 "딸이 모텔에…" 부모 신고→경찰 출동→39세 남성 3층 추락 발명도둑잡기(118.216) 16:35 21 0
2869913 냥덩 또 가짜뉴스 “시진핑 실각설은 국내 반중정서 키우는 독약” 발명도둑잡기(118.216) 16:24 26 0
2869912 '자유총연맹 지원 조례' 반대, 노원구의회 찾아간 청년들 발명도둑잡기(118.216) 16:22 11 0
2869911 20~30대 한남이 병신세대이긴 하네 ㅇㅅㅇ [1] 류류(118.235) 16:21 35 0
2869910 결국 문 닫은 미 USAID…'64년 대외 원조' 역사의 뒤안길로 [2] 발명도둑잡기(118.216) 16:20 19 0
2869909 한국 미사일 "세계최고 수준" 하지만 다른나라의 수출할수없는 이유? 발명도둑잡기(118.216) 16:19 17 0
2869908 트럼프 게임의 룰에 말려든 아시아, 이재명 대통령이 나설 때다 발명도둑잡기(118.216) 16:18 17 0
2869907 [팩트체크] "100만원 서울살이 가능할까"…현실은 '극기훈련' 발명도둑잡기(118.216) 16:16 15 0
2869906 ‘혐중’이란 병을 어떻게 치료할 것인가 [박노자의 한국, 안과 밖] [1] 발명도둑잡기(118.216) 16:14 18 0
2869905 이재명씨는 여성에게만 기회를 많이 주더라 [6] 헬마스터갤로그로 이동합니다. 16:13 51 0
2869904 잭 도시·일론 머스크 "모든 지적재산권 법 없애자"…AI 시대 [2] 발명도둑잡기(118.216) 16:10 23 0
2869903 트럼프 "내년 250주년 독립기념일 축제로 백악관서 UFC 경기" 발명도둑잡기(118.216) 16:08 14 0
2869902 [정동칼럼]전쟁은 두 사람만 미치면 시작된다 발명도둑잡기(118.216) 16:07 16 0
2869901 AI 잘 쓰는 사람은 따로 있다 [김상균의 메타버스] 발명도둑잡기(118.216) 16:06 17 0
뉴스 '꼬꼬무', 연쇄살인범 강호순 자백 최초 영상 공개. . . 10명 외 추가 피해자 존재 가능성 충격 보도 디시트렌드 07.04
갤러리 내부 검색
제목+내용게시물 정렬 옵션

오른쪽 컨텐츠 영역

실시간 베스트

1/8

뉴스

디시미디어

디시이슈

1/2