[0x02] Understanding Onwership
1. What is Ownership?
Ownership은 Rust 프로그램의 메모리 관리 방법에 대한 규칙이다.
- 모든 프로그램들은 실행되고 있을 때, 메모리를 관리해야 한다.
Java같은 언어들은, garbage collector를 통해 낭비되는 메모리를 최소화하는 방법을 사용한다.
C 같은 언어들은, 메모리의 할당과 해제를 명시적으로 해주어야 한다.
Rust는 새로운 접근법으로 메모리를 관리하는데, 바로 컴파일러가 체크하는 Ownership의 개념이다.
컴파일러가 체크하는 만큼, 프로그램의 실행 시 퍼포먼스를 저해하는 일은 없다.
1.1 Ownership Rules
Ownership에 관한 규칙들을 우선 살펴보자.
- 모든
값들은 owner라 불리는 변수를 갖는다.
- 하나의
값에는 하나의 owner만 있을 수 있다.
owner가 scope밖으로 나가게 되면, 값은 drop된다.
1.2 Variable Scope
- 변수의
scope는 다른 언어에서의 개념과 동일하다.
fn main() {
// s 가 아직 선언되지 않음, 아직 유효하지 않음
let s = "Hello"; // s 가 선언됨, 유효함
println!("{}", s); // s 를 이용해 프로그램이 동작함
} // scope가 끝났기 떄문에, s 는 더이상 유효하지 않음
1.3 The String Type
Rust에서의 String은 literal한 스트링과, 자료형으로써의 스트링이 나뉜다.
- 여기서 살펴 볼
String Type은 힙 영역에 저장되며, 가변적인 크기를 가지는 자료형이다.
1.3.1 Memory Allocation
String이 가변적인 크기를 갖는다는 것은 아래 두 과정을 수행한다는 것이다.
- 런타임 중, 메모리 할당자에 의해 메모리가 할당(
allocating)되어야 한다.
- 사용한 메모리를 메모리 할당자에게 반환(
returning)하는 방법이 있어야 한다.
- 메모리를 할당(
allocate)하는 것은 String::from 메소드를 호출할 때 일어난다.
- 메소드 내부에서 메모리 할당자에게 메모리를 요청한다.
- 메모리를 반환(
return)하는 것은 해당 메모리에 대한 owner가 scope를 벗어날 경우 자동으로 일어난다.
Rust는 scope가 끝나는 curly bracket(})에 대해 drop()함수를 자동으로 호출한다.
- 이
drop 함수가 메모리를 반환하는 것이다.
1.4 Ways Variables and Data Interact
1.4.1 [_] Move
- 여기서는
Rust의 Ownership과 관련하여, 메모리 단에서 어떤 일이 일어나는지 살펴본다.
- 아래의 코드를 살펴보자.
{
let s1 = String::from("hello");
let s2 = s1;
}
Rust에서는 String과 같은 힙 영역에 할당되는 데이터에 관해 아래 그림과 같이 이해할 수 있다.
Ownership은 Rust 프로그램의 메모리 관리 방법에 대한 규칙이다.Java같은 언어들은,garbage collector를 통해 낭비되는 메모리를 최소화하는 방법을 사용한다.C같은 언어들은, 메모리의 할당과 해제를명시적으로해주어야 한다.Rust는 새로운 접근법으로 메모리를 관리하는데, 바로 컴파일러가 체크하는Ownership의 개념이다.
컴파일러가 체크하는 만큼, 프로그램의 실행 시 퍼포먼스를 저해하는 일은 없다.
Ownership에 관한 규칙들을 우선 살펴보자.- 모든
값들은owner라 불리는변수를 갖는다. - 하나의
값에는 하나의owner만 있을 수 있다. owner가scope밖으로 나가게 되면,값은drop된다.
scope는 다른 언어에서의 개념과 동일하다.fn main() {
// s 가 아직 선언되지 않음, 아직 유효하지 않음
let s = "Hello"; // s 가 선언됨, 유효함
println!("{}", s); // s 를 이용해 프로그램이 동작함
} // scope가 끝났기 떄문에, s 는 더이상 유효하지 않음Rust에서의 String은 literal한 스트링과, 자료형으로써의 스트링이 나뉜다.String Type은 힙 영역에 저장되며, 가변적인 크기를 가지는 자료형이다.String이 가변적인 크기를 갖는다는 것은 아래 두 과정을 수행한다는 것이다.- 런타임 중, 메모리 할당자에 의해 메모리가 할당(
allocating)되어야 한다. - 사용한 메모리를 메모리 할당자에게 반환(
returning)하는 방법이 있어야 한다.
allocate)하는 것은 String::from 메소드를 호출할 때 일어난다.- 메소드 내부에서 메모리 할당자에게 메모리를 요청한다.
return)하는 것은 해당 메모리에 대한 owner가 scope를 벗어날 경우 자동으로 일어난다.Rust는scope가 끝나는 curly bracket(})에 대해drop()함수를 자동으로 호출한다.- 이
drop함수가 메모리를 반환하는 것이다.
Rust의 Ownership과 관련하여, 메모리 단에서 어떤 일이 일어나는지 살펴본다.{
let s1 = String::from("hello");
let s2 = s1;
}Rust에서는 String과 같은 힙 영역에 할당되는 데이터에 관해 아래 그림과 같이 이해할 수 있다.
- 이 때,
let s2 = s1;코드에 관해서는 아래와 같이Shallow Copy가 일어난다.
- 하지만,
Rust에서는 위 그림과 같이 두 개의 포인터가 하나의 영역을 가리키지 않는다. - 이 때,
Ownership의 개념이 들어온다.
- 따라서, 아래 코드는 에러를 일으킨다.
fn main() {
let s1 = String::from("hello");
let s2 = s1;
println!("{}", world!", s1); // s1의 값은 s2로 `move` 했기 때문에 compile error 를 일으킨다.
}

1.4.2 [_] clone
- 만약
String에 대하여Deep Copy를 하고 싶다면,clone함수를 사용하면 된다.
fn main() {
let s1 = String::from("hello");
let s2 = s1.clone();
println!("{}, world!", s1);
}

1.4.3 [_] copy
- 스택에만 저장되는 타입(
integer등)에 대해서는Copy이라는trait이 구현되어 있다. - 따라서, 힙에 저장되는 데이터처럼
clone함수가 따로 필요 없다.
fn main() {
let x = 5;
let y = x;
}
- 특히, 튜플의 경우 내부 원소들이 모두
Copy가 구현된 타입이라면Copy를 사용할 수 있다.
1.5 Ownership and Functions
1.4에서 살펴본move,clone,copy의 개념을 토대로함수에서와 연관지어 살펴보자.
1.5.1 Parameters (arguments)
- 아래 코드와 주석을 참고하자.
fn main() {
let s = String::from("hello"); // s 가 선언됨
takes_ownership(s); // s 의 데이터는 함수 안으로 `move` 함.
// 따라서, s 는 더 이상 이 scope 내에서 유효하지 않음
let n = 5; // n 이 선언됨
makes_copy(n); // n 은 i32 이므로, `Copy`가 일어남
// 따라서, n 은 이 scope 내에서 계속 유효함
} // s, n 이 scope를 벗어남.
// 하지만, s 는 `move`로 함수 내부에 값이 들어갔으므로 `drop`되지 않음
fn takes_ownership(some_string: String) {
println!("{}", some_string);
} // some_string 이 scope 를 벗어났기 때문에, `drop`이 호출되고 메모리가 반환됨.
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
} // some_integer 가 scope 를 벗어남
- 즉, 저장되는 공간이
스택,힙둘 중 어느 곳이냐에 따라 다르게 동작함을 확인할 수 있다.
1.5.2 Return Value
- 함수가 값을
return하는 것도ownership을 이동시킬 수 있다. - 아래 코드와 주석을 참고하자.
fn main() {
let s1 = gives_ownership(); // gives_ownership() 함수의 return value는
// s1 으로 `move` 한다.
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2); // s2 가 takes_and_gives_back() 함수 안으로 `move` 하므로,
// s2 는 더 이상 이 scope 에서 유효하지 않다.
// 이후 다시 return 되어 s3 로 `move` 한다.
} // s2 는 유효하지 않은 변수이므로 제외하고
// s1, s3 는 scope가 끝났으므로 `drop` 함수의 호출에 의해 메모리를 반환한다.
fn gives_ownership() -> String {
let some_string = String::from("yours"); // some_string 을 선언함
some_string // some_string 이 return 되며 `move` 한다.
} // some_string 은 함수의 리턴값으로서 `move` 되었으므로,
// `drop` 함수를 호출하지 않는다.
fn takes_and_gives_back(a_string: String) -> String { // a_string 으로 소유권이 `move`되어 들어옴
a_string // a_string 이 return 되며 `move` 한다.
} // a_string 은 함수의 리턴값으로서 `move` 되었으므로,
// `drop` 함수를 호출하지 않는다.
- 함수를 호출할 때마다,
ownership을 이동하는 것은 사실 좀 번거로운 작업이다. - 다행히
Rust에서는 reference 라는 개념을 사용할 수 있다!
2. References and Borrowing
2.1 References
Reference란 포인터처럼 어떤 변수가 소유하고 있는 데이터의 주소값이다.
- 하지만, 포인터와는 달리
Reference는 특정 자료형의 유효한 값임을 보장한다.
- 아래 코드는
Reference에 대한 예제 코드이다.
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // `move` 가 아니라 `referencing` 임
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
- 변수
s, s1, 그리고 String::from("hello") 에 대한 개념은 아래 그림을 통해 이해할 수 있다.
Reference란 포인터처럼 어떤 변수가 소유하고 있는 데이터의 주소값이다.Reference는 특정 자료형의 유효한 값임을 보장한다.Reference에 대한 예제 코드이다.fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // `move` 가 아니라 `referencing` 임
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}s, s1, 그리고 String::from("hello") 에 대한 개념은 아래 그림을 통해 이해할 수 있다.
C++에서 자주 사용하던&와 비슷한 개념으로 이해할 수 있다.- 따라서, 위의 코드에서
calculate_length()가 끝났다고 하더라도s: &String은drop되지 않는다.
2.2 Mutable References
- 함수 인자로 전달한 데이터를
mutable하게 다루고 싶을 때는mutable references를 이용하면 된다.
fn main() {
let mut msg = String::from("This is a message");
change_message(&mut msg); // mutable reference
println!("{}", msg);
}
fn change_message(message: &mut String) { // mutable reference
message.push_str(", I added this Lol");
}
Rust에서는mutable reference를 통해 데이터 값을 빌려주는(borrowing) 것은 한 번에 하나만 가능하다.- 아래 코드는 컴파일 에러를 일으킨다.

