타나기
타나기 월드
타나기
전체 방문자
오늘
어제
  • 분류 전체보기 (90)
    • ⚙️ Rust (20)
      • 👀 Tutorial (20)
    • 🗿 Embedded (11)
      • 🐧 OS for ARM (11)
    • 💻 Study (37)
      • 🧩 알고리즘 (37)
    • 🏄🏽‍♂️ Life (21)
      • 🍚 타나구루망 (20)
      • 💡 Light (1)

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
타나기

타나기 월드

⚙️ Rust/👀 Tutorial

[Rust / 튜토리얼] 9. 러스트의 참조자와 빌림

2022. 5. 23. 22:51

참조자와 빌림

참조자를 사용한다면 앞서 소유권이 이동함에 따라 겪었던 불편함을 해소할 수 있다.
아래는 참조자를 사용하지 않고 튜플을 리턴해 인자를 다시 할당하는 코드이다.

fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(s1);
    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); 
    (s, length)
}

문자열을 인자로 전달하고, 문자열의 길이와 문자열을 다시 리턴받았다.
동작상 아무런 문제가 없는 코드이지만, 남들이 보기에 부끄럽지 않은 코드인가? 라고 묻는다면 아닌 코드인 것 같다.

초보자가 짠 것 같은 코드를 조금만 바꾸어 참조자를 사용하면, 한층 우아한 코드가 된다.

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()
}

우선 리턴 값으로 튜플을 사용하지 않았다.
그리고 참조자 &를 사용했다.
이 참조자는 소유권을 넘기지 않고 참조만 할 수 있게 해준다.

여기서 빌림이라는 용어에 주목해 볼 필요가 있다.
집주인에게 집을 빌렸다고 생각해 보자.
내가 그집을 마음대로 벽지고 장판이고 마음대로 뜯어 고칠수 있을까?
러스트에서의 빌림도 마찬가지로 어떠한 변경도 불가능하다.
허락도 안받고 마음대로 바꾸려고 하면 어떤일이 벌어지는지 봐보자.

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.push_str(", World!");
    s.len()
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
error[E0596]: cannot borrow `*s` as mutable, as it is behind a `&` reference
 --> src\main.rs:8:5
  |
7 | fn calculate_length(s: &String) -> usize {
  |                        ------- help: consider changing this to be a mutable reference: `&mut String`
8 |     s.push_str(", World!");
  |     ^^^^^^^^^^^^^^^^^^^^^^ `s` is a `&` reference, so the data it refers to cannot be borrowed as mutable

아래의 결과처럼 빌린 변수이므로 수정이 불가능 하다는 오류문을 출력한다.
친절하게도 가변 참조자로 설정하면 괜찮다고 알려준다.
그럼 가변 참조자로 설정해 보자.

가변 참조자

가변 참조자는 mut키워드를 이용하면 된다.
mutable의 약자로, 해당 변수가 빌려간 상태임에도 불구하고 변경이 가능하다는 것을 알려준다.
세입자의 자율성을 보장해주는 착한 집주인이라고 생각하자.

fn main() {
    let mut s1 = String::from("hello");
    let len = calculate_length(&mut s1);
    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &mut String) -> usize {
    s.push_str(", World!");
    s.len()
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
    Finished dev [unoptimized + debuginfo] target(s) in 1.13s
     Running `target\debug\test_exam.exe`
The length of 'hello, World!' is 13.

수정된 포인트는 전부 변수를 1) 선언하고 2) 인자로 전달하고 3) 인자를 전달 받을때 모두이다.
어느 하나라도 가변 참조자 임을 알려주지 않으면 오류가 발생한다.

안타깝게도 가변 참조자는 하나만 존재할 수 있다.
하나의 변수에 두개 이상의 가변 참조자를 생성하면 컴파일 에러가 발생한다.
다소 까다롭게 느껴진 수 있지만, 이는 데이터 레이스 를 해결하기 위함이다.
데이터 레이스가 발생하는 조건은 다음과 같다.

  1. 두 개 이상의 포인터가 동시에 같은 데이터에 접근.
  2. 그 중 적어도 하나의 포인터가 데이터를 씀.
  3. 데이터에 접근하는데 동기화를 하는 메커니즘이 없다.

