본문 바로가기
IT 지식

RUST 맛보기

by woojin1354 2024. 8. 7.
728x90

해당 자료는 C/C++ 계열 언어와 컴퓨터 과학에 대한 이해가 있으면서 RUST를 가볍게 다뤄보고자 하는 학습자를 위해 작성되었습니다.

The Rust Programming Guide (KR) - (링크)

 

1. Rust의 학습 가치

최근 백악관에서 C, C++ 사용을 멈추고, 이미 C, C++로 개발된 프로그램은 수정이 필요하다고 주장하는 보고서를 발표하였습니다. 이는 C/C++이 가진 메모리 취약성으로 인한 문제가 지속적으로 발생하기에 생긴 이슈입니다. 메모리 취약성을 개선하고 C/C++을 대체하겠다는 목표를 갖고 개발된 Rust는 현재 급속도로 성장하고 있으며, 마이크로소프트, 구글, 클라우드 플레어 등의 대기업에서도 Rust의 가치를 인정하였습니다.

Rust가 C/C++의 자리를 대체할 수 있을 지에 대한 의문은 현재진행형인 상황이지만, Rust를 먼저 학습함으로써 Rust가 강세가 되었을때, 많은 기술적 이점을 가져갈 수 있을 것이라 사료됩니다.

(관련 자료 링크)

 

U.S. Government Urges Developers: Cease Using C, C++

Insecure and Unsafe: Stop using C, C++ for new application development and migrate old applications,” recently, the U.S. White House…

blog.stackademic.com

 

2. 개요

러스트 재단에서 개발되고 있는 메모리 안전성과 성능 및 편의성에 중점을 둔 프로그래밍 언어. 가비지 컬렉터 없이 메모리 안전성을 제공하는 대표적인 언어다. C++의 대체재로써 등장했다.
러스트에 대한 역사는 Rust 공식 GITHUB에 들어가면 확인 가능하다.
(링크)

 

 

rust/RELEASES.md at master · rust-lang/rust

Empowering everyone to build reliable and efficient software. - rust-lang/rust

github.com

 

3. 세팅

VSCODE 설치와 RUST 설치가 필요합니다.

RUST 설치 사이트(링크)

이 가이드는 2024년 4월, 윈도우11 환경을 기준으로 작성되었습니다.

 

4. 시작하기

Rust 프로젝트 생성이 필요합니다.

cmd를 통해 프로젝트를 생성할 디렉토리로 이동 합니다.

cargo new {프로젝트 이름} 형식으로 프로젝트를 생성합니다.

생성된 프로젝트 폴더를 VSCODE로 열면 아래와 같은 모습이 됩니다. 


아래 VSCODE 확장자들을 설치하여야합니다.RUST-Analyzer, CodeLLDB

기본으로 제공되는 코드를 실행하기 위해 컴파일을 진행합니다.CTRL + SHIFT + B 입력으로 rust : cargo build 을 찾아 실행합니다.

오류가 발생하는 경우
1. 환경변수 적용이 되어 있지 않은 경우(시스템을 재시작하면 해결 가능)
2. 폴더를 열때, 프로젝트의 최상위 폴더를 열지 않으면 경로와 관련된 오류 발생

실행 시키기 위해 Run 버튼을 클릭합니다. (하단 사진 참고) 

실행 결과는 터미널에서 확인 가능합니다.

최초 설정이 완료되었으며, 다음 실행부터는 빌드를 할 필요 없이 Run을 클릭하면 자동으로 컴파일을 진행합니다.

 

5. 자료형, 변수

Rust는 기본적으로 정적(Static) 타입 시스템을 갖고 있으며, 모든 변수는 컴파일 시간에 타입이 결정됩니다.

자료형 중 일부를 아래 표에 정리하였습니다.

숫자의 경우 비트수를 상징합니다. 예를 들어 i8은 정수형(integer) 8비트입니다.

정수형(integer) i8, i16 …
실수형(float) f32, f64 …
문자형(char) char
논리형(boolean) bool
문자열(String) String

아래는 정수형, 실수형, 문자열 변수를 생성하고 출력하는 예제입니다. 출력은 이후에 다시 다루겠습니다.

