Rust 언어에 대해 간단하게 정리하기 위해 작성된 포스트입니다.
변수 선언 🦀
fn main() {
// rust가 x의 자료형을 유추합니다
let x = 13;
println!("{}", x);
// 자료형을 명시적으로 지정할 수도 있습니다
let x: f64 = 3.14159;
println!("{}", x);
// 선언 후 나중에 초기화도 가능하지만, 그렇게 하진 않습니다.
let x;
x = 0;
println!("{}", x);
}
Rust에서 변수는 let 키워드를 사용하여 선언합니다.
필자가 주력으로 사용하고 있는 언어인 PHP와 동일하게 변수의 자료형을 유추할 수 있다는 공통정이 있습니다.
다만, 변수의 자료형을 유추할 수 있게 하면 다른 개발자들이 내가 의도한 방향을 이해하기 어려울 뿐더러
유지보수가 매우 불편해지는 단점이 있습니다.
이럴 떄 Rust에서는 자료형을 명시적으로 지정할 수도 있는데
규칙은 let [X: T} = {value}; 로 변수명이 앞에 붙고 자료형을 붙여줄 때는 :(콜론)이 들어간 후 자료형을 붙이면 됩니다.
상단에 소스 코드를 보면 동일한 이름의 변수에 여러번 값을 할당하는 부분이 보이는데
이는 변수 숨김(variable shadowing)이라 불리며, 자료형도 변경할 수 있다고 합니다.
변수 이름을 지을 떄 언제나 스네이크 케이스(SNAKE_CASE) 형태로 짓는다고 합니다.
변수의 값 변경하기 🦀
fn main() {
let mut x: i32 = 42;
println!("{}", x);
x = 13;
println!("{}", x);
}
Rust는 어떤 변수가 변경 가능한지에 대해 굉장히 주의하는데 값에 대해 총 두가지가 존재합니다.
- 변경 가능(mutable) - 컴파일러가 변수에 값이 씌어지고 읽혀지는 것을 허용함
- 변경 불가(immutable) - 컴파일러가 변수의 값이 읽히는 것만 허용함
변경 가능한 값은 mut 키워드로 표시 한다고 한다.
기본 자료형 🦀
부울 값 - 참/거짓 값을 나타내는 bool
부호가 없는 정수형 - 양의 정수를 나타내는 u8 u16 u32 u64 u128
부호가 있는 정수형 - 양/음의 정수를 나타내는 i8 i16 i32 i64 i128
포인터 사이즈 정수 - 메모리에 있는 값들의 인덱스와 크기를 나타내는 usize isize
부동 소수점 - f32 f64
튜플(tuple) - stack에 있는 값들의 고정된 순서를 전달하기 위한 (값, 값, ...)
배열(array) - 컴파일 타임에 정해진 길이를 갖는 유사한 원소들의 모음(collection)인 [값, 값, ...]
슬라이스(slice) - 런타임에 길이가 정해지는 유사한 원소들의 collection
str(문자열 slice) - 런타임에 길이가 정해지는 텍스트
fn main() {
let x = 12; // 기본적으로 i32
let a = 12u8;
let b = 4.3; // 기본적으로 f64
let c = 4.3f32;
let bv = true;
let t = (13, false);
let sentence = "hello world!";
println!(
"{} {} {} {} {} {} {} {}",
x, a, b, c, bv, t.0, t.1, sentence
);
}
Rust에서는 다양하지만 익숙한 자료형들이 있는데
개인적으로 공부하면서 Rust가 진입장벽이 높은 여러 이유 중 한가지인 이 부분인 것 같다라는 생각이 든다.
특히 u..., i... 부분이 많이 헷갈리는 경우가 매우 많았다.
내가 사용하고 싶은 값이 양의 정수만 나타낼 수 있고 양/음의 정수만 나타낼 수 있는데 여러 예제들을 보면서
음의 정수가 필요한데 u로 몇 번 사용해 실수를 한 적이 있다.
아직은 Rust를 겉핥기로만 배우고 있는 입장에서 튜플 / 배열이 따로 나뉘어져 있어 시행 착오가 좀 있었다.
튜플같은 경우는 stack 이라는 자료구조 개념이 들어가 사용할 때 주의해야 한다.
이는 다른 언어에 비해 Rust가 메모리 문제에 신경을 매우 많이 쓰기 때문이라고 한다.
상단의 소스 코드를 보면 숫자형 자료형들은 숫자 뒤에 자료형 이름을 붙여 명시적으로 지정할 수 있다.
상수 🦀
const PI: f32 = 3.14159;
fn main() {
println!(
"아무 재료 없이 애플 {}를 만들려면, 먼저 우주를 만들어야 한다.",
PI,
);
}
Rust에서 상수는 다른 언어에서 나오는 상수와 쓰임새가 같은데 컴파일 타임에 텍스트 지정자를 직접 값으로 대체하는 부분이 있어
기존 Rust에서 변수를 선언할 때와 달리, 상수는 반드시 명시적으로 자료형을 지정해주어야 한다는 점이 있습니다.
상수의 이름은 언제나 스크리밍 스네이크(SCREAMING_SNAKE_CASE) 형태로 짓는걸 약속한다고 합니다.
배열 🦀
fn main() {
let nums: [f32; 3] = [1.25, 2.11, 3.99];
println!("{:?}", nums);
println!("{}", nums[0]);
}
배열은 고정된 길이로 된 모두 같은 자료형의 자료를 가진 컬렉션이다.
백엔드 개발자로써 JAVA를 접해봤다면 문법은 살짝 다르지만 매우 익숙한 구조라고 생각한다.
배열의 자료형은 [T;N] 로 편하며, T는 원소의 자료형, N은 컴파일 타임에 주어지는 고정된 값이다.
함수 🦀
fn add(x: i32, y: i32) -> i32 {
return x + y;
}
fn main() {
println!("{}", add(08, 01));
}
우리가 늘 보던 그 함수이다.
기본적으로 함수에 인자값은 타입 힌팅 개념이 들어가 있어 어느 자료형이 들어갈지 명시적으로 작성을 해야한다.
해당 함수의 반환값도 마찬가지다. 다른 프로그래밍 언어 (PHP, Pyhton... 등) 에서는 타입 힌팅이 필수는 아니지만,
Rust라는 언어가 이런 부분에 있어서 컴파일 타임에 얼마나 꼼꼼하게 체크하는지 새삼 느낄 수 있다.
여러개의 리턴 값 🦀
fn swap(x: i32, y: i32) -> (i32, i32) {
return (y, x);
}
fn main() {
// 리턴 값의 튜플을 리턴
let result = swap(123, 321);
println!("{} {}", result.0, result.1);
// 튜플을 두 변수명으로 분해
let (a, b) = swap(result.0, result.1);
println!("{} {}", a, b);
}
Rust는 함수에 튜플을 리턴함으로써 여러개의 값을 리턴할 수 있습니다.
앞서 튜플은 stack에 있는 값들의 고정된 순서를 전달하기 위한 값을 의미하는데
swap이라는 함수를 바로 실행하여 결과값을 가져오면 리턴 값의 튜플을 그대로 리턴하여 받게 되는데
튜플 자체를 변수로 분해하여 가지고 오면 결과값이 다르다는걸 확인할 수 있다.
아무것도 리턴하지 않기 🦀
fn make_nothing() -> () {
return ();
}
// 리턴 자료형은 ()로 임시
fn make_nothing2() {
// 리턴할 것이 지정되지 않으면 이 함수는 ()를 리턴함
}
fn main() {
let a = make_nothing();
let b = make_nothing2();
// 아무것도 없는 것은 출력하기 힘들기 때문에 a와 b의 디버그 문자열을 출력한다.
// 정리하자면 함수에 리턴형을 지정하지 않은 경우 빈 tuple을 리턴하게 되는데 이걸 unit이라고 합니다.
println!("a의 값은 ?: {:?}", a);
println!("b의 값은 ?: {:?}", b);
}
Rust에서는 함수에 리턴형을 별도로 지정하지 않으면 빈 튜플을 리턴해주는데 이걸 unit이라고 부른다고 한다.
빈 튜플은 () 단순 괄호로 표현이 된다.
위 코드를 보면 알겠지만 리턴형에 빈 튜플을 강제로 넣은 함수와 리턴형을 별도로 지정하지 않은 함수를 호출해보면
둘 다 빈 튜플을 반환해주는걸 확인할 수 있다.
기반이 된 문서 사이트: https://tourofrust.com/00_ko.html