2014-12-04 2 views
30

:값을 함수에 전달하여 참조로 전달하고 Box에서 전달하는 것의 차이점은 무엇입니까? "상자에서"참조로 함수에 값을 전달하고 전달의 차이점은 무엇

fn main() { 
    let mut stack_a = 3; 
    let mut heap_a = Box::new(3); 

    foo(&mut stack_a); 
    println!("{}", stack_a); 

    let r = foo2(&mut stack_a); 
    // compile error if the next line is uncommented 
    // println!("{}", stack_a); 

    bar(heap_a); 
    // compile error if the next line is uncommented 
    // println!("{}", heap_a); 
} 

fn foo(x: &mut i32) { 
    *x = 5; 
} 

fn foo2(x: &mut i32) -> &mut i32 { 
    *x = 5; 
    x 
} 

fn bar(mut x: Box<i32>) { 
    *x = 5; 
} 

heap_a 함수로 이동하는 이유는,하지만 stack_astack_aprintln!에서 계속 사용할 수 있습니다 (없는 성명 : foo() 통화 이후)? 두 번째 println!("{}", stack_a); 주석을

오류 :

error[E0382]: use of moved value: `heap_a` 
    --> <anon>:14:20 
    | 
12 |  bar(heap_a); 
    |   ------ value moved here 
13 |  // compile error if the next line is uncommented 
14 |  println!("{}", heap_a); 
    |     ^^^^^^ value used here after move 
    | 
    = note: move occurs because `heap_a` has type `std::boxed::Box<i32>`, which does not implement the `Copy` trait 

나는이 오류가 수명을 참조하여 설명 될 수 있다고 생각합니다. foo의 경우 stack_a (main 함수에 있음) 함수는 foo 함수로 이동되지만 함수 foo, 의 인수의 수명이 foo으로 끝나는 것을 컴파일러에서 확인합니다. 따라서 foo이 반환 된 후 main 함수에서 변수 stack_a을 사용할 수 있습니다. foo2의 경우 stack_a도 함수로 이동하지만이를 반환합니다.

heap_a의 수명이 bar 끝에서 끝나지 않습니까?

답변

15