- 이런 식의 제한을 두는 이유는 통제된 상황에서 데이터를 변경하고자 함이다.
Rust는 이를 통해, 컴파일 타임에서 발생하는Data Race를 해소할 수 있다.Data Race는 아래 3가지 경우에 발생한다.- 두 개 이상의 포인터가 하나의 데이터에 접근하려 할 때
- 최소한 하나 이상의 포인터가
write하려고 할 때 - 데이터로의 접근을
동기화시킬 메커니즘이 없을 때
- 또,
Rust에서는mutable reference와immutable reference를 혼합하여 사용할 수 없다. - 아래의 경우도 컴파일 에러를 일으킨다.
fn main() {
let mut s = String::from("Lol");
let r1 = &s; // immutable, no problem
let r2 = &s; // immutable, no problem
let r3 = &mut s; // mutable, BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
}

- 위 컴파일 에러에 대한 해결방법이 있다!
Reference는&로 지정되고, 마지막으로 사용될 때 까지를 하나의 scope 로 본다.- 따라서, 아래와 같이 코드를 수정하면 컴파일 에러 없이 실행시킬 수 있다.
fn main() {
let mut s = String::from("Lol");
let r1 = &s; // immutable, no problem
let r2 = &s; // immutable, no problem
println!("{}, and {}", r1, r2); // r1, r2 의 사용이 끝났으므로 scope 가 끝난 셈이다.
let r3 = &mut s; // mutable, BIG PROBLEM
println!("{}", r3);
}
2.3 Dangling References
Dangling Pointer와 마찬가지로, 비어있는 위치를 가리키는Reference를 의미한다.- 보통 메모리 할당을 해제하는 타이밍을 잘못 잡았을 때 발생한다고 한다.
Rust에서는 컴파일 단에서 이를 막아준다.
fn main() {
let s = get_msg();
println!("{}", s);
}
fn get_msg() -> &String {
let a = String::from("TEST");
&a
}

