본문 바로가기

개발

[TIL] Rust 공부 : Attribute, Modules, Null in Rust 등

반응형

Rust의 The Book을 다 보고, 복습 겸 실제 Rust 코드가 사용되는 예제를 보고 싶어 yaml-rust 공부를 시작했다. 잊어버린 개념이나 낯선 개념들을 찾아보면서 부족한 부분들을 메울 생각이다.

Attribute

자유로운 형식의 메타데이터이다. 이름, 컨벤션, 컴파일러 등에 따라 해석되는 개념으로 정해진 역할이 있다기보다는 사용법을 익힐 필요가 있다. 커스텀으로 만들 수도 있지만, 우선적으로 Rust에서 자주 사용되는 Attribute는 익숙해질 필요가 있다.

Syntax

InnerAttribute : #![Attr]

  • 해당 Attribute가 선언된 범위(scope) 내에서 적용된다.

OuterAttribute : #[Attr]

  • 해당 Attribute 이후에 오는 것(함수, 모듈, ...)에 적용된다.

Examples

// 모듈 또는 crate 내에서 적용되는 메타데이터
#![crate_type = "lib"]

// 아래의 함수는 유닛테스트시에 사용된다.
#[test]
fn test_foo() {
    /* ... */
}

// 조건부로 컴파일되는 모듈
#[cfg(target_os = "linux")]
mod bar {
    /* ... */
}

// 경고/에러를 무시하는 린트 attribute
#[allow(non_camel_case_types)]
type int8_t = i8;

// 내부의 attribute는 해당 함수 전체에 적용된다.
fn some_unused_variables() {
  #![allow(unused_variables)]

  let x = ();
  let y = ();
  let z = ();
}

cfg_attr attribute

#[cfg_attr(a, b)]

: 만약 #[cfg(a)]를 만족하면 #[b] attribute를 적용한다.

Examples

#![cfg_attr(feature = "cargo-clippy", allow(renamed_and_removed_lints))]

// cargo-clippy feature가 enabled되면 #![allow(renamed_and_removed_limit)] 활성화한다는 의미이다.
// cargo build --features "cargo-clippy"로 빌드하면 해당 feature가 enabled 된다.

derive attribute

특정한 Trait에 대한 기본적인 구현(impl)을 간편하게 제공할 수 있다. 기본적인 구현은 이미 정해져 있으며, 이런 Trait을 derivable 하다고 한다. Eq, PartialEq, Copy, Clone, Debug 등이 derivable 하다.

Examples

#[derive(PartialEq, Clone)]
struct Foo<T> {
    a: i32,
    b: T,
}

위의 코드는 아래와 같다.

impl<T: PartialEq> PartialEq for Foo<T> {
    fn eq(&self, other: &Foo<T>) -> bool {
        self.a == other.a && self.b == other.b
    } 

    fn ne(&self, other: &Foo<T>) -> bool {
        self.a != other.a || self.b != other.b
    }
}

Packages

패키지는 0 또는 1개의 library crates (lib.rs)를 가지고 있거나 0개 이상의 binary crates (e.g., main.rs)를 가지고 있으며 일련의 기능을 제공한다. 하나의 패키지는 하나의 Cargo.toml을 가지고 있다.

Modules

모듈은 가독성과 재사용성을 높이기 위해 코드를 그룹화하는 개념이다. mod라는 키워드로 모듈을 정의할 수 있다. 파일을 분리하면 mod 없이도 해당 파일이 모듈의 역할을 한다.

Path와 Module은 ::를 통해 구분된다.

front_of_house::hosting::add_to_waitlist();

참고: self를 파라미터로 받지 않는 assoicated functions에도 ::가 사용됨

impl Rectangle {
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            height: size,
        }
    }
}

let sq = Rectangle::square(3);

Rust에는 Null이 없다.

다른 언어를 사용할 때 자주 발생하는 문제는 Null 값을 Null이 아닌 것처럼 다룰 때 발생한다. 예를 들어, 파이썬에서는 다음과 같은 문제가 발생할 수 있다.

a = 3

if a == 3:
    a = None

a + 4 # This throws a TypeError

Rust에서는 Null이 없기 때문에 이런 문제를 예방할 수 있다. 하지만, 분명 개념적으로 Null을 사용해야 할 때가 있다. Rust에서는 이를 Enum으로 대체한다

enum Option<T> {
    Some(T),
    None,
}

위의 코드에서 None은 Null values가 아니라 Some(T)와 같은 하나의 'Enum variant'이다. 이렇게 Enum으로 처리하게 되면 해당 Enum을 직접 처리해 줘야 하기 때문에 (e.g., pattern matching) 위에서 언급한 문제를 해결할 수 있다.

let x: i8 = 5;
let y: Option<i8> = Some(5);

let sum = x + y;

이와 같은 이유로 위의 코드는 컴파일 시 에러가 발생하며, Runtime 에러를 예방할 수 있다.

References


The Rust Reference
Derive
Packages and Crates
Defining an Enum

반응형