메뉴

Rust 특징

2021-12-31 03:49:39
지난 번에는 Rust 개발 환경을 만들고 "Hello World" 프로그램을 만들어서 빌드/실행까지 성공했다. 본격적으로 rust를 하기 전에, 구문과 기능적 특징을 살펴보려 한다. 각 특징들에 대한 자세한 내용은 다른 문서들을 통해서 다룰 것이다.

목차

구문

아래 "Hello World!"를 출력하는 rust 프로그램을 보자.
fn main() {
    println!("Hello World!")
}
Ruest는 괄호, 중괄호, 제어문(if, else, while, for), 함수정의 등에서 C/C++과 유사한 모습을 보여준다. 하지만 C/C++과의 구문이 유사함에도 불구하고 ML 계열 및 Haskell 언어의 의미체계와 유사하다. 아래의 코드를 보자. 재귀함수를 구현하고 있는데, if/else로 구성되는 삼항문은 ALGOL 60에서 사용하는 관용구를 대신 사용하고 있다. 마찬가지로 세미콜론(;)이 생략된 경우 return없이 값을 반환 할 수 있다.

fn main() {
    let x = factorial(5);
    print!("{}",x )
}

fn factorial(i: u64) -> u64 {
    match i {
        0 => 1,
        n => n * factorial(n-1)
    }
}

Iterator는 "..=" 연산자를 이용해서 iterator 구간을 설정할 수 있다.
fn factorial(i: u64) -> u64 {
    (2..=i).product()
}

추가적으로 type polymorphism을 위한 제너릭을 지원한다. 다음은 제너릭 함수를 사용하는 덧셈 프로그램이다.
use std::ops::Add;

fn sum<T:Add<Output = T> + Copy> (num1: T, num2:T) -> T {
    num1 + num2
}

fn main() {
    let r1 = sum (10,20);
    println!("Sum is : {:?}", r1);
    let r2 = sum (10.24,20.25);
    println!("Sum is : {:?}", r2);
}

기능

언어는 그 나름대로의 목표가 있고, 그 목표를 위한 기능적 특징을 가진다. Rust는 큰 시스템에서 동시성과 고도의 안전성을 보장하는 것을 목표로 한다. 이를 위하여 다른 언어와는 차별된 기능들을 제공한다. 정리하자면 "안정성을 희생하지 않으면서 C++ 만큼희 효율적이고 높은 이식성을 가진 소프트웨어를 개발하는 것"이 목표다.

메모리 안전

Rust는 메모리 안전성을 중요한 기능으로 고려한 언어다. null 포인터, 댕글링(Dangling) 포인터, 데이터 경합 등을 허용하지 않는다. 이러한 기능을 오버헤드없이 제공하기 위해서 Ownership(소유권)과 Lifetime(수명)이라는 다른 언어에는 없는 개념을 이용해서 컴파일 단계에서 메모리를 관리한다. 메모리 관리에 이상이 있을 것 같으면 아예 컴파일을 실패하는 것이다.

소유권

Ruest에서는 모든 자원에대한 소유권한을 가지고 있으며, 오직 하나의 소유자만 가질 수있다. 따라서 하나의 값을 두 개 이상의 변수가 참조해서 사용하는게 불가능하며, 리소스가 한번 이상 해제되는 것을 원천적으로 방지 할 수 있다. "Ruest 컴파일 타임에 이러한 모든 규칙을 지키고 있는지를 검사하기 때문이다.

유형(타입)과 다형성

Rust의 유형 시스템은 Haskell 언어의 유형 클래스에서 영감을 받은 Traits 라는 메커니즘을 지원한다. Traits는 타입들간의 공통 기능을 정의하는데 사용한다. Java의 인터페이스, C++의 추상 클래스오ㅓㅏ 비슷하다고 보면 된다. Rust Book에서는 이 점을 아래와 같이 설명하고 있다.