- 에러 메세지는
borrowed value를 받아 오지만, 데이터가 없음을 말하고 있다.
3. The Slice Type
Slice는 이름 그대로 전체 데이터가 아니라 연속된 일부 데이터를 의미한다.
Reference의 일종이므로, Ownership이 없다.
- 아래 코드를 보자.
fn first_word(msg: &String) -> ?
- 어떠한
String 값을 받되, Ownership은 move로 받지 않는다고 할 때
String의 일부인 '첫 번째 단어의 사이즈'를 return 하고자 한다면, 아래와 같이 코딩할 수 있다.
fn main() {
let s = String::from("This is a message");
let word = first_word(&s);
println!("{}", word);
}
fn first_word(msg: &String) -> usize {
let bytes = msg.as_bytes(); // `공백`을 찾기 위해 String 을 bytes로 변환함.
for (i, &item) in bytes.iter().enumerate() { // iter() 는 bytes 에서 iterating 하기 위해 호출
// enumerate() 는 iter 의 결과를 wrap 해서 tuple로 반환
// tuple 의 첫 번째 값은 index
// tuple 의 두 번째 값은 원소의 Reference
// destructure 를 통해 i 와 &item 에 값을 받음
if item == b' ' {
return i;
}
}
msg.len()
}
- 이런 식으로 구현은 할 수 있겠지만, 여기서 받은 *첫 번쨰 단어의 사이즈인 변수
word의 크기가 계속 유효하다는 보장은 없다.
fn main() {
let s = String::from("This is a message");
let word = first_word(&s);
s.clear();
// ...
}
- 위 코드에서
s.clear() 이후에는 더 이상 word == 4 라고 할 수 없다.
- 이는,
s 와 word 가 각각 따로 존재하는 변수이기 때문이다.
Rust 에서는 이러한 임의 collection 에 대해 참조하기 위해 Slice 라는 개념을 제공한다.
3.1 String Slice
String Slice란 , String 일부에 대한 Reference 로 아래와 같이 이용할 수 있다.
- 구체적으로,
String Slice의 타입은 &str 이다.
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
- 즉,
String Slice의 format은 [starting_index .. ending_index] 이다.
- 내부적으로
Slice 는 starting_index와 ending_index - starting_index의 값을 이용해서 일부분을 참조한다.
- 위 코드에 대한 개념적인 그림은 아래와 같다.
Slice는 이름 그대로 전체 데이터가 아니라 연속된 일부 데이터를 의미한다.Reference의 일종이므로, Ownership이 없다.fn first_word(msg: &String) -> ?String 값을 받되, Ownership은 move로 받지 않는다고 할 때String의 일부인 '첫 번째 단어의 사이즈'를 return 하고자 한다면, 아래와 같이 코딩할 수 있다.fn main() {
let s = String::from("This is a message");
let word = first_word(&s);
println!("{}", word);
}
fn first_word(msg: &String) -> usize {
let bytes = msg.as_bytes(); // `공백`을 찾기 위해 String 을 bytes로 변환함.
for (i, &item) in bytes.iter().enumerate() { // iter() 는 bytes 에서 iterating 하기 위해 호출
// enumerate() 는 iter 의 결과를 wrap 해서 tuple로 반환
// tuple 의 첫 번째 값은 index
// tuple 의 두 번째 값은 원소의 Reference
// destructure 를 통해 i 와 &item 에 값을 받음
if item == b' ' {
return i;
}
}
msg.len()
}word의 크기가 계속 유효하다는 보장은 없다.fn main() {
let s = String::from("This is a message");
let word = first_word(&s);
s.clear();
// ...
}s.clear() 이후에는 더 이상 word == 4 라고 할 수 없다.s 와 word 가 각각 따로 존재하는 변수이기 때문이다.Rust 에서는 이러한 임의 collection 에 대해 참조하기 위해 Slice 라는 개념을 제공한다.String Slice란 , String 일부에 대한 Reference 로 아래와 같이 이용할 수 있다.String Slice의 타입은 &str 이다. let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];String Slice의 format은 [starting_index .. ending_index] 이다.Slice 는 starting_index와 ending_index - starting_index의 값을 이용해서 일부분을 참조한다.
Slice를 다음과 같이 사용하는 것도 가능하다.
let s = String::from("hello");
let slice = &s[0..3];
let slice = &s[..3]; // == [0..3]
let len = s.len();
let slice = &s[2..len];
let slice = &s[2..]; // == [2..len]
let slice = &s[..]; // entire string
한 가지 주의할 점은
UTF-8이 4Byte를 갖는다는 것에 유의하여 slice 를 해야 한다는 것
- 이제
Slice를 이용해서 처음에 구현하고자 한first_word()함수를 구현하면 아래와 같다.
fn first_word(msg: &String) -> &str {
let bytes = msg.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &msg[..i];
}
}
&msg[..]
}
- 위 코드에 대해 앞에서 했던
s.clear()같은 코드를 추가하면 컴파일 단계에서 에러가 나는 것이 자명하다.
3.2 String Literals
String Literals는 바이너리 코드에 그대로 저장된다는 점을 상기해보자.
let s = "Hello, world!";
- 이 때, 변수
s의 타입은&str이다. String Literals가immutable임을 확인할 수 있다.
3.3 String Slices as Parameters
- 앞에서 구현한
first_word()는 함수 인자로&String을 받았었다. - 하지만,
&String대신&str을 쓰면, 두 타입을 모두 넘겨받아 쓸 수 있다. - 이는
Deref Coercions라는 특성에 의해 가능한데, 이는 15 장에서 확인할 수 있다.
3.4 Other Slices
- 일반적인
Slice도 가능하다. - 아래의 코드처럼
Array에 대해서도 slice 를 사용할 수 있다.
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
assert_eq!(slice, &[2, 3]);
- 위에서
slice변수의 타입은&[i32]이다.
Summary
Ownership, Borrowing, Slices 들은 Rust 의memory safety를 가능하게 해준다.
Rust는 다른 언어들처럼 메모리 사용에 대한 권한을 제공해줌과 동시에, ownership이라는 개념으로 메모리를 자동으로 해제할 수 있도록 한다.
- 이
Ownership 이라는 개념은 Rust 전체에 걸쳐 중요한 개념이므로, 앞으로의 챕터들에서도 주의깊게 살펴보도록 한다.
Author And Source
이 문제에 관하여([0x02] Understanding Onwership), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://velog.io/@c0np4nn4/0x02-Understanding-Onwership
저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Ownership, Borrowing, Slices 들은 Rust 의memory safety를 가능하게 해준다.Rust는 다른 언어들처럼 메모리 사용에 대한 권한을 제공해줌과 동시에, ownership이라는 개념으로 메모리를 자동으로 해제할 수 있도록 한다.Ownership 이라는 개념은 Rust 전체에 걸쳐 중요한 개념이므로, 앞으로의 챕터들에서도 주의깊게 살펴보도록 한다.Author And Source
이 문제에 관하여([0x02] Understanding Onwership), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@c0np4nn4/0x02-Understanding-Onwership저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)