소유권(Ownership)은 러스트에서 메모리 안전성을 보장하는 핵심 개념입니다. 러스트의 소유권 시스템은 누군가가 메모리를 소유하고, 다른 사람이 그 메모리 자원을 변경할 때마다 명시적으로 이를 추적하도록 만들어, 메모리 오류를 방지합니다. 소유권은 러스트의 가장 중요한 특징 중 하나이며, 프로그램이 더 안전하고 효율적으로 동작하도록 도와줍니다.
Copy가 가능한 타입들은, 값을 복사할 때 소유권이 이전되지 않습니다. 예를 들어, 기본적인 숫자 타입(i32, u8 등)은 복사가 가능하여, 여러 변수에서 동일한 데이터를 가질 수 있습니다.fn main() {
let s1 = String::from("Hello"); // s1이 "Hello"를 소유
let s2 = s1; // 소유권이 s1에서 s2로 이동 (s1은 더 이상 사용 불가)
println!("{}", s1); // 오류: s1은 이미 소유권이 이동되어 사용 불가
}
이 예시에서 s1은 String 데이터를 소유하고 있고, s1을 s2에 할당할 때, s1의 소유권이 s2로 이동합니다. s1을 다시 사용하려 하면 오류가 발생합니다.
소유권을 넘기지 않고 참조를 통해 데이터를 빌릴 수 있습니다. 빌림은 불변 참조(immutable borrow)와 가변 참조(mutable borrow)로 나뉩니다.
fn main() {
let s1 = String::from("Hello");
let s2 = &s1; // 불변 참조
println!("{}", s1); // 불변 참조는 원본 데이터를 변경할 수 없음
}
fn main() {
let mut s1 = String::from("Hello");
let s2 = &mut s1; // 가변 참조
s1.push_str(", world!"); // 가변 참조를 통해 값을 변경할 수 있음
println!("{}", s1);
}
소유권 시스템은 라이프타임과 밀접한 관계가 있습니다. 라이프타임은 참조가 유효한 기간을 명시적으로 지정하여, 참조가 더 이상 유효하지 않게 되는 경우를 방지합니다.
Copy 트레잇은 값을 복사할 수 있는 타입에 적용됩니다. Copy 트레잇을 구현한 타입들은 소유권이 이동하지 않고, 데이터를 복사하기 때문에 여러 변수에서 동일한 값을 공유할 수 있습니다.
Copy 트레잇 예시
fn main() {
let x = 5; // i32는 Copy 트레잇을 구현한 타입
let y = x; // x의 값이 복사됨, 소유권 이동 없음
println!("x: {}", x); // 여전히 x를 사용할 수 있음
println!("y: {}", y); // y도 복사된 값을 가짐
}
i32와 같은 간단한 숫자 타입은 Copy 트레잇을 구현합니다. 그래서 x를 y에 할당하면 x의 값이 복사되어 y가 그 값을 갖게 됩니다.x와 y는 각각 독립적인 변수로 존재할 수 있습니다. 그래서 x를 사용해도 문제가 발생하지 않습니다.Copy가 적용되지 않는 예시반면, String이나 힙에 할당된 데이터는 Copy 트레잇을 구현하지 않기 때문에 소유권이 이동합니다.
fn main() {
let s1 = String::from("Hello");
let s2 = s1; // s1의 소유권이 s2로 이동
println!("{}", s1); // 오류: s1은 더 이상 유효하지 않음
}
이렇게 String과 같은 동적 할당 데이터는 Copy를 구현하지 않아 소유권이 이동하며, 소유권 이동 후에는 원래 변수(s1)를 사용할 수 없게 됩니다.
소유권 시스템은 러스트에서 메모리 안전성을 보장하는 핵심 개념입니다. 소유권을 통해 데이터가 어떻게 관리되는지, 빌림을 통해 안전하게 참조할 수 있는 방법을 배워두면, 메모리 오류를 방지하며 안정적인 프로그램을 작성할 수 있습니다.