데이터 레이스는 개발자가 의도하지 않은 오류를 만들 가능성이 아주 크다.
심지어 디버깅 하는데도 한세월이 걸릴 가능성이 크다.
어디서 레이스가 발생하는지 콕 찝기가 까다롭기 때문이다.

러스트에서는 이 상황을 원천 차단함으로써 해결했다.
다소 무식해 보이지만 악은 새싹부터 잘라야 하는 법이다.

혹시라도 여러개의 가변 참조자를 만들고 싶으면 어떻게 해야 할까?
이전에 배웠던 생명 주기를 이용하면 된다.

변수의 생명주기는 스코프에 의해 결정된다.
따라서 다음처럼 생명주기를 이용해 하나의 변수를 완전히 끝장내고 다음 변수를 선언 하면 어떠한 컴파일 오류도 발생하지 않는다.

fn main() {
let mut s = String::from("hello");

{
    let r1 = &mut s;

} // 여기서 r1은 스코프 밖으로 벗어났으므로, 우리는 아무 문제 없이 새로운 참조자를 만들 수 있습니다.

let r2 = &mut s;
}

마지막으로 심화과정에 들어가 보자.
위의 상황들이 복합적으로 나타난다면 어떻게 될까?
가변 참조자 + 불변 참조자가 나란히 쓰인 상황을 만들어 보자.

fn main() {
    let mut s1 = String::from("hello");
    let s2 = &s1;
    let s22 = &s1;
    let s3 = &mut s1;
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
warning: unused variable: `s2`
 --> src\main.rs:3:9
  |
3 |     let s2 = &s1;
  |         ^^ help: if this is intentional, prefix it with an underscore: `_s2`
  |
  = note: `#[warn(unused_variables)]` on by default

warning: unused variable: `s22`
 --> src\main.rs:4:9
  |
4 |     let s22 = &s1;
  |         ^^^ help: if this is intentional, prefix it with an underscore: `_s22`

warning: unused variable: `s3`
 --> src\main.rs:5:9
  |
5 |     let s3 = &mut s1;
  |         ^^ help: if this is intentional, prefix it with an underscore: `_s3`

warning: `test_exam` (bin "test_exam") generated 3 warnings
    Finished dev [unoptimized + debuginfo] target(s) in 0.82s
     Running `target\debug\test_exam.exe`

띠용?? 🤷‍♂️ 아무런 오류가 발생하지 않았다.
이상하다 분명히 튜토리얼에서는 오류가 발생 했는데...
뭔가 느낌이 와서 마지막에 모든 변수를 출력해 보았다.

fn main() {
    let mut s1 = String::from("hello");
    let s2 = &s1;
    let s22 = &s1;
    let s3 = &mut s1;
    println!("{} {} {} {} !!!", s1, s2, s22, s3);
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
error[E0502]: cannot borrow `s1` as mutable because it is also borrowed as immutable
 --> src\main.rs:5:14
  |
3 |     let s2 = &s1;
  |              --- immutable borrow occurs here
4 |     let s22 = &s1;
5 |     let s3 = &mut s1;
  |              ^^^^^^^ mutable borrow occurs here
6 |     println!("{} {} {} {} !!!", s1, s2, s22, s3);
  |                                     -- immutable borrow later used here

error[E0502]: cannot borrow `s1` as immutable because it is also borrowed as mutable
 --> src\main.rs:6:33
  |
5 |     let s3 = &mut s1;
  |              ------- mutable borrow occurs here
6 |     println!("{} {} {} {} !!!", s1, s2, s22, s3);
  |                                 ^^           -- mutable borrow later used here
  |                                 |
  |                                 immutable borrow occurs here
  |
  = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)     

For more information about this error, try `rustc --explain E0502`.
error: could not compile `test_exam` due to 2 previous errors

이제야 기대한 오류가 출력 되었다.
불변 참조자를 가지고 있는 동안에는 가변 참조자를 만들 수 없다.
불변 참조자를 사용하는 동안에는, 값이 바뀌길 바라지 않는데, 가변 참조자는 이를 변경할 수 있기 때문이다.
우리의 사려깊은 러스트는 이를 미연에 방지해 준다.

println!() 함수를 실행하기 전에 오류가 발생하지 않았던 것은 추측컨데, 불변 참조자를 사용하는 부분이 없어서 였을 것이다.
불변 참조자를 사용하지 않았으니 가변 참조자에 의해 값이 변경된 것을 신경 쓰지 않아도 됐을 것이다.
불변 참조자는 여전히 기대한 값을 가지고 있을 수 있으니 컴파일러가 생각하기엔 괜찮았나 보다.
힌트는 위의 데이터 레이스의 2) 그 중 적어도 하나의 포인터가 데이터를 씀 항목에서 얻었다.
혹시라도 이 글을 읽는 누군가가 진실을 안다면 꼭 알려줬으면 좋겠다.

