Rust-Lang의 특징 Hello, Rust!

'러스트 박살내기(?)' 시리즈Rust-Lang의 특징 Hello, Rust!

baealex

소비적인 일보단 생산적인 일을 좋아합니다.

Sign in to view email

Rust-Lang이 추구하는 것은 C, C++보다 빠르거나 동등한 속도를 내면서도 Python 혹은 JavaScript 처럼 안정적인 것이었고 현재 러스트는 빠르고 안정적인 언어로 높이 평가 받고있다. 러스트는 어떻게 이러한 목표를 달성할 수 있었을까?


컴파일러 언어

C 혹은 C++는 프로그래머가 메모리를 제어한다. 프로그래머의 역량에 따라 속도 및 안정성에 많은 영향을 받는다. 메모리를 해제하지 않으면 메모리 누수가 발생할 수 있고 메모리를 중복하여 해제하면 보안에 큰 결함이 발생한다. 결과적으로 이러한 문제는 사용자에게 각종 오류를 안겨준다.


인터프리터 언어

위와같은 문제를 극복하기 위해서 대부분의 인터프리터 언어(Python, JavaScript)들은 프로그래머가 실수할 수 있는 메모리 관리를 직접 도와준다. 프로그래밍의 난이도를 낮춰주고 안정적이지만 이들이 CC++에 비해서 느리다는 말을 익히 들어봤을 것이다. 이유는 다양하다. 변수의 형태를 동적으로 인식한다는 인터프리터의 특성 그리고 위에서 언급한 것 처럼 메모리를 언어차원에서 관리해주기 떄문이다. 대표적인 기술이 Garbage Collection이다.

Garbage Collection

가비지 컬렉션은 아무런 비용소모 없이 동작하는 마법이 아니다. 파이썬의 가비지 컬렉션은 메모리가 얼마나 참조되고 있는지 Reference Counting을 실시하고 Reference Cycles이 발생하는지 감시한다. 가비지 컬렉션은 많은 메모리와 연산을 필요로하므로 결과적으로 코드의 속도를 저하시킨다.

사람이 느끼는 빠른 반응 속도란 예측 가능한 일관적인 반응 속도를 내는 것인데 파이썬과 자바의 경우에는 이를 보장하기가 어렵다. 어느 순간에 가비지 컬렉션이 발생할지 프로그래머와 사용자는 예측할 수 없기 때문이다. 당장 결과가 필요한 순간이라도 가비지 컬렉션은 발생할 수 있다. 물론 하드웨어 성능의 향상과 가비지 컬렉션의 알고리즘은 뛰어난 엔지니어들의 손에서 눈부신 발전을 거듭하는 중이다.


그리고 러스트

그리고 다시 러스트로 돌아와 러스트는 빠르고 안정적인 언어를 어떻게 구현하였는가. 우선 러스트는 컴파일 언어다. 컴파일 언어면서 어떻게 안정적인 코드를 작성할 수 있도록 유도할 수 있었을까? 필자가 인식한 러스트는 한 마디로 '컴파일 시간에 가비지 컬렉션을 돌리는 언어'라고 생각했다. 물론 러스트에는 가비지 컬렉션이라는 존재는 없다. 러스트의 공식 문서에선 러스트를 아래와 같이 표현하고 있다.

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

소유권 규칙?

러스트는 위처럼 소유권이라는 개념을 만들어서 가비지 컬렉션이 필요하지 않은 메모리 안전성을 구현시켰다. 컴파일러가 체크하는 규칙은 아래와 같다.

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

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

배우고 싶은 언어

필자가 존경하는 개발자인 포프님의 조언에 따라 필자는 두가지 언어를 꼭 마스터 할 것이다. 하나는 C++고 하나는 Python이다. 그리고 추가적으로 이 러스트를 꼭 익혀보고 싶다. 세 언어는 공통의 목표를 달성하고자 하지만 달성하는 방법은 모두 다르다. 이 세가지 언어의 사고방식을 마스터하여 나 역시 새로운 방법으로 이 목표에 도달해보고 싶다.

학습 시작!

항상 시작하면 작심삼일로 그치는 경우가 많았는데... 2-3일에 하나씩은 꼭 러스트를 학습한 내용과 알게된 내용 다른 언어와 차이점을 블로그에 반드시 기록할 생각이다. 아래 코드는 러스트의 튜토리얼을 따라하며 작성한 코드이며 전체적인 그림을 그려보기 좋은 코드인 것 같아서 올렸다.

// 외부에 의존하는 크레이트가 있음
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;
            },
        }
    }
}

'러스트 박살내기(?)' 시리즈
러스트... 부셔버리겠어! 🔥🔥🔥 는... 내가 박살나는 중... 😥😫
작성된 댓글이 없습니다!
로그인된 사용자만 댓글을 작성할 수 있습니다.