러스트가 추구하는 것은 C보다 빠르면서 안정적인 것이다. C나 C++은 프로그래머가 메모리를 제어한다. 그러한 이유로 실수가 발생할 수 있다. 메모리를 해제하지 않으면 메모리 누수가 발생할 수 있고 메모리를 중복하여 해제하면 보안에 큰 결함이 발생한다. 또한 이러한 오류는 프로그램의 각종 오류를 발생시킨다.


인터프리터 언어

그래서 Java와 Python 같은 언어들은 프로그래머가 실수하기 쉬운 메모리 관리를 직접 해준다. 프로그래밍의 난이도를 낮춰주고 안정적이지만 이들이 C나 C++에 비해서 느리다는 것을 익히 들어봤을 것이다. 이들이 느린 이유는 변수의 형태를 동적으로 인식한다는 인터프리터의 특성도 있지만 위에서 언급한 것 처럼 메모리를 언어 차원에서 관리해주기 때문이다. 대표적인 기술이 가비지 컬렉션(Garbage Collection)이라는 기술이다.

가비지 컬렉션

가비지 컬렉션은 아무런 비용소모 없이 동작하는 기적의 마법이 아니다. 파이썬만 하더라도 메모리가 얼마나 참조되고 있는지 레퍼런스 카운팅(Reference Counting)을 실시하고 순환 참조(Reference Cycles)가 발생하는지 감시한다. 가비지 컬렉션은 많은 메모리와 연산을 필요로 한다. 이처럼 가비지 컬렉션은 많은 비용을 소모시키고 궁극적으로 속도를 떨어트린다.

사람이 느끼는 빠른 속도란 예측 가능한 일관적인 반응 속도를 내는 것인데 파이썬과 자바의 경우에는 이를 보장하기가 어렵다. 어느 순간 가비지 컬렉션이 발생할지 알 수 없기 때문이다. 어떤 사람에게 당장 결과가 필요한 순간일지라도 파이썬은 가비지 컬렉션을 동작시킬 수 있다. 그게 생사를 가르는 순간일지라도 GC는 돌아간다. 물론 하드웨어 성능 향상과 더불어 가비지 컬렉션의 알고리즘은 뛰어난 엔지니어들의 손에서 뛰어난 발전을 거듭하고 있다.


그래서 러스트가

필자가 인식한 러스트라는 언어는 한 마디로 ‘컴파일 시간에 가비지 컬렉션을 돌리는 언어’다. 러스트의 핵심에는 소유권이라는 개념이 있는데 러스트 공식 문서에선 아래와 같이 설명하고 있다.

메모리는 컴파일 타임에 컴파일러가 체크할 규칙들로 구성된 소유권 시스템을 통해 관리됩니다. 소유권 기능들의 어떤 것도 런타임 비용이 발생하지 않습니다.

소유권 규칙

러스트는 이러한 소유권을 통해서 GC가 필요 없는 메모리 안전성을 구현시킬 수 있었다. 위에서 언급된 컴파일러가 체크할 규칙은 아래와 같다.

  • 러스트의 각각의 값은 해당값의 오너라고 불리는 변수를 갖고 있다.
  • 한번에 딱 하나의 오너만 존재할 수 있다.
  • 오너가 스코프 밖으로 벗어나는 때, 값은 버려진다.

물론 이러한 소유권이라는 개념은 러스트에서 새롭게 착안한 방법이므로 프로그래머가 익숙해지는데 시간이 걸릴 것이라고 언급하며 익숙해지면 안전하고 효율적인 코드를 개발하게 되리라고 한다.


배우고 싶은 언어

필자는 존경하는 개발자인 포프님의 조언에 따라 두가지 언어를 꼭 마스터하고 싶다. 하나는 메모리를 관리해주는 언어 하나는 메모리를 관리하는 언어. 관리해주는 언어로는 파이썬을 정복하고 싶고 관리하는 언어로는 러스트를 익히고 싶다. 물론 말씀하신 메모리를 관리하는 언어는 C, C++을 말씀하신 건데 그럼 러스트도 관리해주는 언어로 봐야하나? 여하지간 함수형 프로그래밍을 공부한다는 생각으로 해봐야겠다.

기초적인 학습
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 외부에 의존하는 크레이트가 있음
extern crate rand;

// 러스트는 필요한 라이브러리를 아래와 같이 호출함
use std::io;
use std::cmp::Ordering;

use rand::Rng; // 정수 생성기

// fn은 함수의 시작을 나타냄 ()는 인자가 없음을 표현함 {는 함수의 시작을 뜻함
fn main() {
    // println!에서 !는 매크로를 의미하며 여기서는 단순히
    // 문자열을 매크로를 통하여 화면에 출력하는 것을 나타냄
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1, 101);
    println!("The secret number is: {}", secret_number);

    loop { // 무한루프
        println!("Please input your guess.");

        // 아래는 값을 변수에 저장하는 부분
        // let foo = 5; // 불변
        // let mut bar = 5; // 가변
        let mut guess = String::new(); // 가변 변수인 문자열

        // 라이브러리에 존재하는 함수 사용함
        // &는 참조자로서 guess를 넘겨주는데
        // 참조자는 기본적으로 불변이라 &mut로 선언한 것임
        io::stdin().read_line(&mut guess)
            .expect("Falied to read line");
        // 긴 문법으로 호출할 경우 위와같이 라인을 분리하는 것이 좋음
        // read_line이 돌려주는 값은 io::Result임 이는 열거형으로 되있으나 차후에 다룸
        // expect를 하지 않아도 되지만 컴파일시 경고가 나타남

        // i32(정수), u32(부호없는 정수)
        // 이전에 있던 값을 아래와 같이 shadowing하는 것을 허용함
        // 사용자가 엔터를 입력하면 \n이 입력되므로 trim()을 실시함
        let guess: u32 = match guess.trim().parse() {
            // parse는 Reslt 타입을 반환하므로 match할 수 있음
            // 성공하면 Ok를 반환하고 아래와 매칭됨
            Ok(num) => num,
            // _는 모든 값을 매칭함
            Err(_) => continue,
        };

        // {}는 변경자로서 값이 표시되는 위치
        // println!("x = {}, y = {}", x, y);
        println!("You guessed: {}", guess);

        // cmp 메소드를 이용하여 두 값을 비교
        match guess.cmp(&secret_number) {
            Ordering::Less      => println!("Too small!"),
            Ordering::Greater   => println!("Too big!"),
            Ordering::Equal     => {
                println!("You win!");
                break;
            },
        }
    }
}
WRITTEN BY

배진오

소비적인 일보단 생산적인 일을 추구하며, 좋아하는 일을 잘하고 싶어합니다 :D
im@baejino.com