kakasoo

7. 참조와 사용 본문

프로그래밍/Rust

7. 참조와 사용

카카수(kakasoo) 2024. 3. 31. 22:51
반응형

소유권이 이전될 때의 문제

use std::io; // 어떤 외부 크레이트를 사용할지는 use로 명시 가능하다.

fn main() {
	let mut input = String::new();
	some_fn(input);
	io::stdin().read_line(&mut input); // (1)
	let mars_weight = calcuate_weight_on_mars(100.0);

	println!("weight on Mars: {}kg", mars_weight);
}

fn calcuate_weight_on_mars (weight: f32) -> f32 {
 	(weight / 9.81) * 3.711
}

fn some_fn (s:String) {}   

 

이 코드에서 (1) 구간에는 에러가 났다.

이유는 이 라인에 도달하기 전 input이 some_fn의 파라미터로 전달되어 소유권이 이전되었기 때문이다.

main에서는 더 이상 input 변수의 값에 접근할 수 없으니 그 다음 함수에게 input을 전달할 수 없는 것이다.

소유권은 효과적으로 이중 해제 문제를 해결하는 듯 하지만 조금만 생각해봐도 이상하다는 것을 알 수 있다.

함수에 파라미터로 변수를 전달하여 그 값을 돌려 받는 것은 모든 언어에서 자연스러운 일인데 여기서는 못 쓴다?

 

참조 ( Reference )

러스트에서는 이런 걸 ‘참조’ 라는 이름으로 따로 부르는데, 소유권 없이 값을 참조하는 방법을 의미한다.

방법은 매우 간단하다.

 

fn some_fn (s: &String) {} 

 

함수의 시그니처를 문자열의 소유권을 취하는 대신 “문자열을 참조할 수 있도록” 파라미터에 & 기호를 추가한다.

이러면 변수 s가 범위를 벗어나도 문자열은 제거되지 않는데, 변수 s가 문자열의 소유자가 아니기 때문이다.

이런 식으로 러스트에서는 매개변수를 참조로 전달하는 것을 차용이라고 하는데,

이렇게 소유권을 전달하는 경우와 값 ( = 여기서는 주소를 의미한다 ) 만 빌려주는 것을 엄격히 분리하고 있다.

 

use std::io; // 어떤 외부 크레이트를 사용할지는 use로 명시 가능하다.

fn main() {
	let mut input = String::new(); // 구조체를 사용하여 빈 문자열을 생성한다.
	some_fn(&input);
	io::stdin().read_line(&mut input); // 주소를 참조할 수 있도록 &mut로 명시한다.
	let mars_weight = calcuate_weight_on_mars(100.0);

	println!("weight on Mars: {}kg", mars_weight);
}

fn calcuate_weight_on_mars (weight: f32) -> f32 {
 	(weight / 9.81) * 3.711
}

fn some_fn (s: &String) {}   

 

마찬가지로 전달하는 쪽에서도 & 기호를 붙여서 참조를 전달하는 것이라고 알려주어야 한다.

일반적인 변수처럼 참조는 기본적으로 변경이 불가능하다.

 

fn some_fn (s: &String) {
	s.push_str('s'); // mismatched types expected `&str`, found `char`
}

 

만약 some_fn에서 s를 받아 값을 변경하고자 하면 rustc 에러가 난다.

 

fn some_fn (s: &mut String) { // 가변 참조
	s.push_str('s');
}

 

이렇게 파라미터에도 &mut으로 명시해야 변경 가능한, 가변 참조 파라미터임을 알려줄 수 있다.

하지만 이렇게 하면 이제 다시 위에 함수 부분에서 에러가 난다.

 

use std::io;

fn main() {
	let mut input = String::new();
	some_fn(& input); // ERROR, 여기도 '&mut'로 변경해야 한다.
	io::stdin().read_line(&mut input);
	let mars_weight = calcuate_weight_on_mars(100.0);

	println!("weight on Mars: {}kg", mars_weight);
}

fn calcuate_weight_on_mars (weight: f32) -> f32 {
 	(weight / 9.81) * 3.711
}

fn some_fn (s: &mut String) {
	s.push_str("s");
}

 

가변과 불변 차용을 혼용할 때의 케이스

fn main() {
	let input = String::new();
	let mut s1 = &mut input; // 가변으로 차용
	let s2 = &input; // 불변으로 차용할 때 ERROR 발생
}

 

앞서 가변으로 차용한 걸 다시 불변으로 차용하면 에러가 발생한다.

 

fn main() {
	let input = String::new();
	let mut s1 = &mut input; // 가변으로 차용
	let mut s2 = &mut input; // 가변으로 차용할 때 ERROR 발생
}

 

동시에 두 개의 가변 차용을 하는 것도 에러 케이스이다.

동시에 값을 변경할 수 있는 변수가 2개 있다는 것은 시스템을 불안정하게 만들기 때문이다.

불변 차용은 개수 상관없이 원하는 만큼 할 수 있다면 가변 차용은 단일 가변 차용만 가능하다는 것을 알아야 한다.

불편하다고 느낄 수 있지만 이 방식은 데이터 레이스가 일어날 일이 없다는 것을 완벽하게 보장한다.

 

fn main() {
	let mut input = String::new();
	let s1 = & input; // 가변 변수를 불변으로 차용하지만 어차피 값이 이전 코드에 값 변경이 없었으므로 허용
	let s2 = & input; // 가변 변수를 불변으로 차용하지만 어차피 값이 이전 코드에 값 변경이 없었으므로 허용
	
	some_fn(&mut input);
}

 

또한 컴파일러는 영리하게도, 가변 변수를 불변으로 차용하더라도 값이 변경된 적이 없었다면 허용해준다.

 

fn main() {
	let mut input = String::new();
	some_fn(&mut input);
	
	let s1 = & input; // ERROR
	let s2 = & input; 
}

 

만약 가변 변수를 선언 후 다른 함수에 가변 참조로써 전달하고 그 다음 값을 참조한 다른 변수를 선언하면 에러다.

이 예제 코드들은 러스트 컴파일러가 얼마나 영리한지 보여준다.

 

입출력 예제 코드

use std::io; // 어떤 외부 크레이트를 사용할지는 use로 명시 가능하다.

fn main() {
	let mut input = String::new(); // 구조체를 사용하여 빈 문자열을 생성한다.
	io::stdin().read_line(&mut input); // 주소를 참조할 수 있도록 &mut로 명시한다.
	println!("Input: {}", input);
}

 

input을 입력받아 출력하는 예제 코드.

반응형