[RUST] 3. 일반 프로그래밍 개념
- 📕 Book/러스트 프로그래밍 공식 가이드
- 2022. 4. 9.
변수와 가변성
기본적으로 변수는 변경이 불가능 하다. 하지만 필요하다면 변수를 변경할 수 있게 선언할 수도 있다. 확인을 위해 variables 라는 프로젝트를 만들자
fn main() {
let x = 5;
println!("x의 값: {}", x);
x = 6;
println!("x의 값: {}", x);
}
위 코드 작성후 빌드를 실행하면 에러가 발생한다.
에러 메시지를 읽어보면 불변 변수 x에 값을 두번 할당했기 때문이라고 나온다. 변수 이름 앞에 mut 키워드를 추가하면 가변 변수를 선언할 수 있다.
fn main() {
let mut x = 5;
println!("x의 값: {}", x);
x = 6;
println!("x의 값: {}", x);
}
이후 프로그램을 다시 실행하면 다음 결과가 나온다.
변수와 상수의 차이점
변수의 값을 변경할 수 없다는 개념은 다른 언어들이 가진 상수(constants)개념에 가깝다. 그렇지만 변수와 상수는 다음과 같은 차이점이 있다.
- 상수는 mut 키워드를 사용할 수 없다. 상수는 기본 선언만으로 불변 속성이 아니라 항상 불변이다.
- 상수는 let 키워드 대신 const 키워드를 사용한다.
- 상수는 반드시 상수 표현식에서 사용해서 값을 할당해야한다.
- 러스트에서는 상수이름에 대문자만 사용하며 단어사이에 밑줄을 추가하는 규칙을 사용한다.
const MAX_POINTS: u32 = 100_000;
변수 가리기
선언한 변수의 이름과 똑같은 이름의 변수를 선언할 수 있다. 이때 이전에 선언한 변수는 새로 선언한 변수 때문에 가려진다. 가려졌다(shadowed)라고 표현한다.
fn main() {
let x = 5;
let x = x + 1;
let x = x * 1;
println!("x의 값: {}", x);
}
변수 가리기는 mut 키워드를 이용하는 방법과는 다르다.
let spaces = " ";
let spaces = spaces.len();
첫번째 spaces 변수는 문자열 변수이고, 두번째 변수는 숫자타입의 새로운 변수이다.
let mut spaces = " ";
spaces = spaces.len();
위 코드는 변수의 타입을 변경할 수 없다는 컴파일 에러를 출력한다.
데이터 타입
러스트는 정적 타입 언어라서 컴파일 시점에 모든 변수의 타입이 결정되어야 한다. 컴파일러는 타입 추론을 통해 변수에 할당된 값이나 변수의 사용을 보고 실제 타입을 예측한다. 여러 타입을 사용할 수 있을 때는 타입 애노테이션(annotation)을 이용해 타입을 명시해 주어야 한다.
스칼라 타입
스칼라(Scalar) 타입은 하나의 값을 표현한다. 러스트는 정수(integer)와 부동 소수점 숫자(floating point number), 불리언(Booleans), 문자(characters)등 네가지 종류의 스칼라 타입을 정의하고 있다.
정수 타입
정수는 소수점이 없는 숫자다. 다음의 정수타입 종류가 있다.
크기 | 부호 있음 | 부호 없음 |
---|---|---|
8bit | i8 | u8 |
16bit | i16 | u16 |
32bit | i32 | u32 |
64bit | i64 | u64 |
arch | isize | usize |
isize와 usize 타입은 컴퓨터의 아키텍처 환경에 따라 크기가 달라진다. 그리고 다음과 같이 정수 리터럴을 표현할 수 있다. 바이트를 제외한 모든 숫자 리터럴에는 57u8과 같이 타입 접미사(suffix)를 붙일 수 있고, 1_000 과 같이 밑줄을 이용해 자릿수를 표현할 수도 있다.
숫자 리터럴 | 예시 |
---|---|
Decimal | 98_222 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1111_0000 |
Byte(u8전용) | b'A' |
부동 소수점 타입
러스트의 부동 수수점 타입인 f32와 f64 타입은 각각 32bit와 64bit 크기다. 러스트는 f64를 기본 타입으로 규정하고 있다. 부동 소수점 숫자는 IEEE754 표준에 따라 표현한다. f32타입은 단정도(single-precision) 부동 소수점이며, f64는 배정도(double-percision) 부동 소수점을 표현한다.
사칙 연산
러스트는 모든 숫자타입을 대상으로 덧셈과 뺄셈, 곱셈, 나눗셈, 나머지 등 기본 사칙 연산을 지원한다.
fn main() {
let sum = 5 + 10;
let difference = 95.5 - 4.3;
let product = 4 * 30;
let quotient = 56.7 / 32.2;
let remainder = 43 % 5;
}
불리언 타입
불리언 타입은 true 혹은 false 값중 하나를 표현하며 크기는 1byte 이다.
문자 타입
러스트는 문자도 지원한다. 러스트의 char 타입은 언어가 제공하는 가장 기본적인 알파벳 타입이며 4byte 크기의 유니코드 스칼라값이므로 ASCII보다 더 많은 문자를 표현할 수 있다.
컴파운드 타입
컴파운드 타입(compound type)은 하나의 타입으로 여러 개의 값을 그룹화한 타입이다. 러스트는 기본적으로 튜플(tuple)과 배열(array) 두 가지 컴파운드 타입을 지원한다.
튜플 타입
튜플은 서로 다른 타입의 여러 값을 하나의 컴파운트 타입으로 그룹화하기에 적합한 타입이다. 튜플을 생성할 때는 괄호 안에 값의 목록을 쉼표로 구분해서 표기하면 된다. 튜플의 각 요소는 타입을 가지며 반드시 같을 필요는 없다. 튜플에서 개별 값을 읽으려면 패턴 매칭을 이용해 튜플 값을 해체(destruct)할 수 있으며, 마침표의 인덱스를 지정하여 튜플의 각 요소를 직접 참조할 수도 있다.
fn main() {
let tup = (500, 6.4. 1);
let (x, y, z) = tup;
println!("y의 값: {}" ,y);
let five_hundred = tup.0;
let six_point_four = tup.1;
let one = x.2;
}
배열 타입
배열(array)은 튜플과 달리 같은 타입으로 이루어 져야하며, 다른 언어의 배열과는 달리 고정된 길이다. 배열에 저장할 값은 대괄호(square bracket,[]) 안에 쉼표로 구분해서 나열한다.
fn main() {
let a = [1, 2, 3, 4, 5];
}
배열은 다루고하자는 데이터를 힙이 아닌 스택 메모리에 할당하거나 항상 고정된 개수의 요소들을 다룰 때 유용하다. 배열에 저장할 원소의 개수를 지정하려면 다음과 같이 사용할 수 있다.
fn main() {
//i32 타입으로 5개 원소가 존재한다고 지정
let a: [i32; 5] = [1, 2, 3, 4, 5];
//5개의 원소를 3으로 초기화
let a = [3; 5];
}
배열의 요소에 접근하기
배열은 스택에 할당된 한 덩어리의 메모리이기에 인덱스를 활용하여 각 요솟값을 읽을 수 있다.
fn main() {
let a = [1, 2, 3, 4, 5];
let first = a[0];
let second = a[1];
}
유효하지 않은 배열 요소에 접근하는 경우
컴파일 시점에는 에러가 발생하지 않지만 프로그램을 실행하면 런타입 에러가 발생한다.
fn main() {
let a = [1, 2, 3, 4, 5];
let index = 10;
let element = a[index];
println!("요소의 값: {}", element);
}
에러 메시지에서 보듯이 패닉(panic)이 발생한다.
함수
러스트 코드는 함수와 변수의 이름에 스네이크 케이스(snake case)를 사용한다. 스네이크 케이스란, 소문자와 함께 단어를 밑줄 기호로 구분하는 형식을 말한다.
fn main() {
println!("안녕하세요!");
another_function();
}
fn another_function() {
println!("또 다른 함수");
}
함수 매개변수
함수는 매개변수(parameter)를 포함할 수 있다. 함수 시그니처에는 각 매개변수의 타입을 명시해야한다. 여러 개의 매개변수를 선언할 때는 각 매개변수를 쉼표로 구분하면 된다.
fn main() {
println!("안녕하세요!");
another_function(5);
another_function2(5, 6);
}
fn another_function(x: i32) {
println!("x의 값: {}", x);
}
fn another_function2(x: i32, y: i32) {
println!("x의 값: {}", x);
println!("y의 값: {}", y);
}
함수 본문의 구문과 표현식
함수 본문은 여러 개의 구문(statements)으로 구성되며, 선택적으로 표현식(expression)으로 끝나기도 한다. let 키워드를 이용해 변수를 생성하고 값을 대입하는 것은 구문이다. 구문은 값을 리턴하지 않기때문에 구문안에 구문을 포함 시킬수 없다. 표현식은 마지막에 세미콜론을 포함하지 않는다.
fn main() {
let y = 6; //구문
let x = (let y = 6); //구문 안의 구문 컴파일 에러
let x = 5;
let y = {
let x = 3;
x + 1 //세미콜론이 없다
}; //표현식
println!("y의 값: {}", y);
}
값을 리턴하는 함수
함수는 자신을 호출한 코드에 값을 리턴할 수 잇다. 리턴값에는 이름을 부여하지는 않지만, 리턴할 값의 타입은 화살표(->) 다음에 지정해 주어야한다. 표현식 마지막에 세미콜론을 넣게되면 구문으로 평가되어 리턴할 값이 없게되고 에러가 발생하게 된다.
fn five() -> i32 {
5
}
fn main() {
let x = five(); //let x = 5 와 동일
println!("x의 값: {}", x);
}
주석
일반적인 주석에 대한 내용이므로 넘어가자
흐름 제어
if 표현식
if 표현식은 조건에 따라 코드를 분기한다. if문의 조건은 반드시 불리언 타입중 하나를 리턴해야 한다. 조건이 불리언을 리턴하지 않으면 에러가 발생한다.
fn main() {
let number = 3;
if number < 5 {
println!("조건이 일치");
} else {
println!("조건이 불일치");
}
if number { //컴파일 에러 number != 0 등으로 수정해야함!
println!("변수의 값은 3입니다");
}
}
else if 문으로 여러 조건 처리
알고 있는 내용이므로 넘어가자
let 구문에서 if 표현식 사용하기
if 표현식의 결과가 대입되도록 할 수 있다. 물론 모든 결과는 같은 타입이어야 한다.
fn main() {
let condition = true;
let number = if condition {
5
} else {
6
};
}
루프를 이용한 반복
러스트는 loop, while, for 세 가지 종류의 루프를 제공한다.
loop를 이용한 반복 실행
fn main() {
loop { //무한 반복 break 키워드로 탈출할 수 있다.
println!("다시 실행!");
}
}
루프에서 값 리턴하기
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
while을 이용한 조건 루프
fn main() {
let mut number = 3;
while number != 0 {
println!("{}!", number);
number = number -1;
}
println!("발싸!");
}
for를 이용해 컬렉션을 반복 처리하기
fn main() {
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("요소의 값: {}", element);
}
for element in a.rev() { //배열 역순 탐색
println!("요소의 값: {}", element);
}
}
요약
- 변수, 스칼라와 컴파운드 데이터 타입의 개념
- 함수, 주석, if문, 반복문의 개념