Rust의 특별한 용어 정리

Rust에는 소유권, 참조, 라이프타임 등 고유한 용어들이 있는데 이외에도 생소하거나 중요한 용어인 variant, field, pattern, match arm, block, scope, associated type, attribute에 대해서 살펴 보겠습니다.

1. variant

  • 정의: enum에서 각각의 경우(상태/종류)를 의미.
  • 예시:
enum Color {
Red,
Blue,
Green,
}
let c = Color::Red; // Red는 Color 타입의 variant

여기서 Red, Blue, Green이 각각 variant이다.

2. field

  • 정의: struct(또는 enum의 variant)에 속하는 개별 데이터 항목.
  • 예시:
struct Point {
x: i32, // field
y: i32, // field
}
let p = Point { x: 1, y: 2 };

x와 y가 각각 field이고, i32는 각각의 field의 타입(type of the field 또는 필드형/필드 타입)입니다.

enum Message {
Quit, // 필드 없음
Move { x: i32, y: i32 }, // 필드 x, y가 있는 구조체 스타일
Write(String), // 이름 없는 튜플 스타일의 필드 String
ChangeColor(i32, i32, i32), // 이름 없는 튜플 스타일의 필드 i32
}

Message enum의 variant가 갖는 값 또는 구조가 곧 해당 variant의 “필드”입니다. 구조체 스타일인 경우는 필드 개념이 동일하며, 튜플 형식인 경우는 구조체와 달리 필드가 없지만 값이 필드가 됩니다.

enum은 이 필드(데이터) 덕분에, variant별로 타입에 따라 다양한 정보를 유연하게 표현할 수 있습니다.

3. pattern

  • 정의: 값을 구조적으로 분해 처리하기 위한 형태. match, let, 함수 매개 변수 등에서 사용합니다.
  • 예시:
// struct Point 정의
struct Point {
x: i32,
y: i32,
}

fn main() {
// 튜플 패턴 예제
let (a, b) = (1, 2);
println!("a = {}, b = {}", a, b);

// 구조체 및 구조체 패턴 매칭 예제
let p = Point { x: 10, y: 20 };
match p {
Point { x, y } => println!("({}, {})", x, y),
}
}

패턴을 이용해 복잡한 데이터를 쉽게 분해할 수 있다.

  • let (a, b) = (1, 2); => 오른쪽 (1, 2)는 타입이 (i32, i32)인 튜플이며, 이 값을 튜플로 받아서, 첫 번째 요소는 변수 a에, 두 번째 요소는 변수 b에 바인딩해줘”라는 뜻입니다.
  • match p {
    Point { x, y } => println!(“({}, {})”, x, y),
    } 에서
    Point { x, y }는 매칭 대상(p)이 해당 구조와 일치하는지 검사하고, 일치한다면 그 필드값을 변수로 분해해주는 패턴 역할을 합니다. 따라서 match에서 구조체 내부 값을 분해하고 싶으면 항상 이런 형태의 패턴을 사용하게 됩니다.

4. match arm

  • 정의: match 구문의 각분기(패턴 + 처리 블록).
  • 예시:
let n = 3;
match n {
1 => println!("One"), // arm 1
2 | 3 | 5 => println!("Prime"), // arm 2
_ => println!("Other"), // arm 3
}

각각이 match arm이며, 패턴과 실행할 코드 블록으로 구성되어 있다.

match arm에서의 패턴(pattern)은, 매칭 대상이 되는 값이 어떤 구조나 값을 가지고 있는지 비교하고, 해당 구조와 일치하면 그 arm의 코드를 실행하도록 하는 역할을 합니다.
즉, match 구문의 각 arm(갈래, 분기)은 패턴 => 실행코드 형태로 이루어지며, 패턴은 값의 형태를 설명하거나 내부 값을 분해하는 구조입니다

4. block

  • 정의: 중괄호 {}로 둘러싸인 코드 구역. 블록은 표현식이며 값과 타입을 가진다.
  • 예시:
let result = {
let x = 2;
x * x // 마지막 표현식이 결과값이 됨
};
// result = 4

