본 게시글은 Udemy "Rust : 실제 애플리케이션 구축을 통한 Rust 완벽 정복" 강의 내용을 정리한 글입니다.
변수 선언, 반복분, 등 프로그래밍 기초 관련 문법은 없으니 참고바랍니다.
Rust는 안전성, 속도, 그리고 병행성을 강조하는 시스템 프로그래밍 언어입니다. 최근 C/C++의 대체언어로 유명해지면, 배우고 싶은 언어로 뽑히는 언어이기도 합니다.
Rust에서의 메모리 관리
Rust가 다른 언어와 유별나게 눈에 띄는 특징은 "컴파일만 되면 메모리 문제가 발생하지 않는 언어"라는 점입니다. 물론 그만큼 빡센 문법을 준수해야 하지만 그 안전성과 성능때문에 많은 IT기업에서 차차 사용되고 있습니다.
수동 메모리 관리
스택 메모리
스택 메모리는 함수 호출 시 지역 변수와 매개 변수를 저장하는 임시 메모리 공간입니다. 연속적인 메모리 블록을 사용하며, 빠른 접근 속도를 자랑합니다
fn main() {
let a = 2;
let result = stack_only(a);
}
fn stack_only(b: i32) -> i32 {
let c = 3;
return b + c;
}
- 위 코드에서 main함수에는 변수 a가 스택메모리에 할당되고 stack_only함수에 매개변수 b, 지역변수 c가 할당됩니다.
- 각 함수가 호출될때마다 Stack메모리에 StackFrame이 할당되며 StackFrame 내부에 지역 변수에 stack메모리가 쌓이는 형태이다.
- 코드를 실행하다가 함수 실행이 종료되면 Stack메모리에 해당 함수의 StackFrame를 소멸한다. 만약 재귀함수로 함수 반환이 이루어지지 않고 계속 함수가 호출되거나 지역변수가 선언되면 Stack overflow가 발생한다.
- 만약 stack_only가 한창 실행중일때 Stack메모리의 형태 아래와 같다
힙메모리
힙 메모리는 프로그램 실행 중에 동적으로 할당되는 메모리 영역입니다. 힙 메모리는 주로 동적 데이터 구조(예: 링크드 리스트, 트리)와 큰 데이터를 처리할 때 사용됩니다
fn main() {
let a = 2;
let result = stack_and_heap();
dbg!(result);
}
fn stack_and_heap() -> i32 {
let d = 5;
let e = Box::new(7);// heap메모리 할
return d + *e;
}
- 위 코드에서 stack_and_heap 함수가 실행되면 지역변수 d는 stack메모리에 할당되고, 동적할당 변수 e는 Heap메모리에 할당된다.
- Heap메모리는 OS에서 확보한 메모리 크기만큼 할당하여 그 주소값을 Stack메모리에 저장한다.
- 코드를 실행하다가 함수 실행이 종료되더라도 개발자가 따로 Heap메모리를 해제 하지 않으면 프로세스가 종료되지 않고서는 해제 되지 않는다. 그러면 Memory Leak이 발생하게 된다.
- 만약 stack_and_heap가 한창 실행중일때 메모리의 형태 아래와 같다
스마트 포인터
- 스마트 포인터의 경우 Heap메모리를 할당할때 Heap영역에 있는 메모리 주소값을 추적하는 기능을 제공한다.
- Stack frame에서 동적할당된 변수의 메모리 주소값을 추적하다가 동적 변수가 Stack frame를 벗어나면 동적할당된 변수를 자동으로 해제하면서 Memory Leak 현상을 방지한다.
Rust 기본 자료형
Rust에서 자료형은 변수의 크기를 기준으로 선언하고 bool, char, 실수, 정수, 아키텍처 변수(32bit OS에서는 4byte, 64bit OS에서는 8byte)를 지원한다.
자료형 크기 | 유부호 | 무부호 |
---|---|---|
8byte 정수 | i8 | u8 |
16byte 정수 | i16 | u16 |
32byte 정수 | i32 | u32 |
64byte 정수 | i64 | u64 |
128byte 정수 | i128 | u128 |
아키텍처 종속 | isize | usize |
32byte 실수 | f32 | - |
64byte 실수 | f64 | - |
bool(1byte) | bool | - |
char(4byte) | char | - |
함수 선언
Rust에서 함수선언은 다음과 같다.
fn cal(val:i32)-> i32 {
return (weight / 9.81) * 3.711
}
val:i32
: 매개변수
-> i32
: 반환 자료형이다. 함수를 반환할때는 기본적으로 Result 타입의 값에 담겨져 반환된다.
가변성
Rust에서는 변수를 할당하면 기본적으로 불변(immutable)이다. 만약 특정 변수를 선언하고 이를 변경하고 싶르면 mut로 선언하여 가변하다는 것을 명시해야한다.
let immVar = 4; // 불변변수
immVar = 5; //!!!컴파일 에러, 불변변수 변경 시
let mut mutVar = 4; //가변변수
mutVar = 5; // 변경 가능
표준 출력
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();//입력
println!("Number : {} / String : {}", 12, "str");//출력
- 위 코드에서 String 자료형을 동적할당하고 이를 가변변수로 선언하였다.
- io 입력을 수행하고 들어온 입력값이 저장될 주소값인 input을 가변변수 주소로 넘겨준다.
- input 매개변수 앞에 붙은 &mut는 소유권과 관련한 내용이다.
- 2번째줄 뒤에 .unwrap()은 read_line함수의 반환 Result<> 타입이 Err일 경우 프로그램을 졸료시켜버리는 안전장치이다.
소유권
Rust언어의 가장 특징적인 부분이다. Rust에서 모든 변수들은 다음과 같은 규칙에 의해 관리된다.
- Rust의 모든 값은 변수에 의해 관리된다.
- 소유권을 가진 변수의 Scope를 벗어나면, 해당 변수는 메모리 해제된다.
- 모든 변수는 단 하나의 소유권을 가진다.
예를 들어보자
fn main()
{
let mut input = String::new(); // 1
let mut s = input;//2
io::stdin().read_line(&mut input).unwrap();//3 !!!!컴파일 에러
io::stdin().read_line(&mut s).unwrap();//4
println!("Number : {} / String : {}", 12, "str");
}
- 1번줄에서 String::new()는 동적할당 주소값을 가변변수 input의 소유로 부여된다.
- 2번줄에서 input의 소유로 관리되던 String::new()주소를 s변수의 소유로 변경된다.
- 그러면 모든 값은 단 1개의 소유로 관리되어야하기 때문에 input 변수는 stack 메모리에서 해제되며 사용 불가능하다.
- 그러한 원리로 3번 줄에서 컴파일 에러가 발생한다.
- 4번줄에서 소유권이 변경된 변수를 사용하면 문제가 없다.
그렇다면 다음과 같은 경우를 짚고 넘어가자
fn main()
{
let mut a = 1;
let mut b = a; //값이 복사됨
println!("a : {} / b : {}", a, b);
}
- 기본 자료형의 소유권을 변경할 시에는 소유권을 이전하지 않고 값을 복사해버린다.
- 그런데 문제가 있다. main함수의 input변수를 다른 함수에 매개변수를 넘기고자 한다. 그런데 다른 함수의 매개변수로 넘기며 소유권을 넘기면, 다른 함수가 끝날때 Scope에서 벗어나서 input변수가 해제된다.
fn main()
{
let mut input = String::new();
another(input);// 1. 소유권이 s : String으로 넘어감
io::stdin().read_line(&mut input).unwrap(); // 3. input을 사용할수 없음
println!("Number : {} / String : {}", 12, "str");
}
fn another(s : String)
{
//2. 이 함수가 끝나면 s는 할당 해제
}
Rust에서는 소유권으로 발생하는 이러한 문제를 해결하기 위해 참조와 차용이라는 개념이 있다.
참조와 차용
참조 : 아래 코드처럼 변수를 가져다 사용하고 싶다면 &를 붙혀서 값을 가져올 수 있다. 하지만 이를 수정할수는 없다. 불변 참조는 여러개를 동시에 여러개 참조해도 문제가 없다.
let mut input = String::new();
let a = &input;// input 변수에 대한 불변 참조
let b = &input;
// 여기까지는 문제 없음
b.push_str("str"); //!!!컴파일에러!!! 불변 참조인데 수정을 시도하면 오류
차용 : 만약 참조한 값을 수정하고 싶다면 가변 차용를 넘기면 된다. 이를 위해 &mut를 붙혀 넘기면 된다. 하지만 1.가변 차용를 수행했다면 불변 참조는 사용할수 없으며, 2. 가변차용를 동시에 여러개 사용할 수 없다.
let mut input = String::new();
let a = &mut input;
let b = &input;
// !!!컴파일 에러!!! 가변 참조를 수행했다면 불변 참조는 사용할 수 없다.
let a = &mut input;
let b = &mut input;
// !!!컴파일 에러!!! 가변참조를 동시에 여러개 사용할 수 없다.
다만 Rust의 컴파일러는 똑똑하기 때문에 다음과 같은 맥락을 이해할 수 있다.
let mut input = String::new();
let a = &input;// input 변수에 대한 불변 참조
let b = &input;
// 값이 변경될 일이 없음
io::stdin().read_line(&mut input).unwrap();
//앞에서 값이 변경될 일이 없기 때문에 가변 차용을 허용한다.
위 코드에서 a,b 변수에서 불변 참조를 했기 때문에 a,b에 의해 input 값이 변경될 일이 없다. 이후 가변 참조가 발생하면 한번은 가변 차용을 허용해준다.
let mut input = String::new();
let a = &input;
io::stdin().read_line(&mut input).unwrap();
//여기서 가변 차용 됨
let b = &input;
// !!! 컴파일 에러!!! 이전에 가변차용된 부분이 있어 불변차용 불허
하지만 위 코드처럼 이미 가변 차용된 변수를 불변 참조 하면 컴파일단에서 오류를 발생시킨다.
마무리
지금까지 Rust 언어의 기본적인 문법과 특징적인 부분을 훑어보았다. 소유권과 참조, 차용의 개념으로 메모리의 안전성을 보장할수 있다는 것이 신기하고 좀더 Rust에 대해 파고들고 싶다.
'공부 학습' 카테고리의 다른 글
Rust 개발 환경 설정 및 빌드 방법 (1) | 2024.09.22 |
---|---|
게임 서버 공부_네트워크 (0) | 2023.02.28 |
게임 서버 공부_멀티스레드 2 (0) | 2023.02.28 |
게임 서버 공부_멀티스레드 프로그래밍 1 (0) | 2023.02.28 |
Docker 명령어 정리 (0) | 2022.12.17 |