fn main() {
    // 정수형 출력
    let integer = 5;
    println!("{}", integer);

    // 실수형 출력
    let float = 0.5;
    println!("{}", format!("1/2 == {}", float));

    // 문자열 출력
    let message = format!("Hello, {}!", "world");
    println!("{}", message);
}

 

변수 타입 고정(Static Typing)

변수가 선언될 때 해당 변수의 타입이 컴파일 시점에 결정되고, 그 이후에는 변경되지 않는 특성을 의미합니다. Rust는 정적 타입 시스템을 가지고 있어서 변수의 타입을 컴파일러가 추론하거나 직접 명시해야 합니다. 이러한 특징은 Rust 코드의 안정성과 성능을 보장하는 데 중요한 역할을 합니다.
개발자(사용자)가 직접 타입을 고정하려면

let integer = 5; 대신에 let integer: i32 = 5; 을 사용하면 integer 변수가 i32 자료형으로 타입이 고정됩니다.


6. 입력과 출력

우선 출력에 대해 다루어 보겠습니다.

출력에는 다양한 방법이 존재하지만, 가장 간편하고 쉽게 이해할 수 있는 출력 방식으로 ‘println!’ 매크로가 있습니다. 해당 매크로를 ‘format!’ 매크로와 함께 사용하여 원하는 형태(format)로 출력 가능합니다.

매크로에 대한 내용은 추후 자세히 다루겠습니다.

변수 생성 및 출력 예시

fn main() {
    // 정수형 출력, 타입 고정
    let integer: i32 = 5;
    println!("{}", integer);

    // 실수형 출력
    let float = 0.5;
    println!("{}", format!("1/2 == {}", float));

    // 문자열 출력
    let message = format!("Hello, {}!", "world");
    println!("{}", message);
}

입력은 자바에서 사용하는 버퍼 입력 방식과 굉장히 유사합니다.

stdin().read_line(&mut buffer) 으로 표준 입력(stdin)에 전달되는 값을 buffer 변수에 저장 가능합니다.

buffer에 저장된 데이터를 파싱하여 사용합니다.

stdin().read_line() 은 한 줄 단위로 입력을 받는 방식이며, 한 줄 입력을 받고, 다음 입력을 받으려면 buffer.clear()를 실행하여야합니다.

입력된 숫자 2개를 합한 결과를 출력하는 예제

use std::io::stdin;
fn main() {
    // 변수 선언
    let mut buffer = String::new();
    let mut answer: i32 = 0;
    // 두 개의 숫자를 입력 받고, answer에 더함
    stdin().read_line(&mut buffer).unwrap();
    answer += buffer.trim().parse::<i32>().unwrap();
    buffer.clear(); // 하나의 입력마다 buffer을 비워줘야함
    stdin().read_line(&mut buffer).unwrap();
    answer += buffer.trim().parse::<i32>().unwrap();
    // 입력된 두 개의 숫자를 합한 결과가 출력
    println!("{}", answer);
}

한 줄당 2개의 숫자가 전달되는 예제

use std::io;
fn main() {
    let mut buffer: String = "".to_string();
    let mut get_on: i32;
    let mut get_out: i32;
    for _ in 0..10 {
        buffer.clear();
        io::stdin().read_line(&mut buffer).unwrap();
        let mut splited = buffer.split_ascii_whitespace().flat_map(str::parse::<i32>);
        get_out = splited.next().unwrap();
        get_on = splited.next().unwrap();
    }
}


7. 조건문과 반복문

조건문과 반복문은 여타 다른 언어들의 방법과 크게 다르지 않습니다.

조건문 (if)

조건문 중 if문을 보겠습니다.

// 코드의 일부분임으로 이대로는 실행이 불가합니다
if x > y {
	println!("{}", "X > Y");
}
else if x > z {
	println!("{}", "Y > X > Z");
}
else {
	println!("{}", "Y, Z > X");
}

if문 하나와 선택적으로 따라오는 else if 문과 else문이 존재합니다.

다른 언어와 같이 else if 문은 여러 개를 연달아 사용할 수 있으며, else 문은 모든 조건 분기에 해당되지 않으면 실행됩니다.(Default)

 

반복문 (for)

반복문에는 while, for 등이 존재합니다. 이 중에서 for문을 살펴보겠습니다.

일단 for문의 문법은 C/C++보단 Python과 유사합니다.

// 코드의 일부분임으로 이대로는 실행이 불가합니다
for i in 0..10 {
	println!("{}", i);
}

