Rust (프로그래밍 언어)/문법
이 문서에서는 Rust 프로그래밍 언어로 프로그램을 개발하기 위한 프로세스와 언어 문법을 다룬다.
일단 Rust는 객체지향 프로그래밍(Object Oriented Programming)을 목적으로 만든 언어가 아니다. 따라서 C언어의 class 관련 기능은 물론이고, 캡슐화/다형성/상속에 직접 대응하는 기능이 언어 차원에서는 없다. Rust의 대표 특징 중 하나인 Trait 개념은 오히려 Javascript의 Prototype에 가까운 형태이나, Rust의 등호 기호(=)의 동작이 복사가 아닌 단방향 데이터 이동임을 생각하면 Trait는 Prototype과는 또 다르며, C++ 언어의 인터페이스와도 유사한 측면이 있는 등 Rust만의 독특한 특징으로 꼽힌다.
Cargo 기반 개발 환경 세팅 및 프로그램 기본 구조
물론 rustc로 직접 rs 파일을 컴파일할 수 있지만, 보통은 Rust의 공식 패키지 매니저인 cargo로 프로젝트를 생성한다.
cargo 사용법과 toml 파일
먼저 cargo new (프로젝트 명칭)으로 프로젝트를 생성한다.
프로젝트를 테스트할 때에는 cargo run으로 컴파일 및 실행을 동시에 하며, 배포할 때는 cargo build를 사용한다.
외부 라이브러리를 가져올 때는 cargo add (패키지 명칭)로 (프로젝트 명칭).toml 파일에 라이브러리 의존성을 추가한다.
rs 파일의 기본 구조
use std::time::{Duration, SystemTime};
use std::thread::sleep;
fn main() {
let now = SystemTime::now();
// we sleep for 2 seconds
sleep(Duration::new(2, 0));
match now.elapsed() {
Ok(elapsed) => {
// it prints '2'
println!("{}", elapsed.as_secs());
}
Err(e) => {
// an error occurred!
println!("Error: {e:?}");
}
}
}위 코드는 Rust의 SystemTime 구조체 예제에서 가져왔다. main 함수를 정의하면 거기가 프로그램의 진입점이 되고, use로 C++언어의 #include 전처리기와 네임스페이스 처리와 같은 기능을 동시에 수행한다.
참고로 내장 함수는 이름 끝에 !에 붙어 있으며, println!으로 문자열을 한 줄 출력하는 함수 등이 있다.
한 줄 주석은 C언어처럼 // 뒤에 넣는다.
변수와 상수, 기본 연산
다음 접두어를 붙여 변수나 상수 등을 정의할 수 있다.
- const: 상수, 처음 정의한 뒤에 그 값은 물론이고, Trait로 가져온 메서드로도 내부의 데이터를 바꾸지 못한다.
- static: 프로그램이 끝날 때까지 남아있는 변수. 특정 상황에서는 변경이 가능하며, 이 때문에 이런 종류의 변수를 다루는 것은 unsafe하여 메모리 관련 에러를 낼 수 있다.
- let: 일반 변수 선언. 하지만 값을 변경하는 행위에 제한이 있어 Trait에 따라 정의된 메서드로만 바꾸는 것이 가능한 경우가 일반적이다.
- let mut: 완전히 자유로운 변경이 가능한 변수 선언. 이런 변수에 RAM 메모리 관련 기능을 넣는 것은 unsafe한 경우가 있다.
mut 접두어는 변수를 다루는 데에 높은 자유도를 보장하지만, Rust의 개발 배경이기도 한 C언어 등의 기존 시스템 프로그래밍용 언어의 환장할 수준의 메모리 관리 난이도를 Rust에도 가져오는 모순적인 문제로 인해 가급적 덜 쓰는 것을 권장한다. 특히 RAM 영역을 다루는 Trait가 있다면 mut를 최대한 안 쓰려고 하는 것이 매우 중요하다.
사칙연산과 나머지는 C와 동일하게 +, -, *, /, %이다. 또 연산 후 이동인 +=, -=, *=, /=, %=도 있으나, C언어의 1 증감 연산자인 ++, --는 없다. 소괄호 (, ) 안에 연산식을 넣어서 복잡한 연산의 우선 순위를 끌어올릴 수 있다.
비교 연산자는 C언어처럼 <. >, ==, !=, <=, >=이 있다. 단, IEEE 754 부동소수점 타입의 변수는 직접 좌변과 우변에 변수를 두지 말고 두 변수의 차를 f32::EPSILON 또는 f64::EPSILON과 비교하는 식으로 하여 정밀도에 따른 오차로 발생하는 부정확한 비교 문제를 예방해야 한다.
기존 변수의 참조(메모리 주소만 가져옴)는 &을 앞에 붙이는 것으로 가능하며, 만일 어느 참조 변수의 원본을 찾고싶다면 참조 변수 이름 앞에 *을 띄어쓰기 없이 붙이면 된다(곱셈 연산자와 헷갈릴 수 있으므로 소괄호로 묶는 것을 추천한다)
데이터 자료형
또 let var1: u32 = 32;let var2 = 32.5_f32; 같이 변수의 데이터 타입을 지정하여 변수명 뒤에 콜론과 함께 붙이거나 초기화 수치 뒤에 언더바와 함께 붙인다. 지정 가능한 타입은 다음과 같다.
- u8 : 부호 없는 8비트 정수
- u16 : 부호 없는 16비트 정수
- u32 : 부호 없는 32비트 정수
- u64 : 부호 없는 64비트 정수
- i8 : 부호 있는 8비트 정수
- i16 : 부호 있는 16비트 정수
- i32 : 부호 있는 32비트 정수
- i64 : 부호 있는 64비트 정수
- f32 : 32비트 IEEE 754 단정밀도 부동소수점 실수
- f64 : 64비트 IEEE 754 배정밀도 부동소수점 실수
- usize : CPU가 한 사이클에 처리 가능한 최대 비트 크기의 부호 없는 정수
- isize : CPU가 한 사이클에 처리 가능한 최대 비트 크기의 부호 있는 정수
그 외에 숫자 앞에 0b로 2진법, 0o로 8진법, 0x로 16진법 수치를 나타내며, println! 내장 함수로 이런 포맷으로 출력하려고 한다면 {} 대신 각각 {:b}, {:o}. {:x} 구문을 쓰면 된다.
변수 타입 캐스팅은 변수 뒤에 as (변환하려는 타입)를 붙여서 서로 다른 타입의 변수를 비교할 수 있다.
Expression, Statement의 차이
조건문 및 반복문
- for
for 문은 C++11 이후의 C++의 범위 기반 반복문과 유사하며, C++98까지의 for문과 다르다.
가령 list라는 배열을 elem이라는 변수로 순회하고 싶다면 for elem in list{ ... }처럼 쓰는데, 이때 주의할 것은 list가 for 반복문을 돈 다음에 수명이 끝나 사라진다는 것이다. 즉 저 for 문을 사용한 이후에는 list를 사용할 수 없다. 따라서 for 문 이후에도 list를 사용하려면 for elem in &list{ ... }처럼 & 기호로 참조 처리를 해야한다. 또 Immutable Variable에 집착하는 Rust답게 list는 for 문 내에서 기본적으로 변경이 안 되기에 elem으로 list에 변경을 가하려면 for elem in &mut list{ ... }처럼 &mut 접두어를 list 앞에 붙여야 한다.
만일 단순한 n회 반복이 목적이라면 for _ in 0..n처럼 익명 변수를 쓰자. 여기서 n에는 가급적 상수를 쓰고 변수나 함수의 결과값을 넣지 않아야 성능 하락을 막을 수 있다.
- continue: 이 문구를 만나는 시점에서 continue가 있는 반복문의 현재 순회를 건너뛰고 다음 순회를 시작한다.
- break: 이 문구를 만나는 시점에서 현재 반복문을 중단한다.
- break (레이블 이름) : 레이블의 이름을 뒤에 붙일 경우
'(레이블 이름):접두어가 붙은 loop를 중단하며, 그 안에 있는 중첩된 반복문(Nested loop)도 같이 중단된다. goto 기능의 대용으로 쓴다.
- break (레이블 이름) : 레이블의 이름을 뒤에 붙일 경우
- while: 특정 조건이 된다면 계속 순회한다. 단 무한 루프를 의도하는 경우 loop가 낫다.
- loop: break를 마주할 때까지 무한반복. 이건 프로그래머들이 의도한 무한루프(예: Main 함수 내에서 중요 변수들을 초기화한 다음 종료할 때까지 하는 무한 루프)를 의도하지 않은 버그성 무한 루프와 코드 단계에서 구분하기 위해 만들었다. 버그성 무한 루프를 빨리 찾아 디버그 난이도를 낮추는 목적으로 사용이 권장된다.
- if, else if, else: C언어에도 있는 조건문. 활용할 상황이 C언어와 똑같으나, 해당 접두어 뒤에 소괄호를 붙여 그 안에 조건을 적는 C언어와 달리 소괄호를 넣지 않고 바로 조건을 적은 다음 중괄호 내 작업 구문이 나타난다.
if a == 42 {
} else if a > 42 {
} else {
}Rust의 함수 구조와 메모리 관련 기능
match로 람다 함수 구현하기
enum
match와의 결합으로 모나드 구현
예외 처리
Generic
Trait와 오버로딩
모듈을 사용한 코드 분할
패키지에서 Crate 가져오기, 네임스페이스
주요 Crate
- num : 실수 외에 복소수 연산 같은 것도 지원한다.
- regex: 정규 표현식 라이브러리
- time: 시스템 시간 라이브러리
- rand: 임의의 숫자를 뽑는 Random Value 관련 기능