댕글링 참조자 (Dangling References)

C언어처럼 포인터가 있는 언어들은 댕글링 포인터 라는게 있다.
이는 메모리에 할당된 공간을 가르키는 포인터가 그 공간이 해제되어 존재하지 않음에도 여전히 그 공간을 가리키는 포인터를 말한다.
깐깐한 러스트 답게 댕글링 참조자 또한 잡아내 준다.

 fn main() {
    let dangling_ref = make_dangling();
}

fn make_dangling() -> &String {
    let s = String::from("hello!");
    &s
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
error[E0106]: missing lifetime specifier
 --> src\main.rs:5:23
  |
5 | fn make_dangling() -> &String {
  |                       ^ expected named lifetime parameter
  |
  = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
  |
5 | fn make_dangling() -> &'static String {
  |                       ~~~~~~~~

For more information about this error, try `rustc --explain E0106`.
error: could not compile `test_exam` due to previous error

빌린값을 리턴해 주는 함수인데 실제로 빌림값이 존재하지 않는다고 한다.
그 이외에도 라이프 타임이나 static같은 키워드들이 있는데, 이건 나중에 더 자세하게 알려줄 기회가 있을 것 같다.

코드에서 벌어지는 상황은 아래의 주석을 통해 확인해 보자

 fn main() {
    let dangling_ref = make_dangling();
}

fn make_dangling() -> &String {  // String의 참조자를 리턴
    let s = String::from("hello!");  // String 변수 생성
    &s  // String s의 참조자를 반환
}  // Scope 종료!
   // String s가 더이상 존재하지 않으므로 댕글링 참조자가 만들어짐

이 코드를 동작하게 하고 싶다면 다음처럼 고치면 된다.

fn main() {
    let dangling = make_dangling();
    print!("dangle : {}", dangling)
}

fn make_dangling() -> String {
    let s = String::from("hello!");
    s
}
   Compiling test_exam v0.1.0 (C:\Folder\source\rust\test_exam)
    Finished dev [unoptimized + debuginfo] target(s) in 0.86s
     Running `target\debug\test_exam.exe`
dangle : hello!

댕글링 참조자를 만들지 않도록 소유권을 완전히 밖으로 이동시켰다.

참조자의 규칙

참조자들은 아래의 규칙을 따른다.

  1. 어떠한 경우든 아래 둘 중 하나만 만족할 수 있다.
  • 하나의 가변 참조자
  • 여러개의 불변 참조자
  1. 참조자는 항상 유효해야 한다.
저작자표시 (새창열림)

'⚙️ Rust > 👀 Tutorial' 카테고리의 다른 글

[Rust / 튜토리얼] 11. 러스트에서 구조체를 정의하고 생성하기  (0) 2022.05.27
[Rust / 튜토리얼] 10. 러스트의 슬라이스 (slice)  (0) 2022.05.25
[Rust / 튜토리얼] 8. 러스트의 소유권  (0) 2022.05.20
[Rust / 튜토리얼] 7. Rust의 제어문  (0) 2022.05.16
[Rust / 튜토리얼] 6. 함수의 동작  (0) 2022.05.11
    '⚙️ Rust/👀 Tutorial' 카테고리의 다른 글
    • [Rust / 튜토리얼] 11. 러스트에서 구조체를 정의하고 생성하기
    • [Rust / 튜토리얼] 10. 러스트의 슬라이스 (slice)
    • [Rust / 튜토리얼] 8. 러스트의 소유권
    • [Rust / 튜토리얼] 7. Rust의 제어문
    타나기
    타나기
    #include<all>

    티스토리툴바