본문 바로가기

Rust Programming

[Rust] 2. A Basic Calculator: Integers, Variables, Branching(if/else), Panics

Chapter 2에서는 Rust를 calculator로 사용하는 법에 대해 배운다.

calculator를 만들면서 다음과 같은 것들을 배울 수 있다.

  • 함수를 정의하고, 호출하는 법
  • 변수를 선언하고, 사용하는 법
  • 원시 자료형들(integers나 booleans)
  • 산술 연산자들(overflow와 underflow까지)
  • 비교 연산자들
  • Control flow
  • Panics

이런 것들을 배울 수 있다고 하며, intro에서는 다음과 같은 간단한 exercise를 했다.

 

그냥 calculator를 만들 준비가 됐냐고 묻는 것이었으며,

준비됐다고 return string만 바꿔주면 간단하게 통과할 수 있는 exercise였다.

만든 분이 상당히 귀여우신 것 같다.


 

용어들은 웬만하면 영어로 사용하도록 하겠다.

한글로 번역하면 이상하게 해석되는 경우가 많고, 모든 자료들은 대부분 영어기 때문이다.


u32 type이 무엇인가?

-> Rust의 primitive types 중 하나다.

 

그럼 primitive types는 무엇인가?

-> language 안에 내장되어 있고, language의 가장 기본적인 building blocks다.

 

primitive types를 가지고 무엇을 할 수 있는가?

-> primitive types를 조합하여 더 복잡한 types를 만들 수 있다.

 

다시 한번, u32로 돌아와서 u32 type은 unsigned 32-bit integer를 나타내는 type이다.

unsigned integer는 non-negative numbers를 뜻하며, 한국말로 하면 음이아닌 정수를 의미한다.

signed integer는 positive와 negative numbers를 모두 다룬다.

 

u32의 u는 unsigned를 나타내며, signed integer를 나타내고 싶으면 i32로 사용하면 된다.

여기서 i는 integer를 의미한다.

그리고 32는 메모리에서 수를 나타내는 bits의 수를 의미하며,

Rust에서는 정수형에 대해 8, 16, 32, 64, 128(bits)을 지원한다.

 

32bits의 i32 type은 -2^32부터 2^31-1까지의 정수를 나타낼 수 있으며,

범위의 양 끝 값은 i32::MIN과 i32::MAX로 사용할 수 있다.

 

 

즉, 위의 표와 같이 사용할 수 있다.

 


 

literal은 소스 코드에서 고정된 값을 표현한다.

example) 42는 Rust literal이며 42를 표현한다.

 

하지만! Rust의 모든 값들은 type을 가진다.

그럼... 위 42에 대한 type은 무엇인가?

 

Rust compiler는 위 literal이 어떻게 사용되느냐에 따라 type을 유추할 것이다.

만약, 아무 context도 없다면, compiler는 default로 i32로 type을 유추할 것이고,

다른 type으로 사용하고 싶다면, 코드 작성자가 원하는 integer type을 suffix로 붙이면 된다.

example) 42u64는 42를 u64 type으로 사용한다는 뜻이다.

 

또한, _와 같은 underscores를 literal을 표현할 때, 사용할 수 있다.

이는 큰 숫자의 가독성을 높일 수 있다.

example) 1_000_000은 1000000을 의미한다.

 


 

Rust의 integers는 다음과 같은 산술 연산자를 지원한다.

  • +, 더하기
  • -, 빼기
  • *, 곱하기
  • /, 나누기
  • %, 나머지

연산자 우선순위나 연산자 결합 법칙은 수학과 같다.

다음 예시와 같이 괄호를 통해 기본적인 연산자 우선순위를 override할 수 있다.

example) 2 * (3 + 4)

 

나누기 연산자인 /는 정수끼리 연산할 때, 몫을 연산자의 결과로 내놓는다.

따라서, 5 / 2의 결과는 2.5가 아니라 2다.

 


 

Rust는 type coercion에 대해 엄격하다.

자동 형 변환도 지원되지 않는다.

그래서, 사용자가 직접 명시적으로 형 변환을 해줘야한다.

 

예를 들어, u8 type의 값이 u32 값에 유효하더라도, 

u8 type의 값을 u32 type의 변수에 할당 할 수 없다.

 

error[E0308]: mismatched types
  |
3 |     let a: u32 = b;
  |            ---   ^ expected `u32`, found `u8`
  |            |
  |            expected due to this
  |

 

위와 같은 에러가 뜬다.

 


 

형 변환 관련한 것이나, 연산자 overloading에 대한 것은 나중에 다룬다고 한다.

그리고 위 규칙들에 대한 몇몇 예외들도 이후에 다룬다고 한다.

그냥 "All conversions are explicit"을 잘 지키는 것이 멘탈적으로 좋다고 한다.

 

너무 쉬운 것도 자잘자잘하게 다 다뤘기 때문에 시간이 많이 소요된다.

그래서 내가 아는 부분은 다소 짧게 다루도록 하겠다.


 

01_integers의 exercise는 literal의 type을 변수 a, b의 type과 맞추는 것이었다.

 

그냥 코드를 보고 쉽게 할 수 있다.

 


 

변수는 let으로 선언할 수 있다.

모든 변수는 Rust에서 type을 갖고 있으며,

개발자에 의해 명시적로 설정되거나, compiler에 의해 유추되거나 둘 중 하나다.

 

