프로그래밍 언어 실전 가이드

[Rust입문 #2] Rust 첫 프로그램부터 변수, 데이터 타입, 함수 완전정복

devcomet 2025. 7. 7. 18:01
728x90
반응형

[Rust입문 #2] Rust 첫 프로그램부터 변수, 데이터 타입, 함수 완전정복 - 썸네일
[Rust입문 #2] Rust 첫 프로그램부터 변수, 데이터 타입, 함수 완전정복

 

Rust Hello World 프로그램 작성부터 변수 선언, 데이터 타입, 함수 사용법까지 초보자도 쉽게 따라할 수 있는 실전 가이드를 제공합니다.

이전 글 Rust란 무엇인가? 특징, 장점, 설치와 개발환경 완벽 가이드에서 Rust를 설치하고 개발환경을 구성했다면, 이제 본격적으로 Rust 프로그래밍을 시작해보겠습니다.


첫 번째 Rust 프로그램: Hello World 작성하기

프로그래밍 언어를 배울 때 가장 먼저 작성하는 프로그램이 바로 "Hello World"입니다.

이전 글에서 Rust 개발환경을 성공적으로 구성했다면, 이제 첫 번째 Rust 프로그램을 작성할 준비가 완료되었습니다.

 

[Rust입문 #1] Rust란 무엇인가? 특징, 장점, 설치와 개발환경 완벽 가이드

Rust는 메모리 안전성과 성능을 동시에 보장하는 시스템 프로그래밍 언어로,Discord, Dropbox, Figma 등 글로벌 서비스와 Solana, Polkadot 등 주요 블록체인 프로젝트에서 활용되고 있는 Mozilla 개발 언어입

notavoid.tistory.com

Rust Hello World 프로그램을 통해 Rust의 기본 문법과 구조를 이해해보겠습니다.

프로젝트 생성하기

먼저 새로운 Rust 프로젝트를 생성합니다.

cargo new hello_world
cd hello_world

Cargo는 Rust의 패키지 매니저이자 빌드 도구입니다.

자동으로 프로젝트 구조를 생성하고 필요한 파일들을 만들어줍니다.

main.rs 파일 구조 이해하기

생성된 src/main.rs 파일을 열어보면 다음과 같은 코드가 있습니다:

fn main() {
    println!("Hello, world!");
}

실행 결과:

Hello, world!

이 간단한 코드에서 중요한 Rust의 특징들을 확인할 수 있습니다.

fn 키워드는 함수를 정의할 때 사용하며, main 함수는 프로그램의 진입점입니다.

println! 매크로는 텍스트를 출력하는 기능을 제공합니다.

 

Rust Hello World 프로그램 실행 화면
Rust Hello World 프로그램 실행 화면

프로그램 실행하기

작성한 프로그램을 실행해보겠습니다.

cargo run

터미널에 "Hello, world!"가 출력되는 것을 확인할 수 있습니다.

이것으로 첫 번째 Rust 프로그램 작성이 완료되었습니다.


Rust 변수 선언과 특징

Rust에서 변수는 기본적으로 불변(immutable)입니다.

이는 Rust의 메모리 안전성을 보장하는 중요한 특징 중 하나입니다.

기본 변수 선언

fn main() {
    let x = 5;
    println!("x의 값: {}", x);
}

 

실행 결과:

x의 값: 5

let 키워드를 사용하여 변수를 선언합니다.

위 코드에서 x는 불변 변수로, 한 번 값이 할당되면 변경할 수 없습니다.

가변 변수 선언 (mut 키워드)

변수의 값을 변경하려면 mut 키워드를 사용해야 합니다.

fn main() {
    let mut y = 10;
    println!("변경 전 y: {}", y);

    y = 20;
    println!("변경 후 y: {}", y);
}

 

실행 결과:

변경 전 y: 10
변경 후 y: 20

mut 키워드를 사용하면 변수의 값을 자유롭게 변경할 수 있습니다.

이는 Rust 변수 선언의 핵심 개념입니다.

변수 vs 상수

Rust에서는 변수와 상수를 명확히 구분합니다.

Rust 변수 시스템
├── 변수 (let)
│   ├── 기본: 불변 (let x = 5)
│   └── 가변: mut (let mut y = 10)
└── 상수 (const)
    ├── 항상 불변
    └── 컴파일 시 결정 (const MAX: u32 = 100)
구분 키워드 특징 예시
변수 let 기본 불변, mut로 가변 가능 let x = 5;
가변 변수 let mut 값 변경 가능 let mut y = 10;
상수 const 항상 불변, 컴파일 시 결정 const MAX: u32 = 100;

 

상수는 const 키워드로 선언하며, 반드시 타입을 명시해야 합니다.

const MAX_POINTS: u32 = 100_000;

Rust 데이터 타입 완전 분석

Rust는 정적 타입 언어로, 모든 변수의 타입이 컴파일 시점에 결정됩니다.

타입 추론 기능을 통해 명시적으로 타입을 지정하지 않아도 컴파일러가 자동으로 타입을 추론합니다.

Rust 데이터 타입 분류
├── 스칼라 타입 (Scalar Types)
│   ├── 정수형: i8, i16, i32, i64, isize, u8, u16, u32, u64, usize
│   ├── 부동소수점: f32, f64
│   ├── 불린: bool (true/false)
│   └── 문자: char (4바이트 유니코드)
└── 복합 타입 (Compound Types)
    ├── 튜플: (i32, f64, char) - 서로 다른 타입 조합
    └── 배열: [i32; 5] - 같은 타입, 고정 크기

 

Rust data types classification diagram showing scalar and compound types hierarchy
Rust 데이터 타입 분류 다이어그램

스칼라 타입 (Scalar Types)

스칼라 타입은 하나의 값을 나타내는 타입입니다.

스칼라 타입 분류
├── 정수형 (Integer)
│   ├── 부호 있음: i8, i16, i32, i64, i128, isize
│   └── 부호 없음: u8, u16, u32, u64, u128, usize
├── 부동소수점 (Floating Point)
│   ├── f32: 32비트 단정밀도
│   └── f64: 64비트 배정밀도 (기본값)
├── 불린 (Boolean)
│   └── bool: true 또는 false
└── 문자 (Character)
    └── char: 4바이트 유니코드 스칼라 값
타입 분류 크기 범위 기본값
i32 32비트 -2³¹ ~ 2³¹-1
u32 32비트 0 ~ 2³²-1  
f64 64비트 IEEE 754 배정밀도
bool 1바이트 true, false  
char 4바이트 유니코드 스칼라  

정수 타입

fn main() {
    let a: i32 = 42;        // 32비트 부호 있는 정수
    let b: u32 = 42;        // 32비트 부호 없는 정수
    let c: i64 = 42;        // 64비트 부호 있는 정수
    let d: usize = 42;      // 포인터 크기 정수

    println!("i32: {}", a);
    println!("u32: {}", b);
    println!("i64: {}", c);
    println!("usize: {}", d);
}

 

실행 결과:

i32: 42
u32: 42
i64: 42
usize: 42

정수 타입은 크기와 부호 유무에 따라 다양하게 구분됩니다.

일반적으로 i32가 가장 빠른 성능을 제공합니다.

부동소수점 타입

fn main() {
    let x = 2.0;        // f64 (기본값)
    let y: f32 = 3.0;   // f32

    println!("f64: {}", x);
    println!("f32: {}", y);
}

 

실행 결과:

f64: 2
f32: 3

부동소수점 타입은 f32f64 두 종류가 있습니다.

기본적으로 f64가 사용되며, 현대 CPU에서 f32와 거의 같은 속도를 제공합니다.

불린 타입

fn main() {
    let t = true;
    let f: bool = false;

    println!("true 값: {}", t);
    println!("false 값: {}", f);
}

 

실행 결과:

true 값: true
false 값: false

불린 타입은 bool이며, true 또는 false 값을 가집니다.

크기는 1바이트입니다.

문자 타입

fn main() {
    let c = 'z';
    let z: char = 'ℤ';
    let heart_eyed_cat = '😻';

    println!("문자 c: {}", c);
    println!("유니코드 문자 z: {}", z);
    println!("이모지: {}", heart_eyed_cat);
}

 

실행 결과:

문자 c: z
유니코드 문자 z: ℤ
이모지: 😻

문자 타입은 char이며, 유니코드 스칼라 값을 나타냅니다.

4바이트 크기로 ASCII뿐만 아니라 모든 유니코드 문자를 지원합니다.

복합 타입 (Compound Types)

복합 타입은 여러 값을 하나의 타입으로 그룹화합니다.

복합 타입 비교
├── 튜플 (Tuple)
│   ├── 서로 다른 타입 조합 가능
│   ├── 예시: (i32, f64, char)
│   ├── 구조 분해로 값 추출
│   └── 인덱스 접근 (tuple.0, tuple.1)
└── 배열 (Array)
    ├── 같은 타입만 저장
    ├── 예시: [i32; 5]
    ├── 고정 크기
    └── 인덱스 접근만 (array[0])
특징 튜플 배열
타입 조합 서로 다른 타입 가능 같은 타입만
크기 고정 (컴파일 시) 고정 (컴파일 시)
접근 방법 구조 분해 + 인덱스 인덱스만
메모리 스택 할당 스택 할당
예시 (1, 3.14, 'a') [1, 2, 3, 4, 5]

튜플 (Tuple)

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    // 구조 분해를 통한 값 추출
    let (x, y, z) = tup;
    println!("y의 값: {}", y);

    // 인덱스를 통한 직접 접근
    let first = tup.0;
    let second = tup.1;
    println!("첫 번째 요소: {}", first);
    println!("두 번째 요소: {}", second);
}

 

실행 결과:

y의 값: 6.4
첫 번째 요소: 500
두 번째 요소: 6.4

튜플은 서로 다른 타입의 값들을 함께 저장할 수 있습니다.

한 번 선언되면 크기를 변경할 수 없습니다.

배열 (Array)

fn main() {
    let a = [1, 2, 3, 4, 5];
    let months = ["January", "February", "March"];

    // 타입과 크기 명시
    let b: [i32; 5] = [1, 2, 3, 4, 5];

    // 같은 값으로 초기화
    let c = [3; 5]; // [3, 3, 3, 3, 3]

    // 요소 접근
    let first = a[0];
    let second = a[1];

    println!("첫 번째 배열: {:?}", a);
    println!("월 배열: {:?}", months);
    println!("같은 값 배열: {:?}", c);
    println!("첫 번째 요소: {}", first);
    println!("두 번째 요소: {}", second);
}

 

실행 결과:

첫 번째 배열: [1, 2, 3, 4, 5]
월 배열: ["January", "February", "March"]
같은 값 배열: [3, 3, 3, 3, 3]
첫 번째 요소: 1
두 번째 요소: 2

배열은 같은 타입의 여러 값을 저장하며, 크기가 고정됩니다.

스택에 할당되어 빠른 접근이 가능합니다.


Rust 함수 사용법과 고급 기능

반응형

함수는 Rust 프로그램의 기본 구성 요소입니다.

fn 키워드를 사용하여 함수를 정의하며, 함수 정의를 통해 코드의 재사용성과 모듈화를 실현합니다.

Rust 함수 구조
fn function_name(param1: Type1, param2: Type2) -> ReturnType {
    // 함수 본문
    expression  // 반환값 (세미콜론 없음)
}

구성 요소:
├── fn: 함수 정의 키워드
├── function_name: 함수명 (snake_case 규칙)
├── 매개변수: (param: type) 형태, 타입 필수
├── 반환 타입: -> ReturnType (생략시 () 반환)
└── 함수 본문: { } 내부의 실행 코드
구성 요소 설명 필수 여부 예시
fn 키워드 함수 정의 시작 필수 fn
함수명 snake_case 규칙 필수 calculate_area
매개변수 타입 명시 필요 선택 (width: f64, height: f64)
반환 타입 -> 뒤에 명시 선택 -> f64
함수 본문 중괄호 내부 필수 { width * height }

 

Rust function structure diagram showing fn keyword, parameters, return type and function body components
Rust 함수 구조와 매개변수, 반환값 다이어그램

기본 함수 정의

fn greet() {
    println!("안녕하세요!");
}

fn main() {
    greet(); // 함수 호출
}

 

실행 결과:

안녕하세요!

가장 기본적인 함수 정의 방법입니다.

매개변수도 없고 반환값도 없는 함수입니다.

매개변수가 있는 함수

fn greet_with_name(name: &str) {
    println!("안녕하세요, {}님!", name);
}

fn add(x: i32, y: i32) {
    println!("{}과 {}의 합은 {}입니다.", x, y, x + y);
}

fn main() {
    greet_with_name("철수");
    add(5, 3);
}

 

실행 결과:

안녕하세요, 철수님!
5과 3의 합은 8입니다.

함수의 매개변수는 반드시 타입을 명시해야 합니다.

여러 매개변수를 받을 수 있으며, 각각 타입이 다를 수 있습니다.

반환값이 있는 함수

fn add_numbers(x: i32, y: i32) -> i32 {
    x + y  // 세미콜론 없음 - 이것이 반환값
}

fn multiply(x: i32, y: i32) -> i32 {
    return x * y;  // 명시적 return 사용
}

fn main() {
    let result = add_numbers(5, 3);
    println!("결과: {}", result);

    let product = multiply(4, 7);
    println!("곱: {}", product);
}

 

실행 결과:

결과: 8
곱: 28

함수의 반환 타입은 -> 화살표 뒤에 명시합니다.

마지막 표현식이 반환값이 되며, 세미콜론을 붙이지 않습니다.

표현식과 문장의 차이

Rust에서는 표현식(expression)과 문장(statement)을 구분합니다.

Rust 코드 구성 요소
├── 문장 (Statement)
│   ├── 작업을 수행하고 값을 반환하지 않음
│   ├── 세미콜론으로 끝남
│   └── 예시: let x = 5; / fn main() {}
└── 표현식 (Expression)
    ├── 값으로 평가됨
    ├── 세미콜론 없음 (반환값인 경우)
    └── 예시: 5 + 3 / { let x = 1; x + 1 }

 

구분 문장 (Statement) 표현식 (Expression)
특징 작업 수행, 값 반환 안함 값으로 평가됨
세미콜론 필수 반환값일 때 없음
예시 let x = 5; 5 + 3
용도 변수 선언, 함수 정의 계산, 반환값
블록 - { } 자체도 표현식

 

fn main() {
    let y = {
        let x = 3;
        x + 1  // 표현식 - 값을 반환
    };

    println!("y의 값: {}", y); // 4 출력
}

 

실행 결과:

y의 값: 4

중괄호 블록도 표현식이며, 마지막 줄이 반환값이 됩니다.

이는 Rust의 함수형 프로그래밍 특징을 보여줍니다.

함수의 고급 활용

fn get_user_info(age: u32) -> (String, bool) {
    let name = String::from("사용자");
    let is_adult = age >= 18;
    (name, is_adult)
}

fn process_data(data: &[i32]) -> Option<i32> {
    if data.is_empty() {
        None
    } else {
        Some(data[0] * 2)
    }
}

fn main() {
    let (name, adult) = get_user_info(25);
    println!("{}는 성인인가? {}", name, adult);

    let numbers = [1, 2, 3, 4, 5];
    match process_data(&numbers) {
        Some(result) => println!("처리 결과: {}", result),
        None => println!("데이터가 비어있습니다."),
    }
}

 

실행 결과:

사용자는 성인인가? true
처리 결과: 2

함수는 튜플을 반환하여 여러 값을 한 번에 반환할 수 있습니다.

Option 타입을 사용하여 실패 가능성을 명시적으로 처리할 수 있습니다.


타입 추론과 명시적 타입 지정

Rust의 타입 추론 시스템은 매우 강력하며, 대부분의 경우 명시적으로 타입을 지정하지 않아도 됩니다.

Rust 타입 시스템
├── 타입 추론 (Type Inference)
│   ├── 컴파일러가 자동으로 타입 결정
│   ├── 사용 컨텍스트를 분석하여 추론
│   ├── 코드 간결성과 개발 속도 향상
│   └── 예시: let x = 5; // 자동으로 i32로 추론
└── 명시적 타입 지정 (Explicit Typing)
    ├── 개발자가 직접 타입 명시
    ├── 애매한 상황에서 필요
    ├── 명확한 의도 표현과 타입 안전성
    └── 예시: let x: u32 = 5; // 명시적으로 u32 지정
방식 타입 추론 명시적 타입 지정
코드 예시 let x = 5; let x: i32 = 5;
장점 코드 간결, 빠른 개발 의도 명확, 안전성
사용 시점 일반적인 경우 애매한 상황
타입 결정 컴파일러 자동 개발자 직접
가독성 높음 (단순한 경우) 높음 (복잡한 경우)

타입 추론의 동작 방식

fn main() {
    let x = 5;          // i32로 추론
    let y = 3.14;       // f64로 추론
    let z = "hello";    // &str로 추론

    println!("x (i32): {}", x);
    println!("y (f64): {}", y);
    println!("z (&str): {}", z);

    // 컨텍스트에 따른 타입 추론
    let mut vec = Vec::new();
    vec.push(1);        // 이 시점에서 Vec<i32>로 추론
    println!("벡터: {:?}", vec);
}

 

실행 결과:

x (i32): 5
y (f64): 3.14
z (&str): hello
벡터: [1]

컴파일러는 변수의 사용 방식을 분석하여 적절한 타입을 추론합니다.

때로는 사용 컨텍스트에 따라 타입이 결정되기도 합니다.

명시적 타입 지정이 필요한 경우

fn main() {
    // 파싱 시 타입 명시 필요
    let guess: u32 = "42".parse().expect("Not a number!");
    println!("파싱된 숫자: {}", guess);

    // 여러 타입이 가능한 경우
    let numbers: Vec<i32> = (0..5).collect();
    println!("숫자 벡터: {:?}", numbers);

    // 제네릭 함수 사용 시
    let result = Vec::<i32>::new();
    println!("빈 벡터 길이: {}", result.len());
}

 

실행 결과:

파싱된 숫자: 42
숫자 벡터: [0, 1, 2, 3, 4]
빈 벡터 길이: 0

타입 추론으로 해결할 수 없는 애매한 상황에서는 명시적 타입 지정이 필요합니다.

특히 제네릭 타입이나 트레이트 객체를 다룰 때 자주 발생합니다.


변수 섀도잉과 스코프

Rust에서는 같은 이름의 변수를 다시 선언할 수 있으며, 이를 섀도잉(shadowing)이라고 합니다.

변수 섀도잉과 스코프 개념
├── 섀도잉 (Shadowing)
│   ├── 같은 이름의 변수 재선언 가능
│   ├── 타입 변경도 허용
│   ├── 새로운 변수를 생성 (mut와 다름)
│   └── 예시: let x = 5; let x = x + 1;
└── 스코프 (Scope)
    ├── 변수의 생명주기 범위
    ├── 중괄호 { }로 구분
    ├── 내부 스코프가 외부 스코프보다 우선
    └── 스코프 벗어나면 변수 소멸
개념 섀도잉 가변 변수 (mut)
키워드 let (재선언) let mut (한 번만)
타입 변경 가능 불가능
메모리 새 변수 생성 기존 변수 수정
예시 let x = 5; let x = "hello"; let mut x = 5; x = 10;
용도 타입 변환, 값 변환 값 수정

스코프 동작 방식

스코프 계층 구조
main 함수 스코프
├── let x = 1;  // 외부 스코프 변수
└── { 내부 스코프
    ├── let y = 2;      // 내부 스코프 전용
    ├── let x = x + y;  // 외부 x를 섀도잉
    └── } // y 소멸, x 섀도잉 해제
    // 다시 원래 x = 1로 복원

변수 섀도잉

fn main() {
    let x = 5;
    let x = x + 1;      // 이전 x를 섀도잉
    let x = x * 2;      // 다시 섀도잉

    println!("x의 값: {}", x); // 12 출력

    // 타입 변경도 가능
    let spaces = "   ";
    let spaces = spaces.len();
    println!("공백 수: {}", spaces);
}

 

실행 결과:

x의 값: 12
공백 수: 3

섀도잉은 변수의 타입을 변경할 수 있는 유연성을 제공합니다.

mut와 달리 새로운 변수를 생성하는 것입니다.

스코프와 변수 생명주기

fn main() {
    let x = 1;

    {
        let y = 2;
        let x = x + y;  // 외부 x를 섀도잉
        println!("내부 스코프 x: {}", x); // 3
    }

    println!("외부 스코프 x: {}", x); // 1
    // println!("y: {}", y); // 에러: y는 스코프를 벗어남
}

 

실행 결과:

내부 스코프 x: 3
외부 스코프 x: 1

변수는 선언된 스코프 내에서만 유효합니다.

내부 스코프에서의 섀도잉은 외부 스코프의 변수에 영향을 주지 않습니다.


실제 예제로 배우는 종합 실습

728x90

지금까지 학습한 내용을 종합하여 실제 프로그램을 작성해보겠습니다.

계산기 프로그램 예제

fn main() {
    // 변수 선언과 초기화
    let num1 = 10;
    let num2 = 5;

    // 각종 연산 함수 호출
    let sum = add(num1, num2);
    let difference = subtract(num1, num2);
    let product = multiply(num1, num2);
    let quotient = divide(num1, num2);

    // 결과 출력
    print_results(num1, num2, sum, difference, product, quotient);

    // 타입 추론 실습
    demonstrate_type_inference();

    // 가변 변수 실습
    demonstrate_mutability();
}

fn add(a: i32, b: i32) -> i32 {
    a + b
}

fn subtract(a: i32, b: i32) -> i32 {
    a - b
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b
}

fn divide(a: i32, b: i32) -> f64 {
    a as f64 / b as f64
}

fn print_results(a: i32, b: i32, sum: i32, diff: i32, prod: i32, quot: f64) {
    println!("=== 계산 결과 ===");
    println!("{} + {} = {}", a, b, sum);
    println!("{} - {} = {}", a, b, diff);
    println!("{} * {} = {}", a, b, prod);
    println!("{} / {} = {:.2}", a, b, quot);
}

fn demonstrate_type_inference() {
    println!("\n=== 타입 추론 실습 ===");

    let integer = 42;
    let float = 3.14;
    let boolean = true;
    let character = 'A';
    let string = "Hello, Rust!";

    println!("정수: {}", integer);
    println!("실수: {}", float);
    println!("불린: {}", boolean);
    println!("문자: {}", character);
    println!("문자열: {}", string);
}

fn demonstrate_mutability() {
    println!("\n=== 가변성 실습 ===");

    let immutable_var = 10;
    println!("불변 변수: {}", immutable_var);

    let mut mutable_var = 20;
    println!("가변 변수 (변경 전): {}", mutable_var);

    mutable_var = 30;
    println!("가변 변수 (변경 후): {}", mutable_var);

    // 상수 사용
    const MAX_VALUE: i32 = 100;
    println!("상수: {}", MAX_VALUE);

    // 조건부 값 할당 (다음 글에서 자세히 다룰 예정)
    let condition = true;
    let conditional_value = if condition { 42 } else { 0 };
    println!("조건부 값: {}", conditional_value);
}

이 예제는 지금까지 배운 모든 개념을 종합적으로 활용합니다.

실제 프로그램을 작성하면서 Rust의 특징들을 체험할 수 있습니다.

 

실행 결과:

=== 계산 결과 ===
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2.00

=== 타입 추론 실습 ===
정수: 42
실수: 3.14
불린: true
문자: A
문자열: Hello, Rust!

=== 가변성 실습 ===
불변 변수: 10
가변 변수 (변경 전): 20
가변 변수 (변경 후): 30
상수: 100
조건부 값: 42

자주 발생하는 오류와 해결 방법

Rust를 처음 배울 때 자주 마주치는 오류들과 해결 방법을 알아보겠습니다.

불변 변수 수정 오류

// 잘못된 예제
fn main() {
    let x = 5;
    x = 10; // 컴파일 오류!
}

// 올바른 해결 방법
fn main() {
    let mut x = 5;
    x = 10; // 정상 작동
    println!("x의 값: {}", x);
}

 

실행 결과 (올바른 코드):

x의 값: 10

가장 흔한 오류 중 하나입니다.

변수 값을 변경하려면 mut 키워드를 사용해야 합니다.

타입 불일치 오류

// 잘못된 예제
fn main() {
    let x: i32 = 5;
    let y: f64 = x; // 컴파일 오류!
}

// 올바른 해결 방법
fn main() {
    let x: i32 = 5;
    let y: f64 = x as f64; // 타입 변환
    println!("x: {}, y: {}", x, y);
}

 

실행 결과 (올바른 코드):

x: 5, y: 5

Rust는 암묵적 타입 변환을 허용하지 않습니다.

명시적 타입 변환이 필요합니다.

함수 반환값 오류

// 잘못된 예제
fn add(a: i32, b: i32) -> i32 {
    a + b; // 세미콜론 때문에 () 반환
}

// 올바른 해결 방법
fn add(a: i32, b: i32) -> i32 {
    a + b  // 세미콜론 없음
}

fn main() {
    let result = add(3, 4);
    println!("결과: {}", result);
}

 

실행 결과 (올바른 코드):

결과: 7

표현식에 세미콜론을 붙이면 문장이 되어 값을 반환하지 않습니다.

반환할 표현식에는 세미콜론을 붙이지 않아야 합니다.


성능 최적화와 베스트 프랙티스

Rust를 효율적으로 사용하기 위한 성능 최적화 방법과 베스트 프랙티스를 소개합니다.

적절한 데이터 타입 선택

fn main() {
    // 일반적인 경우 i32 사용
    let count: i32 = 100;

    // 메모리 절약이 중요한 경우 더 작은 타입 사용
    let small_count: u8 = 10;

    // 큰 수를 다룰 때만 큰 타입 사용
    let large_number: i64 = 1_000_000_000;

    println!("일반 카운트: {}", count);
    println!("작은 카운트: {}", small_count);
    println!("큰 숫자: {}", large_number);
}

 

실행 결과:

일반 카운트: 100
작은 카운트: 10
큰 숫자: 1000000000

상황에 맞는 적절한 데이터 타입을 선택하는 것이 중요합니다.

불필요하게 큰 타입을 사용하면 메모리 낭비가 발생합니다.

함수 설계 원칙

// 작고 명확한 함수 작성
fn calculate_area(width: f64, height: f64) -> f64 {
    width * height
}

// 재사용 가능한 함수 설계
fn format_currency(amount: f64) -> String {
    format!("${:.2}", amount)
}

// 에러 처리를 고려한 함수
fn safe_divide(a: f64, b: f64) -> Result<f64, String> {
    if b == 0.0 {
        Err("0으로 나눌 수 없습니다".to_string())
    } else {
        Ok(a / b)
    }
}

fn main() {
    let area = calculate_area(5.0, 3.0);
    println!("면적: {}", area);

    let price = format_currency(19.99);
    println!("가격: {}", price);

    match safe_divide(10.0, 2.0) {
        Ok(result) => println!("나눗셈 결과: {}", result),
        Err(error) => println!("에러: {}", error),
    }
}

 

실행 결과:

면적: 15
가격: $19.99
나눗셈 결과: 5

 

함수는 단일 책임 원칙을 따르고, 재사용 가능하게 설계해야 합니다.

에러 처리도 함께 고려하는 것이 좋습니다.


마무리 및 다음 단계

이번 글에서는 Rust의 기본 개념들을 상세히 다뤘습니다.

Hello World 프로그램부터 시작하여 변수, 데이터 타입, 함수까지 Rust 프로그래밍의 기초를 완전히 마스터했습니다.

학습 내용 정리

  • Rust Hello World: 첫 번째 프로그램 작성과 실행
  • 변수와 상수: let, mut, const 키워드의 차이점
  • 데이터 타입: 스칼라 타입과 복합 타입의 종류와 사용법
  • 함수: fn 키워드를 사용한 함수 정의와 매개변수, 반환값
  • 타입 추론: 컴파일러의 자동 타입 추론과 명시적 타입 지정

이러한 기본 개념들은 더 복잡한 Rust 프로그래밍의 토대가 됩니다.

특히 다음 글에서 다룰 조건문(if), 반복문(loop, while, for), 패턴 매칭(match)을 이해하기 위해서는 이번에 학습한 변수와 함수 개념이 필수적입니다.

소유권, 참조, 생명주기 등의 고급 개념을 이해하기 위해서도 이번 내용들이 기초가 됩니다.

계속 학습할 주제들

다음 단계로는 Rust 조건문, 반복문, 패턴매칭 기초 한 번에 끝내기를 학습하는 것을 권장합니다.

 

[Rust입문 #3] Rust 조건문, 반복문, 패턴매칭 기초 한 번에 끝내기

Rust의 조건문(if/else), 반복문(for/while/loop), 패턴매칭(match/if let)을 실전 예제와 함께 완벽하게 마스터하여 효율적인 흐름 제어 프로그래밍을 구현해보세요.들어가며Rust 프로그래밍에서 흐름 제어

notavoid.tistory.com

 

조건문 if, 반복문 loop, while, for, 그리고 강력한 패턴 매칭 match를 마스터하면 Rust의 제어 흐름을 완전히 이해할 수 있습니다.

또한 구조체와 열거형을 통한 데이터 모델링, 제네릭 프로그래밍 등도 중요한 주제들입니다.

꾸준한 연습과 실습을 통해 Rust의 강력한 기능들을 점진적으로 익혀나가시기 바랍니다.

Rust 공식 문서Rust by Example을 참고하여 더 깊이 있는 학습을 진행하실 수 있습니다.


강의추천

인프런 - 웹 개발의 혁신: C, C++, Rust로 시작하는 WebAssembly 마스터 클래스

 

웹 개발의 혁신: C, C++, Rust로 시작하는 WebAssembly 마스터 클래스 강의 | 김대진 - 인프런

김대진 | WebAssembly를 이용해 C, C++, Rust 등의 컴파일 언어로 웹 브라우저에서 동작하는 고성능 웹 애플리케이션을 개발 할 수 있습니다., 이제는 Web도 Qt를 이용해 쉽게 개발하자!. 컴파일 언어로

www.inflearn.com

 

 

728x90
반응형