Rust 책 의 수명 장 을 읽고 있었고 명명 된 / 명시 적 수명에 대해이 예제를 보았습니다.
struct Foo<'a> {
x: &'a i32,
}
fn main() {
let x; // -+ x goes into scope
// |
{ // |
let y = &5; // ---+ y goes into scope
let f = Foo { x: y }; // ---+ f goes into scope
x = &f.x; // | | error here
} // ---+ f and y go out of scope
// |
println!("{}", x); // |
} // -+ x goes out of scope
그것은 컴파일러에 의해 억제되는 오류가 있음을 나에게 매우 분명 처리 할 때 use-after-free 에 할당 된 참조가 x
: 내부 범위를 완료, 후 f
때문에 &f.x
무효되고, 할당 된 안된다 x
.
내 문제는 명시 적 수명 을 사용 하지 않고 문제를 쉽게 분석 할 수 있다는 것 입니다. 'a
x = &f.x;
어떤 경우에 사후 사용 (또는 다른 클래스) 오류를 방지하기 위해 명시적인 수명이 실제로 필요한가?
답변
다른 답변은 모두 명백한 점 ( 명시 적 수명이 필요한 fjh의 구체적인 예 )이 있지만 한 가지 중요한 사항이 누락되었습니다 . 컴파일러가 잘못 설명 했을 때 명시 적 수명이 필요한 이유는 무엇입니까?
이것은 실제로 “컴파일러가이를 유추 할 수있을 때 명시 적 유형이 필요한 이유”와 같은 질문입니다. 가상의 예 :
fn foo() -> _ {
""
}
물론 컴파일러는를 반환한다는 것을 알 수 있습니다 &'static str
. 그래서 프로그래머는 왜 그것을 입력해야합니까?
주된 이유는 컴파일러가 코드의 기능을 볼 수 있지만 의도가 무엇인지 알 수 없기 때문입니다.
함수는 코드 변경으로 인한 영향을 차단하는 자연스러운 경계입니다. 코드에서 수명을 완전히 검사 할 수있게하려면 순진한 모양의 변화가 수명에 영향을 미쳐 멀리있는 함수에서 오류가 발생할 수 있습니다. 이것은 가상의 예가 아닙니다. 내가 이해 한 것처럼 Haskell은 최상위 함수에 형식 유추에 의존 할 때이 문제가 있습니다. 녹은 새싹에서 그 특정 문제를 해결했다.
컴파일러에는 효율성 이점도 있습니다. 유형과 수명을 확인하려면 함수 서명 만 구문 분석하면됩니다. 더 중요한 것은 프로그래머에게 효율성 이점이 있다는 것입니다. 명시적인 수명이 없다면이 기능은 무엇을 하는가?
fn foo(a: &u8, b: &u8) -> &u8
소스를 검사하지 않고는 말할 수 없으며, 이는 수많은 코딩 모범 사례에 위배됩니다.
더 넓은 범위에 대한 참조의 불법 할당을 유추함으로써
범위 는 기본적으로 수명입니다. 좀 더 명확하게, 수명 'a
은 호출 사이트를 기반으로 컴파일 타임에 특정 범위로 특수화 할 수 있는 일반 수명 매개 변수 입니다.
[…] 오류를 방지하기 위해 명시적인 수명이 실제로 필요한가?
전혀. 수명 은 오류를 방지하는 데 필요하지만, 생생한 프로그래머가 가지고있는 것을 보호하려면 명시적인 수명이 필요합니다.
답변
다음 예제를 보자.
fn foo<'a, 'b>(x: &'a u32, y: &'b u32) -> &'a u32 {
x
}
fn main() {
let x = 12;
let z: &u32 = {
let y = 42;
foo(&x, &y)
};
}
여기에서 명시적인 수명이 중요합니다. 결과 foo
는 첫 번째 인수 ( 'a
) 와 수명이 같으 므로 두 번째 인수보다 수명 이 길기 때문에 컴파일됩니다 . 이는의 서명에서 수명 이름으로 표시됩니다 foo
. foo
컴파일러 호출에서 인수를 전환하면 y
오래 살지 못한다고 불평 할 것입니다 .
error[E0597]: `y` does not live long enough
--> src/main.rs:10:5
|
9 | foo(&y, &x)
| - borrow occurs here
10 | };
| ^ `y` dropped here while still borrowed
11 | }
| - borrowed value needs to live until here
답변
다음 구조의 수명 주석 :
struct Foo<'a> {
x: &'a i32,
}
Foo
인스턴스가 포함하는 참조보다 인스턴스가 수명을 초과하지 않도록 지정합니다 ( x
field).
당신이 녹 책에서 온 예는이 때문에 설명하지 않습니다 f
및y
변수가 동시에 범위 밖으로 이동합니다.
더 좋은 예는 다음과 같습니다.
fn main() {
let f : Foo;
{
let n = 5; // variable that is invalid outside this block
let y = &n;
f = Foo { x: y };
};
println!("{}", f.x);
}
이제는로 f
지적한 변수보다 실제로 수명을 연장합니다 f.x
.
답변
구조 정의를 제외하고 해당 코드에는 명시적인 수명이 없습니다. 컴파일러는 수명을 완벽하게 추론 할 수 있습니다.main()
있습니다.
그러나 유형 정의에서 명시 적 수명은 피할 수 없습니다. 예를 들어, 여기에 모호성이 있습니다.
struct RefPair(&u32, &u32);
수명이 달라야합니까, 아니면 같아야합니까? 그것은 사용의 관점에서 문제가 않습니다, struct RefPair<'a, 'b>(&'a u32, &'b u32)
매우 다르다struct RefPair<'a>(&'a u32, &'a u32)
.
이제는 제공 한 것과 같은 간단한 경우 컴파일러 는 이론적 으로 다른 곳에서와 마찬가지로 수명을 없앨 수 있지만 이러한 경우는 매우 제한적이며 컴파일러에서 추가 복잡성이 가치가 없으며이 명확성 향상은 매우 의심스러운.
답변
이 책의 사례는 설계 상 매우 간단합니다. 평생의 주제는 복잡한 것으로 간주됩니다.
컴파일러는 여러 인수가있는 함수에서 수명을 쉽게 추론 할 수 없습니다.
또한 내 자신의 선택적 상자에는 실제로 서명 OptionBool
이있는 as_slice
방법 이있는 유형 이 있습니다.
fn as_slice(&self) -> &'static [bool] { ... }
컴파일러가 그것을 알아낼 수있는 방법은 전혀 없습니다.
답변
http://doc.rust-lang.org/0.12.0/guide-lifetimes.html#returning-references 에서 또 다른 훌륭한 설명을 찾았습니다 .
일반적으로 참조가 매개 변수에서 프로 시저로 파생 된 경우에만 참조를 리턴 할 수 있습니다. 이 경우 포인터 결과는 항상 매개 변수 중 하나와 동일한 수명을 갖습니다. 명명 된 수명은 어떤 매개 변수인지 나타냅니다.
답변
함수가 두 개의 참조를 인수로 받고 참조를 리턴하면 함수의 구현은 때때로 첫 번째 참조를 리턴하고 때로는 두 번째 참조를 리턴 할 수도 있습니다. 주어진 호출에 대해 어떤 참조가 반환되는지 예측할 수 없습니다. 이 경우 각 인수 참조가 다른 수명을 가진 다른 변수 바인딩을 참조 할 수 있으므로 반환 된 참조의 수명을 유추하는 것은 불가능합니다. 명시 적 수명은 그러한 상황을 피하거나 명확하게하는 데 도움이됩니다.
마찬가지로, 구조에 두 개의 참조 (두 개의 멤버 필드로)가 있으면 구조의 멤버 함수가 때때로 첫 번째 참조를 리턴하고 때로는 두 번째 참조를 리턴 할 수도 있습니다. 또 다시 명백한 수명은 이러한 모호성을 방지합니다.
몇 가지 간단한 상황에서,이 평생 생략 컴파일러가 수명을 추론 할 수있다.