728x90
반응형
4장. 소유권 이해하기
4.1 소유권의 기본 개념
소유권이란 무엇인가?
- Rust의 핵심 기능으로 가비지 컬렉터 없이 메모리 안전성 보장
- 메모리 관리를 컴파일 타임에 체크
- 힙 데이터 관리를 위한 방법
예제:fn main() { let s = String::from("hello"); // s는 문자열의 소유자 takes_ownership(s); // s의 값이 이동됨 // println!("{}", s); // 오류: s는 이미 이동됨 }
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string이 스코프를 벗어나고
drop
이 호출됨소유권 규칙 3가지
Rust의 각 값은 해당하는 변수를 소유자로 가짐
한 번에 딱 하나의 소유자만 존재할 수 있음
소유자가 스코프 밖으로 벗어나면 값은 버려짐(drop)
예제:fn main() { let x = 5; let y = x; // 복사 println!("x = {}, y = {}", x, y); // 둘 다 사용 가능 let s1 = String::from("hello"); let s2 = s1; // 이동 // println!("{}", s1); // 오류: s1은 이미 이동됨 println!("{}", s2); // 정상 작동 }
변수의 스코프
- 변수가 유효한 범위
- 중괄호 {}로 정의되는 범위
- 변수는 선언된 시점부터 현재 스코프가 끝날 때까지 유효
예제:fn main() { { // s는 유효하지 않음 let s = "hello"; // s는 이제부터 유효 println!("{}", s); } // 이 스코프는 끝났고, s는 더 이상 유효하지 않음 // println!("{}", s); // 오류: s는 이 스코프에서 유효하지 않음 }
String 타입으로 이해하는 소유권
String과 문자열 리터럴의 차이
힙에 할당되는 String 타입의 메모리 구조
move 시맨틱스와 깊은 복사
예제:fn main() { let s1 = String::from("hello"); let s2 = s1.clone(); // 깊은 복사 println!("s1 = {}, s2 = {}", s1, s2); let x = 5; let y = x; // 정수형은 Copy 트레이트를 구현하므로 복사됨 println!("x = {}, y = {}", x, y); }
예제: 문자열 소유권 이동하기
let s1 = String::from("hello"); let s2 = s1; // s1의 소유권이 s2로 이동 // println!("{}", s1); // 오류: s1은 이미 이동됨
4.2 참조와 대여
참조를 사용하는 이유
- 소유권을 이전하지 않고 값을 사용하기 위해
- 함수에서 값을 여러 번 사용할 때 유용
예제:fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); // s1의 참조를 전달 println!("길이는 {}입니다.", len); println!("s1은 여전히 사용 가능: {}", s1); }
fn calculate_length(s: &String) -> usize {
s.len()
}
가변 참조와 불변 참조
불변 참조는 여러 개 가능
가변 참조는 한 번에 하나만 가능
가변/불변 참조 동시 사용 불가
예제:fn main() { let mut s = String::from("hello"); let r1 = &s; // 불변 참조 ok let r2 = &s; // 불변 참조 ok println!("{}, {}", r1, r2); let r3 = &mut s; // 가변 참조 println!("{}", r3); }
댕글링 참조 방지하기
- 컴파일러가 참조의 유효성 검사
- 수명이 끝난 데이터 참조 방지
예제:fn main() { let reference_to_nothing = dangle(); }
fn dangle() -> &String { // 컴파일 에러
let s = String::from("hello"); &s // s가 함수 끝에서 drop되어 댕글링 참조 발생
}
```예제: 참조로 함수 매개변수 전달하기
fn calculate_length(s: &String) -> usize { s.len() } let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len);
4.3 슬라이스로 데이터 참조하기
문자열 슬라이스란?
- 문자열의 일부분을 참조하는 슬라이스
- 예제:
let s = String::from("hello world"); let hello = &s[0..5]; // "hello" let world = &s[6..11]; // "world"
배열 슬라이스 사용법
- 배열의 일부분을 참조하는 슬라이스
- 예제:
let a = [1, 2, 3, 4, 5]; let slice = &a[1..3]; // [2, 3]
슬라이스 매개변수 활용
문자열 슬라이스를 매개변수로 사용
&str 타입으로 String과 &str 모두 처리 가능
예제:
fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] } // String이나 &str 모두 사용 가능 let my_string = String::from("hello world"); let word = first_word(&my_string); let my_string_literal = "hello world"; let word = first_word(my_string_literal);
예제: 첫 번째 단어 찾기 함수 만들기
fn first_word(s: &str) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
4.4 메모리와 할당
스택과 힙의 차이점
- 스택: 고정된 크기의 데이터 저장
let x = 5; // 스택에 저장 let y = [1, 2, 3]; // 고정 크기 배열도 스택에 저장
- 힙: 크기가 변할 수 있는 데이터 저장
let s = String::from("hello"); // 힙에 문자열 데이터 저장 let v = vec![1, 2, 3]; // 벡터도 힙에 저장
- 스택: 고정된 크기의 데이터 저장
Copy 트레이트와 Drop 트레이트
- Copy: 값의 복사가 가능한 타입
let x = 5; let y = x; // x의 값이 y로 복사됨 println!("x = {}, y = {}", x, y); // 둘 다 사용 가능
- Drop: 메모리 해제가 필요한 타입
let s1 = String::from("hello"); let s2 = s1; // s1의 소유권이 s2로 이동 // println!("{}", s1); // 오류! s1은 이미 이동됨
- Copy: 값의 복사가 가능한 타입
소유권과 함수
함수에 값 전달 시 소유권 이동
fn main() { let s = String::from("hello"); takes_ownership(s); // s의 소유권이 함수로 이동 // println!("{}", s); // 오류! s는 이미 이동됨 let x = 5; makes_copy(x); // x는 Copy 타입이라 복사됨 println!("{}", x); // 정상 동작 } fn takes_ownership(s: String) { println!("{}", s); } fn makes_copy(i: i32) { println!("{}", i); }
예제: Vector를 이용한 메모리 할당/해제
fn main() { let mut v = Vec::new(); v.push(1); v.push(2); v.push(3); // v의 소유권이 print_vector로 이동 print_vector(v); // v는 이미 이동되어 사용할 수 없음 // println!("{:?}", v); // 오류! } fn print_vector(v: Vec<i32>) { println!("Vector: {:?}", v); } // v가 스코프를 벗어나면서 자동으로 메모리 해제
4.5 실전 연습 문제
문자열 처리하기
fn main() { let mut s = String::from("hello world"); let word = first_word(&s); s.clear(); // 오류! word는 여전히 전체 문자열을 참조하고 있음 } fn first_word(s: &String) -> &str { let bytes = s.as_bytes(); for (i, &item) in bytes.iter().enumerate() { if item == b' ' { return &s[0..i]; } } &s[..] }
벡터 다루기
fn main() { let mut numbers = vec![1, 2, 3, 4, 5]; // 벡터의 가변 참조로 수정 modify_vector(&mut numbers); println!("수정된 벡터: {:?}", numbers); // 벡터의 불변 참조로 합계 계산 let sum = calculate_sum(&numbers); println!("벡터의 합: {}", sum); } fn modify_vector(v: &mut Vec<i32>) { v.push(6); v[0] = 10; } fn calculate_sum(v: &Vec<i32>) -> i32 { v.iter().sum() }
소유권 이동 추적하기
fn main() { let s1 = String::from("안녕하세요"); let s2 = s1; // s1의 소유권이 s2로 이동 // println!("{}", s1); // 오류! s1은 이미 이동됨 println!("{}", s2); // 정상 동작 let s3 = s2.clone(); // 깊은 복사로 새로운 소유권 생성 println!("s2: {}, s3: {}", s2, s3); // 둘 다 사용 가능 }
참조 규칙 적용하기
fn main() { let mut data = String::from("테스트"); let r1 = &data; // 첫 번째 불변 참조 let r2 = &data; // 두 번째 불변 참조 (가능) println!("{}, {}", r1, r2); let r3 = &mut data; // 가변 참조 (r1, r2는 더 이상 사용되지 않으므로 가능) r3.push_str("123"); println!("{}", r3); }
728x90
반응형
'IT Best Practise > Rust' 카테고리의 다른 글
06. 열거형과 패턴 매칭 (0) | 2024.11.07 |
---|---|
05. 구조체 (1) | 2024.11.07 |
03. 일반적인 프로그래밍 개념 (0) | 2024.11.07 |
02. 추리 게임 튜토리얼 (0) | 2024.11.07 |
01. Getting Start (0) | 2024.11.07 |