Chap.3 common-programming-concepts
변수
, 타입
, 함수
, 주석
, 제어문
에 대해서 배울 것입니다.
3.1 변수와 가변성
- 변수는 기본적으로 불변입니다.
- 이것은 러스트가 제공하는 안정성과 쉬운 동시성이라는 이점을 얻을 수 있는 방향으로 코드를 쓰게 하는 강제사항(nudge)중 하나입니다.
mut
를 사용해 가변으로 만들 수 있음let mut x = 5;
- 버그를 방지하는 것 외에도 고려해야 할 비용이 있습니다.(함수형 프로그래밍의 불변성 컨셉에 대한 내용)
- 예를 들어, 큰 데이터 구조를 사용할 때, 인스턴스를 알맞게 가변으로 설정하는 것은 새로 인스턴스를 할당하고 복사해서 돌려주는 것보다 빠를 수 있습니다.
- 작은 데이터 구조라면, 새 인스턴스를 만들고 더 함수형 프로그래밍 스타일 로 작성하는 것이 더 흐름을 따라가기 쉽기 때문에, 퍼포먼스가 느려지더라도 명확성을 얻는 것에 대한 패널티로 받아들이는 것이 좋을 수 있습니다.
변수와 상수의 차이
- 먼 저,
mut
와 상수를 함께 사용할 수 없음 - 상수는 항상 불변 - 상수는
const
키워드로 선언하며, 값의 타입은 반드시 어노테이션이 달려야 합니다. - 마지막 차이점은, 상수는 반드시 상수 표현식이어야 하고 함수의 결과값이나 런타임에 결정되는 그 어떤 값이어도 안된다는 것입니다.
- 상수를 위한 러스트의 작명 관례는
대문자 스네이크 표기법
const MAX_POINTS: u32 = 100_000;
덮어쓰기
- 새 변수를 이전 변 수명과 같은 이름으로 선언할 수 있고,
- 새 변수는 이전의 변수를 덮어씁니다.
- 러스트인들은 첫 번째 변수가 두 번째 변수에 의해 덮어쓰였다라고 표현
let x = 5;
let x = x + 1;
let x = x * 2;
->x is: 12
- 덮어쓰기는 변수를
mut
로 표시하는 것과는 다릅니다.let
키워드 없이 값을 재할당 하려고 한다면 컴파일-타임 에러가 발생하기 때문입니다.
mut
과 덮어쓰기의 또다른 차이점은, 같은 변수명으로 다른 타입의 값을 저장할 수 있다는 것입니다.
3.2 데이터 타입
러스트에서 사용되는 모든 값들은 어떤 타입을 갖습니다.
그러니 어떤 형태의 데이터인지 명시하여 러스트 컴파일러가 데이터를 어떤 식으로 다룰 수 있는지 알게끔 해야합니다. 여기서는 타입을 스칼라 타입과 복합 타입, 두 가지 부분 집합으로 나누어 보겠습니다.
러스트는 타입이 고정된 (statically typed) 언어라는 점을 주지하세요.
이게 의미하는 바는 모든 변수의 타입이 컴파일 시점에 반드시 정해져 있어야 한다는 겁니다.
보통 컴파일러는 우리가 값을 어떻게 사용하는지에 따라 타입을 추측할 수 있습니다.
하지만 타입의 선택 폭이 넓은 경우는 다음과 같이 반드시 타입을 명시해야 합니다
let guess: u32 = "42".parse().expect("Not a number!");
스칼라 타입
- 스칼라 타입은 하나의 값을 표현합니다.(JS의
primitive
)- integers,
- floating-point numbers
- Booleans
- characters.
정수형
- 정수형_은 소수점이 없는 숫자입니다.
- 부호 있는 타입은
i
로 시작 - 부호 없는 타입은
u
로 시작
Length | 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 |
- 부호 있는 (signed), 부호 없는(unsigned)으로 나뉨
- 오직 양수만을 가질 것인지
- 부호와 함께 다뤄야 하는 경우 숫자는 더하기 혹은 빼기 기호와 함께 표시하죠.
- 하지만 숫자가 양수라고 가정해도 문제 없는 상황에는 부호 없이 표시하게 됩니다.
- 각 부호 있는 타입의 변수는 -(2n - 1) 부터 2n - 1 - 1 까지의 값을 포괄합니다.
- 여기서 n_은 사용되는 타입의 비트 수 입니다.
- 따라서
i8
은 -(27) 에서 27 - 1 까지의 값, 즉 -128 에서 127 사이의 값을 저장할 수 있습니다. - 부호 없는 타입은 0 에서 2n - 1 까지의 값을 저장할 수 있습니다.
- 그래서
u8
타입은 0 에서 28 - 1 다시 말해, 0 에서 255 까지의 값을 저장할 수 있습니다.
- 따라서
- 추가로,
isize
와usize
타입은 여러분의 프로그램이 동작하는 컴퓨터 환경에 따라 결정됩니다.- 64-bit 아키텍처이면 64bit를, 32-bit 아키텍처이면 32bit를 갖게 됩니다.
- 정수형 리터럴은 Table 3-2에서 보시는 것과 같은 형태로 작성할 수 있습니다.
- byte 리터럴을 제외한 모든 정수형 리터럴에는
57u8
과 같은 타입 접미사와1_000
과 같이 시각적인 구분을 위한_
을 사용할 수 있습니다.
Number literals | Example |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte (u8 only) | b'A' |
그러면 어떤 타입의 정수를 사용해야 하는지는 어떻게 알까요? 확실하게 정해진 경우가 아니면 러스트의 기본 값인 i32
가 일반적으로는 좋은 선택입니다.
이 타입이 일반적으로 가장 빠르기 때문이죠. 심지어 64-bit 시스템에서도요. isize
나 usize
는 주로 컬렉션 타입 종류의 인덱스에 사용됩니다.
정수 오버플로우
-
여러분이 0과 255 사이의 값을 담을 수 있는
u8
타입의 변수를 갖고 있다고 해봅시다. -
만약에 이 변수에 256처럼 범위 밖의 값으로 변경하려고 하면 정수 오버플로우 (integer overflow) 가 일어납니다.
-
코드를 디버그 모드에서 컴파일하는 경우에는 런타임에 정수 오버플로우가 발생했을 때 패닉 (panic) 을 발생시키도록 검사합니다.
- 러스트에서는 에러가 발생하면서 프로그램이 종료되는 경우 패닉이라는 용어를 사용합니다.
-
--release
플래그를 사용하여 코드를 릴리즈 모드로 컴파일하는 경우에는 패닉을 발생시키는 정수 오버플로우 검사를 실행파일에 포함시키지 않습니다.- 대신 오버플로우가 발생하면 러스트는 2의 보수 감싸기 (two's complement wrapping) 을 수행합니다. 짧게 설명하자면, 해당 타입이 가질 수 있는 최대값보다 더 큰 값은 허용되는 최소값으로 “돌아갑니다 (wrap around)”.
u8
의 경우 256은 0이, 257은 1이 되는 식입니다. 프로그램은 패닉을 발생시키지 않으나, 해당 변수는 아마도 여러분이 예상치 못했던 값을 갖게 될겁니다.- 정수 오버플로우의 감싸기 동작에 의존하는 것은 에러로 간주됩니다.
명시적으로 오버플로우의 가능성을 다루기 위해서는 표준 라이브러리가 기본 수치 타입에 대해 제공하는 아래 메소드 종류들을 사용할 수 있습니다:
wrapping_add
와 같은wrapping_*
메소드로 감싸기 동작 실행하기checked_*
메소드를 사용하여 오버플로우가 발생하면None
값 반환하기overflowing_*
메소드를 사용하여 값과 함께 오버플로우 발생이 있었는지를 알려주는 boolean 값 반환하기saturating_*
메소드를 사용하여 값의 최대 혹은 최소값 사이로 제한하기
부동 소수점 타입
러스트의 부동소수점 타입은 f32
와 f64
로, 각각 32bit와 64bit의 크기를 갖습니다.
기본 타입은 f64
인데, 그 이유는 현대의 CPU 상에서 f64
가 f32
와 대략 비슷한 속도를 내면서도 더 정밀하기 때문입니다.
다음은 부동소수점 숫자의 용례입니다: - let x = 2.0; // f64
- let y: f32 = 3.0; // f32
- 부동소수점 숫자는 IEEE-754 표준을 따릅니다.
f32
타입은 1배수 정밀도 (single-precision)인 부동소수점이고,f64
는 2배수 정밀도(double-precision)입니다.
수치 연산
러스트는 모든 숫자 타입에 대해서 여러분이 기대하는 기본 수학 연산 기능을 제공합니다.
부록 B에 Rust가 제공하는 모든 연산자 목록이 있습니다.