let mut val: i32 = 1239;
변수 할당은 let
으로 가능하고, mut
로 가변 여부를 지정할 수 있다.
또한 변수 이름 뒤에 콜론과 함께 자료형을 입력하여 변수의 자료형을 명시적으로 지정해줄 수 있다. 다만 대부분의 상황에서 Rust 컴파일러가 알아서 자료형을 추측하기 때문에 보통은 생략하고, 문자열을 파싱하는 등 다양한 자료형이 올 수 있는 애매한 상황에서만 명시적으로 지정해줘도 된다.
const THIS_IS_CONSTANT_VALUE: f64 = 1929.2323;
const THIS_IS_CONSTANT_VALUE: f64 = 3.14 * 23123;
상수는 const
로 가능하고, Rust에서는 대문자 + 밑줄(_)을 활용하여 상수 이름을 정한다. 그리고 일반 변수와 다르게 반드시 상수의 자료형을 명시적으로 정의해주어야 한다! 또한 상수는 런타임에서 연산이 불가능하다. 굳이 상수에 식을 써야겠다면, 사칙연산 같은 가장 기초적인 연산만 사용할 수 있다는 듯.
let a = 6;
let a = 7;
저번 글에서 간단히 짚고 넘어갔기 때문에 뭐... 굳이 더 설명할 필요는 없을 것 같다.
Rust의 자료형에는 단일 값을 받는 Scalar 자료형과 여러 값을 하나로 묶는 Compound 자료형이 있다고 한다. Scalar에는 정수, 소수(부동소수점), 부울, 문자가 존재하고, Compound에는 튜플과 배열이 있다.
// 정수의 다양한 표현
let dec = 1000;
let hex = 0xff;
let oct = 0o77;
let bin = 0b11;
// 언더바 사용
let easy_to_read = 1_024_768; // 1,024,768
8, 16, 32, 64, 128 비트의 Signed, Unsigned 정수형을 제공한다. 앞에 부호 여부를 쓰고 그 뒤에 비트 수를 붙여주면 된다. u32
, i32
, ..., 이런 식으로. 추가로 isize
, usize
의 경우 시스템의 최대 메모리 공간에 맞추는 듯하다. 32 비트 시스템의 경우는 32 비트로, 64 비트 시스템의 경우는 64 비트로. 추가로 byte
라는 자료형도 있던데, 이거는 아스키 코드 말하는 것 같다.
당연히 진법도 적용 가능하다. 그리고 신기한 점으로 보통 사람들이 숫자를 쓸 때 읽기 쉽게 쉼표를 중간에 붙이곤 하는데(1,024,768처럼), Rust에서는 쉼표의 역할을 하는 밑줄을 제공해준다. 근데 실제로 자주 쓰진 않을 듯.
let mut a = b'l';
a = a.
그리고 정수 연산 중 발생하는 예외 처리를 위한 다양한 함수가 있다고 한다. wrapping_add
나 overflow_add
이런 것들... wrapping_add
를 예로 들면 자료형이 받아낼 수 있는 최대값 이상이 연산될 경우, 그 결과값에 2의 보수법을 적용한 값을 결과로 반환한다고 한다. 다만 이거는 오류를 내지 않기 위한 수단일 뿐, 실제 프로그램 돌아갈 땐 의도되지 않은 결과가 발생할 게 뻔하므로 예외 처리를 빡세게 할 수 있도록 하자.
let a = 3.14; // f64
f32
, f64
의 두 가지 자료형을 제공한다고 한다. 타입 지정 안 하고 소수를 쓸 경우, 기본값은 f64
.
또한 얘는 Signed밖에 없다.
더하기, 빼기, 나누기, 곱하기, 나머지 연산 다 지원한다.
true
그리고 false
.
이건 뭐 코드 좀 만져본 사람이면 다 인정하는 거라.
char
)let character = 'a';
4 바이트의 크기를 갖는 유니코드 형식 문자다. 이러한 특징으로 인해 한국어나 이모티콘까지 모두 char
에 해당한다.
// 선언
let tup = (1, 3.1415, "Alpha");
// 튜플 내 값에 접근
println!("{}", tup.0);
Python에서 보던 그거다. 위 코드처럼 소괄호에 쉼표를 더해 선언할 수 있고, 각자 다른 자료형끼리도 하나의 튜플로 묶을 수 있다.
튜플 내부 값에 대한 접근은 tuple.n
의 형태로 한다. 예를 들어 tuple
의 10 번째 값을 확인하고 싶다면 tuple.9
라고 써 주면 된다. (인덱스가 0부터 시작하기 때문에 9로 표기)
// 튜플 값의 수정
let mut a = (1, 3, 4);
println!("{}", a.0);
a.0 = 3;
println!("{}", a.0);
다만 값의 수정이 불가능한 Python과 달리, Rust에서는 mut
를 붙여 선언할 경우 값의 수정이 가능한 것으로 보인다. 위 코드는 컴파일 잘 되고 값도 바뀐 값으로 출력된다.
// 튜플 값의 분해
let tup = (1, 2, 3);
let (a, b, c) = tup
Rust의 튜플은 분해도 가능하다. 위 코드를 참고하자. 예시로 a
가 tup.0
에 대응된다.
// Unit 튜플
let tup = ();
비어 있는 튜플은 특별히 Unit
이라는 이름으로 불린단다. Kotlin에서 자주 보던 그거. 아마 C 계열의 void
나 Kotlin의 Unit
에 대응되는 요소 같다.
// 선언
let arr = [1, 2, 3, 4, 5];
// 배열 내 값에 접근
println!("{}", arr[0]);
배열은 C의 배열과 거의 유사하다. 선언 형태도 똑같다.
튜플과 달리 단일 자료형의 값으로만 구성할 수 있고 대괄호와 인덱스를 통해 내부 값에 접근한다. 또한 길이가 고정되어 있다! 길이가 가변적인 Python의 리스트와는 다르다. Vector 자료형도 내부 라이브러리에 존재하기는 한다는데 아직은 안 보여줬고 나도 찾아보지는 않았다.
// 배열 자료형 명시하기
let integer_arr: [i32; 5] = [1, 2, 3, 4, 5];
// 하나의 값으로 배열 전체 초기화하기
let same_arr:[i32; 5] = [128; 5];
let
으로 배열을 선언할 때 자료형을 명시하고 싶다면, [자료형; 길이]
로 써 주자.
- Statement 특정 동작을 수행하지만 반환 값 없음
- Expression 특정 값으로 평가되어 반환될 수 있음
Statement의 예시로 let
, fn
등이 있고, Expression은 뒤에 세미콜론이 붙지 않는다. 세미콜론이 있는 경우 이는 Statement라고 한다.
{
let x = 3;
x + 1
}
이상의 코드는 세미콜론이 붙지 않으므로 Expression이다. 대충 예상은 하겠지만 4를 반환한다.
// 함수 예시
fn add_on_pie(value: f64) -> f64 {
value + 3.14
}
// 함수
fn 함수_이름(매개변수: 매개변수 자료형) -> 반환 자료형 {
함수 바디
}
함수는 위 코드와 같이 선언한다. Kotlin의 함수 형태와 거의 유사하다.
// Expression을 활용한 함수 반환
// 1. return과 세미콜론 다 붙이기: 컴파일 됨
fn some_func() -> u32 {
return 120;
}
// 2. 세미콜론만 빼기: 컴파일 됨
fn some_func() -> u32 {
return 120
}
// 3. return과 세미콜론 다 빼기: 컴파일 됨
fn some_func() -> u32 {
120
}
위의 Expression을 활용하면 이런 다양한 반환문을 사용할 수 있다. 이게 가능한 이유는 Rust의 모든 함수는 암시적으로 함수 몸체의 가장 마지막 Expression을 반환하도록 구성되어 있기 때문이라고 한다. 신기한 부분.
//
로 쓰면 된다. /* */
도 된다.
///
은 컴파일러에서 쓰지 말라고 한다.
// loop
loop {
break; // 루프 깨기
}
// while
while condition {
}
// for
for item in array {
}
loop
, while
, for
이 있다. 그리고 셋 모두 break
와 continue
를 지원한다.
loop
의 경우 단순한 무한 반복이다.
while
의 경우 위 코드의 condition
에 해당하는 값이 true
일 경우에만 반복한다. 재밌는 점 하나로 condition
Expression을 소괄호에 묶어줄 필요가 없다. C는 소괄호에 안 묶어주면 오류 내는데. 이건 편리한 부분 같다.
for
의 경우 Python에서 많이 본 형태대로 사용 가능하다.
// 평범한 탈출
loop {
break;
}
// 값을 반환하며 탈출
let a = loop {
break 300;
}
break 뒤에 특정한 값을 붙여 줄 경우 루프를 깨면서 그 값을 반환할 수 있다. 이상의 코드에서 a
의 값은 300이 된다.
// 루프에 레이블 붙이기
'loop_1: loop {
'loop_2: loop {
// body
}
}
루프에 이름도 붙여줄 수 있다. 작은 따옴표 뒤에 이름을 적어주고 루프를 선언해주면 된다. 이거는 보통 중첩 루프문에서 특정 루프를 깨고 싶을 때 사용하는 것 같다.
대충 코드 끄적이면서 몇 가지 특징을 알게 되었다. 간단히 정리해보자.
let tup = (1, 2, 3);
println!("{} {} {}", tup.0, tup.1, tup.2); // "1 2 3" 출력됨
먼저, String
에 복잡한 표현식을 쓰고 싶을 때는 중괄호를 적극 이용하자. C 언어에서 문자열에 변수 넣을 때 썼던 방법과 유사하지만, 여기서는 순서만 잘 지켜주면 된다.
다음으로 Rust에는 후위표기식이 없단다. 후위표기식을 코드에 넣어 봤더니 "우리 집에는 그런 메뉴 없어요~"란다. 난 세상에 이런 기능 없다고 컴파일러가 말하는 경우는 처음 봤다. 재미있는 언어다.
마지막으로 Rust에서 Kotlin의 IntRange
같은 게 존재한다. 1..4
이런 식으로 쓰는데, Kotlin과는 다르게 마지막 값은 포함하지 않는다. 알아둬야 할 듯.