소유권
은 러스트가 가비지 컬렉터 없이 메모리 안정성을 보장
하도록 해줌모두 명확하고 크기가 정해져 있어야 함
컴파일 타임에 크기를 알 수 없거나, 크기가 변경될 수 있는 데이터는 힙
에 저장되어야 함값 중엔 힙 영역의 데이터를 가리키는 포인터도 있을 수 있습니다
)과 메모리 할당자
는 커다란 힙 영역 안에서 어떤 빈 지점을 찾고, 이 지점은 사용 중이라고 표시한 뒤 해당 지점을 가리키는 포인터 (pointer) 를 우리한테 반환
소유자 (owner) only 1명
가 정해져 있습니다. { // s는 아직 선언되지 않아서 여기서는 유효하지 않습니다
let s = "hello"; // 이 지점부터 s가 유효합니다
// s로 어떤 작업을 합니다
} // 이 스코프가 종료되었고, s가 더 이상 유효하지 않습니다
문자열 리터럴
이 불변성 (immutable) 을 지니기에 변경할 수 없다는 점과, 프로그램에 필요한 모든 문자열을 우리가 프로그래밍하는 시점에 알 수는 없다는 점 때문String
을 제공이중 콜론 ::
-> String 타입에 있는 특정된 from 함수라는 것을 지정할 수 있게 해주는 네임스페이스 연산자
let mut s = String::from("hello");
s.push_str(", world!"); // push_str()이 문자열에 리터럴을 추가합니다
println!("{}", s); // 이 줄이 `hello, world!`를 출력합니다
메모리 할당자로부터 메모리를 요청해야 합니다.
메모리를 해제할 (즉, 할당자에게 메모리를 반납할) 방법이 필요
변수가 자신이 소속된 스코프를 벗어나는 순간 자동으로 메모리를 해제하는 방식
으로 해결변수가 스코프 밖으로 벗어나면
drop이라는 특별한 함수를 호출 let x = 5;
let y = x;
let s1 = String::from("hello");
let s2 = s1;
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
clone() 메서드가 필요 없음
copy trait가 있어서!
정수형 등 컴파일 타임에 크기가 고정되는 타입은 모두 스택에 저장되기 때문
따라서 굳이 y를 생성하고 나면 x를 무효화할 필요가 없습니다.
모든 정수형 타입 (예: u32)
true, false 값을 갖는 논리 자료형 bool
모든 부동 소수점 타입 (예: f64)
문자 타입 char
Copy 가능한 타입만으로 구성된 튜플 (예를 들어, (i32, i32)는 Copy 가능하지만 (i32, String)은 불가능합니다)
함수에 변수를 전달하면 대입 연산과 마찬가지로 이동이나 복사가 일어나기 때문
fn main() {
let s = String::from("hello"); // s가 스코프 안으로 들어옵니다
takes_ownership(s); // s의 값이 함수로 이동됩니다...
// ... 따라서 여기서는 더 이상 유효하지 않습니다
let x = 5; // x가 스코프 안으로 들어옵니다
makes_copy(x); // x가 함수로 이동될 것입니다만,
// i32는 Copy이므로 앞으로 계속 x를
// 사용해도 좋습니다
} // 여기서 x가 스코프 밖으로 벗어나고 s도 그렇게 됩니다. 그러나 s의 값이 이동되었으므로
// 별다른 일이 발생하지 않습니다.
fn takes_ownership(some_string: String) { // some_string이 스코프 안으로 들어옵니다
println!("{}", some_string);
} // 여기서 some_string이 스코프 밖으로 벗어나고 `drop`이 호출됩니다.
// 메모리가 해제됩니다.
fn makes_copy(some_integer: i32) { // some_integer가 스코프 안으로 들어옵니다
println!("{}", some_integer);
} // 여기서 some_integer가 스코프 밖으로 벗어납니다. 별다른 일이 발생하지 않습니다.
fn main() {
let s1 = gives_ownership(); // gives_ownership이 자신의 반환 값을 s1로
// 이동시킵니다
let s2 = String::from("hello"); // s2가 스코프 안으로 들어옵니다
let s3 = takes_and_gives_back(s2); // s2는 takes_and_gives_back로 이동되는데,
// 이 함수 또한 자신의 반환 값을 s3로
// 이동시킵니다
} // 여기서 s3가 스코프 밖으로 벗어나면서 버려집니다. s2는 이동되어서 아무 일도
// 일어나지 않습니다. s1은 스코프 밖으로 벗어나고 버려집니다.
fn gives_ownership() -> String { // gives_ownership은 자신의 반환 값을
// 자신의 호출자 함수로 이동시킬
// 것입니다
let some_string = String::from("yours"); // some_string이 스코프 안으로 들어옵니다
some_string // some_string이 반환되고
// 호출자 함수 쪽으로
// 이동합니다
}
// 이 함수는 String을 취하고 같은 것을 반환합니다
fn takes_and_gives_back(a_string: String) -> String { // a_string이 스코프 안으로
// 들어옵니다
a_string // a_string이 반환되고 호출자 함수 쪽으로 이동합니다
}
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
&(앰퍼센트)를 이용한 참조의 반대는 역참조 (dereferencing) 라 합니다. 역참조 기호는 *이며, 8장에서 몇 번 다뤄보고 15장에서 자세한 내용을 배울 예정입니다.
let s1 = String::from("hello");
let len = calculate_length(&s1);
fn calculate_length(s: &String) -> usize { // s는 String의 참조자입니다
s.len()
} // 여기서 s가 스코프 밖으로 벗어납니다. 하지만 참조하는 것을 소유하고 있진 않으므로,
// 버려지지는 않습니다.
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
let mut s = String::from("hello");
{
let r1 = &mut s;
} // 여기서 r1이 스코프 밖으로 벗어나며, 따라서 아무 문제없이 새 참조자를 만들 수 있습니다.
let r2 = &mut s;
참조자는 정의된 지점부터 시작하여 해당 참조자가 마지막으로 사용된 부분까지 유효
let mut s = String::from("hello");
let r1 = &s; // 문제없음
let r2 = &s; // 문제없음
println!("{} and {}", r1, r2);
// 이 지점 이후로 변수 r1과 r2는 사용되지 않습니다
let r3 = &mut s; // 문제없음
println!("{}", r3);
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s); // word는 값 5를 받습니다
s.clear(); // 이 코드는 String을 비워서 ""으로 만듭니다
// 여기서 word에는 여전히 5가 들어있지만, 이 5를 의미있게 쓸 수 있는
// 문자열은 더 이상 없습니다. word는 이제 전혀 유효하지 않습니다!
}
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
문자열 리터럴은 바이너리 내에 저장된다
let s = "Hello, world!";
s는 바이너리의 특정 지점을 가리키는 슬라이스
입니다. &str 타입이죠. &str은 불변 참조자
&str
문자열 슬라이스
리터럴과 String의 슬라이스를 만들 수 있다는 걸 알고 나면 first_word 함수 정의를 다음과 같이 작성할 수 있습니다:
fn first_word(s: &String) -> &str {
fn first_word(s: &str) -> &str {
문자열 슬라이스라면 이를 바로 인수로써 전달할 수 있습니다.
String이라면 String의 슬라이스 혹은 String에 대한 참조자를 전달할 수 있습니다.
이러한 유연성은 역참조 강제 변환 (deref coercions) 기능을 이용하는데, 15장의 ‘함수와 메서드를 이용한 암묵적 역참조 강제 변환’절에서 다룰 것입니다.
String에 대한 참조자 대신에, 문자열 슬라이스를 매개변수로 하는 함수를 정의하면, 기능 면에서 손해보지 않으면서 API를 더 일반적이고 유용하게 만들어 줍니다:
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);