Rust-Lang의 특징 Hello, Rust!

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


메모리 관리

언매니지드 언어

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


매니지드 언어

위와같은 문제를 극복하기 위해서 인터프리터 언어에 속하는 Python, JavaScript과 같은 언어들은 프로그래머가 실수할 수 있는 메모리 관리를 가비지 컬렉션이라는 기술을 통해서 도와준다. 프로그래밍의 난이도를 낮춰주고 안정적이지만 이들이 느리다는 말을 익히 들어봤을 것이다. 코드를 기계어 수준으로 다루지 않는 인터프리터 언어라는 특성도 이유지만 위에서 언급한 것 처럼 메모리를 언어차원에서 관리해주는 것도 영향을 준다. 메모리 최적화 방식이 우리의 소프트웨어에 최적화 된 것이 아니기 때문이다.

가비지 컬렉션

가비지 컬렉션은 아무런 비용 없이 동작하는 마법이 아니다. 파이썬의 가비지 컬렉션은 메모리가 얼마나 참조되고 있는지 레퍼런스 카운팅(객체의 참조 수 측정)을 실시하고 레퍼런스 사이클(자가 참조)이 발생하는지 감시한다. 가비지 컬렉션은 많은 메모리와 연산을 필요로하므로 결과적으로 코드의 속도를 저하시킨다.

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


그리고 러스트

그리고 다시 러스트로 돌아오자. 러스트는 어떻게 빠르고 안정적인 메모리 관리를 구현하였는가. 우선 러스트는 컴파일 언어다. 코드를 기계어 수준으로 다룬다. 그럼 메모리는? 러스트의 공식 문서에선 아래와 같이 설명하고 있다.

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

소유권? 규칙?

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

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

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

배우고 싶은 언어

필자는 두 가지 언어를 마스터 하고 싶다. 하나는 C++고 하나는 Python이다. 그리고 추가적으로 이 Rust를 꼭 익혀보고 싶다는 생각이 추가로 들었다. 세 언어는 공통된 목표를 달성하고자 하지만 달성하는 방법이 모두 다르다. 이 세가지 언어의 사고방식을 마스터하여 나 역시 새로운 방법으로 이 목표에 도달해보고 싶다.

학습 시작!

항상 시작하면 작심삼일로 그치는 경우가 많았는데... 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;
            },
        }
    }
}

이 글이 도움이 되었나요?

신고하기
0분 전
작성된 댓글이 없습니다. 첫 댓글을 달아보세요!
    댓글을 작성하려면 로그인이 필요합니다.