박스형 값을 전달하면 값을 완전히 이동하게됩니다. 당신은 더 이상 그것을 소유하지 않습니다. 당신이 그것을 통과 한 것입니다. Copy이 아닌 유형이라면 (낡은 데이터는 memcpy 'd 일 수 있습니다. 힙 할당은 불가능합니다). 이것은 Rust의 소유 모델이 작동하는 방식입니다. 각 오브젝트는 정확히 한 곳에서 소유됩니다.

내용을으로 변경하려면 Box<i32> 대신 &mut i32을 전달해야합니다. 당신이 측정하지 않고 일을해서는 안되는 큰 유형에 매우 가끔 성능 최적화 (대한 (그들은 오히려 무한한 크기의 것보다 표현 될 수 있도록)

정말, Box<T>은 재귀 적 데이터 구조에 대해서만 유용).

Box<i32>에서 &mut i32을 얻으려면 참조 해제 된 상자에 대한 변경 가능한 참조 (예 : &mut *heap_a)를 사용하십시오.

33

값에 의한 전달은 항상 사본입니다 (관련 유형이 "사소한"경우) 또는 이동 (없는 경우)입니다. Box<i32>은 복사 가능 (또는 데이터 멤버 중 적어도 하나)이 Drop이므로 구현할 수 없습니다. 이는 일반적으로 일종의 "정리"코드로 수행됩니다. Box<i32>은 "소유 포인터"입니다. 그것이 가리키는 것의 유일한 소유자이기 때문에 그 drop 기능에서 i32의 메모리를 비우는 것이 "책임감있는"이유입니다. Box<i32>을 복사하면 어떻게되는지 상상해보십시오. 이제 두 개의 Box<i32> 인스턴스가 동일한 메모리 위치를 가리키게됩니다. 이것은 이중 자유 오류로 이어질 수 있기 때문에 좋지 않습니다. 따라서 bar(heap_a)Box<i32> 인스턴스를 bar()으로 이동합니다. 이렇게하면 힙의 소유자는 항상 하나만 존재합니다. 할당 된 파일은 i32입니다. 그리고 이로 인해 메모리 관리가 매우 간단 해집니다. 누가 소유하든 결국에는 해제됩니다.

foo(&mut stack_a)과의 차이는 값으로 stack_a을 전달하지 않는다는 것입니다. 당신은 단지 foo()stack_afoo()이 돌연변이 할 수있는 방식으로 빌려주 십니다. foo()을 빌려 포인터입니다. 실행이 foo()에서 다시 발생하면 stack_a이 여전히 존재합니다 (가능하면 foo()을 통해 수정 됨). foo()이 방금 동안 만 빌려 왔기 때문에 stack_a이 소유 스택 프레임으로 반환 된 것으로 생각할 수 있습니다.

당신이

let r = foo2(&mut stack_a); 
// compile error if uncomment next line 
// println!("{}", stack_a); 

의 마지막 줄을 주석 처리는하지 실제로 이동으로 stack_a 여부를 테스트 할 이다 혼동 표시되는 부분. stack_a이 아직 있습니다. 컴파일러는 여전히 당신이 그것의 이름을 통해 접근 할 수 있도록 허용하지 않는다. 왜냐하면 당신은 여전히 ​​변경 가능한 참조를 가지고 있기 때문이다 : . 이것은 메모리 안전성에 필요한 규칙 중 하나입니다. 메모리 위치를 변경할 수있는 경우 메모리 위치에 액세스하는 유일한 방법이있을 수 있습니다. 이 예에서 rstack_a에 대한 변경 가능한 차용 참조입니다. 따라서 stack_a은 여전히 ​​가변적으로 차용 된 것으로 간주됩니다. 액세스하는 유일한 방법은 빌린 참조 r입니다. 이름 stack_a : 다시 메모리 위치를 액세스하는 하나 개의 방법이

let mut stack_a = 3; 
{ 
    let r = foo2(&mut stack_a); 
    // println!("{}", stack_a); WOULD BE AN ERROR 
    println!("{}", *r); // Fine! 
} // <-- borrowing ends here, r ceases to exist 
// No aliasing anymore => we're allowed to use the name stack_a again 
println!("{}", stack_a); 

닫는 중괄호 후 :

일부 추가 중괄호 우리는 빌린 참조 r의 수명을 제한 할 수있다. 그래서 컴파일러가 println!에서 사용할 수 있습니다.

이제는 r이 실제로 stack_a을 참조한다는 것을 컴파일러에서 어떻게 알 수 있습니까? 그것에 대해 foo2의 구현을 분석합니까? 아니, 필요 없어. 이 결론에 도달하는 데는 foo2의 함수 서명으로 충분합니다. 그것은 소위 "평생 생략 규칙"에 따라

fn foo2<'a>(x: &'a mut i32) -> &'a mut i32 

에 실제로 짧은

fn foo2(x: &mut i32) -> &mut i32 

을합니다. 이 서명의 의미는 다음과 같습니다. foo2()은 차용 한 포인터를 i32에 가져오고 i32 (또는 원래 i32의 적어도 "part") 인 i32으로 차용 한 포인터를 반환하는 함수입니다. 동일한 수명 매개 변수는 반환 유형에 사용됩니다. 반환 값 (r)을 유지하는 한 컴파일러는 stack_a을 가변적으로 사용한다고 생각합니다.

우리가 왜 앨리어싱과 (잠재적 인) 돌연변이가 동시에 일어나지 않도록해야하는지에 관심이 있으시면 wr.r.t. 일부 메모리 위치는 Niko's great talk을 확인하십시오.

9

"박스에 의해"기준하여 통과하고 차이점은 참조 경우 ("빌려")에서, 호출이 객체의 할당을 해제하는 역할을 담당하지만 있다는 것이다 상자 경우 ("이동"에), 호출 수신자는 객체의 할당을 해제합니다.

따라서 Box<T>은 할당 해제를 담당하는 객체를 전달하는 데 유용하지만 참조는 할당 해제에 대한 책임이없는 객체를 전달하는 데 유용합니다.

아이디어 보여주는 간단한 예 :

fn main() { 
    let mut heap_a = box 3i; 
    foo(&mut *heap_a); 
    println!("{}", heap_a); 

    let mut heap_b = box 3i; 
    bar(heap_b); 
    // can't use heap_b. heap_b has been deallocated at end of "bar" 
    //println!("{}", heap_b); 
} // heap_a is destroyed here 

fn foo(x: &mut int){ 
    *x = 5; 
} 

fn bar(mut x: Box<int>){ 
    *x = 5; 
} // heap_b (now - x) is deallocated here 
관련 문제