반응형
Jake Seo
제이크서 위키 블로그
Jake Seo
전체 방문자
오늘
어제
  • 분류 전체보기 (715)
    • 일상, 일기 (0)
    • 백준 문제풀이 (1)
    • 릿코드 문제풀이 (2)
    • 알고리즘 이론 (10)
      • 기본 이론 (2)
      • 배열과 문자열 (8)
    • 데이터베이스 (15)
      • Planet Scale (1)
      • MSSQL (9)
      • 디비 기본 개념 (1)
      • SQLite 직접 만들어보기 (4)
    • 보안 (7)
    • 설계 (1)
    • 네트워크 (17)
      • HTTP (9)
      • OSI Layers (5)
    • 회고 (31)
      • 연간 회고 (2)
      • 주간 회고 (29)
    • 인프라 (52)
      • 도커 (12)
      • AWS (9)
      • 용어 (21)
      • 웹 성능 (1)
      • 대규모 서비스를 지탱하는 기술 (9)
    • 깃 (7)
    • 빌드 도구 (7)
      • 메이븐 (6)
      • 그레이들 (0)
    • Java (135)
      • 이펙티브 자바 (73)
      • 자바 API (4)
      • 자바 잡지식 (30)
      • 자바 디자인 패턴 (21)
      • 톰캣 (Tomcat) (7)
    • 프레임워크 (64)
      • next.js (14)
      • 스프링 프레임워크 (28)
      • 토비의 스프링 (6)
      • 스프링 부트 (3)
      • JPA (Java Persistence API) (5)
      • Nest.js (8)
    • 프론트엔드 (48)
      • 다크모드 (1)
      • 노드 패키지 관리 매니저 (3)
      • CSS (19)
      • Web API (11)
      • tailwind-css (1)
      • React (5)
      • React 새 공식문서 요약 (1)
      • HTML (Markup Language) (5)
    • 자바스크립트 (108)
      • 모던 자바스크립트 (31)
      • 개념 (31)
      • 정규표현식 (5)
      • 코드 스니펫 (1)
      • 라이브러리 (6)
      • 인터뷰 (24)
      • 웹개발자를 위한 자바스크립트의 모든 것 (6)
      • 팁 (2)
    • Typescript (49)
    • 리눅스와 유닉스 (10)
    • Computer Science (1)
      • Compiler (1)
    • IDE (3)
      • VSCODE (1)
      • IntelliJ (2)
    • 세미나 & 컨퍼런스 (1)
    • 용어 (개발용어) (16)
      • 함수형 프로그래밍 용어들 (1)
    • ORM (2)
      • Prisma (2)
    • NODEJS (2)
    • cypress (1)
    • 리액트 네이티브 (React Native) (31)
    • 러스트 (Rust) (15)
    • 코틀린 (Kotlin) (4)
      • 자바에서 코틀린으로 (4)
    • 정규표현식 (3)
    • 구글 애널리틱스 (GA) (1)
    • SEO (2)
    • UML (2)
    • 맛탐험 (2)
    • 리팩토링 (1)
    • 서평 (2)
    • 소프트웨어 공학 (18)
      • 테스팅 (16)
      • 개발 프로세스 (1)
    • 교육학 (1)
    • 삶의 지혜, 통찰 (1)
    • Chat GPT (2)
    • 쉘스크립트 (1)
    • 컴파일 (2)
    • Dart (12)
    • 코드팩토리의 플러터 프로그래밍 (4)
    • 플러터 (17)
    • 안드로이드 스튜디오 (1)
    • 윈도우즈 (1)
    • 잡다한 백엔드 지식 (1)
    • 디자인 패턴 (1)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 알고리즘
  • serverless computing
  • 외래키 제약조건
  • 슬로우 쿼리
  • 싱글톤
  • 이펙티브 자바
  • 객체복사
  • 자바스크립트
  • rust
  • item8
  • bean Validation
  • 팩터리 메서드 패턴
  • next js app
  • 작업기억공간
  • 스프링 검증
  • 메이븐 페이즈
  • 러스트
  • Next.js
  • try-with-resources
  • 참조 해제
  • 자바스크립트 인터뷰
  • 프로그래머의 뇌
  • MSSQL
  • Javadoc 자바독 자바주석 주석 Comment
  • 도커공식문서
  • 자바
  • pnpm
  • 자바스크립트 면접
  • item7
  • 메이븐 골
  • 서버리스 컴퓨팅
  • 플라이웨이트패턴
  • 싱글톤 패턴
  • NEXT JS
  • Java
  • 추상 팩터리 패턴
  • 빈 검증
  • 싱글턴
  • 자료구조
  • item9
  • 토비의 스프링
  • 자바 디자인패턴
  • prerendering
  • Pre-rendering
  • 이펙티브 자바 item9
  • 디자인패턴
  • 자바 검증
  • 이펙티브자바
  • 메이븐 라이프사이클
  • 느린 쿼리

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Jake Seo