개발자에 의해 명시적으로 설정되는 것은 다음 예시와 같이 :를 사용하면 된다.

let x: u32 = 42;

 

변수 x의 type을 u32로 명시적으로 제한한 것이다.

 

변수의 type을 설정하지 않는다면, compiler는 code의 context에 따라 type을 추론할 것이다.

let x = 42;
let y: u32 = x;

 

그래서 위의 x는 y의 type과 같은 u32 type으로 추론할 것이다.

 

하지만, 이러한 inference에는 한계가 있다.

변수의 사용에 따라 맞는 변수 타입을 추론하는데 compiler가 어려움을 느낄 수 있고,

모호할 때, compiler는 compile error를 통해 명시적 타입 선언을 부탁한다.

 


 

function의 argument 또한 변수다.

하지만, function의 argument와 let으로 선언한 변수는 차이가 있다.

function의 argument는 type infer을 하지 않으며, 꼭! 명시적으로 type을 선언해줘야한다.

 


 

변수를 선언할 때, 꼭 초기화할 필요는 없다.

그러나, 변수를 사용하기 전에, 무조건 초기화를 해야하며,

그렇지 않다면, compile error를 낼 것이다.

 

let x: u32;
let y = x + 1;
error[E0381]: used binding `x` isn't initialized
 --> src/main.rs:3:9
  |
2 | let x: u32;
  |     - binding declared here but left uninitialized
3 | let y = x + 1;
  |         ^ `x` used here but it isn't initialized
  |
help: consider assigning a value
  |
2 | let x: u32 = 0;
  |            +++

 


 

02_variables의 exercise는 거리 변수를 선언해서 거리를 구해주면 되는 것이었다.

 

아직 초반이라 간단하다.

 


let number = 3;
if number < 5 {
    println!("`number` is smaller than 5");
}

 

if는 위와 같이 if 조건문 { code block } 으로 사용된다.

 

let number = 3;

if number < 5 {
    println!("`number` is smaller than 5");
} else {
    println!("`number` is greater than or equal to 5");
}

 

위와 같이 else를 사용할 수도 있다.

 


 

if expression의 조건은 항상 bool type이다.

boolean은 integers와 같이 Rust의 primitive type이며 true와 false 값을 갖는다.

 

if expression의 조건이 boolean type이 아니면 compilation error를 띄운다.

let number = 3;
if number {
    println!("`number` is not zero");
}
error[E0308]: mismatched types
 --> src/main.rs:3:8
  |
3 |     if number {
  |        ^^^^^^ expected `bool`, found integer

 

위는 boolean type이 아닌 type에 대해 automatic conversion을 제공하지 않는 것을 보여준다.

그래서 Rust는 JavaScript나 Python과 같이 truthy나 falsy 값들의 개념이 없다.

 

** truthy와 falsy **

truthy: 참으로 취급 되는 값

falsy: 거짓으로 취급 되는 값

example) c++에서 0은 false, 0이 아닌 정수는 true로 취급된다.

 


다음은 정수간의 조건을 체크할 때 사용되는 비교 연산자들이다.

  • == : 같다
  • != : 다르다
  • < : 왼쪽이 오른쪽보다 작다
  • > : 왼쪽이 오른쪽보다 크다
  • <= : 왼쪽이 오른쪽보다 작거나 같다
  • >= : 왼쪽이 오른쪽보다 크거나 같다

Rust에서 if expressions는 expressions고 statements가 아니다.

expressions는 value를 return하고 해당 value는 변수에 할당되거나 다른 expressions에 사용될 수 있다.

let number = 3;
let message = if number < 5 {
    "smaller than 5"
} else {
    "greater than or equal to 5"
};

 

위 예시에서 if의 각 branch는 message 변수에 할당될 string literal을 평가한다.

위에서 유일한 요구사항은 if branches의 return type을 같게 해야하는 것 뿐이다.

 


03_if_else exercise는 짝수 판별하여 boolean type을 return하는 코드를 작성하는 것이다.

 

별거 없다.

 


 

0으로 나누면 다음과 같은 error message를 주며 프로그램이 종료된다.

thread 'main' panicked at src/main.rs:3:5:
attempt to divide by zero

 

이런 것을 panic이라고 한다.

panic은 프로그램에 문제가 발생해 더이상 실행할 수 없을 때, 발생하는 signal이다.

또한 unrecoverable error라고도 한다.

 


 

개발자는 panic! macro를 사용하여 의도적으로 panic을 유발할 수 있다.

fn main() {
    panic!("This is a panic!");
    // The line below will never be executed
    let x = 1 + 2;
}

 

Rust의 recoverable errors에 대한 다른 mechanism들은 추후에 다룰 것이다.

 


04_panics의 exercise는 custom message를 통한 panic을 발생시키는 것이었다.

 


 

지금까지

  • 함수를 정의하는 법
  • 함수를 호출하는 법
  • 어떤 integer types가 Rust에서 사용가능한지
  • 어떤 산술 연산자들이 정수형에서 사용가능한지
  • 비교와 if/else expressions를 통해 조건문을 실행하는 법

을 배웠다.

 


 

그래서 05_factorial exercise에서 위에서 배운 것을 활용해 factorial을 구현하라고 한다.

 

반복문을 사용하지 말라고 해서, 재귀로 구현했다.

 


 

여기까지가 chapter2의 factorial까지의 내용이다.

아직까지 어려움은 하나도 없다.