블록 내에서 선언된 변수는 해당 블록에서만 유효하다.

5. scope

  • 정의: 변수나 아이템이 유효한 코드의 범위.
  • 예시:
fn main() {
let x = 10; // x는 main 함수 블록(scope)에서만 유효
}

scope이 끝나면 변수는 더 이상 쓸 수 없다.

가. block과 scope 비교

(1) block

  • 코드에서 중괄호 {}로 둘러싸인 부분 자체를 block이라고 합니다.
  • 예시:rust{ let a = 1; println!("{a}"); }
  • 이 부분 전체가 block입니다.

(2) scope

  • scope는 어떤 변수(또는 아이템)를 ‘볼 수 있고 사용할 수 있는 코드의 범위’입니다.
  • scope는 보통 block에 의해 결정되지만, 완전히 같지는 않습니다.
    • 모든 block은 새로운 scope를 열지만,
    • scope의 개념은 block 외에도 함수, 모듈, crate 등 더 넓거나 좁게 적용될 수 있습니다.

(3) 차이점 및 예제

  • block: { ... }로 감싸진 모든 코드 덩어리를 의미.
  • scope: 그 안에서 선언된 변수나 아이템이 유효한 코드의 범위.
  • 모든 block이 scope를 정의하지만, scope는 더 넓은 개념입니다.

예시:

fn main() {                      // main 함수 block, 여기가 scope 시작
let outer = 10; // 'outer'의 scope는 main 함수 전체

{ // 새로운 block 시작, 이 안이 block scope
let inner = 20; // 'inner'의 scope는 이 중괄호 안
println!("{}, {}", outer, inner);
} // inner는 여기서 scope 종료

println!("{}", outer); // ok
println!("{}", inner); // 에러, inner는 scope out!
}

여기서 inner는 작은 block(scope)에만 존재.
outer는 main block(scope) 전체에 존재.

6. associated type

  • 정의: 트레잇에서 타입 매개변수 대신 트레잇에 연결된 타입을 선언.
  • 예시:
trait Iterator {
type Item; // associated type

fn next(&mut self) -> Option<Self::Item>;
}

struct Counter;
impl Iterator for Counter {
type Item = i32;
fn next(&mut self) -> Option<i32> { /* 구현 */ None }
}

Iterator 트레잇에서 Item이 associated type이다.

가. 타입 매개변수와 associated type(연관 타입)의 정의

(1) 타입 매개변수:

  • generic에서 사용하는 타입 변수입니다.
  • 예: trait MyTrait<T> { … }에서 T가 타입 매개변수입니다.

(2) associated type(연관 타입)

  • 트레잇에서 선언해서, 트레잇을 구현할 때 구체적으로 지정하는 타입입니다.
  • trait Iterator { type Item; … }에서 Item이 이에 해당.

두 가지의 차이를 간단히 예시로 정리하면:

방법선언 방식사용 예시주의점
타입 매개변수trait MyTrait<T> { … }struct Foo;
impl MyTrait<u32> for Foo { … }
사용 때마다 타입 지정 필요
associated typetrait MyTrait { type Output; … }struct Bar;
impl MyTrait for Bar { type Output = u32; }
트레잇 구현이 타입 결정

나. 예시

(1) 타입 매개변수(Generic parameter)를 사용하는 트레잇

// 타입 매개변수를 사용하는 트레잇
trait MyTrait<T> {
fn foo(&self, x: T);
}

// 해당 트레잇을 구현하는 타입 예시
struct MyType;

impl MyTrait<i32> for MyType {
fn foo(&self, x: i32) {
println!("MyTrait<i32>::foo called with x = {}", x);
}
}

fn main() {
let t = MyType;
t.foo(100);
}

실행 결과:

MyTrait<i32>::foo called with x = 100

(2) Associated Type(연관 타입)을 사용하는 트레잇 예시

// 연관 타입을 사용하는 트레잇
trait AnotherTrait {
type Output;
fn bar(&self) -> Self::Output;
}