제이크서 위키 블로그

러스트 (Rust) 의 소유권 (ownership) 개념
러스트 (Rust)

러스트 (Rust) 의 소유권 (ownership) 개념

2022. 11. 2. 19:48

러스트의 소유권이란?

  • 말 그대로 변수의 소유권이 누구에게 있냐 를 따져보는 것이다.
  • 소유권이 필요한 이유는 소유권이 붕 떠버린 변수에 할당된 메모리를 반납하기 위해서이다.
  • 변수는 컴퓨터의 메모리를 사용하고, 메모리는 한정적 자원이라 사용이 끝나면 반납되어야 한다.

스택과 힙

소유권을 공부하기 위해 스택과 힙 개념을 알아야 한다.

스택

  • 먼저 들어간 데이터가 가장 나중에 나오는 방식으로 구현된다.
  • 모든 데이터의 크기가 고정된 크기를 가져야 한다.
  • 위의 특징 덕에 안으로 깊숙히 들어간 데이터 위에 중간부터 다른 데이터를 쌓는 것은 불가능하다.
  • 계속 위로만 쌓고, 위에 있는 것 먼저 꺼내오기에 속도가 빠르다
  • 정적으로 관리되기 때문에 할당과 해제가 물흐르듯 자연스럽다.

힙

  • 메모리의 특정 지점을 선정하여 사용 중이라고 표기한다. 이 곳에 데이터를 저장할 준비를 한다. 이를 힙 공간 할당이라 부른다.
  • 동적으로 공간을 할당하기 때문에, 크기가 미리 결정되어 있지 않아 크기가 변하는 데이터의 경우에 힙 메모리로 할당하기에 적합하다.
  • 스택에 비해서 데이터 접근이 느리다. 포인터가 가리키는 곳을 계속 따라가야 하기 때문이다.
  • 동적으로 관리되기 때문에 메모리 할당과 해제에 문제가 발생하는 경우가 많다.
    • 할당된 메모리를 해제하지 않아 메모리가 모자라는 경우도 있고, 해제된 메모리를 한번 더 해제하는 것도 문제가 된다.

소유권 규칙

  • 러스트에서의 값은 해당 값의 오너(owner)라고 불리우는 변수를 가지고 있다.
  • 한번에 딱 하나의 오너만 존재 가능하다.
  • 오너가 스코프 밖으로 벗어나면, 값은 버려진다. (dropped)

변수 스코프 코드 예제

{                      // s는 유효하지 않습니다. 아직 선언이 안됐거든요.
    let s = "hello";   // s는 이 지점부터 유효합니다.

    // s를 가지고 뭔가 합니다.
}                      // 이 스코프는 이제 끝이므로, s는 더이상 유효하지 않습니다.
  • 블록이 끝나면서 변수에 할당된 공간이 회수된다.
  • 우리의 눈엔 보이지 않지만 마지막에 drop 이라는 것이 일어나며, 소유권이 반환된다.

String 타입의 소유권 이동

  • String 타입에 들어가는 값은 동적이라 힙 메모리를 이용한다.
  • 힙 메모리를 사용하는 변수는 '다른 변수로 옮겨지거나', '함수의 인자로 이용될 때' 소유권 이동이 발생한다.

힙 메모리를 사용하는 변수에 소유권 이동이 발생하는 이유는 두번 해제(double free) 오류를 범하지 않기 위함이다. 이로 인해 메모리 손상과 보안 취약성 문제를 일으킬 수 있다.
위키피디아 Memory safety 문서 의 Types of memory errors 를 보면, Double free 문제에 대한 설명이 있다.

소유권 이동 코드 예제: 변수 이동

let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);

위의 코드는 다른 고수준 프로그래밍 언어에서는 문제가 없는 코드겠지만, 러스트에서는 아래와 같은 에러가 발생한다. 소유권이 이동하면서 s1 은 더이상 유효하지 않은 변수가 되기 때문이다.

소유권 이동이 발생하지 않아 같은 값을 참조한다면, s1 과 s2 의 블록 종료 시점이 달라졌을 때, s1 이 먼저 메모리 해제된다면, s2 는 해제된 메모리를 가리키고 있게 된다.