Traits 는 다른 언어들에서 흔히 인터페이스(interface) 라고 부르는 기능들과 유사합니다만 약간의 차이가 있습니다.(Note: Traits are similar to a feature often called interfaces in other languages, although with some differences.)

아래의 코드를 보자.
trait Animal {
    // Static method signature; `Self` refers to the implementor type
    fn new(name: &'static str) -> Self;

    // Instance methods, only signatures
    fn name(&self) -> &'static str;
    fn noise(&self) -> &'static str;

    // A trait can provide default method definitions
    fn talk(&self) {
        // These definitions can access other methods declared in the same
        // trait
        println!("{} says {}", self.name(), self.noise());
    }
}
trait는 "특성"이라는 의미 그대로, 어떤 객체의 특징을 정의하고 있다라고 생각하면 쉽게 이해 할 수 있다.

Animal의 특징을 가지는 Dog의 구현은 아래와 같이 이루어진다.
// Implement the `Animal` trait for `Dog`
impl Animal for Dog {
    // Replace `Self` with the implementor type: `Dog`
    fn new(name: &'static str) -> Dog {
        Dog { name: name }
    }

    fn name(&self) -> &'static str {
        self.name
    }

    fn noise(&self) -> &'static str {
        "woof!"
    }

    // Default trait methods can be overridden
    fn talk(&self) {
        // Traits methods can access the implementor methods
        self.wag_tail();

        println!("{} says {}", self.name, self.noise());
    }
}

Rust의 제너릭은 C++의 템플릿의 구현과 유사하다. 각 인스턴스에 대해서 별도의 코드 복사본이 생성된다. 이것을 모노모피제이션(monomophization)이라고 하는데 Java, Haskell에서 일반적으로 사용하는 유형 삭제 체계와는 대조되는 특징이다.

Ruest에서 사용자 정의 유형은 struct 키워드로(go와 유사하다) 생성한다. 사용자 유형은 다른언어에서 처럼 클래스, 데이터 등이 포함된다. 사용자 정의 유형은 impl키워드로 구현한다.

구성요소

Cargo

Cargo는 Rust의 빌드시스템이자 패키지 관리자다. 종속성을 다운로드하고 종속성을 구축한다. 종속성 정보는 toml파일에 저장되며 Cargo에 빌드 정보를 알려준다. crates.io에서 다양한 패키지들을 검색 할 수 있다.

Rustfmt

Rust의 코드 포맷터다. 코드를 입력하면 Rust 스타일 가이드 혹은 rustfmt.toml 파일에 지정된 규칙에 따라서 공백, 들여쓰기등을 변경한다.

Clippy

Clippy는 Rust 코드의 오류, 성능, 가독성의 개선을 도와주는 Rust의 내장 Lint 툴이다. 2021년 현재 450개 이상의 규칙이 내장되어 있으며, 온라인에서 범주별로 필터링 할 수 있다. 일부 규칙은 기본적으로 비활성화되어 있으며, 프로젝트 환경에 따라서 활성화/비활성화 시킬 수 있다.

RLS

IDE와 편집기에 Rust 프로젝트에 대한 추가적인 정보를 제공하는 Language server다. Clippy를 이용한 link, Rustfmt를 이용한 코드 포맷, Racer를 이용한 코드 자동 완성등의 기능을 제공한다. 최근에는 Racer 보다는 rust-analyer에 힘을 쓰고 있는 것 같다.

매크로

매크로를 이용해서 Rust 언어를 확장 할 수 있다. 크게 세 가지 타입의 매크로가 있다.

이들은 절차적 매크로(Procedural Macros)라고 하며, 컴파일 시간에 코드를 실행 할 수 있다. AST(Abstract Syntax Tree - 추상 구문 트리)를 다른 AST로 만드는 함수로 생각 할 수 있다.

절차적 매크로는 컴파일 시간에 실행되기 때문에 컴파일러와 동일한 리소스를 가진다. 예를들어 컴파일러가 사용하는 표준입력, 표준오류, 표준출력등을 사용 할 수 있다.

채택