// 해당 트레잇을 구현하는 타입 예시
struct MyType;

impl AnotherTrait for MyType {
type Output = i32;
fn bar(&self) -> i32 {
42
}
}

fn main() {
let t = MyType;
let v: i32 = t.bar();
println!("AnotherTrait::bar returned {}", v);
}

실행 결과:

AnotherTrait::bar returned 42

7. attribute

  • 정의: 컴파일러에게 부가 정보를 주는 메타데이터. #[…] 또는 #![…] 형태.
  • 예시:
#[derive(Debug)]
struct MyStruct;

#[cfg(target_os = "linux")]
fn linux_only() {}

#[allow(dead_code)]
fn unused_function() {}

주로 코드 자동 생성(derive), 조건부 컴파일(cfg), 경고 제어 등에 사용된다.

attribute에 대해서는 다음 편에서 더 자세히 다룰 예정입니다.

fmt::Display trait

Debug는 구조체 정보를 그대로 출력하고, fmt::Display는 원하는 방식으로 표현을 커스터마이징할 수 있게 해줍니다. Debug는 #[derive(Debug)]로 자동 구현이 되고, fmt::Display는 impl fmt::Display를 통해 수동 구현이 필요합니다.

구분DebugDisplay
목적디버깅용 출력사용자 친화적 출력
매크로println!(“{:?}”, x)println!(“{}”, x)
구현 방법#[derive(Debug)]로 자동 구현수동 구현 필요 (impl fmt::Display)
포맷 예시Matrix(1,0, 2.0, 3.0, 4.0)(1.0, 2.0)
(3.0, 4.0)
문자열화format!(“{:?}”, x)x.to_string() 또는
format!(“{}”, x)

1. tuple 구조체

가. 선언

#[derive(Debug)]
struct Matrix(f32, f32, f32, f32);
  • [derive(Debug)]는 Rust에서 자동으로 Debug 트레이트를 구현해 달라는 뜻입니다. 이를 통해 해당 구조체(또는 열거형 등)를 {:?} 형식으로 출력할 수 있게 됩니다.
  • Matrix 구조체는 f32 타입의 값 4개를 가지며, 필드에 이름이 없어서 튜플처럼 순서로 접근 합니다.

나. instance 생성 및 Debug 포맷 출력

fn main() {
    let matrix = Matrix(1.1, 1.2, 2.1, 2.2);
    println!("{:?}", matrix);
}
  • Matrix라는 튜플 구조체의 새 인스턴스(instance)를 생성하는 코드입니다.
  • (1.1, 1.2, 2.1, 2.2) 라는 4개의 f32 값을 순서대로 Matrix 구조체에 넣어 새 변수 matrix에 저장합니다.
  • println!(“{:?}”, matrix);는 Debug 트레잇을 이용해 구조체 내부 값을 출력하는 것입니다.
    출력값은 Matrix(1.1, 1.2, 2.1, 2.2)입니다.

2. fmt::Display 트레잇 구현

위는 Debug 트레잇으로 출력하는 것이고, fmt::Display 트레잇을 통해 다양한 포맷으로 출력할 수 있습니다.

use std::fmt;

impl fmt::Display for Matrix {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 원하는 포맷으로 출력 구성
        write!(
            f,
            "( {} {} )\n( {} {} )",
            self.0,self.1, self.2,self.3
        )
    }
}
  • use std::fmt;
    => Rust 표준 라이브러리(std) 안에 있는 fmt 모듈을 현재 스코프(scope)로 가져온다는 뜻입니다.
    fmt는 그 안에 문자열 포맷(formatting) 관련 함수와 트레이트들이 모여있는 모듈(module)입니다.
    use std::fmt;를 하면, 나중에 fmt::Display나 fmt::Formatter 같은 타입이나 트레이트를 쓸 때, std::fmt 전체를 계속 적지 않고 그냥 fmt만 써도 됩니다.

  • impl fmt::Display for Matrix
    => Matrix에 대해 Display 트레이트를 수동으로 구현합니다.
  • fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    => 출력 형식을 어떻게 할지를 정의하는 함수입니다.
    메소드명은 fmt이고, 인수로 자기 자신인 &self와 가변 참조 형식의 fmt::Fomatter 형식(구조체)의 f를 받고, 반환 형식은 fmt::Result입니다.

    fmt::Result는 Result<(), fmt::Error>의 별칭으로,
    성공하면 Ok(()), 실패하면 Err(fmt::Error)를 반환합니다.
  • write! 매크로의 입력 포맷은 write!(f, “{}”, value)입니다. “{}”라는 포맷에 value를 넣고, 그 결과를 f에 기록합니다.
  • “( {} {} )\n( {} {} )”는 괄호안에 값 2개씩을 넣어 출력하는데, \n은 줄 바꿈을 의미합니다.
  • self.0,self.1, self.2,self.3는 포맷에 들어갈 값을 순서대로 나열한 것입니다.

