참조로 데이터 빌리기

RustBeginner
지금 연습하기

소개

String을 이동하면 소유권도 함께 이전되지만, 많은 함수는 데이터를 잠시 사용하기만 하면 됩니다. Rust에서는 이러한 임시 접근을 **빌리기(borrow)**라고 하며, 참조로 나타냅니다.

준비된 게스트 목록 프로그램을 세 단계로 완성합니다. 먼저 공유 참조를 통해 소유한 텍스트를 확인하고, 공유 접근으로는 데이터를 수정할 수 없는 이유를 의도적으로 확인한 다음, 가변 참조를 통해 데이터를 한 번 제어된 방식으로 업데이트합니다. 마지막 출력에서는 main이 여전히 목록의 소유권을 가지고 있으며 목록을 사용할 수 있다는 사실을 확인합니다.

공유 참조로 데이터 확인하기

이 단계에서는 String의 소유권을 함수로 이동하는 대신, 함수에 읽기 전용 접근 권한을 빌려줍니다.

준비된 Cargo 프로젝트로 이동한 다음 소스 파일을 엽니다.

cd /home/labex/project/guest-borrowing
nano src/main.rs

설정 과정에서 완전한 프로젝트가 생성되었고, inspect_guests를 호출하는 부분에는 플레이스홀더 하나가 남아 있습니다. 이 함수의 매개변수 타입은 &String입니다. &는 “String에 대한 참조”를 의미합니다. 함수는 값을 잠시 확인할 수 있지만 소유권을 갖지는 않습니다.

/* Step 1: borrow guest_list */ 부분만 &guest_list로 바꾸어 호출문이 다음과 같아지게 합니다.

    inspect_guests(&guest_list);

여기서 &guest_list는 **공유 참조(shared reference)**를 만듭니다. 공유 참조를 사용하면 String 자체의 소유권을 이전하지 않고 읽기 전용 접근을 공유할 수 있습니다.

Ctrl+O를 눌러 저장하고, Enter를 누른 다음 Ctrl+X로 종료합니다. 프로그램을 실행합니다.

cargo run --quiet

출력은 다음과 같아야 합니다.

Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo

첫 번째 줄은 빌린 참조를 통해 출력됩니다. 두 번째 줄이 중요한 소유권 확인 결과입니다. inspect_guests가 반환된 후에도 main에서 guest_list를 계속 사용할 수 있습니다. 업데이트 함수도 현재 값을 읽지만, 아직 값을 변경하지는 않습니다.

공유 참조의 제한 확인하기

이 단계에서는 공유 참조를 통해 String을 의도적으로 수정해 봅니다. 컴파일러 검사가 실패하는 것이 정상이며, 이를 통해 Rust의 빌리기 규칙을 확인할 수 있습니다.

소스 파일을 다시 엽니다.

nano src/main.rs

add_guest 내부에서 준비된 줄의 맨 앞에 있는 //만 삭제하여 다음과 같이 만듭니다.

    list.push_str(", Mina");

push_str 메서드는 String에 텍스트를 추가하므로 값을 수정할 권한이 필요합니다. 하지만 현재 list의 타입은 &String이며, 이는 공유된 읽기 전용 접근 권한만 제공합니다.

저장하고 종료합니다. 확인하기 전에 결과를 예상해 봅니다. 공유 참조는 가변 접근 권한을 제공할 수 없으므로 컴파일러가 수정을 거부해야 합니다.

검사를 실행합니다. 이 단계에서는 실패가 정상입니다.

cargo check

진단 메시지에서 다음과 같이 변하지 않는 부분을 찾습니다.

error[E0596]: cannot borrow `*list` as mutable, as it is behind a `&` reference
...
consider changing this to be a mutable reference

*list라는 표현은 참조를 통해 접근한 String을 가리킵니다. Rust가 String은 절대 변경할 수 없다고 말하는 것이 아닙니다. 현재 사용 중인 &String에는 값을 변경할 권한이 없다는 뜻입니다. 다음 단계에서 제어된 가변 접근 권한을 부여합니다.

가변 참조로 업데이트하기

이 단계에서는 add_guest에 호출자의 String을 수정할 수 있는 임시 독점 권한을 부여합니다.

가변 참조의 형식은 &mut T입니다. 초보자가 기억하기 좋은 규칙은 여러 독자 또는 한 명의 작성자만 동시에 허용된다는 것입니다. 공유 참조는 읽기를 허용하고, 가변 참조 하나는 임시로 독점적인 쓰기 권한을 부여합니다. 이러한 독점성 덕분에 프로그램의 두 부분이 같은 값을 예기치 않게 동시에 변경하는 일을 막을 수 있습니다.

파일을 엽니다.

nano src/main.rs

다음 세 부분만 수정합니다.

  • add_guest의 매개변수 타입을 &String에서 &mut String으로 바꿉니다.
  • 바인딩에 mut를 추가하여 let mut guest_list = ... 형태로 만듭니다.
  • add_guest 호출에서 &guest_list를 &mut guest_list로 바꿉니다.

완성된 함수 시그니처와 호출문은 다음과 같은 형태여야 합니다.

fn add_guest(list: &mut String) {
    add_guest(&mut guest_list);

두 곳에서 사용하는 mut는 서로 연결되어 있지만 역할은 다릅니다. let mut guest_list는 소유한 값을 변경할 수 있다고 선언합니다. 그다음 &mut guest_list는 임시 가변 참조 하나를 만들어 호출이 진행되는 동안 add_guest에 독점적인 쓰기 권한을 빌려줍니다.

저장하고 종료한 다음 수정 내용을 확인합니다.

cargo check

E0596 없이 Finished 결과가 표시되면 권한 설정이 서로 일치한다는 뜻입니다. 프로그램을 실행합니다.

cargo run --quiet

최종 출력은 다음과 같아야 합니다.

Inspecting: Ari, Bo
Caller still owns: Ari, Bo
Before update: Ari, Bo
Updated: Ari, Bo, Mina

add_guest가 반환되면 가변 빌리기가 끝납니다. 마지막 출력에서는 guest_list를 다시 읽을 수 있습니다. 이를 통해 함수가 제어된 방식으로 값을 업데이트하는 동안에도 소유권은 main에 계속 유지되었음을 확인할 수 있습니다.

요약

공유된 읽기 전용 접근에는 &String을 사용하고, 임시 독점 수정에는 &mut String을 사용했습니다. 두 경우 모두 호출자가 소유권을 유지했으며, 컴파일러는 빌린 데이터를 확인하는 것과 변경하는 것의 차이를 적용하는 데 도움을 주었습니다.