해당 게시판에 올라오는 포스트는 The Rust Programming Language 의 한글 텍스트 버전을 보며 작성된 학습 게시판입니다.
전체 목차
[코딩/Rust] - [Rust] 1장 안정성 높은 언어
[코딩/Rust] - [Rust] 2장 일반적인 프로그래밍 개념
[코딩/Rust] - [Rust] 3장 러스트만의 특이함
[코딩/Rust] - [Rust] 4장 열거형(Enum), 모듈화(Module)
[코딩/Rust] - [Rust] 5장 컬렉션(Collection) - 벡터(Vector), 문자열(String), 해시맵(HashMap)
글 목차
- 변수(Variable)와 가변성(Mutable)
- 데이터 타입(Type)
- 함수(Function, Method)
- 주석(Comment)
- 제어 흐름문(if, loop, for, while)
- 예제(Example)
1. 변수(Variable)와 가변성(Mutable)
작명(Naming)
snake_case 명명 방식을 사용하는 규칙들
- 모듈(Modules)
- 함수(Function), 메소드(Methods)
- 변환 함수(Conversions)
- as_OOO : 비용이 싸며 원본 객체에 영향이 없는 경우.
- to_OOO : 비용이 비싸며 원본 객체에 영향이 없는 경우.
- into_OOO : 원본 객체를 소모(Consume)하는 경우.
- 변환 함수(Conversions)
- 지역 변수(Local variables)
CamelCase 명명 방식을 사용하는 규칙들
- 타입(Types)
- 트레잇(Trait)
- 타동사, 명사, 형용사 를 사용하며 '-able' 과 같은 접미사 사용 자제
- 열거(Enum)
- 정적 변수(Static variables)
- const 포함
불변 변수와 가변 변수(Immutable Variable and Mutable Variable)
러스트에서 변수는 기본적으로 불변(immutable) 이다. 이는 러스트가 제공하는 안정성과 쉬운 동시성을 활용하는 방식으로 코드를 유도한다.
또한 let 키워드를 활용하여 타입 명시를 굳이 하지 않아도 된다. 알아서 타입을 맞춰준다!
//파일명:src/main.rs
fn main() {
let x = 5;
println!("x의 값은 다음과 같다 : {x}");
x = 6;
println!("x의 값은 다음과 같다 : {x}");
}
해당 코드는 컴파일이 불가능하다.
불변 변수 'x'에 값을 두 번 할당할 수 없기 때문이다.
'x'를 가변 변수로 설정하려면 다음과 같이 진행하면 된다.
//파일명:src/main.rs
fn main(){
let mut x = 5;
println!("x의 값은 다음과 같다 : {x}");
x = 6;
println!("x의 값은 다음과 같다 : {x}");
}
이제 이 프로그램을 실행한다면, 다음과 같은 출력을 얻을 수 있다.
x의 값은 다음과 같다 : 5
x의 값은 다음과 같다 : 6
상수(Constant)
상수는 불변 변수와 비슷한데, 항상 불변이며 값의 타입은 반드시 명시되어야 한다. 그리고 상수는 상수 표현식으로만 설정될 수 있고, 런타임에서만 계산될 수 있는 결과값으로는 설정할 수 없다.
let 키워드 대신 const 키워드로 선언한다. 각설하고 예제를 보자.
const KOREA_UTC_HOUR:u32 = 9;
상수의 이름은 'KOREA_UTC_HOUR' 이고 값은 9 이다.
상수는 나중에 업데이트될 하드코딩된 값을 단 한 군데에서 한 번에 변경할 수 있게 한다는 장점이 있다.
섀도잉(Shadowing)
동일한 이름의 새 변수로 이전 변수를 가릴 수 있다.
덮어 썼다 라고 이야기 하지 않고, 왜 가렸다 라고 이야기하는 이유는 다음 코드를 실행해보면 알 수 있다.
//파일명:src/main.rs
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("안쪽 스코프에서 x의 값은 다음과 같다 : {x}");
}
println!("x의 값은 다음과 같다 : {x}");
}
해당 코드의 실행 결과는 다음과 같다.
안쪽 스코프에서 x의 값은 다음과 같다 : 12
x의 값은 다음과 같다 : 6
간단하게 설명을 하자면
- 첫 번째 let x = 5; 에서 x 의 이름으로 새 변수(구분을 위해 x1라고 하자)가 할당 되었다.
- 두 번째 let x = x + 1; 은 x 의 이름으로 새 변수(구분을 위해 x2라고 하자)를 할당한 것이다. 이때 x 는 6이 된다.
- 하지만 중괄호 안쪽 스코프에서 다시 한 번 x 의 이름으로 새 변수(구분을 위해 x3라고 하자)가 할당된다. 이때 x 는 12 가 되면서 출력문에서는 12 가 출력된다.
- 중괄호 안쪽 스코프를 벗어나게 되면, 세 번째 x 변수(x3) 는 스코프를 벗어났음으로 즉시 메모리에서 해제된다.
- 세 번째 x 변수(x3)가 사라짐으로서 가려졌던 두 번째 x 변수(x2) 가 보이게 되어, 마지막 출력문에서는 6이 출력된다.
스코프에서 벗어나는 경우 소유권에 대한 이야기를 해야함으로 3장에서 더욱 자세하게 다루겠다.
또한 mut 키워드와의 차이점이 존재한다.
let name = "korea"; // 문자열 타입
let name = name.len(); // 숫자 타입
let 키워드를 반복해서 쓰는 것은 섀도잉으로 아예 다른 변수로 취급하는 개념이라 변수 타입이 달라져도 상관이 없다.
let mut name = "korea";
name = name.len(); // 에러 발생!
하지만 let mut 키워드를 사용 후 다시 재할당할 때에는 반드시 변수 타입이 동일해야 한다.
2. 데이터 타입(Type)
자세한 설명은 생략
정수(Integer)
소수점 없는 숫자
길이 | 부호 있음(signed) | 부호 없음(unsigned) |
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
arch 는 컴퓨터 환경에 따라 달라지는 값이다. 64-bit 아키텍처는 64비트를, 32-bit 아키텍처면 32비트를 가지게 된다.
부동 소수점(Floating Point)
"." 의 위치가 바뀔 수 있는 소수 숫자
키워드 | f32 | f64 |
딱 두 개 뿐이며, 모두 부호가 있다. IEEE-754 표준을 따른다.
논리(Boolean)
bool 키워드며, true 와 false 를 가진다. 1byte(8-bit) 크기이다.
문자(Character)
char 키워드며, 유니코드 스칼라 값을 가진다. 4byte(32-bit) 크기이다.
그러니까... '😻' 이모티콘 사용이 가능하다...!!!
//파일명:src/main.rs
fn main() {
let c = 'z';
let z: char = 'ℤ'; // 명시적인 타입 어노테이션
let heart_eyed_cat = '😻'; // 오류 안남!!!
}
튜플(Tuple)
다양한 타입의 여러 값을 묶는 일반적인 방법이다. Python 사용자라면 익숙할 것이다.
//파일명: src/main.rs
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1); // 타입을 명시해도 좋고 안해도 좋다
let (x, y, z) = tup;
println!("y의 값은 다음과 같다 : {y}");
}
튜플의 각 요소 접근에는 또 다른 방법이 있다.
//파일명: src/main.rs
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = tup.2;
}
배열(Array)
배열은 스택에 할당 되는 계산 가능한 고정된 크기의 단일 메모리 덩어리 이다.
//파일명: src/main.rs
fn main() {
let a = [1, 2, 3, 4, 5]; // 나열 선언
let a: [i32; 5] = [1, 2, 3, 4, 5]; // 타입과 요소의 개수 명시적 선언
let a = [3; 5]; // 자동 채움 선언 -> let a = [3, 3, 3, 3, 3] 과 동일
}
배열 요소 접근은 다음과 같다.
//파일명: src/main.rs
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
고정된 크기이기 때문에 당연하게도 인덱스에 따른 오류가 발생할 수 있다.
"index out of bounds" 라는 오류는 배열 범위를 벗어났다는 것이다.
JAVA로 따지자면 " IndexOutOfBoundsException"와 동일
3. 함수(Function, Method)
러스트는 절차 지향 함수형 프로그래밍과 유사하다. 아 물론 나중에 더 고급 문법으로 가면 객체 지향 으로 개발할 수 도 있다. 대충 Cpp 처럼 다 짬뽕 쌈뽕 이라고 생각하자.
매개변수(Parameters)
당연하게도 매개변수, 인수를 넘길 수 있다.
//파일명: src/main.rs
fn main() {
korea_is(1);
}
fn korea_is(x: i32) {
println!("한국은 세계 {x}위");
}
let 키워드에서와는 다르게 매개변수의 타입은 반드시 선언해야 한다. 이부분은 Python 과 조금 다르다.
그래서 장점이라고 생각한 이유는 Python 코드를 다른 언어로 포팅할 때, 매개변수의 타입이 뭔지 적혀있지 않은 경우가 있으면 진짜 난감하다... 직접 디버깅 하지 않으면 어떤 타입의 데이터가 들어오는 지 모른다...!!!!
그래서 주변 Python 개발자들에게 함수 매개변수의 타입을 제발 명시해달라고 말하곤 한다.
구문과 표현식(Statement and Expressions)
여기서도 다른 언어와 차이가 발생한다. C언어는 할당문이 할당된 값을 반환하여 x = z = 6; 과 같이 작성할 수 있지만, 러스트에서는 불가능하다.
//파일명: src/main.rs
fn main() {
let y = 6; // 구문이다. 아무것도 반환하지 않는다.
let x = (let z = 6); // 이렇게 하면 오류가 발생한다.
}
표현식을 활용하여 값을 넣는 것은 가능하다. 아래 예제는 중괄호로 만들어진 내부 스코프 블록을 가지는 표현식 코드이다.
//파일명: src/main.rs
fn main() {
let y = {
let x = 3;
x + 1 // 4 반환!
};
println!("The value of y is: {y}");
}
여기에서 왜 4가 반환되었는 지 이해가 가지 않을 수 있다. 그에 대한 내용은 바로 다음 반환 값을 갖는 함수에서 설명할 것이다.
반환 값을 갖는 함수(Function with Returns)
러스트는 return 키워드를 작성하지 않아도 된다. 물론 작성해도 된다.
맨 마지막 명령의 구문 종료(세미콜론, ;)를 제거하면, 마지막 명령값을 반환한다는 의미이다.
함수 반환 타입은 화살표(->) 뒤에 선언되어야 한다.
//파일명:src/main.rs
fn gdp_per_capita() -> f32 {
34997.78 // 뒤에 세미콜론(;)이 없다! 즉, return 이 생략되었다!
}
fn main() {
let value = gdp_per_capita();
println!("한국 1인당 GDP는 다음과 같다 : {value}");
}
만약 마지막 표현식 뒤에 세미콜론이 추가된다면 표현식이 구문으로 변경되어 값을 반환하지 않게 된다.
그렇게 된다면 함수의 정의에서 f32 값을 반환한다고 했지만, 아무것도 반환하지 않으므로 정의된 내용과 상충하게 되어 에러가 발생한다.
은근히 보기도 불편하고, 표현식이랑 구문 구분하기 귀찮아서 그냥 매번 return 키워드를 작성한다.
4. 주석(Comment)
설명 보다는 제트브레인 인텔리제이(Jetbrain IntelliJ Ultimate) 의 단축키를 알려주겠다.
- ctrl + / : 한 줄 주석 생성 혹은 해제
- ctrl + shift + / : 선택된 부분 주석 생성 혹은 해제
// 한 줄 주석
/*
여러줄 주석
*/
/// 함수 혹은 구조체 등 설명 주석
/// 함수나 변수 바로 위에 작성한다.
fn 함수 이름() ...
5. 제어 흐름문(if, loop, for, while)
if 문 (조건문)
조건문이다. () 소괄호를 안 쓴다.
//파일명: src/main.rs
fn main() {
let number = 3;
if number < 5 {
println!("조건 만족");
} else {
println!("아 불만");
}
}
else if 키워드로 여러 조건을 다루는 건 동일하다.
삼항 연산자 대신 let 키워드에서 조건문을 사용할 수 있다.
//파일명: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { 6 };
println!("오... : {number}");
}
다만 모든 표현식이 그렇다싶이, 참일 때의 반환값과 거짓일 때의 반환값의 타입이 동일해야한다.
// 파일명: src/main.rs
fn main() {
let condition = true;
let number = if condition { 5 } else { "아앍 에러 발생시킬거야!" };
println!("이 출력문은 실행되지 않는다 : {number}");
}
러스트에서는 친절히 어느 부분에서 문제가 발생했는 지, 콘솔(console)에서 매우 정확히 알려준다.
반복문
loop
영원히 끝나지 않는 반복
//파일명: src/main.rs
fn main() {
loop {
println!("반복!");
}
}
ctrl + c 로 콘솔을 정지 시키지 않는 한, 해당 프로그램은 영원히 꺼지지 않는다...
loop 반환
반환값을 가지는 반복문
//파일명:src/main.rs
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("반복의 결과는 다음과 같다 : {result}");
}
break 키워드가 return 키워드 대신 있다.
loop 라벨
loop 에 이름을 붙일 수 있다.
//파일명:src/main.rs
fn main() {
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
}
여러 루프들을 구분하기 위해 사용한다.
while
조건이 참이면 반복 거짓이면 종료한다.
//파일명: src/main.rs
fn main() {
let mut elevator = 11;
while number > 1 {
println!("{number}층 입니다!");
number -= 1;
}
println!("아파트 정문에 도착했습니다!");
}
for
집합(Collection)의 모든 요소를 안정적으로 순회하기 위한 반복문
//파일명: src/main.rs
fn main() {
let dec = [10, 20, 30, 40, 50];
for element in dec {
println!("해당 값은 다음과 같다 : {element}");
}
}
집합의 요소의 개수가 달라지더라도 해당 반복문을 수정할 필요가 없으므로 매우 간편해진다. JAVA 에서는 다음과 같다.
// JAVA
void main() {
int a[] = {10, 20, 30, 40, 50};
for(int element : a){
System.out.println("해당 값은 다음과 같다 : " + element);
}
}
또한 범위(Range) 를 활용하여 카운트 다운을 쉽게 구현할 수 있다.
//파일명: src/main.rs
fn main() {
for number in (1..5).rev() {
println!("{number}!");
}
println!("발사!!!");
}
6. 예제(Example)
n번째 피보나치 수 생성하기
하단 코드를 복사하여 피보나치를 구현 해보자.
//파일명:src/main.rs
use std::io;
fn main(){
loop {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("값 읽기 실패");
let n:usize = match input.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
// n을 활용하여 피보나치 수를 구하는 코드를 짜보자
}
}
정답
while 문을 활용한 답
//파일명:src/main.rs
use std::io;
fn main(){
loop {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("값 읽기 실패");
let mut n:usize = match input.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
let mut f_n:usize = 0;
let mut f_nn:usize = 1;
if n == 0 {
f_nn = f_n;
}else if n != 1{
while n > 1 {
let s = f_n + f_nn;
f_n = f_nn;
f_nn = s;
n = n - 1;
}
println!("{f_nn}");
}
}
}
재귀함수를 활용한 답
//파일명:src/main.rs
use std::io;
fn fib(n:usize) -> usize{
if n == 0 || n == 1 {
n
}else{
fib(n-1) + fib(n-2)
}
}
fn main(){
loop {
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.expect("값 읽기 실패");
let n:usize = match input.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
println!("{}", fib(n));
}
}
'코딩 > Rust' 카테고리의 다른 글
[Rust] 5장 컬렉션(Collection) - 벡터(Vector), 문자열(String), 해시맵(HashMap) (0) | 2024.03.16 |
---|---|
[Rust] 4장 열거형(Enum), 모듈화(Module) (1) | 2024.03.15 |
[Rust] 3장 러스트만의 특이함 (1) | 2024.03.08 |
[Rust] 1장 안정성 높은 언어 (2) | 2024.03.05 |