3. main 함수

fn main() {
    let matrix = Matrix(1.1, 1.2, 2.1, 2.2);

    // Debug 포맷
    println!("{:?}", matrix);

    // Display 포맷
    println!("{}", matrix);
}

위에서 첫번째 println! 매크로는 Debug 트레잇으로 출력하는 것으로 위에서 봤고,

두번째 println! 매크로는 fmt::Display 트레잇으로 출력하는 것으로 2번에서 구현한 바 있습니다.

출력하면 아래와 같이 튜플 구조체의 값이 두 개씩 괄호안에 싸여 두 줄로 표시됩니다.

( 1.1 1.2 )
( 2.1 2.2 )

4. fmt::Formatter의 주요 메서드

가. width(): Option<usize>

  • 출력 시 최소 너비를 설정하는 값입니다.
  • Some(n)이면, 출력 문자열이 최소 n칸은 차지해야 함을 의미합니다.
  • 예: println!(“{:5}”, “hi”) → ” hi” (공백 3개 + hi)

사용 예:

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let width = f.width().unwrap_or(0);
write!(f, "{:width$}", "hi", width = width)
}

나. precision(): Option<usize>

  • 소수점 이하 자리수 또는 최대 출력 길이를 설정합니다.
  • Some(n)이면, 최대 n자리 또는 n글자까지만 출력합니다.

사용 예:

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let prec = f.precision().unwrap_or(2);
write!(f, "{:.prec$}", 3.14159, prec = prec)
}
// precision이 2이면 → 3.14

또는 문자열에 적용하면:

write!(f, "{:.5}", "Hello, world!") // → Hello

다.align(): Option<fmt::Alignment>

  • 정렬 방식 설정입니다. 리턴값은 Option이고, Alignment는 열거형입니다.
의미
Some(Left)왼쪽 정렬 (:<)
Some(Right)오른쪽 정렬 (:>)
Some(Center)가운데 정렬 (:^)
None정렬 지정 없음

사용 예:

use std::fmt::{self, Alignment};

fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match f.align() {
Some(Alignment::Left) => write!(f, "왼쪽 정렬됨"),
Some(Alignment::Right) => write!(f, "오른쪽 정렬됨"),
Some(Alignment::Center) => write!(f, "가운데 정렬됨"),
None => write!(f, "기본 정렬"),
}
}

라. Matrix 구조체에 width, precision 적용하기

impl fmt::Display for Matrix {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let w = f.width().unwrap_or(5);
let p = f.precision().unwrap_or(2);
write!(
f,
"( {:>w$.p$} {:>w$.p$} )\n( {:>w$.p$} {:>w$.p$} )",
self.0, self.1, self.2, self.3,
w = w, p = p
)
}
}

사용:

let m = Matrix(1.2345, 12.3456, 123.4567, 1234.5678);
println!("{:8.1}", m); // 최소 너비 8칸, 소수점 아래 1자

출력:

(     1.2    12.3 )
( 123.5 1234.6 )

바. format!와 write!

방식장점
format!() 사용문자열을 먼저 만들어 두기 편함
write!() 직접 사용메모리 절약, 포맷 버퍼 덜 생성함

