Rust Enum과 참조

mohadang·2023년 1월 27일
0

Rust

목록 보기
20/30
post-thumbnail

메모리 복사가 발생하는 예

// Option 안의 타입이 원시 타입이라 이동 안됨
// 대신 복사가 가능하여서 복사 발생
let a: Option<i32> = Some(123);
let b = a;
println!("{}", a.unwrap());

소유권 이동이 발생하는 예

// Option 안의 타입이 Box 타입이라 이동 가능
let a: Option<Box<i32>> = Some(Box::new(123));
let b = a;//소유권 이동
println!("{}", a.unwrap());//error, 소유권이 이동 되었는데 참조

참조

여기서 참조가 개입되면 메모리 동작은 달라진다.
참조는 복사를 하지 않는다.
참조된 변수끼리 주고 받을떄는 기본적으로 소유권 이동이 발생한다.
하지만 메모리 소유권 이동이 불가능한 상태에서는 복사가 발생

let a: &Option<i32> = &Some(123);
let b = a;//이동 하려고 하였지만 복사 발생
println!("{}", (*a).unwrap());//이동 하려고 하였지만 복사 발생
println!("{}", (*a).unwrap());//이동 하려고 하였지만 복사 발생
println!("{}", (*b).unwrap());//이동 하려고 하였지만 복사 발생

소유권 이동이 가능한 값에 대해서는 이동을 시키려 하지만 에러가 발생한다.

참조는 그 소유자 보다 더 오래 살 수 없다. 참조는 항상 유효한 메모리를 가리킨다.

참조된 메모리를 이동 시키면 원래 메모리가 소멸되고 참조는 원래 메모리를 참조할 수 없다.

// unwrap 호출 -> 메모리 이동(unwrap 함수 안으로) 발생 -> a 참조가 가리키던 메모리 소멸 -> error
// 참조는 그 소유자 보다 더 오래 살 수 없다는 원칙 위배

let a: &Option<Box<i32>> = &Some(Box::new(123));//Option안의 값이 이동 가능한 Box이다
println!("{}", a.unwrap());//error

이런 문제는 멤버 변수에 대해서도 나타난다.
멤버 변수는 멤버 변수를 소유하고 있는 객체와 생명주기를 같이 한다.

fn main() {
    let h = Box::new(32);
    let a = Node{data:Some(h)};// h 소유권이 이동됨
    Func(a);// a 소유권이 이동됨
    println!("{}", *h);// error, h 소유권은 이미 없어짐
}

이런 이유로 소유권을 전달 받은 함수 안에서는 객체의 메모리를 마음대로 수정 가능하다

struct Node {
    data: Option<Box<i32>>
}

fn Func(a: Node) { //메모리 소유권 전달 받음
    let _b = a.data;//Box 메모리 소유권이 _b로 이동
    println!("{}", a.data.unwrap());// error, a.data 소유권 없음
}

fn main() {
    let a = Node{data:Some(Box::new(32))};
    Func(a);
}

여기서 함수 인자를 참조로 받으면 Func를 호출한 호출자도 고려 해야한다.
data의 타입을 이동이 될 수 있는 Box가 아닌 i32로 바꾸면 복사가 발생 하며 문제는 발생하지 않는다.

struct Node {
    data: Option<i32>
}

fn Func(a: &Node) {
    let mut b = a.data;
    b = Some(64);
    println!("{}", a.data.unwrap());
    println!("{}", a.data.unwrap());
    println!("{}", b.unwrap());
}

fn main() {
    let a = Node{data:Some(32)};
    Func(&a);
    println!("{}", a.data.unwrap());
}

하지만 data의 타입이 이동이 될 수 있는 Box 같은 메모리일 경우 문제가 발생한다.
호출자에서 변수를 참조로 넘겼으니 호출자에서도 유효한 메모리를 사용할 수 있어야 하기에 컴파일러가 에러로 처리한다.

struct Node {
    data: Option<Box<i32>>
}

fn Func(a: &Node) {
	//호출자로 부터 전달된 인자에서
    //메모리 소유권 이동이 발생하면 호출자에서는 
    //a의 멤버변수 data를 사용할 수 없다. 더 이상 메모리가 유효하다고
    //보장 할 수 없음
    let _b = a.data;//error
    
    //a.data.unwrap()도 메모리 이동이 발생하여 소멸될 수 있음으로 에러 발생
    println!("{}", a.data.unwrap());
    
    //수정도 안된다. 호출자는 해당 변수를 mut(수정 가능)하게 빌려 주지 않았다.
    //수정하고 싶다면 mut &로 넘김녀 된다.
    a.data = Some(Box::new(32));
}

fn main() {
    let a = Node{data:Some(Box::new(32))};
    Func(&a);
    println!("{}", a.data.unwrap());//소유권 이동이 아닌 참조로 넘겨져서 메모리 사용 가능
}

만약 mut 하게 바꾸면 문제가 해결 될까 ? 일부는 에러가 발생하고 일부는 성공한다.

fn Func(a: &mut Node) {
    let _b = a.data;//error
    println!("{}", a.data.unwrap());//error
    
    //이것은 가능, 호출자에서 유효한 메모리를 참조 가능
    //호출자에서는 변경된 멤버 변수값을 확인 가능 할것이다.
    a.data = Some(Box::new(24));
}

fn main() {
    let mut a = Node{data:Some(Box::new(32))};
    Func(&mut a);//참조로 넘김
    println!("{}", a.data.unwrap());//소유권 이동이 아닌 참조로 넘겨져서 메모리 사용 가능
}

take를 이용하면 스택 메모리에 있는 소유권을 강제로 가져올 수 있다.

fn Func(a: &mut Node) {
	//컴파일 에러 발생하지 않음
    let _b = a.data.take();//스택 메모리의 소유권이 넘어감
    
    //이미 소유권이 넘어갔는데 접근하고 있음
    //런타임 에러 발생
    println!("{}", a.data.take().unwrap());
    
    //수정도 안된다. 호출자는 해당 변수를 mut(수정 가능)하게 빌려 주지 않았다.
    // 수정하고 싶다면 mut &로 넘김녀 된다.
    // a.data = Some(Box::new(24));
}

fn main() {
    let mut a = Node{data:Some(Box::new(32))};
    Func(&mut a);
    //Func 함수로 소유권이 넘어가서 런타임 에러 발생 할것이다.
    println!("{}", a.data.unwrap());
}

정리

a = b 대입이 발생 하였을때 메모리는 어떻게 동작 할 것인가 ?
스택 메모리의 값 타입(enum<Option, Result>, primitive, struct...)등은 복사가 발생 할것이고 '
힙 메모리의 참조 타입(Box)들은 소유권 이동이 발생 할 것이다.

ex) data: Option<i32> : Option과 안에 있는 i32가 복사 발생
ex) data: Option<Box<i32>> : Option은 복사, 안에 있는 Box는 소유권 이동 발생

profile
mohadang

0개의 댓글