자동으로 깊은 복사를 하거나, 두 개의 변수가 하나의 메모리 주소를 참조하지 않는다.

error[E0382]: use of moved value: `s1`
 --> src/main.rs:4:27
  |
3 |     let s2 = s1;
  |         -- value moved here
4 |     println!("{}, world!", s1);
  |                            ^^ value used here after move
  |
  = note: move occurs because `s1` has type `std::string::String`,
which does not implement the `Copy` trait

.clone() 을 이용한 깊은 복사 코드 예제

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() 과 같은 메서드를 이용하지 않아도 정상적으로 수행된다.
  • 정수형 타입은 정적인 크기를 가진 채로 스택에 저장되어 있기 때문에 메모리 할당, 해제에 대한 문제가 없기 때문이다.
    • 런타임에 크기가 커지거나 작아질 일이 없다.

스택에 저장되는, 복사가 가능한 타입들

  • u32 와 같은 모든 정수형 타입들
  • true, false 와 같은 boolean 타입 bool
  • f64 와 같은 부동 소수점 타입들
  • Copy 가 가능한 타입만으로 구성된 튜플들
    • ex) (i32, i32), 단 (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가 스코프 밖으로 벗어났습니다. 별다른 일은 발생하지 않습니다.

위의 코드에서는 소유권이 이동하지만, 힙 영역을 사용하는 s 는 함수에 소유권을 뺏겨 함수 이후부터 이용이 불가능하고, 스택 영역을 사용하는 x 는 여전히 이용이 가능하다.

함수의 반환 값을 이용한 소유권 반환 예제 코드

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는 스코프 밖으로 벗어났으며 drop이 호출됩니다. s2는 스코프 밖으로
  // 벗어났지만 이동되었으므로 아무 일도 일어나지 않습니다. s1은 스코프 밖으로
  // 벗어나서 drop이 호출됩니다.

fn gives_ownership() -> String {             // gives_ownership 함수가 반환 값을
                                             // 호출한 쪽으로 이동시킵니다.

    let some_string = String::from("hello"); // some_string이 스코프 안에 들어왔습니다.

    some_string                              // some_string이 반환되고, 호출한 쪽의
                                             // 함수로 이동됩니다.
}

// takes_and_gives_back 함수는 String을 하나 받아서 다른 하나를 반환합니다.
fn takes_and_gives_back(a_string: String) -> String { // a_string이 스코프
                                                      // 안으로 들어왔습니다.

    a_string  // a_string은 반환되고, 호출한 쪽의 함수로 이동됩니다.
}

튜플을 이용해 여러 값을 반환받는 예제

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(); // len()함수는 문자열의 길이를 반환합니다.

    (s, length)
}

참조자

사실 참조자라는 것을 이용하여, 소유권을 함수로 넘기지 않고 변수를 이용하는 방법도 있다.

이는 다음 포스팅인 러스트 참조자와 빌림 개념 에서 알아볼 수 있다.

레퍼런스

https://rinthel.github.io/rust-lang-book-ko/ch04-01-what-is-ownership.html#%EB%B3%80%EC%88%98%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0%EA%B0%80-%EC%83%81%ED%98%B8%EC%9E%91%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EC%9D%B4%EB%8F%99move

반응형
저작자표시 비영리 (새창열림)

'러스트 (Rust)' 카테고리의 다른 글

러스트 (Rust) 의 슬라이스 (Slice) 개념  (0) 2022.11.02
러스트 (Rust) 의 참조자 (References) 와 빌림 (Borrowing) 개념  (0) 2022.11.02
러스트 (Rust) 의 반복문 정리  (0) 2022.11.01
러스트 (Rust) 의 제어문 문법 정리  (0) 2022.11.01
러스트 (Rust) 함수 사용법 핵심 정리  (0) 2022.11.01
    '러스트 (Rust)' 카테고리의 다른 글
    • 러스트 (Rust) 의 슬라이스 (Slice) 개념
    • 러스트 (Rust) 의 참조자 (References) 와 빌림 (Borrowing) 개념
    • 러스트 (Rust) 의 반복문 정리
    • 러스트 (Rust) 의 제어문 문법 정리
    Jake Seo
    Jake Seo
    ✔ 잘 보셨다면 광고 한번 클릭해주시면 큰 힘이 됩니다. ✔ 댓글로 틀린 부분을 지적해주시면 기분 나빠하지 않고 수정합니다. ✔ 많은 퇴고를 거친 글이 좋은 글이 된다고 생각합니다. ✔ 간결하고 명료하게 사람들을 이해 시키는 것을 목표로 합니다.

    티스토리툴바