Rust에서는 둘 다 가능하지만, 성능이나 메모리를 조금 더 아끼고 싶다면 write!()를 직접 쓰는 방식이 더 낫습니다.

5. 튜플 위치 변경(reverse)

// Tuples can be used as function arguments and as return values.
fn reverse(pair: (i32, bool)) -> (bool, i32) {
    // `let` can be used to bind the members of a tuple to variables.
    let (int_param, bool_param) = pair;

    (bool_param, int_param)
}

fn main() {
    let pair = (1, true);
    println!("Pair is {:?}", pair);

    println!("The reversed pair is {:?}", reverse(pair));
}
  • fn reverse(pair: (i32, bool)) -> (bool, i32) {
    => reverse 함수는 pair라는 인수를 받는데, i32와 bool 타입의 튜플이며, 반환 형식은 bool, i32 타입의 튜플입니다.
  • let (int_param, bool_param) = pair;
    => pair라는 튜플에서 값을 튜플 형식으로 받는데 int_param과 bool_param은 튜플의 멤버와 묶입니다.
  • (bool_param, int_param)은 표현식으로 reverse 함수의 return 값입니다. 인수는 i32, bool 타입이었는데, 순서가 바뀌어서 bool, i32 타입으로 반환됩니다.
  • let pair = (1, true);
    => pair란 변수에 1, true를 멤버로 하는 튜플을 대입합니다.
  • println!(“Pair is {:?}”, pair);
    => pair란 튜플을 debug 포맷으로 출력합니다.
  • println!(“The reversed pair is {:?}”, reverse(pair));
    => reverse 함수를 이용해 pair 튜플의 순서를 바꿔서 출력합니다.
  • cargo run으로 실행하면
    입력한 값대로 (1, true)로 출력되고,
    그 다음은 reverse 함수가 적용되어 순서가 바뀌어 (true, 1)로 출력됩니다.
Tuple 필드 reverse

6. 구조체 위치 변경(transpose)

use std::fmt;

#[derive(Debug)]
struct Matrix (f32, f32, f32, f32);

impl fmt::Display for Matrix {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "( {} {} )\n( {} {} )",
            self.0,self.1, self.2,self.3
        )
    }
}

fn transpose(matrix: Matrix) -> Matrix {
    Matrix(matrix.0, matrix.2, matrix.1, matrix.3)
}

fn main() {
    let matrix = Matrix(1.1, 1.2, 2.1, 2.2);
    println!("{:?}", matrix);
    println!("Matrix:\n{}", matrix);
    println!("Transpose:\n{}", transpose(matrix));
}
fn transpose(matrix: Matrix) -> Matrix {
Matrix(matrix.0, matrix.2, matrix.1, matrix.3)
}
  • fn transpose(matrix: Matrix) -> Matrix
    => 이 부분을 함수 signature라고 부르며,
    의미는 transpose 함수를 만드는데, 인수는 matrix이고, matrix의 타입은 Matrix 구조체입니다. 또한 반환 형식도 Matrix 구조체입니다.
  • { Matrix(matrix.0, matrix.2, matrix.1, matrix.3) }
    => 중괄호 안에 있는 것은 함수 본문으로 세미콜론이 없기 때문에 반환값을 나타내는 표현식입니다.

    Matrix(matrix.0, matrix.2, matrix.1, matrix.3)는 matrix라는 인수의 필드에 접근하는데, 인덱스가 0, 1, 2, 3이지만 순서를 바꾸기 위해 0, 2, 1, 3으로 표시했습니다. 이들 필드를 결합해서 Matrix 구조체를 반환합니다.
    println!("Matrix:\n{}", matrix);
println!("Transpose:\n{}", transpose(matrix));

fmt::Display trait을 이용해 matrix와 transpose(matrix)를 출력합니다.

출력 결과는
Matrix:
( 1.1 1.2 )
( 2.1 2.2 )
Transpose:
( 1.1 2.1 )
( 1.2 2.2 )
입니다.

1.2와 2.1의 위치가 달라졌습니다.