이것의 간단한 구현을 보자 .
struct Parent {
count: u32,
}
struct Child<'a> {
parent: &'a Parent,
}
struct Combined<'a> {
parent: Parent,
child: Child<'a>,
}
impl<'a> Combined<'a> {
fn new() -> Self {
let parent = Parent { count: 42 };
let child = Child { parent: &parent };
Combined { parent, child }
}
}
fn main() {}
오류와 함께 실패합니다.
error[E0515]: cannot return value referencing local variable `parent`
--> src/main.rs:19:9
|
17 | let child = Child { parent: &parent };
| ------- `parent` is borrowed here
18 |
19 | Combined { parent, child }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ returns a value referencing data owned by the current function
error[E0505]: cannot move out of `parent` because it is borrowed
--> src/main.rs:19:20
|
14 | impl<'a> Combined<'a> {
| -- lifetime `'a` defined here
...
17 | let child = Child { parent: &parent };
| ------- borrow of `parent` occurs here
18 |
19 | Combined { parent, child }
| -----------^^^^^^---------
| | |
| | move out of `parent` occurs here
| returning this value requires that `parent` is borrowed for `'a`
이 오류를 완전히 이해하려면 메모리에 값이 표시되는 방식과
해당 값 을 이동할 때 발생하는 상황에 대해 생각해야 합니다. Combined::new
값의 위치를 보여주는 가상 메모리 주소로 주석 을 달자 .
let parent = Parent { count: 42 };
// `parent` lives at address 0x1000 and takes up 4 bytes
// The value of `parent` is 42
let child = Child { parent: &parent };
// `child` lives at address 0x1010 and takes up 4 bytes
// The value of `child` is 0x1000
Combined { parent, child }
// The return value lives at address 0x2000 and takes up 8 bytes
// `parent` is moved to 0x2000
// `child` is ... ?
어떻게 child
됩니까? 값이 방금 이동 parent
한 경우 더 이상 유효한 값이 보장되지 않는 메모리를 나타냅니다. 다른 코드는 메모리 주소 0x1000에 값을 저장할 수 있습니다. 정수라고 가정하여 해당 메모리에 액세스하면 충돌 및 / 또는 보안 버그가 발생할 수 있으며 Rust가 방지하는 주요 오류 범주 중 하나입니다.
이것은 평생 예방할 수있는 문제입니다 . 수명은 약간의 메타 데이터로, 현재 메모리 위치 에서 값이 얼마나 오래 유효한지 알 수 있습니다 . 그것은 Rust 이민자들이 흔히 저지르는 실수이기 때문에 중요한 차이점입니다. 녹 수명은 객체가 생성 된 시점과 파괴 된 시점 사이의 기간 이 아닙니다 !
비유로서, 다음과 같이 생각하십시오 : 사람의 삶 동안, 그들은 각각 다른 주소를 가진 많은 다른 위치에있을 것입니다. Rust의 수명은 미래에 죽을 때마다가 아니라 현재 거주 하는 주소와 관련 이 있습니다 (죽음도 주소를 변경하더라도). 주소가 더 이상 유효하지 않으므로 이동할 때마다 관련이 있습니다.
또한 수명 이 코드를 변경 하지는 않습니다 . 코드는 수명을 제어하고 수명은 코드를 제어하지 않습니다. 한마디로 말하면 "수명은 규범이 아니라 묘사 적"입니다.
Combined::new
평생을 강조하기 위해 사용할 줄 번호로 주석 을 달자 .
{ // 0
let parent = Parent { count: 42 }; // 1
let child = Child { parent: &parent }; // 2
// 3
Combined { parent, child } // 4
} // 5
콘크리트 수명 의는 parent
1 내지 4이고, (I과 같이 표현됩니다있는 포함 [1,4]
). 의 콘크리트 수명 child
IS [2,4]
, 및 반환 값의 콘크리트 수명이다 [4,5]
. 0에서 시작하는 구체적인 수명을 가질 수 있습니다. 즉, 기능 또는 블록 외부에 존재하는 것에 대한 매개 변수의 수명을 나타냅니다.
수명 child
자체는 [2,4]
이지만 수명이 0 인 값을 나타냅니다[1,4]
. 이는 참조 값이 유효하기 전에 참조 값이 유효하지 않은 한 괜찮습니다. child
블록에서 돌아 오려고하면 문제가 발생합니다 . 이것은 자연 길이를 넘어서 수명을 "연장"시킵니다.
이 새로운 지식은 처음 두 가지 예를 설명해야합니다. 세 번째는의 구현을 살펴보아야합니다 Parent::child
. 기회는 다음과 같습니다.
impl Parent {
fn child(&self) -> Child { /* ... */ }
}
이것은 명시적인 일반 수명 매개 변수를 쓰지 않기 위해 수명 제거를 사용합니다 . 다음과 같습니다.
impl Parent {
fn child<'a>(&'a self) -> Child<'a> { /* ... */ }
}
두 경우 모두,이 방법은 Child
의 구체적인 수명으로 매개 변수화 된 구조가 리턴 될
것이라고 말합니다 self
. 다른 방법으로, Child
인스턴스 는 인스턴스 Parent
를 생성 한 것에 대한 참조를 포함 하므로 해당 Parent
인스턴스 보다 오래 살 수 없습니다
.
또한 생성 기능에 문제가 있음을 인식 할 수 있습니다.
fn make_combined<'a>() -> Combined<'a> { /* ... */ }
다른 형식으로 작성된 것을 볼 가능성이 높지만 :
impl<'a> Combined<'a> {
fn new() -> Combined<'a> { /* ... */ }
}
두 경우 모두 인수를 통해 제공되는 수명 매개 변수가 없습니다. 이것은 Combined
매개 변수화 될 수명이 어떤 것에 의해 제한되지 않는다는 것을 의미합니다 -호출자가 원하는 것이 될 수 있습니다. 호출자가 'static
수명을 지정할 수 있고 해당 조건을 충족시킬 방법이 없기 때문에 이것은 무의미 합니다.
어떻게 고치나요?
가장 쉽고 권장되는 솔루션은 이러한 항목을 동일한 구조에 함께 배치하지 않는 것입니다. 이렇게하면 구조 중첩이 코드 수명을 모방합니다. 데이터를 소유하는 유형을 구조에 함께 배치 한 다음 필요에 따라 참조를 포함하는 참조 또는 객체를 얻을 수있는 메소드를 제공하십시오.
평생 추적이 지나치게 까다로운 특별한 경우가 있습니다. 힙에 무언가가 있으면. Box<T>
예를 들어을 사용할 때 발생합니다
. 이 경우 이동 된 구조에는 힙에 대한 포인터가 포함됩니다. 지정된 값은 안정적으로 유지되지만 포인터 자체의 주소는 이동합니다. 실제로는 항상 포인터를 따르기 때문에 이것은 중요하지 않습니다.
임대 상자 (더 이상 유지되거나 지원되는) 또는 owning_ref 상자는 이 사건을 표현하는 방법입니다,하지만 그들은 기본 주소가해야 이동하지 않습니다 . 이것은 돌연변이 벡터를 배제하는데, 이는 재 할당 및 힙 할당 값의 이동을 야기 할 수있다.
렌탈로 해결 된 문제의 예 :
다른 경우에, 당신은 사용하여 같은 참조 카운팅, 어떤 종류의로 이동하실 수 있습니다 Rc
또는 Arc
.
추가 정보
parent
구조체로 이동 한 후 컴파일러가 구조체에서 새 참조를 가져 와서 parent
할당 할 수없는 이유는 child
무엇입니까?
이론적으로는 가능하지만 그렇게하면 많은 복잡성과 오버 헤드가 발생합니다. 객체가 움직일 때마다 컴파일러는 참조를 "수정"하기 위해 코드를 삽입해야합니다. 이것은 구조체를 복사하는 것이 더 이상 아주 저렴한 작업이 아니라는 것을 의미합니다. 그것은 가상 옵티마이 저가 얼마나 좋은지에 따라 이와 같은 코드가 비싸다는 것을 의미 할 수도 있습니다.
let a = Object::new();
let b = a;
let c = b;
프로그래머는 매번 이동할 때마다 이를 발생시키는 대신 호출 할 때만 적절한 참조를 취하는 메소드를 작성하여 언제 발생 하는지 선택할 수 있습니다.
자체를 참조하는 유형
자체를 참조하여 유형을 만들 수 있는 특정 사례가 있습니다 . Option
그래도 두 단계로 만드는 것과 같은 것을 사용해야합니다 .
#[derive(Debug)]
struct WhatAboutThis<'a> {
name: String,
nickname: Option<&'a str>,
}
fn main() {
let mut tricky = WhatAboutThis {
name: "Annabelle".to_string(),
nickname: None,
};
tricky.nickname = Some(&tricky.name[..4]);
println!("{:?}", tricky);
}
이것은 어떤 의미에서는 효과가 있지만 생성 된 값은 매우 제한적이므로 절대 이동할 수 없습니다 . 특히 이것은 함수에서 반환하거나 값으로 전달할 수 없음을 의미합니다. 생성자 함수는 위와 같이 수명과 동일한 문제를 보여줍니다.
fn creator<'a>() -> WhatAboutThis<'a> { /* ... */ }
무엇에 대해 Pin
?
Pin
Rust 1.33에서 안정화 된 모듈 설명서에 다음 이 있습니다 .
이러한 시나리오의 주요 예는 자체 참조 구조체를 작성하는 것입니다. 포인터를 사용하여 객체를 이동하면 객체가 무효화되어 정의되지 않은 동작이 발생할 수 있기 때문입니다.
"자기 참조"가 반드시 참조를 사용 하는 것을 의미하지는 않습니다 . 실제로 자기 참조 구조체 의 예는 구체적으로 (강조 광산)이라고 말합니다.
이 패턴은 일반적인 차용 규칙으로 설명 할 수 없으므로 일반적인 참조로 컴파일러에 알릴 수 없습니다. 대신 우리는 원시 포인터 를 사용하지만 null이 아닌 것으로 알려진 것은 포인터를 문자열을 가리키는 것으로 알고 있기 때문입니다.
이 동작에 대한 원시 포인터를 사용하는 기능은 Rust 1.0부터 존재했습니다. 실제로, 소유-참조 및 임대는 후드 아래에 원시 포인터를 사용합니다.
Pin
테이블에 추가 되는 유일한 것은 주어진 값이 움직이지 않는다는 것을 나타내는 일반적인 방법입니다.
또한보십시오:
Parent
과 같습니다Child
.