해당 프로그램의 출력 결과는 0, 1, 2, … ,8, 9 로 0부터 9까지가 출력됩니다.

for x in α..β 의 경우 α부터 β-1까지, β-1-α번 실행됩니다.

이는 {x∣α≤x<β,x∈Z x | α ≤ x < β, x ∈ Z } 로 표현 가능합니다.

8. 배열

Rust에서 배열 형식을 먼저 보겠습니다.

let array_name: [DataType; ArraySize] = [value1, value2, ...];

 

4개의 수가 저장된 배열 예제

fn main() {
    let array: [i32;4] = [10, 20, 30, 40];
    println!("example array {:?}, size is {}", array, array.len());
}
 

9. Rust로 PS 하기(예제 모음)

여러 줄에 걸쳐 주어지는 2개의 정수 처리 (백준 2460번 지능형 기차2)백준 2460번 지능형 기차2 문제를 Rust로 풀어보겠습니다.

// 전체 코드, 실행 가능
use std::io;
fn main() {
    let mut buffer: String = "".to_string();
    let mut get_on: i32;
    let mut get_out: i32;
    let mut current: i32 = 0;
    let mut answer: i32 = 0;
    for _ in 0..10 {
        buffer.clear();
        io::stdin().read_line(&mut buffer).unwrap();
        let mut splited = buffer.split_ascii_whitespace().flat_map(str::parse::<i32>);
        get_out = splited.next().unwrap();
        get_on = splited.next().unwrap();
        current = current - get_out + get_on;
        if answer < current {
            answer = current;
        }
    }
    println!("{}", answer);
}

한 줄에 2개의 숫자를 총 10번 입력받고 처리해야합니다.stdin().read_line()을 통해 한 줄씩 데이터를 받고, 이를 화이트스페이스(whitespace) 기준으로 쪼갠 뒤, 이를 flat_map() 을 통해 i32 형으로 파싱하였습니다.

std::iter::FlatMap<std::str::SplitAsciiWhitespace<'_>, Result<i32, std::num::ParseIntError>, fn(&str) -> Result<i32, <i32 as FromStr>::Err>>

이는 Python에서의 map 함수 반환값과 굉장히 유사한 결과물입니다. ex) map(int, input().split())

파싱 된 결과의 데이터형은 다음과 같습니다.

// 코드의 일부분 입니다 for _ in 0..10 { buffer.clear(); io::stdin().read_line(&mut buffer).unwrap(); let mut splited = buffer.split_ascii_whitespace().flat_map(str::parse::<i32>); get_out = splited.next().unwrap(); get_on = splited.next().unwrap(); }

많은 조건 분기(백준 2985번 세 수)성립 가능한 세 수간의 관계성 중에서 하나를 출력하면 되는 문제이며, 조건 처리를 꼼꼼히 하는 것이 중요한 문제입니다. (문제 링크)

// 전체 코드, 실행 가능
use std::io;
fn main() {
    let mut buffer: String = "".to_string();
    io::stdin().read_line(&mut buffer).unwrap();
    let mut splited = buffer.split_ascii_whitespace().flat_map(str::parse::<i32>);
    let num1: i32 = splited.next().unwrap();
    let num2: i32 = splited.next().unwrap();
    let num3: i32 = splited.next().unwrap();
    if num1 + num2 == num3 {
        println!("{}+{}={}", num1, num2, num3);
    }
    else if num1 == num2 + num3 {
        println!("{}={}+{}", num1, num2, num3);
    }
    else if num1 - num2 == num3 {
        println!("{}-{}={}", num1, num2, num3);
    }
    else if num1 == num2 - num3 {
        println!("{}={}-{}", num1, num2, num3);
    }
    else if num1 * num2 == num3 {
        println!("{}*{}={}", num1, num2, num3);
    }
    else if num1 == num2 * num3 {
        println!("{}={}*{}", num1, num2, num3);
    }
    else if num1 / num2 == num3 {
        println!("{}+{}={}", num1, num2, num3);
    }
    else if num1 == num2 / num3 {
        println!("{}={}/{}", num1, num2, num3);
    }
}

한 줄에 3개의 수가 입력되므로, read_line() 으로 한 줄을 읽고 이를 whitespace(공백)을 기준으로 split 하여 처리합니다.

728x90